Repository: dOpensource/dsiprouter Branch: master Commit: 9fd2f01d679c Files: 506 Total size: 5.2 MB Directory structure: gitextract_y6_vpjk0/ ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── bug-report.md │ │ └── config.yml │ └── PULL_REQUEST_TEMPLATE/ │ └── default.md ├── .gitignore ├── .readthedocs.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CONTRIBUTORS.md ├── HA/ │ ├── consul/ │ │ ├── consul.fc │ │ ├── consul.hcl │ │ ├── consul.service │ │ ├── consul.te │ │ ├── installConsulCluster.sh │ │ └── server.hcl │ ├── mysql/ │ │ ├── installAAGaleraReplication.sh │ │ └── installAAGroupReplication.sh │ ├── pacemaker/ │ │ ├── AWS/ │ │ │ └── ocf-floatip │ │ ├── DO/ │ │ │ ├── assign-ip │ │ │ └── ocf-floatip │ │ ├── installKamCluster.sh │ │ ├── nodeutil.sh │ │ └── scripts/ │ │ ├── stage1.sh │ │ ├── stage2.sh │ │ └── stage3.sh │ └── shared_lib.sh ├── Jenkinsfile ├── Jenkinsfile.common ├── LICENSE ├── README.md ├── cloud/ │ ├── build_image.sh │ ├── build_instance.sh │ ├── cloud-init/ │ │ ├── configs/ │ │ │ ├── AWS.cfg │ │ │ ├── AZURE.cfg │ │ │ ├── DO.cfg │ │ │ ├── GCE.cfg │ │ │ └── VULTR.cfg │ │ └── templates/ │ │ ├── hosts.almalinux.tmpl │ │ ├── hosts.amzn.tmpl │ │ ├── hosts.centos.tmpl │ │ ├── hosts.debian.tmpl │ │ ├── hosts.rhel.tmpl │ │ ├── hosts.rocky.tmpl │ │ └── hosts.ubuntu.tmpl │ ├── find_hosts_tmpl.sh │ └── pre-snapshot.sh ├── dnsmasq/ │ ├── almalinux/ │ │ └── install.sh │ ├── amzn/ │ │ └── install.sh │ ├── centos/ │ │ └── install.sh │ ├── configs/ │ │ ├── dnsmasq_sh.conf │ │ ├── ifupdown/ │ │ │ ├── default.conf │ │ │ ├── networking-pre.sh │ │ │ └── override.conf │ │ ├── networkmanager/ │ │ │ ├── dsiprouter.conf │ │ │ └── wait-override.conf │ │ ├── resolv.conf │ │ ├── resolvconf_def │ │ ├── resolvconf_upd │ │ ├── systemdnetworkd/ │ │ │ ├── docker.network │ │ │ ├── dsiprouter.network │ │ │ ├── networkd-pre.sh │ │ │ ├── override.conf │ │ │ └── wait-override.conf │ │ └── systemdresolved/ │ │ └── dsiprouter.conf │ ├── debian/ │ │ ├── 12.sh │ │ └── install.sh │ ├── init.d/ │ │ └── dnsmasq │ ├── rhel/ │ │ └── install.sh │ ├── rocky/ │ │ └── install.sh │ ├── systemd/ │ │ ├── dnsmasq-v1.service │ │ ├── dnsmasq-v2.service │ │ └── dnsmasq-v3.service │ └── ubuntu/ │ └── install.sh ├── docker/ │ ├── dsiprouter/ │ │ ├── dockerfile │ │ └── wait-for-dsiprouter-mysql.sh │ └── mysql/ │ └── dockerfile ├── docker-compose.yml ├── docs/ │ ├── Makefile │ ├── requirements.in │ ├── requirements.txt │ └── source/ │ ├── _static/ │ │ └── placeholder │ ├── _templates/ │ │ └── placeholder │ ├── conf.py │ ├── dev/ │ │ ├── database.rst │ │ ├── dsiprouter.rst │ │ ├── index.rst │ │ ├── modules.rst │ │ ├── settings.rst │ │ ├── shared.rst │ │ ├── sysloginit.rst │ │ └── util.rst │ ├── index.rst │ ├── routes/ │ │ ├── details.rst │ │ ├── index.rst │ │ └── summary.rst │ └── user/ │ ├── api.rst │ ├── carrier_groups.rst │ ├── command_line_options.rst │ ├── conf.py │ ├── configuring.rst │ ├── debian_install.rst │ ├── domains.rst │ ├── global_outbound_routes.rst │ ├── images/ │ │ └── DID_test.csv │ ├── inbound_did_mapping.rst │ ├── index.rst │ ├── installing.rst │ ├── pbxs_and_endpoints.rst │ ├── resources.rst │ ├── rhel_install.rst │ ├── supported_configurations.rst │ ├── troubleshooting.rst │ ├── upgrade_0.50_to_0.51.rst │ ├── upgrade_0.522_to_0.523.rst │ ├── upgrade_0.621_to_0.63.rst │ ├── upgrading.rst │ └── use-cases.rst ├── dsiprouter/ │ ├── almalinux/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── amzn/ │ │ └── 2.sh │ ├── centos/ │ │ ├── 7.sh │ │ ├── 8.sh │ │ └── 9.sh │ ├── debian/ │ │ ├── 10.sh │ │ ├── 11.sh │ │ ├── 12.sh │ │ └── 9.sh │ ├── dsip-net-cfg.py │ ├── dsip_completion.sh │ ├── dsip_lib.sh │ ├── pbkdf2.sh │ ├── rhel/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── rocky/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── sudoers.d/ │ │ └── 99-dsiprouter │ ├── systemd/ │ │ ├── dsiprouter-v1.service │ │ └── dsiprouter-v2.service │ └── ubuntu/ │ ├── 20.sh │ ├── 22.sh │ └── 24.sh ├── dsiprouter.sh ├── gui/ │ ├── database/ │ │ └── __init__.py │ ├── dsiprouter.py │ ├── dsiprouter_cron.py │ ├── dsiprouter_ut.py │ ├── modules/ │ │ ├── api/ │ │ │ ├── api.sql │ │ │ ├── api_functions.py │ │ │ ├── api_routes.py │ │ │ ├── auth/ │ │ │ │ ├── __init__.py │ │ │ │ ├── functions.py │ │ │ │ ├── ldap/ │ │ │ │ │ ├── __init__.py │ │ │ │ │ └── interface.py │ │ │ │ └── routes.py │ │ │ ├── carriergroups/ │ │ │ │ ├── functions.py │ │ │ │ ├── plugin/ │ │ │ │ │ └── twilio/ │ │ │ │ │ ├── carrier_plugintype_version.py │ │ │ │ │ └── interface.py │ │ │ │ └── routes.py │ │ │ ├── cron_functions.py │ │ │ ├── install.sh │ │ │ ├── kamailio/ │ │ │ │ ├── errors.py │ │ │ │ └── functions.py │ │ │ ├── licensemanager/ │ │ │ │ ├── classes.py │ │ │ │ ├── cli.py │ │ │ │ ├── functions.py │ │ │ │ └── routes.py │ │ │ ├── mediaserver/ │ │ │ │ ├── plugin/ │ │ │ │ │ ├── fusion/ │ │ │ │ │ │ └── interface.py │ │ │ │ │ └── fusionpbx │ │ │ │ └── routes.py │ │ │ └── sample_api.py │ │ ├── cdr/ │ │ │ ├── cdrs.sql │ │ │ ├── cron_functions.py │ │ │ └── install.sh │ │ ├── certificates/ │ │ │ ├── certificates.sql │ │ │ └── install.sh │ │ ├── custom_routing/ │ │ │ ├── custom_routing.sql │ │ │ └── install.sh │ │ ├── dnid_enrichment/ │ │ │ ├── dnid_enrichment.sql │ │ │ └── install.sh │ │ ├── domain/ │ │ │ ├── __init__.py │ │ │ ├── domain_mapping.sql │ │ │ ├── domain_routes.py │ │ │ └── install.sh │ │ ├── flowroute/ │ │ │ └── __init__.py │ │ ├── frauddetection/ │ │ │ ├── fraud.py │ │ │ └── install.sh │ │ ├── fusionpbx/ │ │ │ ├── dsiprouter-provisioner.conf │ │ │ ├── dsiprouter-provisioner.tpl │ │ │ ├── dsiprouter.nginx │ │ │ ├── dsiprouter.nginx.tpl │ │ │ ├── fusionpbx_sync_functions.py │ │ │ ├── html/ │ │ │ │ └── images/ │ │ │ │ └── placeholder.txt │ │ │ └── install.sh │ │ └── upgrade/ │ │ └── __init__.py │ ├── requirements.txt │ ├── settings.py │ ├── shared.py │ ├── static/ │ │ ├── css/ │ │ │ ├── bootstrap-theme.css │ │ │ ├── bootstrap-toggle.css │ │ │ ├── bootstrap.css │ │ │ ├── carriergroups.css │ │ │ ├── cdrs.css │ │ │ ├── certificates.css │ │ │ ├── combobox.css │ │ │ ├── dashboard.css │ │ │ ├── highlight/ │ │ │ │ ├── LICENSE │ │ │ │ ├── codepen-embed.css │ │ │ │ ├── darcula.css │ │ │ │ ├── default.css │ │ │ │ ├── github-gist.css │ │ │ │ ├── github.css │ │ │ │ ├── googlecode.css │ │ │ │ ├── idea.css │ │ │ │ └── monokai-sublime.css │ │ │ ├── main.css │ │ │ ├── msteams.css │ │ │ └── titatoggle-dist.css │ │ ├── fonts/ │ │ │ └── icomoon.json │ │ ├── js/ │ │ │ ├── backupandrestore.js │ │ │ ├── bootstrap-toggle.js │ │ │ ├── bootstrap.js │ │ │ ├── carriergroups.js │ │ │ ├── cdrs.js │ │ │ ├── certificates.js │ │ │ ├── combobox.js │ │ │ ├── dashboard.js │ │ │ ├── datatables.js │ │ │ ├── domains.js │ │ │ ├── endpointgroups.js │ │ │ ├── highlight/ │ │ │ │ ├── LICENSE │ │ │ │ └── highlight.pack.js │ │ │ ├── inboundmapping.js │ │ │ ├── jquery.js │ │ │ ├── jquery.tabledit.js │ │ │ ├── license_manager.js │ │ │ ├── main.js │ │ │ ├── msteams.js │ │ │ ├── npm.js │ │ │ ├── outboundroutes.js │ │ │ ├── stirshaken.js │ │ │ ├── teleblock.js │ │ │ ├── transnexus.js │ │ │ ├── upgrade.js │ │ │ ├── util.js │ │ │ └── validator.js │ │ └── template/ │ │ └── DID_example.csv │ ├── sysloginit.py │ ├── templates/ │ │ ├── backupandrestore.html │ │ ├── carriergroups.html │ │ ├── carriers.html │ │ ├── cdrs.html │ │ ├── certificates.html │ │ ├── dashboard.html │ │ ├── domains.html │ │ ├── endpointgroups.html │ │ ├── error.html │ │ ├── fullwidth_layout.html │ │ ├── inboundmapping.html │ │ ├── includes/ │ │ │ └── overrides.js │ │ ├── index.html │ │ ├── license_manager.html │ │ ├── license_required.html │ │ ├── login_layout.html │ │ ├── msteams.html │ │ ├── outboundroutes.html │ │ ├── stirshaken.html │ │ ├── table_layout.html │ │ ├── teleblock.html │ │ ├── transnexus.html │ │ ├── upgrade.html │ │ └── util.jinja2.html │ └── util/ │ ├── conversions.py │ ├── cron.py │ ├── file_handling.py │ ├── ipc.py │ ├── kamtls.py │ ├── letsencrypt.py │ ├── networking.py │ ├── notifications.py │ ├── parse_json.py │ ├── persistence.py │ ├── pyasync.py │ ├── security.py │ └── time_funcs.py ├── kamailio/ │ ├── almalinux/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── amzn/ │ │ └── 2.sh │ ├── centos/ │ │ ├── 7.sh │ │ ├── 8.sh │ │ └── 9.sh │ ├── configs/ │ │ ├── kamailio.cfg │ │ ├── stir-shaken.cfg │ │ ├── tls.cfg │ │ └── transnexus.cfg │ ├── debian/ │ │ ├── 10.sh │ │ ├── 11.sh │ │ ├── 12.sh │ │ └── 9.sh │ ├── defaults/ │ │ ├── address.csv │ │ ├── address.sql │ │ ├── dispatcher.csv │ │ ├── dispatcher.sql │ │ ├── dr_gateways.csv │ │ ├── dr_gateways.sql │ │ ├── dr_gw_lists.csv │ │ ├── dr_gw_lists.sql │ │ ├── dr_rules.csv │ │ ├── dr_rules.sql │ │ ├── dsip_call_settings.sql │ │ ├── dsip_cdrinfo.sql │ │ ├── dsip_forwarding.sql │ │ ├── dsip_gw2gwgroup.sql │ │ ├── dsip_gwgroup2lb.sql │ │ ├── dsip_lcr.sql │ │ ├── dsip_maintmode.sql │ │ ├── dsip_notification.sql │ │ ├── dsip_settings.sql │ │ ├── subscribers.sql │ │ └── uacreg.sql │ ├── htable-kam57.patch │ ├── kamdbctl.patch │ ├── modules/ │ │ └── dsiprouter/ │ │ ├── Makefile │ │ ├── README.md │ │ ├── mod_dsiprouter.c │ │ ├── mod_funcs.c │ │ └── mod_funcs.h │ ├── rhel/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── rocky/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── stir_shaken.patch │ ├── systemd/ │ │ ├── kamailio-v1.service │ │ ├── kamailio-v2.service │ │ └── kamailio.conf │ ├── uac.patch │ └── ubuntu/ │ ├── 20.sh │ ├── 22.sh │ └── 24.sh ├── mysql/ │ ├── almalinux/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── amzn/ │ │ └── 2.sh │ ├── centos/ │ │ ├── 7.sh │ │ ├── 8.sh │ │ └── 9.sh │ ├── debian/ │ │ ├── 10.sh │ │ ├── 11.sh │ │ ├── 12.sh │ │ └── 9.sh │ ├── rhel/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── rocky/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── systemd/ │ │ ├── dummy.service │ │ ├── override.conf │ │ └── override.sh │ └── ubuntu/ │ ├── 20.sh │ ├── 22.sh │ └── 24.sh ├── nginx/ │ ├── almalinux/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── amzn/ │ │ └── 2.sh │ ├── centos/ │ │ ├── 7.sh │ │ ├── 8.sh │ │ └── 9.sh │ ├── configs/ │ │ ├── dsiprouter.conf │ │ └── nginx.conf │ ├── debian/ │ │ ├── 10.sh │ │ ├── 11.sh │ │ ├── 12.sh │ │ └── 9.sh │ ├── rhel/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── rocky/ │ │ ├── 8.sh │ │ └── 9.sh │ ├── selinux/ │ │ └── centos.te │ ├── systemd/ │ │ ├── nginx-stop.sh │ │ ├── nginx-v1.service │ │ ├── nginx-v2.service │ │ ├── nginx-watcher-v1.service │ │ ├── nginx-watcher-v2.service │ │ └── nginx-watcher.path │ └── ubuntu/ │ ├── 20.sh │ ├── 22.sh │ └── 24.sh ├── resources/ │ ├── apt/ │ │ ├── debian/ │ │ │ ├── 10/ │ │ │ │ ├── official-releases.list │ │ │ │ └── official-releases.pref │ │ │ ├── 11/ │ │ │ │ ├── official-releases.list │ │ │ │ └── official-releases.pref │ │ │ ├── 12/ │ │ │ │ ├── official-releases.list │ │ │ │ └── official-releases.pref │ │ │ └── 9/ │ │ │ ├── official-releases.list │ │ │ └── official-releases.pref │ │ └── ubuntu/ │ │ ├── 20.04/ │ │ │ ├── official-releases.list │ │ │ └── official-releases.pref │ │ ├── 22.04/ │ │ │ ├── official-releases.list │ │ │ └── official-releases.pref │ │ └── 24.04/ │ │ ├── official-releases.list │ │ └── official-releases.pref │ ├── git/ │ │ ├── check_syntax.py │ │ ├── commit-msg │ │ ├── gitattributes │ │ ├── gitconfig │ │ ├── gitignore │ │ ├── gitwrapper.sh │ │ ├── hooks/ │ │ │ ├── commit-msg │ │ │ ├── post-commit │ │ │ ├── pre-commit │ │ │ ├── pre-push │ │ │ └── prepare-commit-msg │ │ └── merge-changelog.sh │ ├── logrotate/ │ │ ├── consul │ │ ├── dsiprouter │ │ ├── kamailio │ │ └── rtpengine │ ├── man/ │ │ └── dsiprouter.1 │ ├── mysql/ │ │ ├── asterisk-realtime-config.sql │ │ └── asterisk-realtime-setup.sql │ ├── stir_shaken/ │ │ └── generate_self_signed_cert.sh │ ├── syslog/ │ │ ├── consul.conf │ │ ├── dsiprouter.conf │ │ ├── kamailio.conf │ │ ├── rsyslog.conf │ │ └── rtpengine.conf │ ├── terraform/ │ │ └── do/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── main.tf │ │ ├── terraform.tfvars.sample │ │ └── variables.tf │ ├── upgrade/ │ │ ├── v0.72/ │ │ │ ├── scripts/ │ │ │ │ ├── bootstrap.sh │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.721/ │ │ │ ├── scripts/ │ │ │ │ ├── bootstrap.sh │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.73/ │ │ │ ├── scripts/ │ │ │ │ ├── bootstrap.sh │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.74/ │ │ │ ├── scripts/ │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.75/ │ │ │ ├── clear_defaults.sql │ │ │ ├── migrate_data.sql │ │ │ ├── pre_import_data.sql │ │ │ ├── scripts/ │ │ │ │ ├── bootstrap.sh │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.76/ │ │ │ ├── scripts/ │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ ├── v0.77/ │ │ │ ├── clear_defaults.sql │ │ │ ├── scripts/ │ │ │ │ └── migrate.sh │ │ │ └── settings.json │ │ └── v0.78/ │ │ ├── dsip-fwd-new.sql │ │ ├── dsip-fwd-old.sql │ │ ├── scripts/ │ │ │ └── migrate.sh │ │ └── settings.json │ └── uploadOutRoute.py ├── rtpengine/ │ ├── almalinux/ │ │ └── install.sh │ ├── amzn/ │ │ ├── install.sh │ │ └── rtpengine.spec │ ├── centos/ │ │ └── install.sh │ ├── configs/ │ │ ├── default.conf │ │ └── rtpengine.conf │ ├── deb-mr11.5.1.11.patch │ ├── debian/ │ │ └── install.sh │ ├── el-mr11.5.1.11.patch │ ├── rhel/ │ │ └── install.sh │ ├── rocky/ │ │ └── install.sh │ ├── rtpengine-start-pre │ ├── rtpengine-stop-post │ ├── systemd/ │ │ ├── dummy.service │ │ ├── rtpengine-v1.service │ │ ├── rtpengine-v2.service │ │ └── rtpengine-v3.service │ └── ubuntu/ │ └── install.sh └── testing/ ├── 0.sh ├── 1.sh ├── 10.sh ├── 11.sh ├── 12.sh.dev ├── 13.sh.dev ├── 14.sh.dev ├── 15.sh.dev ├── 16.sh ├── 17.sh ├── 18.sh ├── 19.sh ├── 2.sh ├── 20.sh ├── 21.sh ├── 3.sh ├── 4.sh ├── 5.sh ├── 6.sh ├── 7.sh ├── 8.sh ├── 9.sh.dev ├── INVITE.sip ├── Makefile ├── README.md ├── api/ │ └── dsiprouter.postman_collection.json ├── include/ │ └── common ├── payload.json └── sql/ ├── v0.522/ │ └── kamailio.sql ├── v0.523+ent/ │ ├── grants.sql │ └── kamailio.sql └── v0.60+ent/ ├── grants.sql └── kamailio.sql ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] ================================================ FILE: .github/ISSUE_TEMPLATE/bug-report.md ================================================ --- name: Bug Report about: Report unexpected program behavior to help us improve title: "[BUG] Your Issue Name Here" labels: bug, needs validation assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. Add any applicable logs as well; such as an `dsiprouter.log`, or `kamailio.log`, etc... **Server Info:** - OS: *output from* `uname -a` - Distro: *output from* `cat /etc/os-release` - dSIPRouter Version: *output from* `dsiprouter version` *If not on a release version include the branch name and last commit id* - Kamailio Version: *output from* `kamailio -v` - RTPengine Version: *output from* `rtpengine -v` - Python Package Versions: *if applicable, include output from* `/opt/dsiprouter/venv/bin/python -m pip freeze` **Client Info:** - Device: *e.g. Polycom VVX 350, Lenovo Thinkpad X1, ..* - OS: *e.g. Windows 11, Ubuntu 22.04, ..* - Client Software: *e.g. Mozilla Firefox 103.0, Zoiper 5.5.13, ..* **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: dSIPRouter Community Slack url: https://dsiproutercommunity.slack.com/ about: Community hangout where questions can be asked and answered. - name: dOpenSource Official Support url: https://dopensource.com/shop/ about: Official support and addons can be purchased here. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE/default.md ================================================ ## Description Include a summary of the changes and the related issue. Include relevant motivation and context. List any dependencies that are required for this change. ## Type of change Please delete options that are not relevant. - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] This change requires a documentation update ## How Has This Been Tested? Describe the tests that you ran to verify your changes. Provide instructions that we can use to reproduce your results. List any relevant details for your test configuration. - [ ] Test Script Committed - [ ] Instructions Provided Below **Test Configuration**: * Hardware: * OS/Distro: * Software: ## Checklist: - [ ] My code follows the Contributing guidelines of this project - [ ] This PR is not a duplicate of another open PR? - [ ] I have performed a self-review of my code - [ ] I have made corresponding changes to the documentation - [ ] I have added tests that prove my fix is effective or that my feature works - [ ] New and existing unit tests pass locally with my changes - [ ] My code passes integration testing on a live system ================================================ FILE: .gitignore ================================================ # dSIPRouter Project Specific Ignored Files .idea /resources/terraform/do/*.tfstate /resources/terraform/do/*.backup /resources/terraform/do/.terraform /resources/terraform/do/*.hcl /resources/terraform/do/terraform.tfvars */__pycache__/ venv/ # TODO: these following files could be generated elsewhere # adding them to python path where needed to cleanup project dir docs/build/ gui/modules/fusionpbx/certs/cert.key gui/modules/fusionpbx/certs/cert_combined.crt ================================================ FILE: .readthedocs.yml ================================================ # .readthedocs.yml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: python: "3.8" # You can also specify other tool versions: # nodejs: "20" # rust: "1.70" # golang: "1.20" # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/source/user/conf.py # Build documentation with MkDocs #mkdocs: # configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF formats: - pdf # Optional but recommended, declare the Python requirements # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: docs/requirements.txt ================================================ FILE: CHANGELOG.md ================================================ [//]: # (START_SECTION HEADER) ## CHANGELOG [//]: # (END_SECTION HEADER) [//]: # (START_SECTION COMMITS 7c86d935be95d9838f45727025d442f55a8456a4 844fdfd70c526e3e3f4b94ca91de5a74bab12213 8261be6d3e1772d94acb1d8c8de194a0b4afbdd6 c1b270c5efb9f1640ed0a32a7d9c0d7f96b1a02d 58096c74533221acc325c101627b8f8a8ada1282 c8c6ba9ded2fc109099a8fd3c9e7775ea61560c5 a252b7e6646a684a44946f48bc93f78335d559f4 31d9a61fe5e5698a83ba460a347ca2d9d681904f 902c53e6ea1b56ba293362fd7147f954fe5b87f0 10cdc797dddb3053dfa574dcd10e5fe0696cffe8 2485a964ccf7d5eab5c719d9fe1d24221e79ff41 37fe6c03ed7e798119732367dc1896fa93d3182f 49ba3a7b761ef8f7c71de501c66bdadeeddc70ac 5995b5b7296506b72e5863aba07d1343acb9e113 23b1395497f61ec9a8f60ab63dd394397e1866b6 3007c0e8499cc95673704a420dee938dd424ef9e e1e287dd579ff03c0407b90d047d76e10a2ad52f 8f427d4f46d64c4ead9c32402d56f509f63850c9 155f1594e37d88e14dc9699cc75cba04f3d6b2f0 ababcdd35782a7234b14e9a8c9630e2127bfc52e 73cb9b23631150873b18c39696018a4f599738a9 303bf86be04320131b8fce613e19aa71dfd7ce75 52e07933e0845f85c95c6c05af3e4a7b9ad3f2c2 a39ef41fa5eb058903ca03f8b923570aac7ff15a 3dd1156097fefd98be296dc312bbc03eb3c58002 fd1696c6f460afdac86e5e56414b975ecc3c37ec 0717ce38b28066de5749decbc3aa65bf65051956 b580cc323974d7aba05932392940050f14350858 5c5eb60127bc8b176e933c39f30a60a56d08a312 49e9c8c9f4ab16572259a178182437a5b8ef44d8 b46787c059a00d4a8b17a957df3f696f3dad682d e606c4765084386cf03740c99cb8916bcc78f7f3 17a10889ddc23eee8f9392319fee27e6401bcb70 08ea093039491b124b09bd4b21f63fd629019afd 9e2e812346a002b11e66ef6bd87ce72e130b198d f298cc3ba4118807586579090c473df2035189e5 a85ae0c02f89bcf0539c56201a58e5d01585f062 fbd0f7c84e4cd66e7f0d694a965abc259f333683 a638bc71661c89be5881a9d776b9f73a6ad0af97 952f0655d49ad7ab60c106b4f108945070b7c8eb a058f7f1054f5c65c330f0424e732ce6861793bc f3993379b9ba18b9b4a26eba2611c03ac64b568f febc0d9073bf1834e0fc13dbaf893cb9179a82d6 01171453f8bfe861f37aceb332ada09057ad4b59 d61ec9564deeca1999c61f0683af50bf8b74730d cdfda0c7319e70217b1d6f5570ba7d242e181b4b fe6dee1e3bb7f94a87dca23d8f78512fbd7c03b7 43d053b938a14191b97ce44048a8751021fc6255 68a2dd685172e2b5521f1b698456b2f0dde077b4 4f2a9f8ad407110852092ce543461972687588df 656fbc1c50d503d7f14d13674da597ebc4e1f117 186f48f0d295f28527564a3dc3103cab8993f65c 2b76d501e66b0b22acc911e944f24ca09588cbd7 9b1cab3e12f978ae79c7317b530881a1c6cbe8ae 6fc2548389b0c6672f9141baf4e53e71bc297184 3f73914f1225b2836cab1f13e366582a61c183b4 6265abc736c8c08348a951da48b643bd67034b0e b57a2a3670df1bf0005a4992f2af073e4d85738a a977b58eee52a00f10ea32f7e9a05f2222037a3d f47832ae5061628cef09e3198b7a33525f2cd0d9 f2a522d40e62c96bd1e55de7e098cfda9c98fbfe 92176ca0a908d5a778cb9a1081d7bd3a80df719c 50380c10d0fbeb07295d13cde20aa6ac0a221def e6cbcdbf74426a74a890a97cd5a1ef93813eda99 14712ee1e39628bc178d8591e22d8fc6b355a21d 7a264c878a5440330ee03bccc501ea83c4fa33bb 2ab2a009610830601e70476cea99ec7d867ae181 36eb14bf4f109b1db9e11497f036f7768669a8bd bda6aadd069dbdf2027b782e81a83af66e7e890a 5d95510edf523ea0be6d417ff69360f77eb610e6 223522fe8e8011377b663409529bf04eb74cf1da 96e17cb5f60b07b90c0f62639b5239e7e1e08873 c9131b49277ad01931aa7fd19e56bf85a4b37e79 a106f645bebbd0f39f073f2dad55bf8ad5733463 4f23a2e1158d415856c027cc516069d4be7b0b2f 4859f366f289a1e8aa03d19fcb902cedba2129d7 e8f8f8c07902241cd86428ab860dde834ba2977a 8c3d1ec2aec7952602f6c19af5d94648d272257a ce9efc694149c243243e366d9303f12a6ff21090 a6690af4543a9458baf971404c7d21d03ec19bb3 a89c77066094932562cb1aee1ade444c1335b02b 16e7d16b9aaf5a230f1b4faa2ad032127befbe6e db26338c391f2b39463967f03ec2f123cc4c1f56 ce3312c9020a8c1e208143ba51ae6ab19fb75fec c7d98a3c26f298b4416fe27bb9ed6145a3eed3c3 77b0d03bf0c0ca0dcc9be4730aff129aaf2a0fca dd4564e01ccc4d5bd3ef101a1d1abf368771ce1e 02207a75314811582d3895d35c2eabab968422e7 2302d89fd3752bbc6231f9e8131105a04a9f14ac f429ed2052552e1d8ea2c8182ce4825b1432a8e0 40eac0dbad1c95998ca0b5346187b47abceb1f95 5a5f7558d67477c8b69dd1e23ac4f2ebc65ea2a3 84d1f13d647cc2d7eeab523587435e9fb36b1688 1c0b86cc905d6c6a1aa4a11b89eb9361fdd97907 2fab7686d38b88730594a8cd57d25eaf88076cbb e1c2bd0628d34c0e32c48d7645810d29d9296718 db746a1ef367383edbc0896f52f31ef82f3e0439 ad5f92b9896911a020caa3971dd9b0a01ea56164 9c19f242e8a7ad0196f88a2078e0be061ca16cbf 4ce5a4a0d4125defd1fe11046d72efe8e10b42f0 db53233a086170ff69b829ab1a203ce78d404a36 582aa4c755a129dbaf30fa951232bcfc6b174c48 029833e37ba7c808c64a2062836edfffb0bf87af 96ae8e0ad6b59ffcfa5eb5e22071ef01cda3b386 4c603fe4465e0777bc9ab83f138b24bdc790857c 9e3fba57211e2d32b1d4cc2abca9d0212e23cc6f 4fadad93e3aa6411e567a424759a451d053f9a58 b4c5011cba0f5d39566336fd926158c4c0bcfa3a 9d1a14335d324a834625d1e922c8de2d4057b649 103bc6e4a3c22b37c1f52bdfa79e48d90dd86e2e ba663bd3cc3c16f7c33c8f7977e1b4d5051efc0d 41a1a512aba83310e57d6c2c4d6e19a35a633900 325b4c18db320c491021681490d1bc364524b000 5edf7567a52854a403c21b66c785c4a529b8dd7e 472c343c957240dcebe500f405e3d818fd65f6fc 3400b7d680e252f1b193294c24385740e0ea4cf9 6bfcbbe3ccc56fef2378e7d1305751318ac508e6 8a9eefe4814db0d20e6d5030f6e9865f47711513 5737d1f33c99de90e0ef9188ca6c840292896567 cced1d2cd835ca6d546a22b78dcc6d46f2c1a874 357eca71bf3767d10f36b6e2db1249048a399c54 cd0db1a1f6bf45e09e31ff56e6e3541bd617ea70 8b79cd5e57ee370fe39d8d2234b995c7a0c3667f 435b3cadb5cdc837343d12c04199ef58ecc88123 ec09a74859312f30a48ec11dd57ed682f4da3868 edc1122b046b2948c5ccff28940693c0dcae2821 deb523e80b4f468c47b0f56e89480b8d19b2c899 2e52b3173dc0bfec031da450adc0bc06ff51dab2 2d2491605e0912a54a66a6e141b5f093120f6ee6 abc7359613387ce22dc82a1f8bf9a336835f505e fe0127248147c98388771c02900afa45c17eb565 f8aecea6bd3c217b2073234c9f6ba3fae4c4e5f8 4bae4f3d0153a244c1441213327a83884547ea96 f833e8374efecbc4a9319251db9941f68f331617 17084e4bb069807f065f847086b5b002ff4d54ed fea919d0b4f5184c4ae2d319ed39cfdfb1e3a1dd 351dc8ef45d4f09072584b1b906bc0b04973df8d bda08eb49639f20e85563131cc2356770eaea61f 3408b9a6c8d68a473bb2b7a53ec31ceff6900f91 014c30de6ac92ab9ca51e758b74c8563a875d98b b46462ea5d8160b69a7999de6116f0b2aa0c42ed 1c46e8cd1d1cf82cced662b3e6af94b667c77ac9 dcf2b437e94bf987e60d1c16bf85b331de1ff592 f0ac44f8041b10a3675f5a2accfe29199a795990 482bd41f6d5bc599e632c76095c3f0ea09ee7712 559fec30e82c5553b483acfe7c59d801d65cfd5d d2f71046f6eb8b7dabc13cbb88b74d530d686367 1b8064c9760fd98037e0471373fdc6288f8c2910 313906939ddd7d64a3ef9562c3e86c7101bdff8c 1d7bc2d6a526458a943b79cc23b49169cdf4974e 9b5afa06020b84b598576c56edc09f3fa98be14c 3da760ed578537f9f39475bb6404d3931af22dfc 92eefb44fc4b1cb17ad9908a77b7587f5a408baa e19f0de2ab42f6630ca65aff48a7cea1b2f656c8 9560aab94b63edc79181c8e973a226cffaf89aa3 59a745237cd9586f8dc2224a423365724a90ad21 8bc13858f70633ec2ec2ade2b878759a3d6715b0 6c73f8c46eb1477fcbaee6014e2a861e80f04c81 073aae22bea1191cd548d7ee676d571df4487891 0a9b0322870cd277aa41e0abb7f95ed026909d4f 2f42fe9d97523d96b226f74ad9c25f60ff3a992f 4ef9068ca819def5eb7df6abdd9f799c4baaedd4 a5513488ce466250a812748d7d757af95acdff8e a10cce22b9a632e0b4a1540b0fc06ed42a4412a3 52cfbb1f29bb83db1dc2f4d4454d1da048f2a467 f1d9e0098908132570596a1fa9fd2ceb17978d14 1ab82314fab1f8521df7b2ec8a9954d92548958a ee088949886ce13d66ddea732d23b51b90ee4ffb c3eb4d56b294866513087636aa96f6c3a748982a f1800b77e276cc28469c6f11bf2cd315271d6d17 a183a8c60c6774bb8823b8f73be8a414066ab7fb 7309bf1dffba660067e70977ec9f636eee86a510 8a2675034dd8293dcb3b1aa0efdb3641f5c1005b 5ec677df29e26d2d78251e8d9582202d50665ac5 8759e869034f9410f2622fed0ab40e5dbb107d97 75b3c327bbb19116894e0703ca417b8b1654cb4a 426a4c2b0a5bacc8f4727b2f35127d94237cac40 6b3ea5bb772a6e0d75eb91368d9a0f0a1e15c9e6 f13aec39adf65e0d750820e4f7851da645eae6e7 5a824fe4be28edbef8103b3cd1a8013f91e25492 9eb470f8a4dd79a2562395324c7d2e60583315bc 543e2f3a85e2256879d22f7651cd924b6fc47a95 4e748d9225c0471535e529d428247ec110caaf0d 05d8a3db546cddf462cbe5ba7072f940acfededd 7816850f83fad98085c3fec4fe1470c014f6b244 39fd67f26e204be7f0389d16f7f51bb51411bdda a0e020d98ca138243813a25931ce2bfd44f856f7 87a29f49f7e0ef6e456aadfeb58d4c2155665999 53e56fa76503e703d7fa27170ed403927d33d741 ca380a7b55e3dcb5f1baf08c70dd734b917188d9 ca0d727353041c1fd1ac7a1054dcdeb9c927cb11 0bf4420aabbefb4d6d2e9e32a5f4ee641edfe683 2e00a284992b45dbf5d75c4de87f4704fd78395c 3bee7a4c7e837e0e8a70acf18a65964d6edf0600 b7a5a67c73f390e26d5d6d912a1a48d41636382c d77f068ca08ee2c842a62cbcb58d7038bd138534 83d6af70e487b84ee55c3b9522410fe30db23ad8 575cceede455b4809e2da145592ca78b5cbf6156 fc2aa26bd5fc83eb74ab86f81dc9c630a777f989 c0a47655b5a2b5739121efbc9a0e03c059a291b4 10523849ac49dfa56c99eadab438d1f576666966 7cf4ac77b3693c06d94f6a3ec3c96e44acbaf713 44ef8c01b6c02603d423425141e0557cf7602a3b c1f16fd78645972af29cede0046df2412d0f1aac 5cc5742704f45c6cffee27f1dda3dad5a4fd2da7 27dd5e3a87f3bac5942a454cd13dce5f5f60cfe4 32face946d43cbc0df179550f6d612786808230a 6b6e1b6dc489867566e398ca50913ef4be733c59 8804887bf59e6317ccea7e4e64d95d092825d9fc bca5ab613fbb009b4b835e2903290888ba7f3574 17f6768ba9b4d3f58f20a2239f88b8728c67eaef 1d13139952f73f9ff9dfca79789c0ad1e36a2577 3882f40b0177622d09e4b1267a007c3911cae25d bcdcf178a97f27ddcb129d830f794c5a3fe0c717 692157482f7971a2ce2c90fe75184b3e51f23734 b1f784d5484b7a53e64ffc94200149500529d3b9 64640ba1e2bf8ad5a2ffefba4ff9c00682954af8 c0f2604cbe4bed4a9681348ea7cd2ce4067fbc06 8630f8cee5b28b0ac9eaebf5751c1df3b17409fb 6fc903b9086178ddd02322d17b5999719df733bc 13989cbf1e16ee377db7ab6a4d1138aad614417b a7f092254269a5a33732667ab0cae56c4c562bcc c041cfd3e048e48df24d927680e38d383da9c126 14e8619eab2e549ea5522cc70f65ffb629d4dfc5 17bad27719f91cc0ba496a31453be46816224546 fbf026c7e76dad7a0f6e17947c9200b73b98c985 f87aa6653d56da4c29bef3c80aa1b94100ae46ab de0aa6126df9eccf60d9cb1d921d7454f90e4aec 9e1a4649f51c94ff9f187a3f7ac31e2a8f616d64 7652a2c8b52da1097b5b2bf736f655547e13c175 d2d63c7bfc9a96a447e3d3d6e293be630d117985 1fe2f3ee3f8bfb2bfc1bed9a2a60e4b0c1b40b83 bd478e44de1b6a3a53018b761db19fa2b3de4e9f 5eac4d78b45be6c85cba5bcb4b0e18a3434be119 d755ccb6bde29d678adcc0b5373be9e8b65866d1 d906755d15830244286b5c7ae84168777e57b7da 65eb074c0d896d2fee33ff5424763187d4219cde 0a1d91c4ba6061fd95f1218556e5c8bd6bee8b2d d4e003b62a7e28af4047926dc5c01019e7564167 70a9734b9d723d8384bf483c509c5d911f6a1688 25ebab6b83380c069e94c322049135363073bd05 d6af461097c8d15b052bcd478c618b6ff5e10c6a 61917b2d0fa7da63d671d9ca81d0c23953af5427 400837f00c4758b2344aec005c93f44d7819d3d6 1108bb9f9266eacb3b49a9ac3d733c76731d68af 8df5e8f8dcaacb911e03448982ec10f470dbd18c fbc8e958f4c431dc26b91f95196f77013a74f4b3 80f143450a09cd7eae6e081d62a96d7eca14b3b3 da8e89e81c280e09035b82e7dfa349522c7b8e85 c66b16684b0206e1bd71907882bb64cfa95195b2 a53cd57604f817365dbbccde3741dbd83e87e722 78920dded1b5cd7afe0c8dd916b780b18e284374 bb168ab0f564235717dece05cd43a625e5da85b0 ac530a50b2f7c2e3e4c7135dbaef019ca00645bd 09d219da3dfa0c44ab928953c3a88bb92a4fc19c 5515922c7c178bdabb4d159ed167f21c63615b5c 60cbe341aaec7bc0447dc667a569bc205a1f06aa aa2789a6cb688d9e9bfa30f4972a883dbd92145d 529685e23ec84d6da5fa8b945b87b17386cadfbb 0e272fbbd64c5ab1855e8d9ffa500205da93cf99 64b1947c7da99eecfb02530cf75c211ddc74c2b0 be105c9f5058d5b57470e15136dde89363353156 c1d7e509bedfbf6a8e4814ce021bbccf58a5b705 4dc7317a42efd4e33f341cd1992ea2249841116e addbc3dea61239991370950c3f106d1a73a3ff55 618f43763704f8e5782258a50fd974ebec02cad1 1e603d96e885d05dd7c6a8008f34f7cf051e0756 13b4377ee5658eede8dca625e1a07705a3d6ecfc a89a3bff193bb6c0b69807082fe475fae043fafe 8bd961101b57c5fa7254c6be4f81dc49977a3555 03a79b1b952fc523b208c381315d58a001fcc69f b7e435108650eb13d77f95ced6e01fd4d9a3c871 a593cfa496a38bc552e047eba9b61174ea2647e1 0b6e53cda06ab5496ab7e9197c28cc9311d34b87 86f0006ee519a0e7a3a5863a7c781b824bfb9b19 8d4b1cc918e6efe4543fe693002c4f31a2d47fb6 c529665900eccccc6cb0eeaa76f12e13a95e7d7a 0dfe64bd87d43fb425da70b86be312e0f59fa332 532f85a25b4611e2b3bc98a1a2358891ed04efcb 1c255d5935685ba5d4ec28e4be3b67da7f993183 66001fadffe3bb7f231a2f2c9968f190293418f5 39180322dde955544c3ab31b8e0e9689c366ef78 58c7da52f023c6b5f4e6392c602158d1fe8a073c 8eebafba384a4604c3a754a2b9c708eae462b3ea e856b11068c25bd8a5bfe8ec53629d94aba1c5c7 c6c4bb239aa4870dd8e24d24e68950ec76ece921 3df4ea7af6b656a233208f36b3b7558e07d7094e b803e61f8f862aaee163bfdeecd587de319761a5 c38834a439f34e16c6a8370a1609f1c501513826 b1bb9d026fa0a2c5de1a28ef73f2fbeebdd39a58 9e71bfb6ef03e5ae7573adabf44578691a1b0402 239a79a38937b5844b47c2351118155642419320 fad1ddc79f5567760d323634fe09adb5ba8cf27d 5cee36cfb362c6fceb14348494749a93cdb1a2d2 28bf73e59979e32ba4952f8923db4aee3f25f87c d6c45d81e09759f98a897d36fe73754c261f9d4a 29d9de5df30bdb2dbe0fb1d1de74c3e654d6920b 4b835bbc8546269a75c79fe7b81a6313af2536b1 5caff2334b4dbc19e315b98e9e3b5978d1a60a30 8796289a7fbb2a88a44883baae68259b944de22c 91223e1f6fac5d29ffc064745080839dc6a0892b 89ab6283a42c62d625f9a0cf4d48fe76c8c0049a c9596877cfa5d559efe756718bb24370605c5b67 bed7432a0883791f9cca6913ab03b708f10f0b85 0f94ab7184a1e81feab9d5154226630de54a720c e32711d41e34c6e07ef5bfdf48eb3b14cc2f5e4b ec3d3696c2c77ef8c2715c32a3cb001422e5ecfa 9d11a5062fafabc3f39573c583552e6caee2aa51 10f4deb3e10de44df76acb637d7002fb00907231 1fa981a268f0a17f1d3ec33ad05739130dbf3315 5b9cbccd2d522d713434ec0da90e10c9b26b665e 8c5e8907aba41c1b92adbc0f19c6084e0502a08d cb9112446174e67633d26ec6b71956060a8531ab 8b1ccf2e53f1da403341957b57bbb4c318f64330 ca9e5da8c80b3f9fb09752ebd8e1d2662a55f59a d663e2342ac65b1d559376fe3e7db6582648efec 71f2577294ad7dddf7206fe30a362d29880a124d 768793973e59ce600a909a29d744e5edaee3f934 0e7e43b0471528a7dc9175c0939bb93bf9f34c85 997f911ecce2f4df036a30bed2e488ca983c3fba e1f402ac7faf5604be924bc693cd389fd4ebd3fb 6d2aa297487b38829ba832aec10ddc1e3b3d7f10 8adead794dcb8fcea85b07819c2738ada08771ae 55ac4519606a16f54c383c6de10d34725261c8a7 d4a6a915bc7a4ef12c14899214df01a840c4fe01 51eb9e2614dae7f49dca658e10c268433cf19702 350e777e101b3e0b70134f8571a598348ede7d44 904879953e2217394b364f7464101dafcdce5e98 94f1469f2ed8b7412f8bf2f8b4d6e644415ffefd b69eb35a5b634101080a43c52ab323fd1ac3faa0 e4e43e45f6d1c02c0ffa91a77719a2436dab66d5 5f3fc87b08437f11bcd55d92618f5cc8129c8931 8e0fae1a415b8e13427bf66489fd33f362054785 e188ee0611c594e582f7b3c18adb2c65ce2fe3a9 5d5c37dd97e4337a71782b42878114c1ba391dd7 49c5034d79504c2558b57d751ad1bc63006ad2d2 fc1fc199a1859d5f09454010c6e5d9e91d6ad68c 84c412b8cd2e945e630120f567402f311defd46e 473fb813f9d5ccd80270d63d67a02d31a95ce124 b51c7a8db53f623a2dff1f84ac004da9de06b4d8 e2a6e5fc12e9ce7015a7e73c4a93627a24fa93ce 709d07e8f693bd7fb2899735f3ac0a37024f211b 035438eb210cc122a233eb1c1543946f0bb890f6 242f6999566658e2cc385639da0fd5d169bd1bac 626da8c3d4947651afcbb7061e6cf07e2bd3739c 816f78bd4adc634bd31ff95086f27813b78dcc41 2a54a13ce90f9ec2b0907631e9995457d7f778ef 3209ac195e1b47e42811a44ce5affb4764d4072b 6758cb002b0bbc88e1e8c736f4a770fb190accc7 d8c889ecc66770e3d388c91860bad31a68fa0803 25b049f577776529d1e6eb1b651c895eeb8363e2 eeb1eaad119a1ee5809f88b9af203956fc7f02eb 86cb8df0f93f1a6cd07f560396de031e4a7abe08 91d62644a07fc9b3693fffbb3ccfdcbddb4db794 7ccb9c615e62c349a67e2509eed9741d5d064cae 47c73b18c4d1eed30af136cdcb36f44fa8f0229f 28309c1a2d37d8ec3b3e3bfdbce1afda1c33ca28 e0bbbfa860e57f9549736149142c9df8be72433b a659fedb44ef2a844282d3de0d5503c567837948 baa6415f1d630b695cd28de742ed609eb65f1399 bc560f0fd419cbe271e22c0801e64e92a9469397 0209c81fa9302517ac674725cefc1f4799d70d28 c850b78988eed29591fcacbc01d4c26f2f49c22a 36297d60599a1f214e8d183f978d2688236b4479 fbeeb172c70957d7397f66a612811e75fd31dd6c 2ec6da12a319bb378df586332c56261484ae1ace d2b1e55b3a9ea5743f68389a1ef864b4c06642a5 4e717ae72b7d49bd35dbb9d6bef4b37b3fdb66ae 1c939ac8a7fc954a21354672fab2cb0e8f877ce4 8351d90c77289f126aad66c145eef2dc753591e9 cbea100db0f75e72d39ae6eeeaaf6164221cdc4e 7b883cf29bc9ae40ad5cc09a58f9830a4d7a8942 5674c7f6191b002b43d9f2e89736b26c890e1ea7 852f7412f7ad9c0502eaab76579a6e0f71f51f60 80ae06ac5d83a3d219cbcca7432cae4de6403fc5 669a4e115aad6efc4027cf9bb989e0fc2ea52729 898f6dc5d884c92a049e06bbf4b3aac1f2a01f01 5f7bec3e9b4802429701f3cdbc159ac379370f48 dda4c22b944c3a1972fff24cd112a377b92f8d64 9ee06fb3c6c2f1eed7b2db7bcd750ed152bc8895 5522370d4d6f461c03c1caf31d6c7bebb90d9712 98e5622375544c0eb7ae3f8233dc676f01de80e2 d43dd36b6fcd625fcf80b9ce7cd380cc699c4666 b599ffed2123ac4a8db6e572d71c540e44031ca3 4b5f72a3b6d30d2bc660e947bcb2f924785979b7 8258df375bc73cfc17b19cc92cc1e371295b00e9 fb34d3bc071c54e92d5840c5c818bdb79f8209cc 1d46d255fed53ec1511b103af5a99d64edf82edb a1738ef6ecf934e6775afcc7045adac7b1e31819 34cd79c63c171b3bcb8b37a6857bb386b0aad127 a54465a970ab50e701cf5a8c44ddb2fd55f6acee 2953f326f02116afc1dc4fb5acbb458c63dba834 6843ead09ca062c856a7c199a247aaf817b88306 b935436bb85ffc97b3abb14458ff6d8de657f204 96799b3245092cc24ed082212edd023802ad60b8 c08a7f914caf43d37ffeff09bb8f9bf75dd442dd 05f1eca4837d1676905d882a44d112de5c92562b 6e931ce7092aecb7ce511059b3e40d4f6b1c9fda aaf31fce475ee60b5dccce765c34d9208a3af280 3b341f4168e65f653fdf7191a08978e967e86237 b926ea7f70d26818a04a8c75b68f295cdb8f4978 02ea31c21261e6c9c374cf51e67484b0d358409f 3c29315838afcc70648170e0cd12d8bcde3cb6ae cce7d0412b19159e65436c667241a7580b384263 89450c0d2b9cbff0f9c5360b3301811bde4a17c1 41736eb05ed2c9469622d49d9f41b7bc5b8125d0 a7b22b34d458ba0b53af1bf452064caa61897078 e415705f275a8aa4ec19f595c156338ecbed1075 e24d53894f2f613e8d76d61b0065fce3fe63adcb 2c80df409e6cb9e0782d6f7544096041ebfa225b 69b735d9bf020d111d5251e70281767fb4e94faa 7dbe2558f6c045311ef043f2c5859c711345491b 33c3bee02578082f7c3192d3496b0067af992f9c f0f02584af775a85e723c6ccdb09db2c6141a145 d7f7da68512e560bd9bc5f2dbb35b07e6630b7f9 eb720e2961d73a826fbd6fae55a0b7e93eef25bf bbcfaeeb0d88324f3dc8a9a85b3875e7ff3e76dd 050f5fbb39bdb1e2073fa98fb471acf01777f0f1 94bfc589ae9777163855e52b511fc90f3f4adf1d 13fcb33a140bba7391d9ad8b52a1aa7f25a09955 72cd4261677d9de449577489edb50fafce56b662 b7d9a30838189b0e240a590f610b66505d85d0ec 8e19d470d3fff85329e78239c84e54998f39f247 a1a55ec01f46433fb7ac64eae58089cc8e506a19 5a34993875502fc1cd9f21dec686a3035a8dcb92 8cac48d464f70bd5da4c8227abfc7025a23a82a0 63ac499d92fac90f8bb1c3fabc7c03136abd505f 2bba54fc6e7061893b205149d7c9172c97239cbf 50348be3339f4d3f9be532dcd4164964992bdf17 a1174d1e922ffd85eb767b7047fb84e3e3e0745e da72adbd503e5fce324c8a801d90c1e0b38ca054 2a19a64551c0a191adc60a956111f25b2a6a340c 6dfb127bd0e7f504522e80a4ad60ac01118f2c6f bb58600d9294fbb5ce39c2e273155602d7981638 cc8a46218083c85894e090228f3cd55eb5a238c7 681c2c432b735a3f67d6732264f50638b47b1552 799d68b0365eeda8c2d17811575b925a0023ae2a 9a82a5efab8b2d6a3bed6c0611888b0a191119c5 e354fbeb2467854770650aec659d98b27dc8d464 98270d267c77f991b3aecf572d38c097fecac68b 16a5a16cf1ef8a7aad44ac3d9da1e4c8706fac35 4082e01348c3279f71685ff9eeaccaee25a6e53d 92d323dc7d6d82645ae7c25a4134679009dba4a6 0b3ed50bbf81577558e79b77dd8b2c65f61ad034 203f8ea5953044b05b047a5565c36c23155cff43 d2144a88b8ccba4dba32265d69782e65635b4997 af39173450198616f9e1f959f233f980a65a4d47 2f31989a6b34be6d213a891c3725dde6c168f2fb 581d8ed65958db142c9c64e3d6f973327a626fcc 3b02dc02eeaee8d4e8ca13741489c2e6f72de230 63dfa5ae605542cb998a8b03155bdddb75bb1472 a787ec5760bf147c755950116a9e440d181a2e9d 35b4d7233eeda44587c59f0593f5599f82898f65 1fc9ae71deb1b5191c68a3171bf9b5269e198b80 200cd34e29349b99b6da38065cee4102e916d98c cf5c5200f68b97d88ad2dec4331d04db3e189a51 ee072c3650e9a008900cf00132804e83af3fbf56 90affe1dbac937b1fd09e2d5ba90ae07491948e6 0a4cf6a2b0a8352659dfd8145692ed6147ba3694 b26b91e04a541685d0d5a78277c4ab7bf564669d f321abf3809cc8b6696be3b6423a073a76f81365 84bd75cd3f05b01b765ad8be4d4a096681ac681d 4452960d95d0bf8e6c9e9c8d60b51b376f6551d9 3341101669b42a7341643d13c284f696da4a0caf f53bfa21e447f53f22ff20a2a64b7b859835e8e6 591aeeb225c1f542bb49a078d6353a7083ee8fc5 c1e0be12cf158786064bc1f1d1c4bdf3db2159f3 b3a9b851f5b0c095eede8d5913f102b4f62b04f0 cdd8aac9f751a212fcca7f01e7a5159d6ab749f2 a1cba27cee11cd0de0a46ebec89c65eeb86a302a c3fa6d356292be28d495bdd1a32973880bb31068 b3f6d0264ce15c28953af1f995738d0d4b335f8b 3238a10fb81d182766c38caaee646e3f79e98e71 a8185ebd21e1e7b26aeadda1e39c36c8d9278052 858d692c20fb15b7e897c58e428ed094423a136a 53d99e58631b8304aeb76ca3e0b62143dd445f68 224667b7be2a2416e73475803fd164f99d1081da 6721e7478d45d27d4af231c34c270a87f6d98b75 f2a5fd1d4363af84a8bbe880bc807563bc00ca0a c854fb813aabca89cd859016f9b6c9d24d2988f4 7087d856b39143ba0c9ed14b4ce5c199152a9687 6e25bc7cd582d62cfa688c2203be577804624435 bfdc34035cb07a2272672d964b855c64b81a63c7 c121e97d6aba9f566a53f017f8695903bee03556 a1738694639365fc09a7f4c49ea4f94bb45d5d2a fff6275b60335280698dbb6a609a1ab34229542e 2cec15e1f3d8d856511225cbd305a35ffc7d33ba 921d13593df8d28e981991d9a5e4aabe5ca0fffa c5e6aa71ce58895c10091db1c7acfac7bb53cdef 273915422552702005f6ad070f3c726ef4836335 0b07c0332e0153766bc273f29979cf2beec337e8 ae07741723edb6b01165ece997a1da2d172fb50e 3e684eef5ec045020461fbafe4ce58245a096de2 bab65db299f3412abc5fa28b2dffc16aff574c55 c0478fe2ab8142e746a703956b89a3f5ec69f524 fe84f3a2e63bc5190493b469fa2524ddbc585cb7 a1d72b29d902e17d92a80db3eeb00b7d0e2d7123 1c4ffe41812941046a1cf1c1cb869b5021273c88 94b70f189ccb0199130e2abfc9a87cf9c9b193a9 f1bd6c453827140e70170b7cd86de21bf5e8aeed fe91ef431b330c83f063b8251cbd3fc0bcbb7d4a 287e9f6759b3d18f48dbf6ca170fa48be2b00c82 460f0c26cd2bb10b4e44f1f62024525d7b1eadf6 bb710c9d8412c556055e4f49a406bab60142dee9 93ae11d1afb8dc6b94739a56bcf278ae3ba8c112 d5b61a29a7bf53a7ad91810072e4737d8939c642 3323aabfeea77579cf71ce92bc6e615332e5390b d32faad6427e6fe36d3f643fdf8585d01f6452ea c028ac3999a88df3c2c62a342e1918f31ea9f0b8 6b1eec58bb6b77641f782b5414f0ba2980fb4317 6fa0a5b3ae7d5ac1d267cfbb7937a283ec3c3598 e61d6d6e9ff125381d4813a5db0782eff5066e66 466bdfbc9a916580d2e70950af6fb54ef4cf8bb5 b5e8df8a4d4b82994d6be2b6a0dbbd26dcb34e5a 431f37ffd6cb1daba38e2a6f654da35dbd81c3a2 d50f15b4c4eb65069dbaae31e3bc5b86075bd57f 56c39e111ed66326b50447bb6ed12826e5cd74cd 96221e574830aea41cfc23c9237cbc6a336a8e95 ccea19047f2b1959fe0bbc0e6cf70a66576e5d15 1e029d26dcbbc3ce204cfe49d25502d1ec23355c 37b20891477f13d5f7d4bd166cf461329d733e32 781eff6363f2efb44e47ddb863651be491421106 9f3515967f6a05789cce7aba8b03bb107ae5582f 99fed4a8840a27dfe1e4de27b95374787874f71f dffdbab53a21c0b96b6fca16f5bf772f0921dcf8 000e370c48da86b40a2d68259c8f42238ca83df7 d3b28a7ce77894f343e51cd6a9bbd78cea91c1b5 62080422649deb5cda3c5ea671c1deb34e256b1a 0391b278289365f96fc6508b6c7da7e49e01706d 48cd1ec6288a0ff4eee7a80b8008f2cd9cd05aa2 06ca6ce25a2c753d46405b198407cc7db62d2b03 1e1bf37e1dafaf7c366702da190a778a0736ea02 11bc68b1196f2232e074eb965496c77182d2155a 383a57bb4bc9ded9ad28942670dcb7a2022fdaf5 d71a50bd712c066bbd538ecee5a3b089d1e4d829 b4b5cc7f8f826c07956bff054d30c84ff5e8de49 9c62c9ed74006af75e9c679498f5e91a39cf9200 6f44e913e57a62e8f4d4ecca83fa84cbf35cbd60 5e305d87a1466106c6658c64d1454a0b1e44ad36 912aa73b0dc8f3c8bc684b22e92f0af878ba1a85 362c144d2ab056c0bfd03d8221ace09767ba5841 08458ff1258fa668ed48edf0e00b9bf6aa38f07a 42681d1ec76c6b4b64acec9fa44c1c1c8821be56 c40702e443e6c3fece31cc66338d051527067d88 6fa6d008767b45244978e9b0c53998f5cb3116d8 92689753326c9a96f642d5cda16b08378ea96c7d 7d76b585c66154d3542e92329c53ae313b6d0ae2 2466ccf4a9638b5428a9e515aa47b52e476b1840 4a3ad21a37a98251a2412d22294faf1f81df6b1b 9c2fbcae2aec2174783e03b53e978e7a9fa62a2f 5cb96769844687f19c809c8b25b37e595256a4bf 43da59b8384e66f889ae00548fbbca76c55c6bc6 56d29640a2d9e59628534fc959b7f66c4eae6673 34e30b6dc3e50e80f8cfa229416d4c409fc49832 2a362b9331c4ee2e7b5473712ca54d98643586b2 3ce7eca6f9e31f5351260e085a4e720ede16d066 1ba6465cfffd6c80d1e0f07314cda1eee1dd25ed 6a9f9b2de498ad93c7d84e92f7be8c43d7fb146b bc32b97a11ff4729a060685a30ae8a019a5491f0 6fe3c84fec77ae9ec554c2eea18eb74e1982bcff 2f6b076c27cdb5c324a9ccfeab9960c6b8952ab7 e9f3dd7059bd73e4a73ebee4e5eabd7bfaffbdc5 162e3ed4fdc9e48d0f5e2c061cbb1d44db317735 34e29bcb6fef9fea67de067928e27aa861ee4063 c879639b5cd8d54da6e96ca9a3f474e734d14594 c83d719784414c97bbf89b9d6f991582b82dfaa1 6eb5efb7f4a3be4515745cdbe8951c5ee5a3b14a c809094393142acd2ac025f641d920d7182136e5 eab2b7b5092a97bfa14369902d5701ac58313944 26294003c2dbb0f98ba0fbacd2498eb91fcbf0ec f477820eb11f1af06e73b4f022c8059a4e6b78e3 c71a5ca053c4deecd0843da72208315964c45569 2a2878e14b3a04aefd74efd015a86ff7d4efacc0 6b90261785a05f79a7ef429d66e15302409f9d78 a5c9d5b01d592c3d619cf7eac6ac86978a2a159c d7733ecb8cefd4f46b082145e7dad84b3d675389 4d4db52fab645f470698e36b2c3580dcd6f7ad8f e2eb9514adda76bfa18b23a58038507d9030f5c5 c2a9e73dd7a7fb9a04aee38b259f189f5d0bc610 de7ec6b999347b86b156ef40014234372912dfac f8fca5557f1734f828e5f8f876a4f200c7993d37 f09171cd444f0f5b0c5570d484f18526283e6cdc 4c0d9220766f3bc7794972a5796ad898ec6ad879 a3b873763dc004f2739099f55161d3e3a5ec671c 6b717430ef4cc75169b4de8573ba9222dfa32d13 11659a3b1a6d9c951a61c812e63d73de4a264270 4ded45556e2c896781186e19c843cc298e0cf140 a7a3355724e86989dbe1d985a63274ccf29251f2 de781ab923da6bea54d6e3998d63fd8ceac1291d 74b38a9eaa123bb6e97aebf90d2d186f27bc6a4e 0303d05981d4e72ff831f582e0cf232ec804d2ae 2d053086bfd08f11997514781f8949c9fb4a017b 467e0353d1ed92c2cdd297b4070e833f8f269313 1398c9f37f6e2aee88fd6d62ab0a1b50ca4529d9 ffa050d4259d859128d75f34368e0430005967a0 868444e2dcfad98ad93fba8bafced1bb0428ae39 6f7e10ef918323380c8f2f1147c443608206d92d d098638cebee6123d756893963770e245dc96d6a 60afad7298ae532c4d51a6214fe118b0c0442a0a dfafb9953c70e936ccb9a3c6772993d43c9be4cb f5ba6be88f6da2d66c52bb039f224b274cb68ca8 fe8e22a60b89b11d803880aba12a3bbfd43d37a7 197eb7aa1b572796a9a1b9fc5d8e8bf52c85684b 5f442e34c294153e1db463c432a0d73464aca3f2 54d48156e8c9953a74121afa7173cde21f3f466c f00b06f414e6708f9381a16a2db46fe4e60e4ab8 d8fbb36687663b2b5fd474043b63732bae8980fd 28a6cec3232c02ad1a55b856fb667a17f16e157a 660a32a135b8bac31aa59c495ae68f9467193bb4 87d4aed54ceb44e31c22a0cf0e6902d3a09ec84e 323f38bf35f4078e9896a8f01a0535832c68ecaa e8f4ae98679f93c18759fb23153dcc61060356cc cd7860138957fad20e630957d3352d31166c42b6 8049a7005ab453e7d10c9f5cdd1e0831ed5374b7 9bd7986c4952c2f473c2fae2eee8ff0aaea06a4c 9355e5408640fa412254ed37ce3de0789cfcbade 24f390f44aec775aceef7578ff75809c03142b39 f5017f5afa9e2f2bfea9fa9fb20ee8989a51ab2d 1254e8e3d5cd29b3ae67ff993a0c110e76fac270 a8c0496fa2525513d8cf81668c57d783eee299d7 5713f323069a50de8d0dce8bd2953e1d142b46bf cdecaf5d747f681ee6addf2b2d6e58479e36da91 ec4219de84f9d346f082fc668d5979aba519c047 d7d03e0bf8481ce48fc6bc5c8d7c1b56fc46575f 6c7532ccec112d6faf8ce47f1d89151e4a46f316 1fb969abeb8eefc633be50e11e3905d688526cdf 64f141ba6ced44e6a0b9f1b1680a8edb5755415f 813dcdc3199fa5157a3b4a83dd52580c5570e659 a77ffb5e2988d7ebb01f59464707cebf33a0adb4 863343903605d610f5b42f72805f993c401cc862 26f16646507d81a840cce049069872c431905ca9 3b41901eb9baad1139d245b6220fb502c7c546ca e44d74fb9848cb5812512da7e216e73c6bbbdad3 9203d051d69e9176f2bb659c70c29cceab90b084 7e4b557bccfd166ac746c73e98e7c76e6c2c9d57 6757683347f947e730ff668bac4a3d578b8044db 1bf0cc47ceaa158aa24880be5e7f9b66dd67c956 063aa3646d4e6a39baeaa5fa26bc329139f05914 a1f4a720a824ed2e11d64e7fa22bfaa6a2da7eab 01042e110ecc152ce807322aeb53baba5c06bc2f a495613e9c6d52bdf921c9d005d4b7206ed6feb2 f59ff1f05c8742ceabc0903bf453732976268ca2 500a9c4cbe3531658e7b6fad0c9298dc38e504ca da5a1a010e9c2950043daeca6766b5c1e5f01e9a 9bd78475c38ea4e4a2e91fbdd3d35e79ec292cf2 00512092012a8774bd3e8bb1d98da705f3ec6515 fb295b01df70c24b149e0ba5e4dbe2078acd2c46 302f209612217053c796a452b904062542326532 8ac9a1d9a2c1966591ab441bbc75958c09949a0c da57ae2e35af3f04ea35987b06bb5becc0801f9f 55ad40f4723e2ac65f197f32e7f00983ee0a464a 34b7d1942f56355684c8dc5b0cc3d0100c72184b e4539774b3e07c22694fc62b945868db06ce4458 eff0125c1711790734301fa41e4c4d9fa7bb9d85 b62c3850ef95ec00a9ee8b3fb27686002999c8fb ab11ffdb7eaf4219bd35fc99e2c62029736991fc 7105186a5f9a22d9a990f683ce7774ce2c585674 c3f5eb7aaa55c037dc5b466cdd1aab3a6fd6b98f 2a2757d6a09801fd30a29af2e0b05bb8352c1f1b 3c66751cd46caf6e8744767793d15ede88a9f5f2 046abc390bf18c6bb5fcbd178ed711d32e507e77 bd4c682017cf089392a47c3f0976649c11ba39d4 9d0f4034155f29da792dad6cfe72365fdca2ba1c 1c3fbd9772d7e68cf002a41646dc519343a180e0 a5ecbd575c741b310e885f2c83e4027a934e6f64 0bf43b396c17bffd42906533d61fdd0d4e8711fc 6ad575e55af96adcf468272a6c6387db9f866505 9be69cb27221c22a01dc51da7640edd5ccac0ef6 8e471cae6e6bf94b9d4feb1675242458bade2b5a abf8f8e90d573342aa0e8ea3bb13d507fc31ee07 9d65dde56b73b840e1091ecbcfd9c52d3905359d 63d11cedc8133bbc611e4943463ba8c8bff88951 4f4a0f41d268ce510724e61ed4c9f77a1e23d666 d6332da4c26f86cba02a24ba1cf9085a6ac49a77 2f3505cebb8048a3ad9eb3aa238a4527ee8a354d eac246c60ddf8f940dc40da34fa8b458ede41004 6b8cf1705666660973df8888803d5f0c132227c1 13ede23fedae0ca8a411d7adb75bffbe4d2e9dd0 8871255c04351ed846eda1ecbdada488c32b6d38 4d2d5345c335af336b00653a4b8f727bac942837 4ffe5caab5d0835e7783d7c552b03c4b55d15524 fcd92513a0a2aee5c64764aa5caec08946459fdf 1b213a4b393285804fb16547590ff56e8f573544 f34291c8c07d1a11226e6d0d6261b32954cb4779 b2facf0143258ffabd83998501dc324ea0d70b2e 056aa54a151d5dd84b0dbba14764dd9689db4bb4 ef947db13678ba04d0871075388ce2ddbd29d3e8 b94d77600ff45d7546888426cab34a326f0c4d45 e36f27762b584aace9c899de266e8a9e9cfcd4a6 c5a6ef31fab26d2bd21351959a0527bde9540736 257da44dfc069b910558e7fdc365ca204e44f9a0 1957202b3c57103ff6a2ecf9dba1b7caa59741ec 24d2dd3c20990f6c38362e96cd31b7905ed72c60 e7c81b1ddb71e72ae3f9fbe77c3447020240e308 59d25e84e0d747cb7e8fd10cd60df6689fe0b3bc f80ef93a71dff7ae697bff7264a3cebf74c28163 f4a00e67519f6fbd9399db44a880634489676e5e 537136db933b369ff0dadd0a35b0f8483fd1f5dc e40a3e109cbaff6d5f4ae9b1cdae253f96b3d407 d6d27ab8bb68fe69433a04eb5854656dc88e7ef3 5a193675fb7907390631c620e2f98a12266347e8 7ddc87fff9a6e124d2ccb1ec772352c4932446f9 48df26485429a3c1e785dfa82a1d64a4f3adda73 2ffa80464ecb90ee77856939114833c074327cb0 0dc523c62986c94ec14f59cf522b4d539420f5f5 646167fb8dc29f3b080ddcf112b5e75dfbdbb9f4 191cbc48d3813ccbf1d12681fe1084fab3c36696 d4dbff15c352ceded8459413be316c62bdcb58ad f8c73f821435298e7e6c388b26a98028b03d9ce3 a77d93c7735658c3a04a7b6650607bb126ee2b53 b72dc72adcf40fb62553db673ff70eef39220c96 17f2e8702863666dd08403219099347711724310 8f0d229dd89916ebe800228e94518a6ff2beda3f 58df9f2d81b8094ff3f29a1c22b4de1b6d7f9eeb eeeb6cf0977ed3ada7ae47fd5607d0e22536b191 c92729b89c5365414978f70453704d099e491eb9 eff1a89624a28f73f080b92d3569a746933518b0 b52e1ef20efd0bae3bb2749d28ce01bc59f8c84d 32581310a59093e773751cd4753a3e156e89e7e8 33535467399804add0ea69b5e7faf71c695014be 21798511e6d33e40efea39d6ab68c640693e81cb 886a9550c56d9317dd85494e1719a3229348fb3d d877f331b9bc248b9880c2edb099104cea420fac d585223c6a69512f4cb56d522d460296599a69e6 2a2131710df199e88189091ab16f307bb1089988 7da872d4edc18917deeedc4dde12f9bbe57a500d 025e5ec18575f08d2306a2c9ca99cc87981e9ee9 e809c5c57ffdf627915db95e5f94e895f90291bb 3db150d5c0f6dd107902b3656026e18711f7b959 2371850c27408aea29c0e98b792db5b548a32eb9 03493c243c12f606f1a5716841c2e7eb4185f32c 916e120ec3bbf7ea7089e93b8386b0facf00c4a0 76c7c679a947122e08cc5985623bbefbeb0681c7 085931328e9be893c65b674c9e491924afa88ca4 c569928e35f25ab269b0aeab25423093f9db96b5 195bd8e7cc20468054182bb08fac2e80b80a0bea 14d26b51fa983c686b95a60afdfb145ea61c1bef cff105fd47bada8ce5bafe07a5e6f85ac2b8579e 85874fade92d329fb0f6d7f824e06035f83eee97 681a80c258d2011826c12d52874924843f17728e 9fc1b9c95e1c930caa9a711477e29f8ac94f8534 78bb2e6ffb5d7e3d4d78ef3128bddb5f1f83104f f74db4f6542d0f008fef614440e195ff724a7879 a409f4049744053e5958ea885b5d68524f15c6c4 d182417156ac4af142138abe951aefd6dea35259 9e45548eef592b97967607829e844913b6ced577 86f9fc51b25f749858999ded032aebda17e7aa14 24a5c2115889e874b9c95a9ca1ffc59c80932d13 3333a6ec58d3c2530f33cf8132c18862c29d74ce 702a0ac1811870197e64aa3dbaa04b9d9afea29f 349e8b733e16eebb7ed693ff5bc0588a3b12cce4 04f2417b5cdc29bc08e217f8bf6f2a0cf07fa3a8 5793453bc1f2476b4361c762611da08325fded96 62ba43bb6af91568e53e5ddcfe29f4740ae41788 9ef7eabc2075f31510565bf75226e46d30511cf6 ba3af5641e1244e52b148dd008e544d4631c86f5 24b32c7b053e73a0a3646854c998a2fc6c653ee4 a39fe323baf2b64f2b79c021b2767e05b2f98f7a 383bc1773a09bd9d2356e788fcf7bebadee489d8 0487fdf7ecc14fb4eea84f2ca8dd77d62191df0d e29447636011bce47e75b8943840e87ef4db9ee3 442594103964d1441361071ae1f367dce714bda9 d39844eda0194d17dc47d205c5bc80ac1b4bb296 a6c9c5a3fd93382c758002354e5e362516e70be7 b883b914b2ca6c0e6d9908c4611e9c4251c2eb0a 348d856841f14014714bc55206cdbbfb4c849445 e17b362a94175aaab6ffc9e711e95fa80df08acc b04ed0097dd4ba5867062c16ec2d654e72ac6d6e 315cbdcde908c5e94fc7f283c2b9d0c76e63f4a6 7f50034e56da5e6562eb3e03c813263a5c13fbb7 52f64d507d7c5d867f3d8fbeb97aa52ad664ec36 302a6275995e2398f18aa59ad871554456d4b68c 979e7f28b2c2efbf113f898b689d6603bfd274e0 7507cd91dffe48de1b52cd1060610332753dc890 bad92e9378ef8164cd37f1c190f92e08ec4c5836 e8480a8c7036b9965c4a7424fc438aa35e4d423b a2e9980ec557656d248823fc4366c7fa0f8edb24 7b65855389c4be45c90bfb866eddc9451a8e1d9b 16d2a8ea03bd62fbce4078450c714bab9625175d 3fd6ffb00ebd98f4e93fe0e8db7a8c4121a6c670 46667fae98f6c8b5ac627d4e0ab717413c9abb08 31515bd149e609f6f676bb357d461d6e83a01608 373b3b573a04bc6dfa83cab99bd9d881e799f741 a4dff281c4621b70e411a6013293396a1b673c70 aeb39781690a8641f1164d35fe1651f1fbfe355c 07639d4e4af2b8232ad4835ca890f2c7e1ad7414 9c97ea3d520064dde2bd147946e9181b3145dcc2 dd45f9e65664bc3f4bf324e6bfbd5053f4d72e99 5b6ff83b93434abffcc48d57b1b716d5194cb2a2 bf8e7fc878dd464ca797d2eea74dad58332261ca 3f3bb7416475875a443b77aa98f7f4886458c207 73ed951374c89b5aacc089e88710118f4fdfc203 e2c2b6d70dcad60da704a37e9800948dbbe469e2 9cf083098084c9e450154138455c57cc2843952f 2687815c353e29a9251ff41bdf334757d5376daf d72d5a8a07999ad84d0a1197db00e979b588f054 22cab0ab976068d4b49ecd7d4fa02e65bea4849a 10c95ab999173de41a257a626d3cd89017b52b1f a1d693a7da90ba8eaaca8134f36260485f2f051c 9243cade3cdb3c1cbbe77a3ca0a46ef8cc593d47 dfcc241369d1b0f5abbbc163493bfbd3273bbc66 6258abfd7c1817492550798d663e3e5a5c3a54c8 247eb3fbb0690de397e3283df37eeee8dd53a6ea 2b6ee47fc7611438ab7f2b0584cce3cf086ca6f4 20c240e9c512c3d4966f0f7ca4142b80e8de896f 9c57c6f953bed6e9348cd47ab06a132357ddf573 db071da5fc4d62ffcb9e97ab8b5ed4a040c01de7 c43e91b217077be43e04f48a2d014d096f274d59 19c08a7c0a935506b65ae130bb5c1edb214cfbbf 4c4110991857ec7ecbf5f1ce0280d3057b4ed9c7 f595b16e67adcf5278e9bdcec065422cbe5c9b4b 0f60305db7c4f3476af529ea7b778540e06a9958 51efbde55b88dcd80688a6055608daa5fdb30a27 e7e33f0ca3224bab3d75b40cdce93d3da36ccac6 ed99fde349f38f1f1f5250ff4d9a02faecab9c3b b3f1701f70ea9a610e81a4bc08fa5e1ff3ac9a93 220ec7f1827267091059abd467b88137e717ad94 81a951367ee99d623c35d9ecad79e57cc24869c0 52a4b5eb30342d16b5272d769b033ad90cd4d196 8976863ab2f8aee31a6222a0010e62d9b08a3be2 2ebaca3cb6894916c37e7746e7f9b4c95473cbe0 e3673ab836946e34bc759df32499a89bad460878 5387bc1373db5438e5a06c0c81b80ab717398163 aa88b8fc61ed0bac537901196b0a7b7fedc79ad4 1693e7fa706fe6c8bb414712ace18f90b86c3585 662ef335d84401bb58c2cefcd88b41533e359a1d 2c71440a60bee65291c6a88981857eb86279d958 3525c446bb0056b05fe6745085ead8ba106403af 02e128e74c941b83c3f8c1aea14c1387070c1393 0b27f18583f624ac932f8d5cbabbd3b5407efd9f 595544e72ea35d3e85034389459821b27716520a 8697661b71b53e5dbf4e7e3755f3d6daea35248a 2d69d1b3639238a9082d9dd1befe7b3b97fa8ad5 fc41bcee84c9a43fc507116dd63d8b62dae7fdb2 ab07be7a8d96b82155c8447d5e73bb7df8c9e6a3 333d72ebbc833e2b2424cc63a0d19b23be9cc3c7 0d97d8d35d0cb44c8ff2444964967acfbab8aaf3 b6c1ba5ef380ab555007b98c54c5af03309c1042 b89473a34ba4911150e72d636e7660763c6eb62d d9039e6c980daefce4e5e3e1acd81fc74864283c 963a62c8dd0f0dea807bd2ecd041cab385e54daa fd2a050cb03bc8a496075f26ae7a8659739b4647 69e29d707000c0414c64ee0b96530628364f0cc8 dc31e424924c5e02936096bb158b6ac210979f68 f73c6e8e4228f1009aa4820521ca638945e9bd20 62485dd6238c815d2714729fac136dc37a7770b9 b5d6c624ae39a2fe49d4107dd05af163936b4d35 61ed44cd0573769b198807e232ebc005561d62a9 2227edd47210f18a730d6a9abefa8256e09b996c 79c5b318678348c4e3b3fa7b19f0f784b334f0e1 3f31a4e61a2aa5e1cca17471bff1073f5652a649 d4e7eaa96c7270a12b833c6d7430db5f1cd69405 0aba1d2e1c1885f67df744297f296f8fa1d41202 ebea34b830e20160c547ee399b0a89a42528354d 97e1f9bdee0f277a10348208ced3c5ecc52fe030 5292d8fdc00d79026d1ce6e3a08245308f8d60d5 d26de7235ab9db2370396b262cdb604814aafde9 a2296b5a2bde683ee4d11558e583777fb3e24f23 514a3ed75f3c8497c6dc15550d1f25ccf79c1734 1a9f29175a7be590bf6e9af55961cfc1450b3f62 8f9a792030bebbd18185c86bacafb1e134b34841 02fc3dc6e85b78e87f8262532ce8dde6c10cdcba 035535dd27f4661db5b130576828a4dae9246175 863138e4529b0176a20a2927a33ee1bc9ddde4f0 3f30c92abead39a75bb0ffcc3b4adf424ceaa792 5a18865fbce5cfd9dab6db8e86aa0fd75782af5d 50d4d13b68096b45d1b76b3ced4f18701da68a56 8618f4f292ecf3547c230744dbd0b5c73192a2c7 a2579b15708e02c1acdf28bc929754b58d9bd41c 041c87f228eca59819fc9af856d9982f38c4e973 78d23b58a76940d8f58e6af1550344598110572b f04ecbdf9d38ad1d9b796bb56a3cbf981f7da4fe 0c7c05ff628c54f8b8fecb0c9b807f92905ded70 cde7d17a518207871133990e4427e2ebe3afb2de c16d04c234423f448de65f8a1825658e9d1544de 19090e57612572aa6007c4ea1e1c752e38ac0fa8 a8055bd9aef058d810836fe24e73e8fe6d8527e2 d4ed38deb180211c1f6744691a7147a77e63adfd df155e9611f1b685c31e895a17117f098f8b64d8 963356453cdded1169f9c4adbf5effa8a16caff4 9f5c7c72eaab5cf9ddb0cead7dca609e60423f3d 46777afc96ba104bcfffddd7a1406a3c21a80f39 1d29383b9c8f4c77ed93584b183ba8c1a314c155 bda4091c8e359bfc3d2ca5f613eac84e37385f05 16824e42f60e3c2d21e0bc3f81a7244b42a75e06 af8797fd57a4b341a53644df2f04785175a8096e 70ab6bea7b72e633ebb43fb979da2ffef6640830 461d7f7677ad4aaa411cbc66d88b7729a05f8b5b 52282aff92f1992955beef6fa70417b9b09410c3 0f337cd8c71673748cfc3272cf5f457053458b47 0c7714a4f95ac3f8981d44e526e35200e8512425 f76634964bd6d89dabac201afc71c392db27d5ae f68f44037b0ced445391958b21e1d862112aae80 6c89858eeaafb8a03f709613572ee28a48ff0032 73e05ee0f38d2f206cd60de6c84a8593371d3e0b 2612c8303932ff5a8fafad624f39ad220e65e72e d0f03270c6429c25f275af3baf0680f4dc69aa10 8b643f37be453043787bc55b33f1c648a9274fcc 586cc5d9e7aebfef68491efcdd2d2621ae8b6b4f f4a320d63763ef5598ad98eb5f9a4da8648736a5 e2a8fa552f782c6dfe955ffd66bc3576c55d72d8 c95f0c44e4c765d890aec8214205e51820224161 acd96690acb1d5298b429816a5ddf2aa5ad7b8ea 96f140127d8092590d12f6a0f0474779b780ae44 deef4bcbe3f9b1ffdb112370672b49a29a34f6af 35ddd3d327dff18a151fcdb52767fddd27e9736f 0121f1cb99c979c68027b16660ccca25a9acc43b 37aaa51a4282e6843a16f560ee3cae2333564562 7ee0942d174d95cb9acf7aca092a883c43fc6cdd c5bb21e63eecd953a2585a0874f447a43d760aec 06f03f513ada2fad54be0a113646e44146bb982d 5cc24e46160f96401ed15304bb893a10fef3ceae ee98cfaa069f12ca4469caeaea2301c10537df5b cb0805ff8bb419be0862283bf395e10eadb32792 accaa33ba8bde121822be1af560944207b1c1c39 947ce453dd787b1d398e5935f768abb3034ba42b fa64ad6c012d104c56280cfb9799297a7884fca4 7c7385b77478d340b548fe63613efb6e0dbafdc2 3259ebdc73fe4256876a2d0652bdcd0ae186629c 5c76eeb103ca9ee1627db466461f7d428c5763d0 1b1add09d1e189297a0198fd0189b0a505616106 03976c746507769e95416b888af101e01d8a3361 c949e68309974eec224f3709667ab2e73d2edf80 a129fb2eb5e4099d82d15e92ba1f83db88606ee7 07d2fd16487913f7174e0e7d2e626f50121c344c f7f14a6b4c8f1bdba9975f6da08e88385185c229 885cb512a28f28cf7a9bfbaa294398bfab2044ed c67c821d22016544fd0cb52111314ce64e9020a0 d720a5ed18a74aa50c1e53eb486b8b62ac36c713 9069634da8b9b8f0aa037702d4ec0394458ded10 86645a3177b6281d66e1b4e35e1811b52338c11c 678dd84fab164bc2c7b31437ba1a98a982d50aee e05fdf5204a0b9955e4d8f1ed38fab19a2c8b1d9 5622efa5487a31dce38ca45e71ff7208bba67778 c250876b72f02c2288d02ae211d5c0b7920bf981 dd215cc504ee085a031aac6577c81ad8ae753a7f e391260463b04eeaed6769f316c95ff9088b3a43 a37c39557a4ba52f53f4e83454a363bd778f1a3f 34de2c54dcb4b73865783cc24ca0b96969b1e87e 25b99e1b211e78ecb9ed20cb803594b1959b3000 5c7816d4ae274af72fadf354bffe70319e4bacdc f43cae7dfbb13d56ea9a8c3d1d816b2e4fe1cfc9 330d69481ece2323d04de2ac8f5b496e503f24cc b6e233d04a02cfbee6e25e62d8f033510b08e527 cd5df131c230e5f47b03dd5a93bcfc9d0b17b019 cb2428d137a134aff9dbb3bc8a61b9bde38d8f36 3b91672c6c9c1357190caf3369545b5150fad649 3f5074c9b8147552ec8e1592d74df60c9e46a50e 2aed9209cbce02686c61aca1c580907f2b174df4 b5de18522aa45387382973031ce9c4861cbc8422 06bbc86f9ac496d95efd98bca3d26a7089d56c94 c49afc5cca89af0112ce7bcc046a993e37363552 e73013afd487afb3ea4b564fc29f101d9412d629 601657baf95fe3ce7884389bb00695bf7bb4330b 3657bfd59850a0b74e2a94c53c72348513abcf50 31359590de49b8f5098a8568432929555645689e 9fab8dbf3eee065837301bec528982ffc32c6089 b3873d2aad6a8a8d58f65a369499771db8162013 e4defd9cd3b98b19fd9b2a50c95559a37d26765f 65b76a75e8c8a28560f096d3ae61df52aae053ca 87b771ec8ea8ec5fcbf08c24fcbf2c34ae76db9f 698ab1c7481fad6fedeaa698dcc4d7b00deea0bc d57d36e0580f4dc5dc69536825fd7f26dfe9b77d 8cf4a51e31112475d0e67fe0ff6faa555f59e71a f33d6146ddd5dbb8fd4a5f5555aa430788c85c72 806831ba03e3a5265bed7022d3d723751c2c6eac 1ee4502f9b109ca2c06d030c1ec12c1b733348a4 e1523407df5d05d6019b36faac922ca53fc603e5 682677ce50baefea11903b0c868a4f5efedbefb2 1d22d38baeffde8996987d5fe2a6fbf91ad3a4b9 33dc11da36ed72996df70997833e5510e7b45c87 2f1aca8ae2ee142d3bb185c204ee5c64b52033ea a958ecfae0c0277bd5b9755e6dacdc480a98d652 7e3ae78ed13627dc420cac7016921ec85d4aa947 3d9118d7f619649dd6cbfedf510fc94cc8365ac3 51d6bafff4fc1e0cbbb8e2f55efcc959da4722a3 53dbbfe3e7d9e264e3706f3a76bd668b6995f805 606d7460776dbc264fa3822396f8ea47dd628e36 52ecf3ad8b0875477bca362c855c22776df30a8b 47c2b4bf8e54fa56e329a878ad13e001eb83d6dc 0e4d9929cfcf243a29b0d0da63cffaaf250fe891 159f5320f9466298e66caccb5b483ef6e5055b77 958e418f8a950b1ed20d690b8309262d05827a01 c7deab623214aa39432e4ef483be4e1b5b578dbf 40e8d85965c90166eafb4425f2159949a2e764ed 29d156221015801c663e86e15c3923c768d62caf cbae76acf8e8549ce8b510ee9e5bf10b9665dce8 3916bd328bf72d08f372edb7f79a0d6958cc4f6a 211cb4b9731b40e36b51c4f1e29b0b5491e280ad adc817e32355e6f5339c35c0c83dd521cb0ad9c8 4db9e48d53dc3b098158a30c418a65ce1d095c24 0fcecf8474d694bbd425388b62c67925786c472d cc096b110266d6a71ced8a3b27ba3ad9589147d8 0e8a5f3c97049b8e101820d33c521215e46c9d28 e71a9384f9d3b9cb5dcb280655a1fe1c86badb8e 14dd6be9cd21681f23d8ef63bb77805b24df71e0 e8668ea90a39405a3518b3a02071ad1f94ae6476 b617dfc5df3a6d6a93ecafb79fbd1291295392b7 2f52ca511ff7f9de4ae3aa2142fe55997c1c3b16 9c184a81ef4d7110ed1f013765fc58306b646fcf 466caaad9e55c4d3887389203f37935fa9e2e74f 2c57eaa22af0449cd931b6c2aaa28226f6eeb5dc 63704b7f57b5225a072d58f2dc84b00cec2f9f6e af0745134f8e9d7efa14ff0cbff2bf05e76bb7c5 371bbf536cbd2982f2f25647e885b061a71b662a 877e46d801a3a6f47a1cb04231ec22a251c1d05d 0208e6faccfd390c8948c344869d283bd0612369 43a3d3db002f3040cb7d4807d65cb4b157ed563e 6e2961f62120e0784b352802f032a7e023a41705 ab88f69fa9fd45e21854fae16956f47b474595c2 6117350ddcd962e464318e8757399155507cb48f 291097a63daf3743365e8a53cdf56e2077802cd8 883ff5e6ba4d94da068e855ce1fc0f309ea7f382 d1c0d7f6556cff41c5ef4dc99ce5502a5e48fe7e 8114297a1d628d8f277dbc21f05e5861c1be9e72 03fb8aece5a640dc3db4b69e79b0d7413a0aa4db 246a128be0e5e83a193dc73b8be1400867ceeca4 8e5192baf3c5772c63bb4ddabebd4a1231950409 e1d46db6f7bed641bdcbd127e18e5c4c796afb65 20fcdf84403bab2f83ccca2b7b059b4c5f838764 73be03970380f6b4ee6bed206672290f2f700f13 49f7abb47ecc6f5799be16aa057c7382bbbdd1cb 9f09554d741c879c37b50561ff4610a635328d17 4d1ca93260487e72f2c9767892e100fcc780127f 62b3f81321d59bc796f2203a349415e54395ec2a 1165c6fcf796434b5924a747527a9d31e3e2513f 4f31943f01eec31d76c18352c3c8da5059d50184 465acc71768d23ed3f544ede236cd8fa8d107cc7 4d67b831ad017435924be58ae4b91d34bc302ab1 9c69fc7899a019d48f63185b1aa1a1aa3cfe36e6 ad576c81c8872ae8f3e2c913bf1a7aa89f2be46f 792ee6d70184c078a4d0e7eb7a5759daffde9575 a726868f2a0352dd69509f29b228753f066bf0b4 d9f9802ae6c4fe2f0e81b3bb033d53c948dc44e1 bad988b5fc9f0656ef94d7f60547b15349f925d7 82c2cc261fc2899d674cae1faf4c3b3f9ee4482f a6e0d1325b70c263ef61bac6480a5724cda69d63 e122a48ff42eb16f07d1e4c60fa5e798c137c133 a57a7d284d6b29d26d661fdcb2fb5a568a3d6399 bc080a6a5218483c333d8a8d62ad3c9f42effc8a 8ffa9ff5428a7e90f5946ab178517578cd5dbe7f 013ef8b186632b8e625a7f70a688291e4fd93204 a297703456ffe8ec046d25411f087d303ee8527b ef54273ce5a79fc9fa4f268c67f647be21854a6a 3c19fec781e2176b5135ee5f082c43008f47bdef efc63d151901fdcb6dcccca38c723091ed3670e5 c03761dd6db163fdac549c68dc765f2b1b2de134 11c48611038c211b9272a39eb1a558beb257ae2a 15a7af2fdbe19138fc1d279301322941d72df051 50e4a4832f60cb11953440d6ab5cc318ba6c292f 71c67854aaf9634bb00d3f0cabf04faa45b1703d 726439a19dd7ea4ae87b185e68bcfd348cf8fdc5 a7db2e9b74cc69f9862d3216d2c857ebaf57dc14 f126871d033c243282abc4e572d68f52a6987d8a f2b2a9202d15e047129f223bfd1046c240f172d0 eeac604c28012dd95c83af6d9164fed333f5c587 6deb85b4571275288851976ec031caac1101dc3d c1ea635a6606e7800f03813ead94aa660f9d374f e244a66db7c5f9bdad8697c4af31e9f33126bcac 470fbe91ac813f08980a9603b4b3a6d77a903825 901af97eb7a61588afcb32a7747c167c6736295f b734fc096d4e3d6534a21f67b053b4491d67513b 67d5925d3dedfb92935a3983010dfba6286ea636 7143eb17c6c1cc9c78c7b6600f30452f921f7f8c eefe02ba547f850e9da5c56892a9ac45cfbb29c4 a6d7a717a154ae35ae26baaa396978d3d7ba4a68 b59ada6025e875ea781d2b0f07cd2cab114543d1 9948823743a42effcd85c3ae147daa1d9d15d31d 4bcdc842c6a4645265230b921ced574dbe15588f cfc1cf6ebf8f8bf9f534b2d640af4d4564213467 5a493fc61cb370b5c2c0c2487aa18f8ca81ef067 14fa0a028a6568cc9c660893502917fb960f93d7 cd8dbc26b3cd8def1aa00e8391bbaf18c01530b1 3aa8587aae55ce50f5389923521388a467d2a7ea 91d038e332e1b38799eb7b5e594d4fb7e93675f7 62dc6be4fc038b2526ccfdd2e9a31e424652571e 817a8062a283827aa22d90a8d5aaa201310effac c2027be6c0363d6a0011bb6df9948ba734021921 fcd9ef8c7d66d8b2af645be760aa0f99a3c3cbac fe4e90763318d06f134b4093e7fec2f9dfd74dcf efa8d3fb5a9c3c28325df28cfe4244711020be28 a4875920e015c40c97023e103cdfa28b21282e37 0a1b04837ef6f9793989fc65d48bad9c7df94bea f55a391d94e4da8c5eaf3ffeeab3a2e27ad6adc9 fe9218330241deaf90871bbdef5a671634bce81e 2c2a83c592a934a35346e5dd9467fa8c4820dc60 df92443f24256ca322a777b2e39623a4373fb681 c4cd5ad10f765a8afc4f33e31fbf919c765507ec 5c507275ea83d7164daedc690b3f8c8879b21e83 0f44d04cf565cc434ad0d76e7a48e41e882acd5a 50ffb4ad4a36804c57a6897a101a9d4a937104d4 91ed94f3e7ba639bcbbd97ee5319416bd64890d4 750a611301607ec8613ffa1e07ce863362a27570 b183b1e717e37c7243b99ec18b8d697aa8b0a1fc c13ff8da9d0e6d04c5f530a992501fd1ff3c3992 a5eac062f964e5485f309248e295d8f0110b2dd7 224322a9885f4b5b452d96bfc68a226ca0a02bd3 a0824bac4caeee87b7f21e22d362f29c9dc8843a 6fcc6255f4eb63135e4c714d65638c524f8278bc dda982bfbd1c8a2f3879197c08b04775eb95be82 3db8d9052c1a7e486657de0b16945b9e2f6cec2b 2d57dbb72c691d6b333499718de50c954ff8acca b1e0868ef0edb473718fdfc8494ea0e3fe54722c 41529c00be9ed9586eec0e9407c83cff258c0c8e c88b214251333b7350b10b27fa2dae683ef3b602 c7bb17fc14cbbc27ac46b7ee34dd8287301a76db 5defb56428352d12eb1a9ff27755c37cf9c7cac2 a92d2e3aa03be1a7bc634f5ba71e085a8a48fe89 3988878bb233c3354cb4f3b3aef7d30c73afc465 586cfb543c7062b0df0d0c5bec468af01cf7fa53 643c07c7d544ad83df7bb855cd90aab8651adbb1 17f31180eeac77b4377c5a854bd40ad613e01210 4dee94abce5cf59e8a14f54a57dd64ee480aab01 9298bd4d47c9210c8ed8c353d14fc4d06e9b9744 ea4bc9f5aa3517aea230ab662dde65592ec5a0a2 50b50a73de61b9c6d6482b1baa15c6b8b8483baf d2c8da09ff1faa9d405d24c9798c41711cecf996 3f6b43155a0af4c5dacf7e493eae2c49caeac858 d9f60de3ad0efb4241b060fb174227ec8700f90f df0dcf4dcdd6bcf3f573c3d6dcc9eae9db6b9bb5 4eb68a4fc61ea3b88fa0c3361393e240b12a1e71 74a3b0670179ce95695a9969fd1fb549de988f0f 9e74d60fb0df2a1b90fd16109e14c1309a2743b7 2ce8f4bea06100605980b80b424e62e988ae1088 a72dd21df0278f8c70539083ae28dfa1b1fb93e2 9ce9add4478a6fcb0f4c328ef1424c02fcf082ad 6d3f5e2e1b6fc5ac57c411d4adb91691b9e96ef6 a60d94f60fd7a9fa869ef1aa4101338e2e982938 41adfd261f7f4544d0a982a5c09cddf455afa97b 9a8c7cb2648822d9247d083f80a352f337ff70e0 d8c506a48ab502f34b44bd173e6e9b836764d6b3 bc4884df2a3ad22b2ed32eb044b66ab298ad4ffc 1e1f55b0670ea5b1d03a4d9ac56610bf2845bf4c ed8786a06b8623938d82c9a1c38982b36df11139 096c9254dc09665a7f9645281306c25fe9ba257b d4747c96ab72b8f2766b5797080d9917363a753f 97f71a42a0a32e1c93904327ac8c195feabde1ec f2eded53fd2b0e9e03af1deb658f0d1315c90f99 eee1276a68eb23b3df81a8f8f0ec0f5ba293ee2e 807b1a2ef9913086bd2eb27aee47b17d872be14b 2f960dfd6dda36d6154656d1ee439abfa44db357 05983fa408408f5f0d5ec0541db5c61c28a55b8c 6ed938930ea697c7ed89332b610fd4b057da66d4 74f3517bc57aade400a68b128da983acf4b0c11a 6a0ca8f77b62006bbd58a5d6ff0897e3c24ee99c 9e487891682a7f7b00211139e64e069e146e8736 de5c873f83ac38efb8b9ba0f5b9a0c6109d3178b 6939b1cdbfb0c98d611fefbaa935462dddeabea7 ab513ab3a8c9e9e74e0aee9e404b33c6cbad8cd9 3c6a47ce1599c071b84814e64cc22bfe70f4e74a a5498c61858525d9781dfbe7c3dea27194ef0b04 8531cbfd4662a25a66a14e73c4f914adeb71145c 3111739f6130a87ac9c379617732a3ae040b590f 0da71dd8bf4cd3919d035f1039baedbd228f684e 56a77e02cec99cd19a00e54e9eddea62040af0cf b47ec8a86d55548b785676fd1ba431386b4e4dae fcd85f373341fd8421801364e362acd30ef2ebe4 a0dd65902d3535e222b9a259744401f54df2c95e 550c842e6a2e45e1eb3493c65aeec3fc3977bb0b 6585ed0b8a888855de07660cbd45e5ddbffde34e a2355994144a5319aaba781c68ccec9cc1341b61 35bd7a2206f730e73e49b74b623d115e5be330bc fa4ae601d9472a0d7fe9e0d45529ec6f56c28147 40a3da0adf309c1f7831f10abcd512275771a9ca 0469c1961ed0b19ba46027db7d537446a9ba2fca 8d6defe515f156e06444884592426c62871ac95f e0d1cb3accc65ccf3f0279e576eabb2ab4712e84 76f93a93638bbb8fb8bc0e5c0a714b0f01e480bd c1007d77f7c4539a299657fbefc14b5a87d527a1 b0d6fac4b564797f4f356f9dc81372df9c61e92f 4d74524706b5b3ee9765518a616079174e10f017 edd4cbca59a1c748928ab7ee81b8caeccc628e8d 7e905c1c721018cd7a0336d2b630ed273fe9b6b3 91b08445116f6b25b51ee1fcd1d27d262e0a5309 7c99508394adb92cb499e62312db871e21a09fc6 bde414d405a8051901fddf90415cb91f82a444da 73fb41db8345debc967eb50dcf1f1c461a9ac499 ecc3f8ca6dc41d000495851de0631b1cc64ce344 04094157b805103337eaca75891ce1121c63cb44 05e699c9f4bc2a91241038e16b80b426320d005a 43300c6e1639679564a12f7aafdb577edfcc20da 2490692a8f8b5913d529a81a70f5f05e11871085 4d31a7f92e1ad98f3309708dc23194c9c001d3b3 d270313ca80adfae3b66015a59a40eb65c3bf498 5eebed34cd7055bece9fb0bd46c7689c1927dcf0 f8d363e6e24e942988aaa48a6fcdbf092a65f936 1a93a8d468627bddba6244aa27a210ded5fe232a 2b647fd576e2c58129686b480b5b32f76f9c0a2d dec8a4c87057cc31951b297e0796bfea37751e17 7ec9412c035ffda8374d701956805021f8ba1bd0 22d40857924ef8d7a4e0e61c9e527891500d3d7c 1dfaa1f43594c6139c2ffcfe76c33af85e596e5d 91cb29f477cb92b35ee77870445f225c798a1cde fd2fd068e5b3d537ccc027d1c14330660cd4a030 b7d2c765ff46acd6c9df645f97b9b181715257d2 18e679561335e0fdf259c8c21b664724ebc03e84 d2b0eb4eafdef691e71691cf6c1da85ac07b9fd9 f6137cc2fc34a4ae334c710c0c237c8f089d7e20 aea0913b894ef74d3935df3ff3198ccb86dc89e6 d3d4628ac44663d42115e68c3195cc57cf48d4c7 dcb2af1cdd32868cc3c900defddea926bb85be0f 786ff9b649a0a9d6f836c4eda390165034573d44 403543b8e778f048dbef3238e38ddca4707b38a2 82f0cbe7e117718aaf97860090bc01edfd939a93 cce099319717c5fa6335cedab8512035b0d2e6b1 f0b772396303c122ef9ebaad2b4c17d34f15f2b2 428b1d2c2324fda6afd6ad43f3d0ff697350ae9f 47cc12a4f06fb26c611be63bf340624cc80e955b c59f76096c9ade54041cdfc8d4319683ddf1f133 5e4b7f22a106a7562cdfc6328ac6349b6eaa317f 77d625cf97b5d206a64809153a85ae6052c1797a d131a82d6ade3c8f05713f7445b9452826d25d72 3971a4df0b2e68e7826c8850edfa74d0678c2120 83e79d97322144cadd49e99c4d568455c00e3fd7 4b037caa02962d80745655b5e1ef6858ad04ea2b 0bd4654bb6b58218dff7e1a437c38f23860c17f1 e0d64dcc93e0fad10bd2f47c62c4b2a39fdee7ad eea03615c43deaad5bdf2989e4dbf4dc6209575c f0adeca772448524d7e14aecece343a4a22fb36a a15d049d16a32896d73267da550d8f2826518526 42dbd37b3d0ebdf11991af51142ce4938d4bce99 c8756c929ccce9793a1e1717439a257c97f22203 edfe59b7a42884380acc1344d17663000f75eff2 5d739ba1d7f82408c41d858329ff9234f1ccd439 6513f4410cb8923286d35492d48458efd5eb1d22 7ffc2a2a3d8727cfec8d298e85a81a5c3fb57bcb 2bdd089bffb74d6c25e5f171748d16ebaea7c200 af9dbcda856289029402349ed003efbe0f10e95e 6a82177b68c97bf88752b5dfb6ecf85d9abbd100 b1dd12b4f0a9d8727d561a1a6b3395ae9c684211 e04b046b75d3a0a31e7eebbcf30e36f10edb55e2 e665ba2b11285ecac9ee987fd1bc4a1ab6b68978 b2da46c1581c79c6d176dacf7d17f622d36e7b23 4c8d56dffb72abe22830054d8936f750578f91b2 702cf8a46262a9eb2e4848faaa34697cfa83bd52 8ce7b4dab224dd31a175f1284cae93b91204eab0 743d40d88ffc853de5daef780fcb8297222a7d54 ae30e7972b8a261a7ce0d4f1a9d908e5547bf70d 2cfc720178153e19c8d537a5aa8ea2cdab0975f9 1d43d2ec96bfefb7132cb6b236dcf8ca06f6a09c 07d8e0f85b48c54ca8da920ec3153fcf02ff7a17 740e37bc645b340a2093c85114b1dcf00c7d5057 7afa13f6302b3793a7ad04bb492f031c84d80f55 ad06c1cfaa2de146df9dab483f00fb43a2295f6b e7a573fa5ae649a379a6385557a42a7112ef095e c8f1c13f26238b9ce8ff5fe9b161210ba70ed48e 7e9de543000ce6b6633b33eec1d82d6854f3dee7 c3b788bdc8b2f59208a0c1f9f74cedd3513a75b0 b666e3fa075ea5245a6aa43d9cbfe1007097c340 6afb800cba67485ac08beb712307b45ea95b64c7 2c8dd08c247cb459e0803564e21a8dc5841ed9d9 257d9be36a877688a002ee68473ba0fdcf47ea12 1a6cddb03a08c61e26b2dd32b9773e020b8f589a 09313aa08dc8cccc7f27e03d5f6e6661d5e2b73d 5697532b99955d48dd8bdc38db85b86616bcd781 c2d3945ca539d59fb3533774c99718ed8529c121 a3a3fc5a7e4b658806e0bba562e6b703cf0f25f7 6880c7142972aabcdb864fc48a786478ffb8be49 1fb03173971da31cdc9a60cf8f8823cfe0eb6a4d 46d70a337e287f93b05db5d9bf57c08eaa41e535 b9ce7647ba8f7e4d49f8824ca24b7914c1ef4404 3e78e39922364d8022190c8e188e07744e35b8ff 00900d28e831b38c5dc78aa3d66b2a4938d0d3a8 32d04837b45a8d15a6d0ef3fba608383f4a67197 e562ab294d1654152a4b8bad1e64590212d6e26d 9997109f3b7c7d397b5af4203765da6d582f9e83 2eebe2e8e8fa3dd9924d95bcf2725f4f58146337 156950f559f1c3cfc05cf1b69396fd39fc4343bc b1bd959c9dd83b36c94f1fe2ae7ddf038e58f8ca fae5015141acbff318d770f2dfa73f031c5fcbf8 2d32a49fe117828144eb2f0503429b7c411816eb 2b9d82e43ce6c4d1bc50d1a7f90b6657c2549633 61a20614269616a9a3f212eee62c988a6e98d531 75a875d51968f5c1e13ad65ab41e67da6e979c72 8bb17331350a9111b5aca33e0869177d47562ed2 87811202be34c5202ee3525cb5c2fda1c06f9d28 daa6bbd9c0543d2687d9d5166f1a8b0b1c29ecc8 4456d9b0414e7458bfa977830d14b32a312de50d c2cab69eadc89cacaef04837c068830abf219cd1 7ac6f555eb29477b3cb60d39f576ecceaf35859d 42a866d4c75503843b0a7471d1f3ec6c4065b1d8 ff11825a7e043d30879b81b6e9436ad248e068ef 36db3ecf92117d20a83581c018e83c09b89ef3a7 3b15194e64db505f314e1e6f980eca588fb1aafd 6f6d1ea3a0b3bb1089e59b710357e83abd331287 b534497026ee08ca01f7a3d15c4fde24376cdb4a 2e8a0fd7cdea999ae4e90f9f1db1bf36aad95e00 00fc1334ea6dcb933d56e9231a98f97b6976afa4 f8d704d99cce7f00696f0aa9ca6272fb7f04c005 c469f0ba9886092a80dcb7b317a0fceecd9ce82d 0c20533d57f9a7b501c48a7e7d91b046a4cf1454 107f39e5db77df3f782b1a45d233e4f2b028dc2c 3bc03c149ff90926257ff722f82ef9bdcdd16d20 a1dd9044d6dc834354326a1cb23d30e018f7921e a1f1377d8f50f44d6635b3eec779da2aa53ee279 b8916a7cb5330c098b9d207305bd75656c98bc95 c190908d01f3d219cc55add4d95d5416eaa7b332 c394864619651a0fd186f0d12d520a25a669e3bb a35858ff9118b1e851b49132eea9581d532537da 8ebf5fba21f99bf904f38569e3bda3306ae8fa9e 75d76e36326e649ff354d6848dd07b34f8456dd9 fa1964b57de7a486694a979d393625c4224ae34c f79dc044b38d7cb50bedc6c8ab5860d0c6c361eb 6204fdb79d99d8a6ec90185d14fffb2ec624693b c6640ff2ecf78225565be2778b9447bad0415571 c855821012ed71159c14759768f173a0fa754495 9da93ccdb5043c45a03ea9efab26d69dda6df426 e3cf3de293dbf4361f384539ef9248b495464953 5929359151b00d7eda040110ab236d2a529137b8 8667c490c5f269cc10e1574e24ba940c7016630e ed9162b21b4851a8443c2fc9e4f86f24da57f536 309020346d1d02532ba6479eb38c5b46fcbe1422 13996bebd47fe648fb374aae3517c6e8ae828478 a711d7e9597a015d2fed25a7a0bd10df04bdc9ab 20cbff25f55c0a305e3ef1528d785c581988e7d5 886179285c09f239edbd573b620257fcad3f3c01 8ea6bb8ccc48d688e73d0ef614f33ee28d0c8a77 b168f8ede2ed17483fa46c52baec81acdcbdbca5 63647ef1e00134c14307c65d29b2539d482607a3 f653ce88a5fe3c38de000f0287eb44023067ab6c cd6fb18aa0d9c911a71e02a430555f4fb27a9bfd 756b12c6da7d180b2b09d9be3f8873bc93774366 1c534efabbce6c81be33b28a2fce4bab307fee70 fb3226afbb6a91e96b04670d362cc18192fa70d4 93a1ed46a904cf3f15fb12cc75fe4cf8c3d8ecf9 259f64c4abff05757940c5cef61e152f7296d451 a3df4ce4e01a93797428ecaa5ffd14e8ccfe6d4f a87041465ba90930947920b9075673ec7884e0cf a2af37311be5ad693a7d76f859f157095eaa6e4b eb706a5a3ac8bf004d015a9de54ada8f7063a3fb f42eb0e6477757d050c07be2adec952672eaa083 f22b141b1ad277f14c0e3dcad9506275a024d985 58cd4786f055db39c15980bf0574f3415e49bd4f 880278affeb5729cb5f860903337bca3e53eb5f7 17b35aea596261032c6183327689184e52515363 eb82e5ab73697559cdaa5359ce2eb664d2fabe14 e55ab3e4eb8ea9a798c761bdf31e3428759cce2e d887aa56a5c40c44f67735af585696fdc624a6f5 d87203edbf363e7e64eb4af1adfc1403d7e93bd9 f90689cc8579169b4705ba603dad1cda5b427ced 2014b89f5c3911f4b37bdde167e713217fe9ebd9 de65083142747932c726e75b451791b50eae56c8 b22bf3ddef855c9ccd95430cbfaae3099d44540a 1648791889665b29540d093f2cc38f4ef6a87ff9 fc0dc02dde3ff3dcdd62d95edcdfeb44e2348f4a ee97002db899a95857307e328775d2db7064e399 414109b4d53ef973361d5ceaefade981d1b43eed 763485c1819485fd5455cd53814a7a5293870a2f b0380ce7373a8053058e1dcc3bc436e241bc3717 6bcb68bb0c651266c30f0e9962f2eec80c771b5c b40709212a5dc2820ae9b737a584f291bdd29669 2dc2a5bd7646b376e9cad23f4f5db917d4068adc 08e71702a2601e75310cebcb95faa5194ba549d3 171513f9e25c57b52492b481999681db7e0997ba a14098100a655d494e48b4e847bb70f4d13d6476 efd9e88fcf3e19f1ef43ff5879d9c742572b1a47 b60953ebe93c0577e2ee2ea292301ecf0a090aee c3faeda237dc394ece62a71cf701930ff5ff6e9f 8be2229d5c810a3152573c1a5c5d0c6f93021eb2 7f5bd28b10d793afb822d01fa2ed8bca6f78b697 c92d4688407e0173c8e6ad66369a8f15e8400587 870d44939d3046d8b79f0bd59f5e6d1cda31e97f 9b3e2cbbd9010213a9e2205408594031807d3eb5 8111636d42d431e521ffbcc8511f61cacef3be00 9247eaefdfee193b8766945c6cd2377d48637c2a cdbf7233f95ebf0034c664574e5b4afe45209567 7c7bd555e3dc8e056f90de9b2175959b3443f482 993a7605687b42aa7c36987e121b70b3e6c6afd3 83d90093a9f5e63379de1bcca145c51bf8618483 3534c6ea4bdd91ece5dbc71b85155bf07f9e4cdd 396c062ec0629ed16a30c714463422979ad83202 f4008680a09bb4faeb340deaa9b81cdd09ec7216 56b3c8974e36565744ffdb592fed64811bbae82d c0c3444708304739612bab676095883c823ef96b 755e1e7bd6bd44f358588a248e038826672944a9 5ac8de10cb3f1fef840d9d2acbe0ecce293712ee 0c974de72660d7e8ebbcc3f7ce495199837bcd26 87c55ef12898069dec92fe13ee704dfac649f33d 6fe68a149f51a757953ca35bcf08591f8a349f67 cad9957616e80eb216a8269ad552c105da553861 51ebf27bd815180fe84a7813b4f7160d63e09abd 9ff4531b0227acd84946a6ebd4f40928036442b2 045c47dc9118fb22a20579c908d071342d3be8ca 1b24a410cd70e3c1a09130db33b66ecdc123524c 6312d09c44a26d0cd989ea283200bdf11bf985bd 936c72146ac85c3a490de4c3441084c4d403ae29 3e8f145578cf290075c710ce46dcdcafabb88898 e4618758b9f80847d459dd053b6c5b60a26bb580 0842d2135ade287427a82842735f404d09b807dd 73c0c257d966bae0c310dcd841fb37d997df9483 dce6af8d9c49166c1619fc762874ef274fc36913 2442d0d467e47af0e62e1a55059a242b2789ff3c c45597efaa5fa9ad6a98deca8c1b9f4ba0c7b388 f5e2d7155ff1f7d69f9d32fb02fc00c728ca0534 565bf63ac62792464caaeaf7fef7e12e6ee55424 e7a5f4f11da1b0219626e642c4183311b745ee3b 8d5d0bb12e5cd91db0edf18448c5704b5639d4f8 55f8ec2471c4df074ee237fe1ec5adbba32db24a 843b6e0db426bacbf21994b137d27fffdc13222d f979796b0c2a29a5e77e6c72dd933cc922a3a610 0b0a6abd67a44d8f3a219e9794ca9fb5dd9c3679 34e4ff3b7f27b17a0e7b6c0cdbb53bd68e012ea1 eddbd60d7351d594c8f56e46a09f7e878424d9eb 8336f450225b1088a02a40c62c6de74818681040 ed0782b28b6c89069bd919709e1cf57222bc734e 2d510c904fe14aa029ee9ea96e98f3aadb9a27a9 309d520d70d01ca5cb2911bb2b51889ee265f1dd 1ef917eb3b5c3674142c40c8d9c83a2542a097ee 3d4490f29327d1eb634369b349edaece42972c6f 625d0d43defef73588a349fe4c9ae4c4b5b513ef 371c4ea6c98df00752e0e43ad40c2017c94596b1 de96347a51d1259827a7feb556d702629363225a 3559b7e68f7f1ed0c0977ca4dde00c5ba84295a6 eb6d3af6e64c3bcb5e358f3f690aa1e2e68bce4a 7943f39c2d5d2fe4b4b0e693ab16e1efcc15df14 e408ca867b6a5af7cdd9804b1adc42a7fc0b428e c7d4923d98be9c4a4b9fed8d8e22d72c99a8a66d 8a2eb618c88996ce96f2b9651945086a9911987b 15d9cb64ccb64e28c7a8a31e8c70d7b11bb8ca45 06a2374ea824885faeb8423146f78977e585c921 d752b7dadc3e93935c4473643ac459501855f69f 32f6c5185b7017a86944ab21ed861f3cda67ead2 763f35552506f642325d124def537b738dade694 e17357b54f0392ae5559328fd404a1c02ad3373f 494fc460f3533bca4e81fbb3cac52f381f0169ce 353887e5360d94ce4ff5a0a891814aa2f03c1be0 f673d614f9955d79b613eb248288d317349c5777 2e7acf4fe904c02ec796c6e8aebe73aa4364c073 b6f4f1481ed3a36c3d6ae02cbca9a2877ddf6702 e892735488702df24d6bc4d9b6847e5c13c57caa c23f6f166ea3ee2e3a9c659b5e1763edc823420c 5c2c32cf69b65865957b495122ea3252a1b74715 8319ce82acb079246cb64d185d99835bf195a11c a7b5433d897630789f687d9504b1d58cfaadb6ba 68f9b1f66a6579f058026aac150edf7d260c1e1b fa306ebbebfe90ed57cecbf5d170a5670efc2151 a77115c64ec17be4382284d7e7da7ceffd81b796 7c7e1cf85df43725857ab626818bb4f464a5fbed ba0abf4fe1b23978a2479d2e47d38777e88fe8da 5c9045fce266d3460c68c8a91db8c0bae73928d4 213dadb37638d3907b081787817dd0bd29c7801d 5dac9a380d67d645ef6ee855bc8db7bb2fa240f4 56aadb779fa63e3751a4fe096fbf0f6fe0b8c6ff 380777056cadabb8b2088bc504764f6f5a988e3a f8e12884d2b5b749d7f004a76bff484938ae9949 ff9cfc9e20b5804ed54ffac937af1c1d923c4b52 11f42d1ca38c9a24d8c86d1e6f3b4f5697b62a50 8cd24935a6a68bc5da3f95ed7b8098c639b286a8 44db64adf744fca1005aaad64cb679c8e46480b9 07a29500e828892fd6f9f3bd32791daa93523c33 00d60f84e5beb610ad2d414caa16071ee8318c20 9e6165884c69419cc706edbe695ee44bf594201c db85442c55b983a53acf28b630c26156f5c78a7a a39c1523d84b91a9fb2153966c01f40545d263a9 e4c32ffe5e1ae60996f08362524f0a31e156e580 53f6940f23637c92e9b3c7f2590489f1a04fee8b a31a5aab83265cae124030e31afb2d8161517a49 4e66d02f7be46804f4291e52a2913dca11398bb4 d9c9a4e85c250fdfc52889e313e88bab93361a67 3d427e156f02e5ae80971c5adedab736e4570218 2817457d03d14b9b8c919d14a4398ad11a9724f2 cf8d21c1423bee2d235b7d2997fb08b13cb2576c 461216e738dfa7790d86d0bf5fa32a94f22f6b30 6fb37dafdc35a91ac066c8b82598db9a565c5c6a dcda7bacf833e3388f96f369bc8e1a713cfff943 6b7441318d53089d8178c5fd92d47b625836c38c ed1d68e393d336f3860968921d17a19c080f82ef 5165fdbdf164623a319c97df2d3a1dda239bc350 edcd5b42498f1658c88938b38d66ef23b3e997b0 a8f51d584f6de535a545173d2e714c34c9f057d5 921ad20d8c0baebf6ad19398fca666347e17305b 44fe70810398f30422f01c23f0b4d5119e5ac269 04d3d7dbe9aa5b49e4e6edcabaa73de93a99007b eed48aadbcb3a0099d6339b16d0ca256efd5b2eb 77f487b70306be50873167a363b7cbc455ac7a1e bdc2997ed709155a166218a613f78877bc8adf99 e5f2cd20edad11523f89f70fb5a9a0bbe976e220 c8f7d9b4118bae93328cd9937a42cf078e12c989 3255032cd4fa5aaa8339ae3307fcc40e2c1e5526 fcb9686346d364b4d16eb7bebaebef1c9bbee05b 453da82b53c59342919230bb97ac103e7d19f5ca 5df0b5ada91bead8b0c1ce43133ce31bb988721d 0c92eda77ba173040b0a35da9232869f02e23b31 c7f3890d56ca343518535df5254586262c26ca14 f3622aab4b6f207b1ce84f7997dc3650f3f50a4e ee463965c59f9fd8f31c162345be3784dd08ae92 0616d64521ad05f27ec02c43467f4246d20439d3 97fe92ebc2bba8e18fdf11834703cd25a9889e55 8cc732379d61f12545214351227b96d33e81d6c8 0817c9ad7c76c8dc9f6c04a5b64230ceac788eb8 daab25528053c5430942d3734593046b09c0735a 6a30501a6e9356518a6d47951b7d201ae7462c00 fa8222a169bfa56c56addb8cfb8e2782a0b291c1 609550397bb6bde78991a8eea0701a9380113af6 3d47aeae457dcaf92ed21b63d134359ed50e1af3 f3543191335117632af7ee612dc7b1f5e65f92ba 9dfa4e37ad094fb0c2076e0d03f547700c9a2250 c6ca8da2268aeb7471a4a7e7ecb0a91b6c1403f7 a2ea8cd20379fda7bde5886f59498235de34ad2d cbd26d3ca965a86c1ca286e0291ea0b7cd6faf31 ce8adf1b5273038f21dfc5dd8b7b74249e395a95 3dc44779f8e0875e678ea03c20e17c9d445024ad 6237b951c3b52feae6841d91aa8e10a25f583921 b8346484331f3344c4b5c1f9caa75cdc542861e2 03adc3156bd16c5fcdbb83619bc0770524930361 3cb41569351838ab46ff9f533d06d9b4ae3d89fc f01c855e592c9851b9c55d7db90b5969fd426868 57395c0f477f7c865266322d3556c7583a5e72a6 8aa1830a3e65eb9e093a055d59110c69094d6242 9f19dcce147e3b82ba5d8a3fea49332c13503366 746ec4c40b56b3f5cf26d83e5c1bc11a4f21359b 3c1c4292af8ec1c90c5b070aa9048bd430f1844b a59a27bda0a5ba38bc4221b9e7855319e7ab36c1 09bf9da137d19cbb57e15cdece55bff490238364 f9afd41bcdc50468b2b12333789f3ecaa598443c 0835acd461d8403200f1dcbdc6840146651d3dc5 0bc8459a29b18b69b630ff293e1b5cbdfe240353 0d81ecee5c630442421dd4bef7825de9841e189e 392844aa937cc2abb498a179debd22f8b48b8072 92f73f2a4e887e1a18c4fbbf58e6e29f0672990e 9d43e76dd04598dff4e2d90dc086ce1126c75a8f 94d678fd6d46196d9c35a0eef6ba84ae9b794b5b 1c49f296e9184f7e2bcbfada9de19d2dca8d315f 0434d161c35d7fd70eb1a28a9376fdd55e8af2ea 4922aad478a5f06bdacec005c741dd4fef76a13f a490a980257e7fbd63b67accfbecfaeab2ee6150 c1200780b220e4b1793e392ef033325e1feb371c a725efdb0090b911f25c5d59c2c80fc2fc4e71f5 09aa41c30a0f6f4f22f4f10680ddb540acfbe892 f4f8ab64127207693ac48d3ff6cf3ddfdfa41aea 8f68f34dfaffc9a8f17f12472a5e5fb8581e5219 96a104220f748e43a43efd15249e1ee2fe932144 cda111f1c8a28c8abdc93ce2843b2c5193173b89 10d61b696779138984cd6393a3de536d90c4102e 60cebcc2a64b4b9bd3e80cbdeffc0b8ffa6ddd24 0b6851cee7a0aca9664ec2f09091514374cdd819 2b7aedc107c5b7e8cf17d71e96ee760f6d0b38f3 663bc588faa0d3c7773adc1d841bee6379b9d46f 60f17c1538c2db3020056e6ef23c76456f738b90 55e5e7ea66be6e2f2b0a2f98e6ea78312ba3dc1c 922f88419d1403bc1e08e30b49070594081ce187 08da76f6d09eb31073cc12c71bca1f645a66cec3 a56861ee45b5ff1b1d90fff3f905785ba6484a9e 2a5e280ea37fdb94fcc120d03369b5ede10e128d d1aa338f6885a69c3e695e3cb3efe83d62caaa09 6ba9817c4b49b3d3d5624d8f28026b4ff4a2cddd 39f04ba4cee1077a03d713be74b12e1d72040060 e215925241dd1e34b67b1cc5f948096769ac2ada 3486102b7ce136fe13d4fcaeba2e8af2b9a15e63 115df31ed442fdc46f586ad953306fa5ae3d375e 060b00fc8acc7fc52e3b6d944e5b306170393850 5069e7ddda117ad784c8124d6d29081ab8ad53a4 6598db3e26ae44c4ac858e846963c4a24c209dfa b39e2701253246c1ddbb41a7c4ace9bdbf88c587 cf978ec560f604e6d9e2ac00c94af68ab8321620 8973ac89c3fbb08f72f46fd6785bcdd3f4cac8dd b30a139ad0c87fb0f02c90f29481f40f12ca808b db3bc54cf0fee96e609fd94e8714f02a064a91b7 f3353970df8f5514290769b898a9368d369b7f4a af41bfe6cb1e93304b43a2b806ce9de97d0a3677 1fbcbd0f6fbf67d8f84918621833e1d6e63de439 20933ae40f783684e90147b154933d86028867f5 9441c946a1b67c621e7647c0bf93c3ecbfc2ad38 3ff193d807dfd3b4511a567bcaa5db5ccf717d39 ff658fa38a9f9e038357decb831e35181656b0a7 bc5465e7b036b3f8b15200103f8006ba54207617 5928ca15b6f45e8fafd468d09000c4da765b3267 65afd1f6e7515175ba2ecd3b6d86e34c39a3a143 7d1c22dc0e55a181f232f58d52ac0adca49f856c 12b76eb311777c6b1671e6f475b40205d7b804b2 a721002c199cc70bc0c18557cba3e8c537d6744b 2642451f9b3d61c5bc1f9a247f38cc695e043ff4 3717ad63180779f1f3119ce38b8b29c0b637c827 cf20c150ef0acb3c2c61b4a8fbe6015c8a924ea9 584eb58f48b8ce5365a92c75407db692001878db 7555cb78caca688287a315ac3ae44e92e2717488 0f214bed7a7cb4fce58819d54234dc67538f897a 1c7454dac153ab89baec68da1e74cdff26a5e476 59181b7efd513109142736c54594559c20b99759 7132baa5bd7686de6190e63dcd538aa0392db08a e2070fe65954daff51a8bc6839b499b3eef6bbe4 ac1a77631ec67159ddffc6009e23606b1803d9f1 75b3dfb19728dbe76654eb4f633bae7a2dcafe85 7fa9cee31dd7c3cad756282f7ff64ea8d120c2fa 1e65dda2670080e312ad1553169b1d3ea4976632 9a87f3a42bb3f2456840d4d98a9dd51d2e3418f4 1e7644e62946bf52d247663ff2514226fde46138 9a6f944d187ce553b84ee511d1ea936e1770d6e9 aadc0547f85cc28ac730b88b809d8b6b58d99f13 3e99df7b4ccc1fd40a5739257186ea24ac292c4e 916b4adc6c3af117a9a7992b872d6b95047adc63 cf311b3f8941a68b155e4190c2f00a5fbd5f53fa ed16d248270e4aa1a5cab2af65b009c685ee1c65 050b95e0f8ad833d5233a6691bea89b6fc6168dd 638fcacb4433104acaf7d59b0c29a34a9f063681 79e0aa07c41d4f4c94d80184af13f9f8fbbd89a2 5fd7d73cb7ab0f751e5ecddce95d0f85bcb30bb0 a95423bc1dfb8763f4d702e9e15995e2634a44dc 33477e607dfcbe3d005b919de124af3ae1b7701c a3cb688818101f47ef7adc65e0a5aa189c89cf3d 85494ea42dba92740cad9829b0231e303a5db0d9 89da2706398db81653a1a5b6509e6c0ec990cc6d c72fda263ead57ae45ccf2eb28ab1ce92962bced 765375ae09d8e3cc08b8c389ec856f75f1f7ba4d 61c5967288d6a33d938d769c02d25f68d9ba4926 ca7f16cd52c720561b40ccdd6beec26f88cbbe5e f23b864a3cb3d5526a99a2d75ca3a69ac795bd3d 76f8d5f07325ef4942885103453f986ea3782f14 ce8acb42553ccf7eb76360b5f65ce778bd6d099f fe9ffc06953ccbe5c65ec134efd7070dbf6310f9 62c0b4fae5e8b9cc366a7de5d3b349d0fc263065 9c1b112fbb2b12dfca47b63646b77af1eaecea8e ac1ebe0c86d4bb1b5d21db0eb29e1313defa8f41 8a7f7ba79856092d3d1202eb41ed585cd294cc52 aea234264abda28edee90db41d92dd6df1dfcc6b 148e79032178755f8fbd7dd6dd15840ae02e96fa 9019969a7522958aab1a4bbc30140a5f1f5df983 6ceb6692fc981af823fe43e16e59a6fa1410a398 f15008333f58a883ef8e5caa27ef99bca6de1611 ec07d7c56028a9076dd834a835a76ebcaf570d94 d97d0cbaa0ca851eec37585bea0e5357e4d6544e 289886ee032f60c41fee15f3b5bba6e671e327e9 f5cc37a37fc4b71a37dacdb993ce07b0e0c2ef79 d6fb1c05de68b63a38282b87d220cb5736d07fcb bfc9a572d9ad25e1755002f8d03b31edf7b5fc9b 6b70420a51f716b3ab5842db978d551dfef88434 393344f5571828dc6e58888245f3718035bdcae3 d5ecbcd2b61f92b5cc10a863f6e95ed8b0b96898 ff2a454b25c17ebbd4428ea41992bfdd71096fba 98046aaaea3c7c203fe80e1c8d2f61618ba7b63e 667b9288e5ee41e2e7cfcf2d4fb7a428503c549f cd06d7a2a94f1abbf466516694c2cfe2149bf56d b7178b68ba8542d149578874d7fbd5290786ffb4 fd2328046e3788c440fa48192a63dbbbc1c79264 dd099c2ff2f187389bd5ed69a5a6e462490c2c86 28f686349a9d05b79cf399468027ad5936a20bc1 9e4033dba0d8728cc8de62cd453027b01af2020e 16a8b5a62341f818a591d178760850fc8bd283a0 588cb3c20dfa3ec6ed041cfce7aa391e37b75f16 5d1fbdfde5a0296973a89f90794e14985e89386a 344be156abed478f0065bef75c3be54679183204 8a878d60cfed669c02afb1eda3d0f6cc8c9d4948 5aab7b87b93d0326fb5e15630ce01aee03c30741 42c6aea2f3ca18499804d48de4bcb70023f37a72 74223b3c883d1f7aa53469454f0a1c5e3e5681f5 47f956b156d70d77dd477dc8e92aa99b4858d4dc bb15224b49be4422bac517b6e18f6aa410ee2571 5d0b75705955ed383f676fe3bb4dc966cecd8583 f29f670b6a9d4454d42679475c05d5933084344c 0aeb7ee3f572865c6ed1d00cb7d94830ee435fef 2daab6b71f884cccd363b6b05a19293cb3fee2b2 602ad423fccd8eb6d7f4ebd297fbc68cd2b08d01 983eee72f935a7ce70d28a7b6d35e6c263fc2fdf 085797c33219df01c930dbe27c29cb11398f03d3 ef332f0ee789177c64c847302f1725cc42d558ec 0695cd31a90572587772fbc3f45dac8d678c8c4e 33815785d90440d319fb6efb7866118f310fd419 21a43c9900c59302cbce48fd2b0d8bf34469cc7c bc7d7326829a388463a5a7e41c99c7801269d2ad cce8539090b295ca6ae86ca7a0900960354798c9 9c0fd79d14bdf43cbc69f752624d30e586e9564e f501053662ee2c79336012b5cf3eeb86539bbadd b879fa2bdc4a4388efc07e8c9e3f4004abe15e98 79318e3ef70daf15dad5b620857efa857b2066ed f8e012438b1cb25abbcc2ff90caf8ebc2ce14ada 2ec4f03b7976b65d99845fbc0950379a9f6b5a59 13a73ea3062ca6e8977b6257dc0a0e80e9ff0d2f dbb03ebac0d3391ecb09345d7cf45ddc509104ff 7535484f98c0098f8358cbdb3d7a82834272f566 ae3d0c71561ce754a77e4b958de94cb494c17ba7 6475fee6ea6534f2e5641a56510bb36256951327 89cbb67300162eea463f9ac40cf533eed2caa6aa ca0d3756116e607ea96d3165151f590dbf605d4a c81cc5897f336db0c8889c06af72db2b3bca30e7 1b983f297226e77b2800630ad2335da42ba976f3 3596c39c740ef83457b75261b774a48894c4420a 22d83a156b253fc2f203a2e08d0538a7241675cc 41d9b947ac9c7c7a2bf53d488b84c31ff436953f 79621819717360f647e693c07704ba1d7c30b16c e7d0e8562eb97199081bb7b9f84c68e218d0b557 b5ebe5ff4f5d9a1e2f5158ee99e1d2bd08f0582f b02dbcb157172a3c6ccd5eadfb2a78477557799a 6098b9f4d04820329d615ec01f8332748132a6de dab249698fd794f5c70475ec326165f2733107a6 7e0faf08240b1026784dab219fc93fbfc88ddab9 5caadc2dd4f99cdb4055f94e82247ac7b4644c82 eeae319e6fba19122867592373a6cf625ead92a0 151d86f75adc877b7d11cc35540cc848fce6d4a2 54b9e6b4096e25aa496d37571a8528afc7f6d586 c3d0d364edf4d383ff904e23008821396e3cbf28 202ecb65234c571f146226d3c8421f7ec53011b1 8f4e543faab2ba15f0609321876286d3d98149b5 b7e8dff20e515cb91bcc486e3573714d5f53e68b d5258fa6c34c0b4d238f6f9c484495b07e42bc70 d1f5701490e421feacd1f2f83526c343524a126e 16ba0130857f157b8ca2d25ca970628b2d6e81b6 b876bb46ad10a9e6b835dbf8017e36716fe566e9 b286809dea53c2a2ea82ffec535bd4519f5c9e8a 61f9af2883329b93f18c7878535f9f97a6b9461c 003a349e1cd36f53ee8c76d0aa7f451b14d09fff 725ceeb3a0a11c0395f58b8524db7a5dc482f3b1 b36d14fb3160a67bef18ef1e98f81822d9f97ebe 04439585a81b86bc2b6efa84ed95c22ed6b4aa2b 817cdea62dfacc1a2b26d76a5585473bdbdf80b2 a67b89861bdcd4e46669fddaf9c9e7c4447a7c75 2fe18f670331c8858287495c2440c218a480eaf4 70fe86b3691b34c50faf1aa9d78f0d7bd29826f4 176580d083e975678c1cb0204af07ec1f4d1e377 2031fbe4e584fd9ff239912990afb64bd8465206 60e2fdab00f8f20d4204e4822909fbf15f2b80a9 ace41e37436366b3b48f28150875d1da8683f971 c190e513d8762ca6cbd34203d4e7f34409718350 dbe86a1d276ffe751749d78b7644bf53d727b235 92f54bd32c64650744622d29e7639e8495298709 974cede9e228fb9f90ea1f02b56762ca3e343a6a 1c5f9ef05f2ac1df72a2c2790e0ce4a85196f899 ad56241386f50a1853c92bd6bc9de657d460e228 ea5e2e2a8a5282c92355f12c5c12210eeb7993d6 14c551b7293d696bfdcafd1c662291f094a1bfaf 25fc05cbf29e3dd7978c5c3ff98c2af837fe6393 44dd4bdf4e26c1dd93494f1aff23a31f1f7a9e79 09821b0afdefd29f0a2d8f1d16d07bfbf4993d65 b190587fe5a11c9bd30338dca34d8ffc5a3cb9a5 5b3223187d5d3c66ba4ac176a28d9d445af3a05d 30711d7e9e349183490fb405f8309d463dfe75c5 466d3cbed9a7de0143d2c409e39e477c2c437665 87ffe7fe3b93ee0a27d4d22c89869325573a7386 a825793487ed70e3969d94660ddf17905edd3d78 127fb5fea981bda64d6d17c298789f1187fcecfd 63814bd3e27245dcfc5d4ba11f765c6ecd727838 2461fc8fd838e233301fd6268c73e227ea812483 39a8cbf850140154611de7842acbe94da5ebf3d5 23b0452d3f57e7e1f1a04e9b65ac79ebf45fe94c 63a6cedf489fe45966aa661ba42ef14cfb50af7f 8861b4b932ea90f9ed891893eecfd4b6e360ad32 e47c3301943a66984671a3fc238fc8d2c48804d7 b5551d43be75252f3f08d9ba1984be8af9b0161b ca51e56e4fad9d83d97dd625061a726c6737a127 cb77b48b62a1149bbf016cacb19529328700942e e0cb8ca5c167da59831038ac75b1896648b844ad 56ed04546303de03c403ca658c16909edea8e265 2ebbf629ddc5fbe7141f961c7a5d819a62d5f194 f5ed526604dda2ea27d924e008fb8822a40b3b40 68dbc78ea23bc3c094df7de434fb829cf5f71291 d7ae04bc83484c23119be8595a4b02dc8fca604d 747a95f491ea34c7e070d8f277b1385df9e4e233 1e46a6d9596d8f7b37f5fe4b532c45bd534d9341 042000aa2f4103516b675429826aae27ee630cff 063cf906081d3894fb0dbe20e2233075d290b259 9e1a6b9565e264379026199507a5f12e9b18d244 8805ecf84743bc0cfeae3becde69e9b930a41bbc 90b4ac84b052fd1eb963ae9ef179de54d17b9339 20bb8fad54023738be95050b43c584376eb38547 c764685364cda45cd7f76ee61026fa92b3435e32 7be3b97d5edc74d655caee605512830105737e9b 6e258008f3fe925994e072daaa9d75c9dbb7760a c7ff8ae6f595b6655eac2bb180b6eb7eb157445a ab13610276804818c8fc149180fbfe5f4529dbe4 dabb36bfa7ea0d68ad7f36e2d15a3f603ad86ac9 66f3b4e7aa86a9192c64fe6d1122ae5a29339b9f a0d919e4c908cc6ac5720f4b147ed8733ebe9608 a7c1280523780c351501ca0e25bd897de61c81a6 16e58ef3a16052bdcc77f8f7379dbfa8ce60571a 66d404c23738071205eba79bf92da94c49f8618d 29721571915c3304053c2a6564255d40fcb09e5f fc6564b7b42dcdc3b00384909c4b21ef8fcb3993 a28988b075950add016891cb1c0d76f28dfbeb21 3d083afdf332473516db6956c822b751574d059b 539c024c671d37fa48ab31cbb977af73913825aa 8be239fc61a6cba28bea5190d8885e1d1e25b598 69e69bd2d6d42ded7df8e31e78581df9167d35ef 018693e7ebd8c5cb202964177d5de298de356ec9 2373ea2a423fbf98ce4dd0c556bf1c7797ad14b2 6e9f2b2e48b22d581ba1f26630c7f442e9ae5f82 2358e58442d261aa832a397f09f62d71ad1c9771 9a156aa2b8e8249e65999f5b08f81d6fe4ca53e2 6290ec80b465320bbd966c90f4bbc38a55a7bc76 dab065f980de93e5d53e031a1cb95a980b90b814 9bc941fa9f3270a5c8db6bad9574f30482f2fc10 aa092df23f44030cc5996d6ccd8df0cffe4e8cfe 3160ab71dadf831e17091cc7215dd9f90d7d96af 92811fc87431326cb07f661db5d9ef72d9ce4e9a 3320d2b23e9e9d8b31d199dbb83c87351a1325a0 a23762fe4157e4cae2715b063624ebb9be06a450 a0a5a63f38340dce64acae8f23ef26ed1e247064 4e035bebb3622c07f57e392da8c6f2afaebc3261 22b1a37803ecbb339f016f881f26bbbe5f2f8c46 c312d8fb2f94a0f765ef40a7085da271ad8f3601 e83477e70105080f6daa89859888b9c788b64831 c40947ea2137dd384adb22367ddf1831d293df15 5625d5f865c3760152f1ec90a35d57c22fdf0c0a 3e39fd2ac22d59097105e019c60ef1998508dd7d dd0049686de8b640ca1123d4dc8f4194021ee887 af1c60e897334324e3d6dd1a7787d50d2e1ee973 a3f8c6157c4263813331935a2635df856de10c27 7b2da29bd66fec284a8ae0ed6e7c1fb58d8a75e5 126d16e9a6419ebc946d76283549d48edf8f577a 6c2a6d5e2a602bca0e066a4338820cf2520300e5 b2500941ec0845017e78a79b376305197de374c1 53d909fc90bed86c508e9602dd3dff0998a87fb4 95673cb589c08f7b9b91f0565161fee9b89d252b 283bec8c2dd843bd9f89e4fea6ee6e001877c803 8ba974019ac339517ab15b654cccd3c9882d3d34 77635a5a66d8aaf180316c16152ba6aa245f8996 6dd381f8cd69e953a5598a0c26a1088649507964 80760c1ddc981f6e94f4e3dba1c98ac109806232 e045ec2409c0502e9b049e5566d5dbaf7d6d8788 69bd7624b90d527ed23406b956ec43dc451ccadf c371d7af384e8716ef3e0be02e2f8da6551e84dd 041abb29901cd235202e0f22755b7db6b2596932 0c2b2794dc45b2566c1baa8b061bd60be3530cbe aaedb6a45fa3f154aefdd12b68ca3b10daa45d26 3dfd12b73b4c30337f0f007ba299b6c550667f52 4a02d86bc5c0cb808bf8b8962fea0425521d3977 eb6c2b821e0f03e4976a073124f91c3025ca2d06 7859611f7fe33530e181b48b558af5827a4022dd e6ab0944d595e85ca3bdb683b42def15d7fa4ddc c1955b27adfa21f3ae38b54178c69cb604e73ced 53ba73e1220feab3a4e6b54265338051fa7c9f91 60594ed03141479578c6a07d11b5bf76753dece0 038b5e5b1e4e9bb1e6f7d8d7969ce07279d474ec eafa71ff350fe9a4cc5b2ef66fa38117bfe094f7 a370bcdbeace0ae48d100a2fcd2801bd4085126c 0739c358539a0629bfcb26a661610748909efea1 8c110668e59f3566b16296b91ea496f20aeecb18 aac8b80f73e9b221e7dc4370aefe2e43a16df286 b935064fa71c603cf65127440c336fef96683426 8cc818b9927be4dc0091eb9b93d86477d6c62c00 cc507c7d912a95f3f01b43188b422a6f268454e7 e4886f3453e9cdb71203ee66f2487d60ca4203ed 1c6203c29dfa8ad426e201dfdd19c09b21fdc834 260133601bf69ecf226350be74a5fbfd20c85861 011becafa6b96e37fc37f9944c63a5b4ce2e4af4 4c6e863cdc20372bed0b306a76443541989f04cb 8e3dddd53488518eb2af7aa822408cae39a60ca5 77f8d5efb19a381b5488a846362aff40ece9a3de 5eb05ac1c1e72234a30f04a7f52d4604bc8b6c06 a11b72e26dc9a71430fb3fd5c4bfd5591409906c e37e821ee0e5e768c60afe17f0ef3959a7136f3f 81023b24f5a899da647ae7ce00669c040592430f 2245c608ed6b13c94406c9d8b64626ab9dfd3d98 77bd401ef2b996e1709999668c2cf3852a691723 2ed683ae1298506449b325df1ef81334f94bc79f 4f8689ee5d22cb18bc88f9d583dc1b083adc0cc1 aa6939f2229a12ef75fe06e5a9476a60fc07f066 902370faf90e28437c058e8dcbba8e54b0c5cadd f694ac7f54a186bdf9ddc123879c97fcb85e8e3f ff660123dca05e16b49e4dc31e55e078dc584110 2a605297459206702f8f3a713577961f49d68b18 42be0e853e6e504790761eb655f6028771cee855 e388cae6f1a14f729f86d57ddf430678ac298f43 5762ec24ff2ccd9a5731a5204015d347ef3b1dd9 91e1ea02b1fbf3f0066af5f6161b11298fdf5feb b0dae40725f2be1ada240392874748c7f8f5ecb1 141dacddf82b531a609d64023e44a0b78db94ad5 90b11bf5c894ecb81c395235ef3b036282c648f6 c0856b90f4271b5e2281a15dad9fc9edbb75452c 9e752dfd6a77d89fef094c0b3b852f1a2e945b82 f6b39da05d9d0c96d73b4b9bb733f70c5a892c1c f8b22d9a12a9cc43e70086231388c4bff00a9b77 a95d1faa9b969ffe0dbcc8033e3c0da37840560d 33433486d2acc9e1aa4dee8c39e2478a2a5596f7 e3c410eb88521e5453d127fae15cb4ef8e98e3b6 5aed2da88eef6ca84b46e16f5085b77bfbcb98bb ed285c33d2b968def5bd3e3e8445d294fa24f244 c9aee0b1847dbc71fdaaa33be01e7d3a65c5408f c4dbb41e7b37e9e0fed01bf48eb4c4698bd6456a b7c8216d1611dddd849b19920d1407fe68f3a7e0 30ec734b5ff705a6b7a18dd7cdd6c5ef0ebd5231 4e9375a0ab4f304a1fee44b01d68d9e30cf0c02a 4bf3482f0b7032d8b42df232d85ffaac68d9b0e8 fb3da587b3194dbc450734af89119ad69bda4456 eff9bf8e4c3b1ddc6619b6b6ce74238cce74bffb d8926c0360032655ffb5bab4cd1fb6f34d9025de 0d3ac2dc82d3d2cbd18381aa7ba3c924114d1650 17e888fb78f4c94277dd9be49b8913966ce80361 92e94b9a2f2a4daa7d6174b2a19156ce5e3242f4 15aeaacc15e86c6fe879d131853c4e2aafd404e2 9b7a09695d8906d7760c4f8a603ee6834639145a d254d07482d160713f4962ee83b2ab3ab0a061a5 d981beee8af23eb1a88959f273c265893706c36a 0e829d781bc79e9aafaf07112a252d3989d07ae1 59b8313b93e359e2e953f5759cfbee35555fc6f3 a927825f41807b8ba3fe834bb4cdf9b19f380160 956943487a928049235d880e5756392f7b646766 942b24602598af530f0b853e5e99a06786a870bd 203831d712b3691c1b9167048326e433475bef56 b77e88e29d7f10ba18566064095cf43575c7ef33 9665f3c663cae93a060a8d42345493467b2afaf4 75e44d879c72f2d5fe03dcfd1be1730d191c34f1 9241e581a68df76911cb74c0d241cdfb98abb97c 589fd170829697735036d0baa90cdcc2c9461e2c 42d9fb33b7cac00cb0bf98dc91556db5978c07b9 4c46c9c2c6507485b069901b400ff2fbfdeb58a4 058c2fd0fbe424772c4ac8da958b108b149fe665 2779721a299d975eb9b449f9fe7d5b933ad4c541 6dbc1d7982c8727e149a2c026ab1ebe88d4007a5 8b1b20920acf629cadbe99b4b06769e8e63912d9 3c602d135d6f170f910c7893c96487db949637af ce76652240bca2e369fb7d09cf7d2c90a465e53e b88fcccee58017d8eca65fca4188a9d8aeef4721 59044ef87ccf8565906b5fd8a1df49a4d450c621 4a42678222549258646340aae4e1cde768f7abc1 13b6151582c0f91a91b3af81a79a5c5b71bdbe82 16dcdaaf7183e24e0244c572f87567afd3ee43b9 513a30c8e55f18e73dae972ce10d9db9563352e8 f59d5160a28f8f4407c2c989f4d41b8b8312b123 3fa8411d14a191f218d4d13edef3d9858bc2e1f4 c6976fc95170d3f8ff7ae5b6ffc261b3a0c8ebbf e7e5b1a40b91e62a5c4b3407035d6eadde539374 3bf9b70afc3ba297a2e384a431867f5b0ed00e54 ce56e4d5705b73accffbff60048fb92f75b106bd c04f2724aed299780444fe36da320baf4cd58f6b eb5d6336b9f7acfbdc5110ada01bfd8946fc4d73 3c014f58a59a8b657652c9c4babd71ff79f3d7c9 457938c59d0867580e96ae978aab47d10424ab2c 83ecc170dca6746831ee32deeaa692ec5d97fa74 793e678e8c54db07602dc70f5d52112e5e186f62 86b349224e745fbd675d56e113138227e0574e9e 4fdcf23e77468179dafa74a2850e0ff0b3cc72b7 9b2c3db2e020b5728ff8047c254b476008f1a4de bdf63dd83891ea9cddd40d02c2278cc8f4ed009f 84cc1f485cdb313c0e8f2085711f4307bcd931dc 1209735bc4779ed18949ef75074c7f6f2468d51e f5a18b7cbe4263f45718a257c2a45f574e8af619 82eca5ccc5d8b09ce7ab6fe8ebbd95119fdc9484 ab1d6631e5a193b7ef8902ff2201c4f4618043fa 4d3b1682c6b865b5367ecc13e32b2338e2f15a1e 8b592bcdcb4b112516600b4be426f20558492029 a60b30d533ddff616416883e153ed1d9fec6a26b 91030ad3b9e0b39481dc276fe243e55a7ddc219a 1165bb26023a7b24757c7360657008438ffeed2e b2e277da3de0961b6717a7cc08bc23aa49de6960 1f767694669f7b7484cf951f7057d05386900a49 778c025c71c48dede13c4da1fe117677a5f9033a 5f93fa3171f93c0edc84806c5924d6361644852b a1e1a90f319fb0440c6c321e1ecfc6ea37002891 75b8116f0e2c8e32e5a1df7efe04582ac9ce46f3 49a18e01a1dce832861babbd2c15fa2079618389 99ae7d5df176c47c5ea230f8392728cffbe02030 66040b97ed790dc5312dfbc9e8592a35e6910c4e 1583b45b01afe9434db335b92fa314a1e1b32f7f ed8da72e7c2efe2cab3c103e6e2c2645dc1150c7 15e06f183af7d908ad11e8b946f575d48323ebec bd863c42a007fb2e991d5312b03ab16fca92efc3 5caf4a6578033a6e34c335b0b9ba6934eb9e0733 c76115212f20709531696572e531d1808d06eaa7 4c8b8da65f27daa006e41869fa6dbcea848134fc 99bed286b6b15f62e27a13e614c00ed6688eab1e 9a98edc95087411000326c3ef635792bf2399b16 2cfd118ad8a498a6a59c04964d85c29d9c3bb99e 5b3805a2715738d8cb999690f3b441c34de25a05 c5dee805da8ec12b2c1cdeb67d371fe61f673ce6 7c3d12fb7a2be0e0fe22e04b9947b299a700fc06 b3e095f30f3b5498926016a3c6825e9982a468eb 921324ddfa980fcc711de28abc906517ee4c40b6 2ecea64d78def7c33892f5cea8d2cf28c6d27108 2593339f116791259429a6ad1cb1e9712bc19310 7dd8a17b8153ac0622ba98c720ea260b30b6b194 1750b693ae732cdf6932c1e1b5d5faf57ed4ad94 cfb00c047adb0dcb0415d1ded819063bc2ea8d3e 93a98ff4aa0a6275db7ce16c01acabfb4968deb3 0d5f2f340ce0326222c34c2d8613ff326ffc2345 099d3584c1f3118b18af460c01fad9f6b432cd32 882de80116bc399a484d6cd6afbfea244c6e3426 6157a28a60dc731c02ac8fb31cc8591c04eb918d 7558d895a2e25800ece0e181ed1b7788e800608b aedfb0d1ee3b89213984234979f2137b2a59df0b fa3e1f7bf740445e180fc574fee2a15265d7c08b 9d794dd375b30004efa9c5d0301522ba80ceb6b0 2b15d386650893591c23669c2ecbfb529556d518 73f6fea1d902e7a9abf341dde9d1725b17e854b9 2c9d64d46eee85049b66048fa9eef659d826f9a4 235f903c9b8c4897a03df7bfae30f1bf45ee2f1d 9e596e2ce492b51c134c3dad22a1ba4ca35c3b02 b6f2f27fffabc0b0a28774c450000e24d4567371 7a2199cb5b30f0a8f08a5975aedbb718aae4778c 66c3fe9e2253315cb6b3150eb23f0ac01f99f6b8 960074e6bf4996db7b0c73c908aae5fea252623e 44393a6e59288da83f0611f92e67119cf55b11a4 7048f82452877ded0f3aa5dd6c51dc3c9fe3078c ba2fb8652c31e34ed00a7b447bbcbca3e17e0dd6 5b4c13a16c7ff45c7f7586ae9c2d0d8a9c1cf454 cada6f3b68096eabc8f62c8a0b6f52f2f2ac181a 906b33da57e3aacd23f255616057dd9da460f7c1 6969d79f6b18b40cc065ffbc52191a5bb8e5f786 20ad3212c883e78ad15895069a6f23b4e5a46ae8 78b6265a9d3e0a2dc89cbf259b0317e8ebe22762 349e61f8d63ac3e021b9181f92e49c7503678f5f 14e6cb436f44a7f265c7ed613a2a9d52d313f056 f7ef55ffd6b038fa002c795613e37a1c040ea732 35388acaaa4b66c8f1347d494ec5e3f38f44a4f2 ddcbba0a2c19a2ae786675ecbf2bb40ffdab3a19 6380ca9f409b6ac30e59fcab658ace2587ab45d4 8322b7b03295246732ec4334f8c66c9073990faa 659de73d00997d6ef6fbe9bace3a4c9939a5df3d e6ff5cf5cb93dae1a7355a6b495f94bd9c746d55 0bfb0af828d877f5e65004dc3780bda2649e80f6 45dccd21c2191002a5bed33c744a397b895c1b0a d726380487cef794abd00e00b27d3c535e18d92b 5357705e425b25400ee584bc84d56ede14667eec a1409c59deb3714ffab1cba0d486cb3ccb2fa51f 013023749f30c305652c61d35ed73d4f58795809 c3f97845900139ba10929105a9c3e8dca34c6672 3f7f2a13de3b202ed48ecce83c8f093bcf421bd9 3d133ee7a6ae25903e1cc01eb8e6a9384b34ac44 4a0337f57b3727affc04511ce9547483986bfb77 3674dc9a515e798d00a84ac4dbf22f7198d9e0ba cef282028674bf43872059c47c179dd4b0170572 9c8404c23592bc4d5b6d932b55932b981a2f63e9 67148356831d88c45e6879817903e8c24157b96d f7b184afe38ba624148fd86bc3ab5a4da05bd588 3d275be3ee2013f36441293189b6af4ded7c7a15 0708b729cacfcad3fd4d28b10f228bc1c542af74 ae02588f5a9b9cd669949895c527aee4c70b2774 b6936e2311fa65faf0c3a17393d57f8227581f8d 24306942fc9cc6c9d87fbd8d92be46558af62cb2 3d81b30cf8af7ba01385391d40474159300a46a2 645597d5629e3f45fe86c4e3d053aca3733f5d65 8fcb849ece00cfcddd2c9f605edeff9a64c21ce6 05c43abafeff549ecbd9e53cc35116bcebde1504 6119a97e5eda62f0c9930266736db34df40d9ab5 e6afe014e74faf0727ae620fdff2dd9b2620558f d824515e08296521080f0715d5b1a2cecab028bf e84029b031eaabf3e7b428cac52edb1026e05f57 84c26dff4642f9f148a8a4fb3a24d24e4ba4c842 36adeabb0282fa7aa704023be624704aeaa7737e 8c5369e76acb622ae7c9e7b0a9f74a288777ee58 bf65ac599de2482fd4016aba17374dd4365c6ab4 ab0422df0420288dfd62d32b0da5f33abfa95058 1c309d0d097ce264b2c749277e22f6afb96d31fa f3c5d0af8f533c56483cc2c32dd59e55ef979553 9fe740e810c39f990d8281bc33cf6614d0a12aba 44345eab11a70553e1aa7c8561fe587279df20a6 6a67776c44afc44deeb53558b02a76fdc9c176a4 da8c57e2686d704b3a51357a70176c412816c2f3 d9e62ccce9363a4e36520ddf53c92023c87fab33 838d4db662059e1b0a3dcd9895b33acf606b5d35 1bfdb1e48899bfe083d359d5cf3a1641bde446fa ec2ffb962fe60b2874cd281f5519ba2226620d88 087b3022cdd7d6fe2fbd47326f9a9bfdde8105ef 3f6dec3f751e3625de3027551713e3a04af0940f 77d4d74989a6cd745cfc20b016fd0cb28515ba1a 6b77313ce0cacfb1dfaf08bbacd804652b763235 515cf15dfc88da8999fa60e018bacf4040f2e64b a56006401802aa6637d781fcb7216ed8d70d1d00 6e3d80f7d4a3c6ab51e75d07a58944ce16a196f7 e325034ee2f4ed1a4b7929b7a8100cd287d703ff 9ae76d535a2fc2e746b914fbf83dc3b097f2855a d807063563a63b7451fdbabe27127f786e4b3ad9 f2d8742473a1630d56bea75ac5dff0d01d9afa99 a26aee01aa92ab38bc22e8b71a9262a4b617a821 d317f3ee531139dd11203d344e6bbbf9f4487d39 e4b77fcd3992a422072251bbeae4132848d80216 2d2d33ee45056b4e43d52d731e47bb59f93c1f3f 69c3f84f4636dc56fdec90e2b2ab7dc09ad70a10 871ca3f67956ee0a8911c9378643e4acafdce45f 47c3c0f1ede32fbe67ae4136eee3a9ba20c6adc5 3a0ad942a884c78fc01fa88ca4cb46a58c85064c fcdff6652036f4ffef6051f1c9454c4527f5e12a efb20947688a815fd2fe6a80f195486fb12881d2 7dfc66fe597230c70477ab11cfdde39769ee4695 cc9c143fa472cdbd7805fc7f55b30773b67c1bbf 50987ac70b7e56827635bbb7cdf47728d93f630c 28cf51f82d6992312b31bb44b6da8ce0524526d0 20d4b7cf6e1e9985858c4ee2d88980ab01d66808 808b69afc436d118108029034614c19e52b872db 0cf97ee957242a8c63993253b38c6907507b413b d958c93d8bb186b6bf27b71b215bf5a43162efef c51b8d173491c8f031d228178411bc7f3c570be9 fbe0b0368dbac3168e3b1344149a36f82bce698d 52e8475c0a63aaf90888339b8fc7c09d33b7d62e 74d5d64ea961bd4e5c7c0fd3cc08af9702960a12 1011dee05818fc5fcde8cbd554c495378333df72 f5c077f3fb4013d56891810f4c6c3a44813d067a 63d466d3429e1c5e022da92b027ac6f972db6c66 97e50e2c965a9f51aad3a29ff0a8d3da9098afb6 cc0e3c88bd89802b937d7e6086a6efe1890fca4d ed4e3511ad2d7d68dd8dc625c0cf369ee3b860aa 019ed3d873e8befadbddb1f56c43314e9d2cb4f5 c99ae5928fcfa2ea35b455e3f9b3ff95aa80ebe7 4c5061669b68ab639c5c1d541684722e6731e73f e1b894edb52211d0f28c06e6f1144cac6b55440a ea91bf399e03c75ad6c87138eea3974bf7eacb1e 4cfca2ca106590bfc26cab6d867d9330393106d1 79fbf8685209d7cd2f796a132a6aa7296106ea65 62e57d310d794ccae30840af16211fefdc7b92cc a3bcc40ab1ea1243ffda1e621c15fd8d645998f1 97d82365edb7cb1c4169223f6046b9e7a9a7dbc4 e7e33fed434bd06a1c57287865e4187ce9237818 689259043d83b6e3eb598d138adf6bfbcd18f38a 998b0be23813de5e4348296f0728e3eed9e51967 2662c086013f5415753cce2c68e1b1b9af86154c 50434336d76dd930cda3d78b5c783c5fe1fe9247 64e257b489b20a21a51825dc26270887ce65d7dc 8f403c25f510d4214b2b443ab70fda5dcb6e5112 d1079ea98ed68b616e38d0399df138f32c4603e3 6a40809978d10ffa91f7be5d4dba309b99843b5e 083930d4bdcfab64fa0dee2e5dff0f89a0f0cc00 56ebe63e308dad18280e711792b7b3639a445bdc c6ebcbbe2152f441a7d2144378d44b8c241001a7 55c6d3a8a0bbacbda24099fb569320f0c4e39acd 505160dc6af62806f7b7c413686ed997e77ff93b 610834d41f8405322ab34d03cfe980bdc2a56d84 bdb7edec43840d9dc08ee617189f028a270bae93 e68e584ba418c5666457e1bc32ac203f79f2cc72 cde2c99ed797eca9cc70508049de0de621990375 f4eba5be0915806967fd6e14ed3f2b7e3e43374a c5fe042405e48e100513c545191cf4a575dd1164 8d4c4f9393dc9f301f81265d86383dd56af1ce7a 84fb9ac5f657b63463d0bafb64cbceefc8f801db ec2ebaa5bbcba32a227d91376fc372e0dcd51a72 c4f07ecf7334ebc66c2b74942cd8ad19c9b80bc1 6a6244ae5ae66857116546a84c64aa70da9fef2c e8c3513f99d1f734281c893f2357374893a689d9 b389459485722e58c508fff3829687f038a14d87 eb75eaf1e8f52081d3ffa19737cf1e386a8e9a69 5de2b94560963733af93ea01cfb48fb3587c3817 82d3f51b5a3f6517489599c678f815f19dbee1eb 425983a27b432824a84b090c08b16ee3f2030659 22f15c2097c4b895ebc9ee9edcd69dcf4ed055b1 456b139ff2fdd8227774fd6e49bbf2249873f9ab cd6fb1e206dc29f01955896415b447752adc9eed 34c97dae0dd8e3c52a4736fef455eb5c221f6bcc e4176de1db50571fe0704de7ac66ae7b33683391 b63f23f6797f6b07bef9c3080bcc6049785af918 6db1e3b0addf8721c2afa088efe6e07550413f6b b578812373655cd5ad6afcae139cea32dcb3350e 15452463de0bc35b8cd8ba9602221cebbaf452d2 46854615c9d3487716ee21defc65158fe9139998 6098310670e6198e1dc71da0c19b47402b388f4d b7026ba379540cd5dbb1cab29b626e7878d69bd0 f95551465323eb8990cddce09f0928a3f20dc9fe eaedd02a8c38094878bec06cccd5820ced6d729f 20cb405ed58d8ea5797ab611a06589a02ab42472 341f1bb1b6ebd2f9e2a9e5bc80ecb8e924c9a16e 74b14d739ff92554a2106839daddf0acd7aa4e37 7df26ba4d81a09c01aa26660744ff0a011d38086 9fdf5e42cc004a5aa42d57872456c04b94dba276 f9713fb262681100a5b9e99222af4ce02ad87295 658695c588c634cab89ec3a65054f4d89f9af18e 9e637b7c2da277d8629ab68dab753386d116a233 60841ddeb6f74b3691a9969c5f20d28058675287 0c9fb3ef6d12bf27f6c034c929a30e8213caf829 1c0045b2078d2dba2a87185d3665e8666c5b12b5 62a95efd8e94072d5fd7a18448e1973407d144a4 95a067a6f1cbf22c965fe6f015f1946d8247de53 7c533fa92e67bf759c63705f3a2ba38bf17cade2 f60054085f8d8395cbf020a86f5aef13d172b696 067e979aff32592a34afc0daba1d4188ea9046c4 55020672413646463728c5abf4716302161896f9 f282dfac1f383786c9e60c4c43974a0dba9c2a5c 09c22d175e78ea1bb8c08e55fd6b5c23cf798bd7 ce2273f8e4556060f2a349a657ab6e78874b45b9 ba3328fc3e2c5d0fe0aca3ead4d7abe4bcae0520 c7ea4c6bfec6a648a34525726f804cdf018d8cb0 6a21a1dd7725ba3f129d20028e5cd79453eb7f6f 0e82f5cf587a8ca2fac5229af6e1da04fbb3c458 99c76f0bd6098aa3172bdabc7ac0b4210c683d39 82b7b1a39a16fdfdf891096aac36bd6ea7a22849 d68e56df12b70ecf354177549c837deff39a10e4 61a9dccb2b41ea89350d8c9a7e361952207e5d47 cdd0d8a4c465e0abea94f38d4c2cf78326a3845f 05dfa018657d794d8678403fac08f7ac841eba8a 852d06bbf8e641b8090d645d5744335f51ae8e13 d3cf85d9f3ed1f2d51c557097800b01735e058f0 bf9af20b132e0e6c51db41fec889aa501601d9a9 3388a9d66fa6f1f3a6f6528c4b862c82c53c7f5d 1b4f27e33f6f20e60ee1604935509db03caa03e3 42592fea9dbd6d08eecfb82d5b88954ddfe4e31d 66c07ef48148c9a3ac21b1c34b6cee146dce63f4 91b954a9ea9cb06507ac7eead1ff24349000bc1d 210b71f1b902564a4a6d869d9610a580b916c41e 6e1a4b67aea0c2422a412a481bc999aa29352103 225f723393d2840c8de81236bc248ffb9d0bf2ef c53130200a6ef06497df2296aad1e6c9ed27f292 5050ffda7bc5977df6e071fe881a163387002a74 47dcb3be33f32f85f442300fd434f2cb2b19aa88 b90b65e99ad68133c7b426eeb0972e77b55401d5 a306484ce89e49fc17020e70f1e5485d3d77f07a a4cd1c7a7b945a9b347e7e7b09518703ad5170cf ae2bd8ea022973b1d3cf74bffc0e420435aa0084 65d9b412c8e628d5a59d6227f865e171d4d6af02 a17c6206d8f41cfe18d7fdf53c9394f46a2d7549 d9136035e52196f9a9443248085a7aaa2ea07177 a1aa7b630ccb45487d329623e182c7bcc41a8149 cd97478cba66bed7cdb99c7788b29f492acddef3 89aba6b54851b992020db17c3ab237a5d46ee27d 656429533dd95fab33de88f043d0cc6480a4883d eb68428a3b6993fda4f8570e8e5c1866ab7b6df7 050707569fb1e92d9d90913feca2c5926791fe9f 4280b0277339b2dfc07456f7e8eb95df01825223 3f8df7b2e0f6f44d385e36c9efa955cf1d313a5b eb2a30656434bd2f8c8689980e595c0662215a14 bec7289e3e52786ebd365ab8dcce90c37535df78 5f8de2d823a05755459bc1ae151f2be58439e757 52d2339f02db3402a12198733055efc0b833cc09 d3e1601c9e5d02c76f65997773a66f69e4c18ac6 0367725739cf98cdf7fd954656b5f0e5f7dc498b d5555b5a88904146f037b1777b1ccbc3fd585976 df6c9add73dde8126c0ac98cf21ff569c5a602e0 7f9fb2325771005478fbf8c10f487149ac28e895 ac93fb29e458a49f2265b0a146165515df4f971a ae06eeb651ac5ee3f36554102feb02d605726da5 7cfe90077c726788d79f663143bf1c7dc55d6cb7 d614b77f9c7b1f712cd2e599688dee294a9bee55 61074d151d4dd157290d7e7b0210570807a499be 27c00857bda1ec3dcd1d73d52fed8156be102825 fbf822d73f3a09e9588cf05c1c79e1deb8b03f3e 31f9a5d9a334c0af187ddb8fedf3d6613b029351 8d387c5bc356376b84696ec825e914a8e1eba605 d06c51b6a55177e102e796aca34750d9f042ffea 69831efae2d540363e6685adce5599aeffd17e30 a3d252949c0a867abdcdbc2fbeace5813875d50c 2c7d51a7e874388f3b62f3000b57decc1d906703 08d10e0e628dda014c9fb1331e2b9700096ea2ca 525b1f44557c90cbd98440092fb53321c3c0ff91 fdd79140e22e4802f865460df3a75a0ad50e66b3 210a2ddc4420e829cf6a2d9c135724e05c1219c4 25c9393d1160c74c3681b7588eb4f5391da2b806 25fc7e99bb85bad0970c9d40d3c061c95f4fa04d 32d177564f89e3267d6ee9f88b32128dc6f66435 31dd5677af4d9640a92061f0fdf039adb647994d ea93010271a1bef8480a93a87ea75e3e068957ea 332c0bb6b79b29fd56a283f6762e72e8133b755b e12ab1b48d452f1554142b63c5832e7be1058837 3983711e773b9f6b41e4ed6df96f881c66a0586e 2207add74d482c604cf70b68f7e61f72d235a96e 860ab8ee1a68078c7f5d871fd27ac1348e3d47a6 a1b39356ab30428b96f3815adfc6b6592430dca0 f50a31c8df66b3ac8cfb46c5fe8f291db60ebc87 30d36d670bd96ace7dd33132387e01e73750021a 84cafa37fe15f86334ad6c156aaff6b5fdbd196b 14d3c26782a92adf62dc26276f43e3b30839fce0 2b9d89de6ee285b749028ffc6fd2544118ff484c ce9f450ea71a35b9e08a1b936431d7750c8d0ea3 79823053f92679a78799d946af4f76021e3d4884 c02abd219f4f5eaf295fe0da9b552190c68b62c4 179cae55486547db2fa5efe8101a9820437adc41 92e804b0f004a91c0e47d88b954b8bc1d882998b 17680f44b08e8891659c7bc09bdfe637cf632127 f96c161f8bab289573e1d4a6cf9dc0fbf3212276 25b110bef1346ae693673afaa2a04553faf99952 244bda0fd487d67b3d5e42c2b1b6a9f8e53dae97 194beeaf4cb72efefce02a69c6c48f5e9463419e bbe0919f659d081ddaf07890b41a56854bd8660d 789683f2bedd0a502e76e19ee2f7dce42023dcae e39f4cb5aa9266b913b79089b6166a1c586a726d 1fd189ee59969884f6d0872df1aa30d3f39ce5b2 43246b5426badd72a5b2ac690f2d78ca14271666 4556d480be5c0c11d7c91152bbbbe08958b6f02d 3027ebd0011ebac64e8d8665ae5078500c72e921 9e9755bde5835ae4d842741b6b4071b8e1c43799 83936bb0cbfff957a846d55acac27b2a2c1d8cd9 0cb7af60b073f8ecd78beafdb13cc540e90a2a6d 1c9d98e912dcd04069c0fed2d7680153a97dc42b d0b5c970a246391e175cd1762138a35d35dc74da f06d26c7ec49e4cb3e44c3f4ce2696562cea8612 0aea024cd5bb0fdfbfc9a757c7a95e53977d27d7 21818f07eadc647bf227fa3ba2ccad0fe81195f5 0df96f063ed8583a16434c58a09761d65ab1aa45 74fbcd830f062b37579daedb439cd29f8fc3426e 5d43060eae83e900f29ed87ec7ecc7c0970ddf36 e8ca9ef732a81609097a40e66cf4595607dc01ad a227c1647381c5c70524fa225454e67bbf0eab51 4482f38fa394bff1d040d7af6ffc3197c8cc2fe8 ae9c8c4c1620295fefa2f6ed6c9204f64e796a5f 34119ac35b82766fbb66f4a3fad07773c20ee08e ac97aa65f331a0007146d31343605d7cff480947 32eb94145b2c89bbcf1ac4059578d99a1905c64b 8e1cdf4240c9f196cc69263c247ee7815810b5e8 159d6005413754434b117140ab19ca173a115ab5 9db51f765cdf90024c8ecabba4031d8e5142717f f0c36be712f69718f3757c8bf3a349cc75f0b61d d40bb4dea60fcbfae7e84a1e8b2669ab31d04944 2043e95408976a879bc153e9f72725fa43e79f71 8a86749753e83b9516beb9224629cba4c142bf9e ce9ef24adf7c44c087c49b85cd1fb2c1f6fd074b 34b15687a0ee6fb6e529d3d652b714be9d53c230 a76a8ec50d485591f24f1218773edb766d9feb4e 5ecec325b90e29462174bf36aec44fb3ac57bf13 62c1ba5ad7c5b74488f9f8c4707504202ecaa498 24af07468760a7d58cafadc94ff648a8d279aa34 faeec111a401183da874d0feaaa63104da45f07a eaa5c1d1256c2c700621d7ab9fbefb692217e93c 604f8b1ed33a7c3fa93889ed77433fa8396348c2 72e7c725860c2656f2d24cb850b2535aed6c1f7d 65720cf08d35968c63c7b85b67fe7ad4f7c0b07a 0244a9e4fd8a186d2a232abe632e75bb33d946ba b67161169bbba39abb6327c9cefb1ee28961e743 057a9c10c0a2da25d237e953f34bb05bf417fc4e 7cfe35f51f54199b21541dd5d1d171c11d1bea3b 90033f6fe351f1862ad13e937442c10814f22ca9 d98c01c94caa8fb1196ecd99a133f147a9d77e32 533adb9aed63672d170d8e7c75c8ffb694249d10 eb69b92f8964cf16740d8a4c6cc86f7c40e2a613 fef2d3bf8aeffef583fc9f083e2fa5b7b2fec7b6 401acdfed4df133a6a02e753def5aa6c5283b630 a737580886187327c2e34c73d21c14fb054f82bc e846c2ed168aa164bae78bac7a101d79d2728c0d 1f4957ca82af70f81849d113ee848db38f37fff0 40b13f23a7fa7b0a9fc81002619cddc98019d1c2 cc021b41837dcf733b79d92a2dfb31a63af81451 4451243dad5172df0253b01c6cd4215859a4fb29 ae4bccd75b75d6bf800cfeb8444788803c3ade2a c4f631d185987e98b17243f406af58941b0c0a64 e51f3bbcc586e59b0e7712448f9e9e1587d76b64 247a804eef2191cf740e6c01ef03ab7bdaaca0f7 99eb299b51fa3a3e2d925472900e3c55ea2d545a afdbb834d28964461afeb3059b2363088692311a c087c50b9350c2a5e38107b9b37b7ea192c618c0 26b951e359896d96a4ce11cc279d26de9dff854d c877002e29b69b5a1e5d54318317124e8a4d66bc b8f206a19ca75c25914dd7f7c1a246d9404dc235 e4413eee1fb2a137ff9628fe4cf7863c34d199e6 ce18ec722cbd8e69d08178ef594768ee10afdc68 2efb34fed0631b19312fc14e958eb79520e3066a 68eceda6b12cb556d57d9a37387be9bc566ec353 eb7bce74395a989ffa97021d37ac6876a6bf0211 4e481da4931c9dc31ddf420a78640910ebc88724 23d2d38febdcd7c166dbc3e0c54f6af0afe9580d ecada9a3a8c3bfb7f046a529511954787cfb374c e9557065aec29b9b749007d8ce2e554bd667e385 cff11fb0915558adc02688aba563ad741b48bedc 1e82263c7d8a89158791b024d8b09385a4a14c29 24e6db8e5157a6398a937aeef764fd3aba2eaad6 3dbe7b98c7ae4d59ca0228130a5f1628a9bad658 0695c19827c5c792e8be0e3df05f14c23922a318 eea952b11083b9a651e568605fca196bd92ebe12 df392036e356dfe86b4941f576df8d69db7f75ee 5952b9b095b21b5e7b98375ec0ca9eba63a5c6fc 92d22847128a8ce2ac8c3e0b202400cf3d4bea16 685a01df3f8e8b610704515029fc493d11b1b3af 7ec83e8664298ec47f8c88b4860f31e6c34b7652 f1dc57887b223f9379e831583629734dd9786eaf ec42725b41311be22dc6c4834763584a88382a5c 537a9c2853e72dcade24a8cacbc095a1395ab5ba d9bbc6e79b13a20b7f760d337bd8bbc59ca4b7ab 30a055177292dcc3e3b23c99d469984b478d04d4 15f0f27b64f9f6bc5331945397d4b59b7fd3567c 2f087482ff0428518cde1258be49d247180d26a7 5942ccef53964881d4497570c3d6c902eb906e6b bc07bb51a36a32ade8d3ffda0ddec66c04283934 cb161110adb2fcedcdf9ce0cce875037b1b4cabb 89ff2191ca0782707302f6978f66261be38e8847 bdfc4db674b9b4524bf91fcf84ff97f98e7472c6 77ac196b2a3270f7ad259f1aa5602b02695d3e07 24a9407b2a713ba31bc76c8958cc92cf993ee98e 8fe5ea6b04a20ebadbb3be2f439c63a5257c0c07 b634c0cc239500f8420d84c2751db1b1a44b6fb8 63da09ec9d97a23a789d99601de00f8445a6ece6 321e790f57468ae335d2a0fd321c441f3100f38a 87742ca2d658a60048d1c61f8d410b625021af7d fbaa02fd24237c9c4d809f64581696b35784943b bc813f49869bc621d16585d7648ae5e2c4bfe81a 1a6728d9a5a2bc8e6f3febf66414c4dd6562df11 4389700a37b7c41741ad9e6f25743072c3b41889 3c3ba20ad9f923cfd9be6fec74a5100fcee432ed 0cebcae69d28faebb58e1f37f9c9c16ee8b097b1 f3bd49a0c980055d373007a7eca60aa24aa1464c a72d5ace87979b2b1647ef627be4ea8cc40d562d 3f6ea6ed37ee0948cfa574897261f181ab0af0f8 3f8691614b484f0060b72cf67442bc1a1b79844b f19f87d7d4325eedb4a266db47cf9a495f6e1ca2 fa9806ab3573a9c353a222da85b041fcc1d87a7c 7f96b230c12f91c3f83238608fb07ff53a32c08c 461c71c97f1e017f0f8827570d5f95f2c741bcef 51f8a8cad4f6faae2f98ca47f47de6670b765786 6b4608c72aa90e30910087cff27b192f18e7c9d1 e682af00fb9e952212cb0eaf63dd5d9b7d2a420b 17b89eea8920d9feec83e8ebbcad6c5063a6a608 864cb5b4b1b86e05dce86410deb6971af9ec9d53 093f4b1b000707456dd95dca2d21b34aa0090af4 574f0d60efa09a5f736ab0cc096ba30114ce5b7d ee9b1c68d5c3770a80c178d0c967ce55f299d096 a5a92ac439283a2876d39bce2e38348441451c99 6f7ec9398b602574396786567bb735ff00d954f8 270505741eaf8f7325be7ab3277c76abf58e9045 a8b26db33bfb995c2f8298654c183eac11174b17 bac4034ad784e796bc013d874a874e0c38777bcd e36449722cb15fbf56b65179c8ea6793ec8be906 47333b5cde16838ba7c4a8c64e99445f53c4e7f3 4d7d4e84888eeeefd12f3692320badb19040fe4d 6117b3fc5b30dcec0e3a162d6df5f976db6d1bdb 0ad302940d9e883763df68fe40fbb560aaaaccd6 c3f8fe6d4d980230dc9a32b6d20079c86bcfdd51 eec8c7ae6a3c49b1fd7a58fa1edb4e0a4fbdaef8 97e3ea8d6762476226e00e110d8a33e5630a3a6f 5f36971c01d9a3bc52ee999454c0199eb27a7ff8 b07a5e065a16755cba48d638d72b28c2bd111dac dbec72854ac57ee4f2937b09eedf373bd49e6e19 4b5a7621be2f99cb6ba75476ea9299e2155b9d16 4278d5b249e8f0302866777e10d57ceff32945cc 5d6cbfff711d6721af44702d9baee487078d77e3 a64b491d710f1258e6b1fd58772726a459ac8e91 14f87963c736094ad35c8e0908514670b2111774 a8f0df2a88064d31ed445a05e032c69ca490fc82 bb9adc17826e2b4df40255f3c7efd366541e1795 088ef02a18ffd0c57fbcc5565358905c624a3dda 0c689b9e7d08a64c320d4769ff6d312ec61f1b00 94fc56e69265f0d61d0cb7e896335d017b77747b 0c5292d265bb33dd3be8b6d217fdfbdb0ad7bcd9 35989c8bb8226c0d15bf02cd07ab23a5237d6eff fb1385b87914ddc040630bbe0d10c5a40bdc8b99 d9a3d2dc612cd1c5956ae98bb9393c4b58f94653 9caa328dcaa35b751c48f2e2197ce92104cdf132 3fb6f45c7a48a148cfc94afd4475e90a8d56f15f 1bbb6616fbf5393a984bda57e2d7423d15e06d7f 8c00bda7dbc94eae694acb5e0204c8b12eec633a c49f1656c78bfe96ea2c562f3bba3f80e294157f 9da44b990faf882f0988bb419456fa7a466e3d67 882cb7a3eac6de74cc913ce9fdc898c8fe0728b2 2da11a5f987d00e235ec6b7d7ea5d072b6a36198 afb668cb39cf623379989209a3f6cf4584eae71b 224b098a0dfc5adf8da010713ae55588d411c011 b7c3abf5004ccda474507dd39c0c52d579186584 63496f31a39d0bbfc35460259d90e4c5053c1db5 52189aced1a306c35cd2ad4af6db8826aa879837 51e91ee29e272e8d6af2854b46ff54096b942108 669cf794d179668c1176d0f7b3e9af0cc266187a 9c8444ae737cab7a94bd62edb0e06a70699cdad1 03c315ebbbfa321d8494f2ee5d32ad09ed2eb4a5 7bd1de71a7e6a8a4e2104fe8a92255c957f2094e 82917923a05f944ff8e283dc3846e06fb4a97b7c f1c1f30dd2b9a0720f3c2821c11a65f6908e50c0 f6912be94e6e4cf913c639398cffac55c5c96fa9 00538e7cc48ade86b72f17a41c4e5dd28ad6051d 659dac168fc571efe6872ed61abfce4a5800fb2c f7e26d7590574085fe389c8f8a8abfd28cb20499 89a2de5b935125ab917af7f8107d7f93e80f7cb6 cdbc3d9be43d180f90d02ba4dce6fb76c6d25774 e7a7d4b282560f435be995fe0a08f992d944bee8 7b958fda8aa5befdb950b226214cc89a982f696b ac333d0828cb9e772799d33a99ee2931fa6800d1 f8e6dac639b6437f73ee607da87f737911aa9c63 5152e54cd53dda2261269fe0ecbd4e73b1d54e79 6a58a375b667a3447bf28527937ddd2cf8ec3a95 f98711e799efc4ce3799b4ffde9e021a0f2ffded 0cac2e6e1a0642f65963b2aceb34a38dce332956 c6d6a174294ac9e6598f4eb7e44114419e083474 81f0bcc195e06efeee1e1c1f38a5e857aebdbc27 6ab256bc23ca59ebca0e282732e9bd3e1e8eb41f 30a0d5b5f6ed421bff78e92320c91841aa7fc5e1 1970e0617e9671f155b866f7b27c70d91975f153 0fbfa1e96b069d605d5cb6fe91aa7e88baeb46dd 9b981ea6e8cbc1c9bab4b23576bb58dd3026ea42 7b12db3d9acd7a1198dba333387b37aaf7db2ff8 16773e75599c05f867f44fa622636031f75bd8c8 00dc8386150c5da6207cd6f59a048ae26e38e136 25479f2a5fffce7ff304d6b38c979581b32dc801 fdd69aa8e1d79ef14fb0448b3ee702982e43f9da 1bc9419eb983a1bb2dd99645476b16670f5a565a 4ff4d11309aa6d40595ed6ec89d800c39bfcdf9c 015fce0deaf6b2edc5b4072d0fdddd65096f61c4 2918708fb475111254a86f6303a0e0fb23ac0c6c 763434d4868607ea78d5d1d233551f41490f6c4c 9161f79fc60c3d022be68c1c919168705af970f6 bdac91314d94d450695afe886069df1def8cd1af dd125a19e527939ff5c128856283e0bc4bc0def3 0ae571409ca712c0e6032ff34a662fea009fde7c d720a14a295d5c1f73db93e8b65b9fa459464018 cb5b188dc0b07d264bbe59f0507a56b1a2a14ea4 e7427ec396688045c49a33df0bd997c6d0baf078 3f1075a79528d14ea34c921fab1a79791af034be 88bf113c19036b381fcc477166af1c2f98bf3e8d 3d8be7ae7c28d86d733165b135781c8947e3330e 61df3f49ca44afb570b4c7508238b7ebfebaab01 b95cb445d214687f20e728012f07606df5a6e9f3 6f7ebede0ca8a72d6b6f7ccf0a9106bb862cac39 feb0bebc498f8bf64ef9f2286c5c1cebb5127c4f 493840379aa8b915916a6ae6135ed8ccc3038bea 2fbe8142d19ed8cf0d6399f63214adcff09c1325 592e3a4b0501cfa5a0dbe85693bd33cab18e5ce9 4dedc70f7dcecedc52e5f8dd2532ec9666308c96 1f65b29dd8870d17100089e934c27e3774d136d1 5cbd3ea1b5a3b584b4fea043c9ecfec6396d9ee8 dbb626353928f9a130d707c057b038ff38ab9dca 64a24f404516f0c54222749fe7e924cce6706b40 0c40065b190b1eabb3ec95eb835a2f8decac81ff e029468a13c3e8c0b6030a3276a179990d1de11e 8c3eb214a98cad32e9798a2082170de6fd040870 5a08d61c877932e39b997fff9e9980b2456dc348 1a77d09fac5c722e294a1a8f9846758caa5a7c2a 918b7ba206cc5e0a97dd86e82695772c25ce8347 7b433cd386abbb190cbde543708eaaf0dfd567bd af7d0f4b31df1bf21bf91cba6946685235ab3ece 55e4d2a715c262dbdd5df12944e966bef9ee3d72 25a091ed28521fea47632909651a725fc7eca153 f46610f88beaa8b937097461b5fb77d2967f7016 0f56674cf0f18f39e18918c2191417b69ed2ea82 b5667380011d1f940fd03d2f22ffefd380fe816e ec411e94da0c4dbcd4078483689d0f409d59d278 8838b5495fa752aa388c09eb28be989f320269f0 d0b603a0cd9ef29aaaeecd5094df91a29222a4b6 18b6620ad991c1e7896cf605377be10fa6569387 d92edfb0b0d787f072513b68b9aff16f2021a678 91c0c1881b5186f479db3f1046e207291b077582 68abc449f309e8b27c18f7e076721736620a1f21 cce6379b13070616fd186c01dee622366ec3c517 69195deb45197ffd6a6a78a72df65df3108fff6a 798f3bb9f5eb16724ad4eef99967155ecd00b602 6921ff7af74065190741e1a718f8a0551825a095 e1490bc7b17ae36c6f081b092014754ae3a8c115 3be4ee51309b570f6617403f24b1c8662f35c487 6546ded1458f724d33ffe527cfaf453d8979da0a 74dd6ca3e4d2f88db6d8205b2118a8e2f08475ed b127757f8912661d9d9292db7567782e042d464b 3994d71451bc8596310945e043cf31018212d815 23e91efb4050638a050125af743862915875bc11 e90589481e62211a0c57fff537c20db4226c3a47 9571c5a872379c646a04d0b2701602d3cff93521 7c71eb8fb1af846c5acb56809d0064a3e1ae25f8 2e195af1554cf51d9d57ad11b63862e9bc6c64e0 5d8e7ca80a359c04b1e5839266647245c8b05a0d fbdd0b7881be637f8893f8f138805e56d06aa22d f852135775123a7d2f4d4338038f97bfda21efd0 17555a94a9fae749331872c0cff2da8b1aca42d4 0df719c6bd8d001311418c3b4bcc31d417d184ab 67e9f09358bfd180d71c19d12377e72b8601a7bf 38f275621c9727894c80d37922023b1e82a62c13 57f965d3c06817386be9519a1384f699d7ddf523 4b3798280cbf3b9735a54a982868e8cc91eb046f a72121b9551921aa3dced32d943c6034ba318f82 955add0b09637e7ecdd62411272b2d0ef84d3aa3 ce6c5aac0db5476dc496c34388e4f9ce2c4b86e5 b46b1e64f06f448bde78b98e3ae8228ce5f96067 END_SECTION COMMITS) [//]: # (START_SECTION 7c86d935be95d9838f45727025d442f55a8456a4) ### Error fixes (#677) > Commit: [7c86d935be95d9838f45727025d442f55a8456a4](https://github.com/dOpensource/dsiprouter/commit/7c86d935be95d9838f45727025d442f55a8456a4) > Date: Fri, 18 Jul 2025 10:33:42 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - * Update v0.78 Upgrade With DB Changes - * Fix "sdp_transport_helper(): malformed or non AVP $dlg_var(src_media_tp)" - `sdp_transport` does not support dlg_var, switch to pv - remove inaccurate dlg_var on MANAGE_REPLY - set default for rtpengine to enabled when dlg flag is not set - * Revert Media Handling Improvements - not enough time to revalidate, will revisit in next release - fix rtpengine enabled on direct media --- [//]: # (END_SECTION 7c86d935be95d9838f45727025d442f55a8456a4) [//]: # (START_SECTION 844fdfd70c526e3e3f4b94ca91de5a74bab12213) ### Fixed error message when configuresslcert is executed > Commit: [844fdfd70c526e3e3f4b94ca91de5a74bab12213](https://github.com/dOpensource/dsiprouter/commit/844fdfd70c526e3e3f4b94ca91de5a74bab12213) > Date: Fri, 18 Jul 2025 13:24:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 844fdfd70c526e3e3f4b94ca91de5a74bab12213) [//]: # (START_SECTION 8261be6d3e1772d94acb1d8c8de194a0b4afbdd6) ### Update README.md > Commit: [8261be6d3e1772d94acb1d8c8de194a0b4afbdd6](https://github.com/dOpensource/dsiprouter/commit/8261be6d3e1772d94acb1d8c8de194a0b4afbdd6) > Date: Fri, 18 Jul 2025 07:59:55 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8261be6d3e1772d94acb1d8c8de194a0b4afbdd6) [//]: # (START_SECTION c1b270c5efb9f1640ed0a32a7d9c0d7f96b1a02d) ### WebRTC Fixes to handle a more compliant WebRTC client called Jambonz, which is based on JsSIP > Commit: [c1b270c5efb9f1640ed0a32a7d9c0d7f96b1a02d](https://github.com/dOpensource/dsiprouter/commit/c1b270c5efb9f1640ed0a32a7d9c0d7f96b1a02d) > Date: Fri, 18 Jul 2025 11:52:25 +0000 > Author: root (root@mack.test.dsiprouter.net) > Committer: root (root@mack.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION c1b270c5efb9f1640ed0a32a7d9c0d7f96b1a02d) [//]: # (START_SECTION 58096c74533221acc325c101627b8f8a8ada1282) ### Update of login page and logo > Commit: [58096c74533221acc325c101627b8f8a8ada1282](https://github.com/dOpensource/dsiprouter/commit/58096c74533221acc325c101627b8f8a8ada1282) > Date: Thu, 17 Jul 2025 04:26:23 +0000 > Author: root (root@mack.test.dsiprouter.net) > Committer: root (root@mack.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 58096c74533221acc325c101627b8f8a8ada1282) [//]: # (START_SECTION c8c6ba9ded2fc109099a8fd3c9e7775ea61560c5) ### Update v0.78 Upgrade With DB Changes > Commit: [c8c6ba9ded2fc109099a8fd3c9e7775ea61560c5](https://github.com/dOpensource/dsiprouter/commit/c8c6ba9ded2fc109099a8fd3c9e7775ea61560c5) > Date: Mon, 14 Jul 2025 12:16:13 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION c8c6ba9ded2fc109099a8fd3c9e7775ea61560c5) [//]: # (START_SECTION a252b7e6646a684a44946f48bc93f78335d559f4) ### Fix Failure Routing > Commit: [a252b7e6646a684a44946f48bc93f78335d559f4](https://github.com/dOpensource/dsiprouter/commit/a252b7e6646a684a44946f48bc93f78335d559f4) > Date: Mon, 14 Jul 2025 10:53:06 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move 3xx blocking up in faliure route - move timeout check to after failover/hard fwd checks - dsip_prefix_mapping view only includes inbound routes now --- [//]: # (END_SECTION a252b7e6646a684a44946f48bc93f78335d559f4) [//]: # (START_SECTION 31d9a61fe5e5698a83ba460a347ca2d9d681904f) ### Cluster Fixes > Commit: [31d9a61fe5e5698a83ba460a347ca2d9d681904f](https://github.com/dOpensource/dsiprouter/commit/31d9a61fe5e5698a83ba460a347ca2d9d681904f) > Date: Mon, 14 Jul 2025 09:44:50 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - do not allow debian images without systemd-resolved to install with `-dns` - fix return code of each install on `clusterinstall` subcommand --- [//]: # (END_SECTION 31d9a61fe5e5698a83ba460a347ca2d9d681904f) [//]: # (START_SECTION 902c53e6ea1b56ba293362fd7147f954fe5b87f0) ### Extra Check for Carrier Load Balancing > Commit: [902c53e6ea1b56ba293362fd7147f954fe5b87f0](https://github.com/dOpensource/dsiprouter/commit/902c53e6ea1b56ba293362fd7147f954fe5b87f0) > Date: Fri, 11 Jul 2025 08:25:06 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - if dispatcher entry does not yet exist when updating, create it --- [//]: # (END_SECTION 902c53e6ea1b56ba293362fd7147f954fe5b87f0) [//]: # (START_SECTION 10cdc797dddb3053dfa574dcd10e5fe0696cffe8) ### Load Balancing Fixes > Commit: [10cdc797dddb3053dfa574dcd10e5fe0696cffe8](https://github.com/dOpensource/dsiprouter/commit/10cdc797dddb3053dfa574dcd10e5fe0696cffe8) > Date: Thu, 10 Jul 2025 11:53:24 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load balancing regression - set more sane defaults for rweight/keepalive --- [//]: # (END_SECTION 10cdc797dddb3053dfa574dcd10e5fe0696cffe8) [//]: # (START_SECTION 2485a964ccf7d5eab5c719d9fe1d24221e79ff41) ### Fix CLI Debug Handling > Commit: [2485a964ccf7d5eab5c719d9fe1d24221e79ff41](https://github.com/dOpensource/dsiprouter/commit/2485a964ccf7d5eab5c719d9fe1d24221e79ff41) > Date: Wed, 9 Jul 2025 11:00:59 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - `dsiprouter start -debug` now works as intended (blocking) - allow environment variables being passed when started in debug mode --- [//]: # (END_SECTION 2485a964ccf7d5eab5c719d9fe1d24221e79ff41) [//]: # (START_SECTION 37fe6c03ed7e798119732367dc1896fa93d3182f) ### Fix Record Routing for Some NAT Scenarios > Commit: [37fe6c03ed7e798119732367dc1896fa93d3182f](https://github.com/dOpensource/dsiprouter/commit/37fe6c03ed7e798119732367dc1896fa93d3182f) > Date: Tue, 8 Jul 2025 13:16:09 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 37fe6c03ed7e798119732367dc1896fa93d3182f) [//]: # (START_SECTION 49ba3a7b761ef8f7c71de501c66bdadeeddc70ac) ### Resolves #640 > Commit: [49ba3a7b761ef8f7c71de501c66bdadeeddc70ac](https://github.com/dOpensource/dsiprouter/commit/49ba3a7b761ef8f7c71de501c66bdadeeddc70ac) > Date: Tue, 11 Mar 2025 11:51:05 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 49ba3a7b761ef8f7c71de501c66bdadeeddc70ac) [//]: # (START_SECTION 5995b5b7296506b72e5863aba07d1343acb9e113) ### Resolves #637 > Commit: [5995b5b7296506b72e5863aba07d1343acb9e113](https://github.com/dOpensource/dsiprouter/commit/5995b5b7296506b72e5863aba07d1343acb9e113) > Date: Thu, 13 Mar 2025 13:21:33 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 5995b5b7296506b72e5863aba07d1343acb9e113) [//]: # (START_SECTION 23b1395497f61ec9a8f60ab63dd394397e1866b6) ### Fix Typos in Deb12 DnsMasq Install > Commit: [23b1395497f61ec9a8f60ab63dd394397e1866b6](https://github.com/dOpensource/dsiprouter/commit/23b1395497f61ec9a8f60ab63dd394397e1866b6) > Date: Thu, 20 Mar 2025 12:04:50 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 23b1395497f61ec9a8f60ab63dd394397e1866b6) [//]: # (START_SECTION 3007c0e8499cc95673704a420dee938dd424ef9e) ### Fix Amzn2 RTPEngine Install > Commit: [3007c0e8499cc95673704a420dee938dd424ef9e](https://github.com/dOpensource/dsiprouter/commit/3007c0e8499cc95673704a420dee938dd424ef9e) > Date: Wed, 9 Apr 2025 08:24:31 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - bump rtpengine to mr11.5.1.11 - update dependencies --- [//]: # (END_SECTION 3007c0e8499cc95673704a420dee938dd424ef9e) [//]: # (START_SECTION e1e287dd579ff03c0407b90d047d76e10a2ad52f) ### Fix Amzn2 Kamailio Install > Commit: [e1e287dd579ff03c0407b90d047d76e10a2ad52f](https://github.com/dOpensource/dsiprouter/commit/e1e287dd579ff03c0407b90d047d76e10a2ad52f) > Date: Mon, 7 Apr 2025 13:56:51 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION e1e287dd579ff03c0407b90d047d76e10a2ad52f) [//]: # (START_SECTION 8f427d4f46d64c4ead9c32402d56f509f63850c9) ### Fix AWS Image Creation > Commit: [8f427d4f46d64c4ead9c32402d56f509f63850c9](https://github.com/dOpensource/dsiprouter/commit/8f427d4f46d64c4ead9c32402d56f509f63850c9) > Date: Tue, 1 Apr 2025 14:54:49 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - various updates to cleanup logic for golden images - add support for `dnf` in cloud build scripts --- [//]: # (END_SECTION 8f427d4f46d64c4ead9c32402d56f509f63850c9) [//]: # (START_SECTION 155f1594e37d88e14dc9699cc75cba04f3d6b2f0) ### Update Demo API Token > Commit: [155f1594e37d88e14dc9699cc75cba04f3d6b2f0](https://github.com/dOpensource/dsiprouter/commit/155f1594e37d88e14dc9699cc75cba04f3d6b2f0) > Date: Tue, 11 Mar 2025 13:30:00 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 155f1594e37d88e14dc9699cc75cba04f3d6b2f0) [//]: # (START_SECTION ababcdd35782a7234b14e9a8c9630e2127bfc52e) ### Add Support for Upgrading to v0.78 (#674) > Commit: [ababcdd35782a7234b14e9a8c9630e2127bfc52e](https://github.com/dOpensource/dsiprouter/commit/ababcdd35782a7234b14e9a8c9630e2127bfc52e) > Date: Thu, 3 Jul 2025 11:17:04 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - * Add Support for Upgrading to v0.78 - * Fix Multiple Settings Effected by Updates - fixes `setConfigAttrib()` matching other settings partially --- [//]: # (END_SECTION ababcdd35782a7234b14e9a8c9630e2127bfc52e) [//]: # (START_SECTION 73cb9b23631150873b18c39696018a4f599738a9) ### Add FusionPBX Provisioning Permissions to chown Subcommand > Commit: [73cb9b23631150873b18c39696018a4f599738a9](https://github.com/dOpensource/dsiprouter/commit/73cb9b23631150873b18c39696018a4f599738a9) > Date: Thu, 3 Jul 2025 07:50:02 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 73cb9b23631150873b18c39696018a4f599738a9) [//]: # (START_SECTION 303bf86be04320131b8fce613e19aa71dfd7ce75) ### Version and logo update > Commit: [303bf86be04320131b8fce613e19aa71dfd7ce75](https://github.com/dOpensource/dsiprouter/commit/303bf86be04320131b8fce613e19aa71dfd7ce75) > Date: Wed, 2 Jul 2025 11:16:53 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 303bf86be04320131b8fce613e19aa71dfd7ce75) [//]: # (START_SECTION 52e07933e0845f85c95c6c05af3e4a7b9ad3f2c2) ### Fix from merge > Commit: [52e07933e0845f85c95c6c05af3e4a7b9ad3f2c2](https://github.com/dOpensource/dsiprouter/commit/52e07933e0845f85c95c6c05af3e4a7b9ad3f2c2) > Date: Sun, 29 Jun 2025 01:28:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 52e07933e0845f85c95c6c05af3e4a7b9ad3f2c2) [//]: # (START_SECTION a39ef41fa5eb058903ca03f8b923570aac7ff15a) ### Added changes for WebRTC fixes: #666 > Commit: [a39ef41fa5eb058903ca03f8b923570aac7ff15a](https://github.com/dOpensource/dsiprouter/commit/a39ef41fa5eb058903ca03f8b923570aac7ff15a) > Date: Sun, 29 Jun 2025 01:16:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a39ef41fa5eb058903ca03f8b923570aac7ff15a) [//]: # (START_SECTION 3dd1156097fefd98be296dc312bbc03eb3c58002) ### Added a commit to allow WebRTC clients using PassThru auth to automatically translate secure SDP into non-secure SDP if the Media setting on the endpoint is proxy > Commit: [3dd1156097fefd98be296dc312bbc03eb3c58002](https://github.com/dOpensource/dsiprouter/commit/3dd1156097fefd98be296dc312bbc03eb3c58002) > Date: Sat, 28 Jun 2025 00:38:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3dd1156097fefd98be296dc312bbc03eb3c58002) [//]: # (START_SECTION fd1696c6f460afdac86e5e56414b975ecc3c37ec) ### Added support for OnHold > Commit: [fd1696c6f460afdac86e5e56414b975ecc3c37ec](https://github.com/dOpensource/dsiprouter/commit/fd1696c6f460afdac86e5e56414b975ecc3c37ec) > Date: Fri, 23 May 2025 04:58:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fd1696c6f460afdac86e5e56414b975ecc3c37ec) [//]: # (START_SECTION 0717ce38b28066de5749decbc3aa65bf65051956) ### Added logic to handle BYE's and ACK's for WebRTC > Commit: [0717ce38b28066de5749decbc3aa65bf65051956](https://github.com/dOpensource/dsiprouter/commit/0717ce38b28066de5749decbc3aa65bf65051956) > Date: Thu, 19 Jun 2025 03:10:46 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0717ce38b28066de5749decbc3aa65bf65051956) [//]: # (START_SECTION b580cc323974d7aba05932392940050f14350858) ### Fixed ACK issue > Commit: [b580cc323974d7aba05932392940050f14350858](https://github.com/dOpensource/dsiprouter/commit/b580cc323974d7aba05932392940050f14350858) > Date: Fri, 13 Jun 2025 22:36:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b580cc323974d7aba05932392940050f14350858) [//]: # (START_SECTION 5c5eb60127bc8b176e933c39f30a60a56d08a312) ### Added logic in the LOCATION route to make it transport and signaling aware so that calls are properly routed and translated via the RTPENGINE > Commit: [5c5eb60127bc8b176e933c39f30a60a56d08a312](https://github.com/dOpensource/dsiprouter/commit/5c5eb60127bc8b176e933c39f30a60a56d08a312) > Date: Wed, 11 Jun 2025 21:24:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5c5eb60127bc8b176e933c39f30a60a56d08a312) [//]: # (START_SECTION 49e9c8c9f4ab16572259a178182437a5b8ef44d8) ### Added support for OnHold > Commit: [49e9c8c9f4ab16572259a178182437a5b8ef44d8](https://github.com/dOpensource/dsiprouter/commit/49e9c8c9f4ab16572259a178182437a5b8ef44d8) > Date: Fri, 23 May 2025 04:58:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 49e9c8c9f4ab16572259a178182437a5b8ef44d8) [//]: # (START_SECTION b46787c059a00d4a8b17a957df3f696f3dad682d) ### Added a flag that would trigger RTPENGINEANSER > Commit: [b46787c059a00d4a8b17a957df3f696f3dad682d](https://github.com/dOpensource/dsiprouter/commit/b46787c059a00d4a8b17a957df3f696f3dad682d) > Date: Fri, 16 May 2025 22:02:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b46787c059a00d4a8b17a957df3f696f3dad682d) [//]: # (START_SECTION e606c4765084386cf03740c99cb8916bcc78f7f3) ### Added a commit to allow WebRTC clients using PassThru auth to automatically translate secure SDP into non-secure SDP if the Media setting on the endpoint is proxy > Commit: [e606c4765084386cf03740c99cb8916bcc78f7f3](https://github.com/dOpensource/dsiprouter/commit/e606c4765084386cf03740c99cb8916bcc78f7f3) > Date: Sat, 28 Jun 2025 00:38:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e606c4765084386cf03740c99cb8916bcc78f7f3) [//]: # (START_SECTION 17a10889ddc23eee8f9392319fee27e6401bcb70) ### Added support for OnHold > Commit: [17a10889ddc23eee8f9392319fee27e6401bcb70](https://github.com/dOpensource/dsiprouter/commit/17a10889ddc23eee8f9392319fee27e6401bcb70) > Date: Fri, 23 May 2025 04:58:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 17a10889ddc23eee8f9392319fee27e6401bcb70) [//]: # (START_SECTION 08ea093039491b124b09bd4b21f63fd629019afd) ### Revert "Location Lookup Improvements" > Commit: [08ea093039491b124b09bd4b21f63fd629019afd](https://github.com/dOpensource/dsiprouter/commit/08ea093039491b124b09bd4b21f63fd629019afd) > Date: Fri, 27 Jun 2025 01:14:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - This reverts commit a85ae0c02f89bcf0539c56201a58e5d01585f062. --- [//]: # (END_SECTION 08ea093039491b124b09bd4b21f63fd629019afd) [//]: # (START_SECTION 9e2e812346a002b11e66ef6bd87ce72e130b198d) ### Revert "Revert Carrier Redial Location Lookup" > Commit: [9e2e812346a002b11e66ef6bd87ce72e130b198d](https://github.com/dOpensource/dsiprouter/commit/9e2e812346a002b11e66ef6bd87ce72e130b198d) > Date: Fri, 27 Jun 2025 01:14:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - This reverts commit f298cc3ba4118807586579090c473df2035189e5. --- [//]: # (END_SECTION 9e2e812346a002b11e66ef6bd87ce72e130b198d) [//]: # (START_SECTION f298cc3ba4118807586579090c473df2035189e5) ### Revert Carrier Redial Location Lookup > Commit: [f298cc3ba4118807586579090c473df2035189e5](https://github.com/dOpensource/dsiprouter/commit/f298cc3ba4118807586579090c473df2035189e5) > Date: Thu, 26 Jun 2025 13:14:15 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - too close to release, reverting this extra feature --- [//]: # (END_SECTION f298cc3ba4118807586579090c473df2035189e5) [//]: # (START_SECTION a85ae0c02f89bcf0539c56201a58e5d01585f062) ### Location Lookup Improvements > Commit: [a85ae0c02f89bcf0539c56201a58e5d01585f062](https://github.com/dOpensource/dsiprouter/commit/a85ae0c02f89bcf0539c56201a58e5d01585f062) > Date: Thu, 26 Jun 2025 13:03:51 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #669 - allow phones on other side of carrier to redial a registered device - remove local exten length check and continue routing if no device found --- [//]: # (END_SECTION a85ae0c02f89bcf0539c56201a58e5d01585f062) [//]: # (START_SECTION fbd0f7c84e4cd66e7f0d694a965abc259f333683) ### - Added logic to properly enable the RTPEngine on 200OK's - fixed #661 - Added logic to correctly process Extension to Extension calls that use Domain Routing > Commit: [fbd0f7c84e4cd66e7f0d694a965abc259f333683](https://github.com/dOpensource/dsiprouter/commit/fbd0f7c84e4cd66e7f0d694a965abc259f333683) > Date: Thu, 26 Jun 2025 18:11:06 +0000 > Author: root (root@ip-172-31-53-209) > Committer: root (root@ip-172-31-53-209) > Signed: --- [//]: # (END_SECTION fbd0f7c84e4cd66e7f0d694a965abc259f333683) [//]: # (START_SECTION a638bc71661c89be5881a9d776b9f73a6ad0af97) ### Updated device provisioning documentation > Commit: [a638bc71661c89be5881a9d776b9f73a6ad0af97](https://github.com/dOpensource/dsiprouter/commit/a638bc71661c89be5881a9d776b9f73a6ad0af97) > Date: Sun, 22 Jun 2025 07:59:26 -0400 > Author: Mack Hendricks (mhendricks@sangoma.com) > Committer: Mack Hendricks (mhendricks@sangoma.com) > Signed: --- [//]: # (END_SECTION a638bc71661c89be5881a9d776b9f73a6ad0af97) [//]: # (START_SECTION 952f0655d49ad7ab60c106b4f108945070b7c8eb) ### Moves nginx changes to the FusionPBX module install script > Commit: [952f0655d49ad7ab60c106b4f108945070b7c8eb](https://github.com/dOpensource/dsiprouter/commit/952f0655d49ad7ab60c106b4f108945070b7c8eb) > Date: Thu, 19 Jun 2025 21:50:24 +0000 > Author: root (root@mack.test.dsiprouter.net) > Committer: root (root@mack.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 952f0655d49ad7ab60c106b4f108945070b7c8eb) [//]: # (START_SECTION a058f7f1054f5c65c330f0424e732ce6861793bc) ### Added logic to write out the Nginx configuration for FusionPBX Provisioning within the /etc/nginx/sites-enabled directory > Commit: [a058f7f1054f5c65c330f0424e732ce6861793bc](https://github.com/dOpensource/dsiprouter/commit/a058f7f1054f5c65c330f0424e732ce6861793bc) > Date: Thu, 19 Jun 2025 21:13:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a058f7f1054f5c65c330f0424e732ce6861793bc) [//]: # (START_SECTION f3993379b9ba18b9b4a26eba2611c03ac64b568f) ### Added logic to handle BYE's and ACK's for WebRTC > Commit: [f3993379b9ba18b9b4a26eba2611c03ac64b568f](https://github.com/dOpensource/dsiprouter/commit/f3993379b9ba18b9b4a26eba2611c03ac64b568f) > Date: Thu, 19 Jun 2025 03:10:46 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f3993379b9ba18b9b4a26eba2611c03ac64b568f) [//]: # (START_SECTION febc0d9073bf1834e0fc13dbaf893cb9179a82d6) ### Fixed ACK issue > Commit: [febc0d9073bf1834e0fc13dbaf893cb9179a82d6](https://github.com/dOpensource/dsiprouter/commit/febc0d9073bf1834e0fc13dbaf893cb9179a82d6) > Date: Fri, 13 Jun 2025 22:36:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION febc0d9073bf1834e0fc13dbaf893cb9179a82d6) [//]: # (START_SECTION 01171453f8bfe861f37aceb332ada09057ad4b59) ### Added logic in the LOCATION route to make it transport and signaling aware so that calls are properly routed and translated via the RTPENGINE > Commit: [01171453f8bfe861f37aceb332ada09057ad4b59](https://github.com/dOpensource/dsiprouter/commit/01171453f8bfe861f37aceb332ada09057ad4b59) > Date: Wed, 11 Jun 2025 21:24:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 01171453f8bfe861f37aceb332ada09057ad4b59) [//]: # (START_SECTION d61ec9564deeca1999c61f0683af50bf8b74730d) ### Cleaned up xlog comments > Commit: [d61ec9564deeca1999c61f0683af50bf8b74730d](https://github.com/dOpensource/dsiprouter/commit/d61ec9564deeca1999c61f0683af50bf8b74730d) > Date: Tue, 10 Jun 2025 03:57:09 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d61ec9564deeca1999c61f0683af50bf8b74730d) [//]: # (START_SECTION cdfda0c7319e70217b1d6f5570ba7d242e181b4b) ### Fixing broken edit/delete links on domains > Commit: [cdfda0c7319e70217b1d6f5570ba7d242e181b4b](https://github.com/dOpensource/dsiprouter/commit/cdfda0c7319e70217b1d6f5570ba7d242e181b4b) > Date: Fri, 6 Jun 2025 23:03:26 -0500 > Author: Micah Quinn (micah.quinn@sipiq.com) > Committer: Micah Quinn (micah.quinn@sipiq.com) > Signed: --- [//]: # (END_SECTION cdfda0c7319e70217b1d6f5570ba7d242e181b4b) [//]: # (START_SECTION fe6dee1e3bb7f94a87dca23d8f78512fbd7c03b7) ### Added logic to route NOTIFY messages when the RURI contains it's private ip address. We use the TO header to lookup the real contact address in the location table > Commit: [fe6dee1e3bb7f94a87dca23d8f78512fbd7c03b7](https://github.com/dOpensource/dsiprouter/commit/fe6dee1e3bb7f94a87dca23d8f78512fbd7c03b7) > Date: Sun, 1 Jun 2025 01:49:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fe6dee1e3bb7f94a87dca23d8f78512fbd7c03b7) [//]: # (START_SECTION 43d053b938a14191b97ce44048a8751021fc6255) ### Added support for OnHold > Commit: [43d053b938a14191b97ce44048a8751021fc6255](https://github.com/dOpensource/dsiprouter/commit/43d053b938a14191b97ce44048a8751021fc6255) > Date: Fri, 23 May 2025 04:58:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 43d053b938a14191b97ce44048a8751021fc6255) [//]: # (START_SECTION 68a2dd685172e2b5521f1b698456b2f0dde077b4) ### Added some notes around setting up Phone Provisioning when dSIPRouter is being used to proxy FusionPBX UI requests > Commit: [68a2dd685172e2b5521f1b698456b2f0dde077b4](https://github.com/dOpensource/dsiprouter/commit/68a2dd685172e2b5521f1b698456b2f0dde077b4) > Date: Wed, 21 May 2025 00:16:42 -0400 > Author: Mack Hendricks (mhendricks@sangoma.com) > Committer: Mack Hendricks (mhendricks@sangoma.com) > Signed: --- [//]: # (END_SECTION 68a2dd685172e2b5521f1b698456b2f0dde077b4) [//]: # (START_SECTION 4f2a9f8ad407110852092ce543461972687588df) ### Added a flag that would trigger RTPENGINEANSER > Commit: [4f2a9f8ad407110852092ce543461972687588df](https://github.com/dOpensource/dsiprouter/commit/4f2a9f8ad407110852092ce543461972687588df) > Date: Fri, 16 May 2025 22:02:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4f2a9f8ad407110852092ce543461972687588df) [//]: # (START_SECTION 656fbc1c50d503d7f14d13674da597ebc4e1f117) ### Converted back the commit > Commit: [656fbc1c50d503d7f14d13674da597ebc4e1f117](https://github.com/dOpensource/dsiprouter/commit/656fbc1c50d503d7f14d13674da597ebc4e1f117) > Date: Wed, 14 May 2025 20:45:37 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 656fbc1c50d503d7f14d13674da597ebc4e1f117) [//]: # (START_SECTION 186f48f0d295f28527564a3dc3103cab8993f65c) ### Fixes #660 - Added logic to test if the SIP Dialog was already created. If so, don't create a new SIP Dialog " Fixes #554 - Confirmed that it's fixed Fixes #545 - Confirmed that it's fixed Fixes #520 - Confirmed that it's fixed > Commit: [186f48f0d295f28527564a3dc3103cab8993f65c](https://github.com/dOpensource/dsiprouter/commit/186f48f0d295f28527564a3dc3103cab8993f65c) > Date: Wed, 14 May 2025 20:35:30 +0000 > Author: root (root@demo.test.dsiprouter.net) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 186f48f0d295f28527564a3dc3103cab8993f65c) [//]: # (START_SECTION 2b76d501e66b0b22acc911e944f24ca09588cbd7) ### Fixes #660 - Added logic to test if the SIP Dialog was already created. If so, don't create a new SIP Dialog " Fixes #554 - Confirmed that it's fixed Fixes #545 - Confirmed that it's fixed Fixes #520 - Confirmed that it's fixed > Commit: [2b76d501e66b0b22acc911e944f24ca09588cbd7](https://github.com/dOpensource/dsiprouter/commit/2b76d501e66b0b22acc911e944f24ca09588cbd7) > Date: Wed, 14 May 2025 20:35:30 +0000 > Author: root (root@demo.test.dsiprouter.net) > Committer: root (root@demo.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 2b76d501e66b0b22acc911e944f24ca09588cbd7) [//]: # (START_SECTION 9b1cab3e12f978ae79c7317b530881a1c6cbe8ae) ### Fixes: - Fixed the contact so that the transport parameter is changed based on the destination - Fixed the Path header so that the transport parameter is defined based on the destination > Commit: [9b1cab3e12f978ae79c7317b530881a1c6cbe8ae](https://github.com/dOpensource/dsiprouter/commit/9b1cab3e12f978ae79c7317b530881a1c6cbe8ae) > Date: Tue, 22 Apr 2025 02:06:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9b1cab3e12f978ae79c7317b530881a1c6cbe8ae) [//]: # (START_SECTION 6fc2548389b0c6672f9141baf4e53e71bc297184) ### Fixed issue with RE-INVITES from a Carrier to MSTeam causes a 488 from MSTeams with this error message: 'CannotChangeRtcpMultiplexing, InternalErrorPhrase: Cannot change RTCP multiplexing from existing active media' > Commit: [6fc2548389b0c6672f9141baf4e53e71bc297184](https://github.com/dOpensource/dsiprouter/commit/6fc2548389b0c6672f9141baf4e53e71bc297184) > Date: Fri, 18 Apr 2025 00:29:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6fc2548389b0c6672f9141baf4e53e71bc297184) [//]: # (START_SECTION 3f73914f1225b2836cab1f13e366582a61c183b4) ### Fixed REMOVE_REFER > Commit: [3f73914f1225b2836cab1f13e366582a61c183b4](https://github.com/dOpensource/dsiprouter/commit/3f73914f1225b2836cab1f13e366582a61c183b4) > Date: Mon, 7 Apr 2025 13:15:07 -0400 > Author: Mack Hendricks (mhendricks@sangoma.com) > Committer: Mack Hendricks (mhendricks@sangoma.com) > Signed: --- [//]: # (END_SECTION 3f73914f1225b2836cab1f13e366582a61c183b4) [//]: # (START_SECTION 6265abc736c8c08348a951da48b643bd67034b0e) ### Fixed Internal Transfers > Commit: [6265abc736c8c08348a951da48b643bd67034b0e](https://github.com/dOpensource/dsiprouter/commit/6265abc736c8c08348a951da48b643bd67034b0e) > Date: Mon, 7 Apr 2025 11:25:21 -0400 > Author: Mack Hendricks (mhendricks@sangoma.com) > Committer: Mack Hendricks (mhendricks@sangoma.com) > Signed: --- [//]: # (END_SECTION 6265abc736c8c08348a951da48b643bd67034b0e) [//]: # (START_SECTION b57a2a3670df1bf0005a4992f2af073e4d85738a) ### Fixed issue with MSTeams calls not hanging up properly > Commit: [b57a2a3670df1bf0005a4992f2af073e4d85738a](https://github.com/dOpensource/dsiprouter/commit/b57a2a3670df1bf0005a4992f2af073e4d85738a) > Date: Sat, 5 Apr 2025 16:50:25 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b57a2a3670df1bf0005a4992f2af073e4d85738a) [//]: # (START_SECTION a977b58eee52a00f10ea32f7e9a05f2222037a3d) ### Trigger Jenkins build > Commit: [a977b58eee52a00f10ea32f7e9a05f2222037a3d](https://github.com/dOpensource/dsiprouter/commit/a977b58eee52a00f10ea32f7e9a05f2222037a3d) > Date: Fri, 28 Mar 2025 14:36:48 -0400 > Author: chelseatcarter (chelseatcarter@gmail.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a977b58eee52a00f10ea32f7e9a05f2222037a3d) [//]: # (START_SECTION f47832ae5061628cef09e3198b7a33525f2cd0d9) ### Trigger Jenkins build > Commit: [f47832ae5061628cef09e3198b7a33525f2cd0d9](https://github.com/dOpensource/dsiprouter/commit/f47832ae5061628cef09e3198b7a33525f2cd0d9) > Date: Fri, 28 Mar 2025 14:25:21 -0400 > Author: chelseatcarter (chelseatcarter@gmail.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f47832ae5061628cef09e3198b7a33525f2cd0d9) [//]: # (START_SECTION f2a522d40e62c96bd1e55de7e098cfda9c98fbfe) ### Fixed Issues - REGISTER will now follow the signal settings in the endpoint group for domain routing. Fixes #656 - Fixed an issue with the REPLACE_CONTACT_DOMAIN route, the semi-colon was missing in the protocal portion of the contact > Commit: [f2a522d40e62c96bd1e55de7e098cfda9c98fbfe](https://github.com/dOpensource/dsiprouter/commit/f2a522d40e62c96bd1e55de7e098cfda9c98fbfe) > Date: Fri, 28 Mar 2025 16:03:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f2a522d40e62c96bd1e55de7e098cfda9c98fbfe) [//]: # (START_SECTION 92176ca0a908d5a778cb9a1081d7bd3a80df719c) ### WIP on SUBSCRIBE Interdomain Messaging > Commit: [92176ca0a908d5a778cb9a1081d7bd3a80df719c](https://github.com/dOpensource/dsiprouter/commit/92176ca0a908d5a778cb9a1081d7bd3a80df719c) > Date: Wed, 12 Mar 2025 15:13:32 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - fix POSIX REGEX check on Contact - remove non-useful domain code on PUBLISH --- [//]: # (END_SECTION 92176ca0a908d5a778cb9a1081d7bd3a80df719c) [//]: # (START_SECTION 50380c10d0fbeb07295d13cde20aa6ac0a221def) ### Fixes for Domain Routing Use Cases > Commit: [50380c10d0fbeb07295d13cde20aa6ac0a221def](https://github.com/dOpensource/dsiprouter/commit/50380c10d0fbeb07295d13cde20aa6ac0a221def) > Date: Tue, 11 Mar 2025 14:25:41 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - resolves #622 - fix Contact on REGISTER, SUBSCRIBE, and PUBLISH - use dispatcher routes for presence when a domain is matched --- [//]: # (END_SECTION 50380c10d0fbeb07295d13cde20aa6ac0a221def) [//]: # (START_SECTION e6cbcdbf74426a74a890a97cd5a1ef93813eda99) ### Fixed issues for MSTeams: - Fixed internal transfers - Fixed issue with hangups when initiated from the carrier side > Commit: [e6cbcdbf74426a74a890a97cd5a1ef93813eda99](https://github.com/dOpensource/dsiprouter/commit/e6cbcdbf74426a74a890a97cd5a1ef93813eda99) > Date: Thu, 3 Apr 2025 09:10:31 -0400 > Author: Mack Hendricks (mhendricks@sangoma.com) > Committer: Mack Hendricks (mhendricks@sangoma.com) > Signed: --- [//]: # (END_SECTION e6cbcdbf74426a74a890a97cd5a1ef93813eda99) [//]: # (START_SECTION 14712ee1e39628bc178d8591e22d8fc6b355a21d) ### Trigger Jenkins build > Commit: [14712ee1e39628bc178d8591e22d8fc6b355a21d](https://github.com/dOpensource/dsiprouter/commit/14712ee1e39628bc178d8591e22d8fc6b355a21d) > Date: Fri, 28 Mar 2025 14:36:48 -0400 > Author: chelseatcarter (chelseatcarter@gmail.com) > Committer: chelseatcarter (chelseatcarter@gmail.com) > Signed: --- [//]: # (END_SECTION 14712ee1e39628bc178d8591e22d8fc6b355a21d) [//]: # (START_SECTION 7a264c878a5440330ee03bccc501ea83c4fa33bb) ### Trigger Jenkins build > Commit: [7a264c878a5440330ee03bccc501ea83c4fa33bb](https://github.com/dOpensource/dsiprouter/commit/7a264c878a5440330ee03bccc501ea83c4fa33bb) > Date: Fri, 28 Mar 2025 14:25:21 -0400 > Author: chelseatcarter (chelseatcarter@gmail.com) > Committer: chelseatcarter (chelseatcarter@gmail.com) > Signed: --- [//]: # (END_SECTION 7a264c878a5440330ee03bccc501ea83c4fa33bb) [//]: # (START_SECTION 2ab2a009610830601e70476cea99ec7d867ae181) ### Fixed Issues - REGISTER will now follow the signal settings in the endpoint group for domain routing. Fixes #656 - Fixed an issue with the REPLACE_CONTACT_DOMAIN route, the semi-colon was missing in the protocal portion of the contact > Commit: [2ab2a009610830601e70476cea99ec7d867ae181](https://github.com/dOpensource/dsiprouter/commit/2ab2a009610830601e70476cea99ec7d867ae181) > Date: Fri, 28 Mar 2025 16:03:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2ab2a009610830601e70476cea99ec7d867ae181) [//]: # (START_SECTION 36eb14bf4f109b1db9e11497f036f7768669a8bd) ### WIP on SUBSCRIBE Interdomain Messaging > Commit: [36eb14bf4f109b1db9e11497f036f7768669a8bd](https://github.com/dOpensource/dsiprouter/commit/36eb14bf4f109b1db9e11497f036f7768669a8bd) > Date: Wed, 12 Mar 2025 15:13:32 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - fix POSIX REGEX check on Contact - remove non-useful domain code on PUBLISH --- [//]: # (END_SECTION 36eb14bf4f109b1db9e11497f036f7768669a8bd) [//]: # (START_SECTION bda6aadd069dbdf2027b782e81a83af66e7e890a) ### Fixes for Domain Routing Use Cases > Commit: [bda6aadd069dbdf2027b782e81a83af66e7e890a](https://github.com/dOpensource/dsiprouter/commit/bda6aadd069dbdf2027b782e81a83af66e7e890a) > Date: Tue, 11 Mar 2025 14:25:41 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - resolves #622 - fix Contact on REGISTER, SUBSCRIBE, and PUBLISH - use dispatcher routes for presence when a domain is matched --- [//]: # (END_SECTION bda6aadd069dbdf2027b782e81a83af66e7e890a) [//]: # (START_SECTION 5d95510edf523ea0be6d417ff69360f77eb610e6) ### Fix DNSmasq Install for Debian > Commit: [5d95510edf523ea0be6d417ff69360f77eb610e6](https://github.com/dOpensource/dsiprouter/commit/5d95510edf523ea0be6d417ff69360f77eb610e6) > Date: Wed, 5 Mar 2025 15:59:35 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 5d95510edf523ea0be6d417ff69360f77eb610e6) [//]: # (START_SECTION 223522fe8e8011377b663409529bf04eb74cf1da) ### Fixes #646: Set a default value of 1 for rweight when an endpoint is created > Commit: [223522fe8e8011377b663409529bf04eb74cf1da](https://github.com/dOpensource/dsiprouter/commit/223522fe8e8011377b663409529bf04eb74cf1da) > Date: Mon, 17 Mar 2025 23:15:18 +0000 > Author: root (root@sbc1.customers.dsiprouter.net) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 223522fe8e8011377b663409529bf04eb74cf1da) [//]: # (START_SECTION 96e17cb5f60b07b90c0f62639b5239e7e1e08873) ### Added DLGURI back into the config > Commit: [96e17cb5f60b07b90c0f62639b5239e7e1e08873](https://github.com/dOpensource/dsiprouter/commit/96e17cb5f60b07b90c0f62639b5239e7e1e08873) > Date: Mon, 3 Mar 2025 23:17:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 96e17cb5f60b07b90c0f62639b5239e7e1e08873) [//]: # (START_SECTION c9131b49277ad01931aa7fd19e56bf85a4b37e79) ### Revert "Removed duplicate route that was added in during merge from QA" > Commit: [c9131b49277ad01931aa7fd19e56bf85a4b37e79](https://github.com/dOpensource/dsiprouter/commit/c9131b49277ad01931aa7fd19e56bf85a4b37e79) > Date: Mon, 3 Mar 2025 22:45:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - This reverts commit 4f23a2e1158d415856c027cc516069d4be7b0b2f. --- [//]: # (END_SECTION c9131b49277ad01931aa7fd19e56bf85a4b37e79) [//]: # (START_SECTION a106f645bebbd0f39f073f2dad55bf8ad5733463) ### Revert "Merge branch 'master' into qa" > Commit: [a106f645bebbd0f39f073f2dad55bf8ad5733463](https://github.com/dOpensource/dsiprouter/commit/a106f645bebbd0f39f073f2dad55bf8ad5733463) > Date: Mon, 3 Mar 2025 22:38:45 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - This reverts commit ffbc368d783e83ae47809ef7cadad68e7496640f, reversing - changes made to e8f8f8c07902241cd86428ab860dde834ba2977a. --- [//]: # (END_SECTION a106f645bebbd0f39f073f2dad55bf8ad5733463) [//]: # (START_SECTION 4f23a2e1158d415856c027cc516069d4be7b0b2f) ### Removed duplicate route that was added in during merge from QA > Commit: [4f23a2e1158d415856c027cc516069d4be7b0b2f](https://github.com/dOpensource/dsiprouter/commit/4f23a2e1158d415856c027cc516069d4be7b0b2f) > Date: Sun, 2 Mar 2025 04:06:15 +0000 > Author: root (root@demo.dsiprouter.net) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4f23a2e1158d415856c027cc516069d4be7b0b2f) [//]: # (START_SECTION 4859f366f289a1e8aa03d19fcb902cedba2129d7) ### Removed duplicate route that was added in during merge from QA > Commit: [4859f366f289a1e8aa03d19fcb902cedba2129d7](https://github.com/dOpensource/dsiprouter/commit/4859f366f289a1e8aa03d19fcb902cedba2129d7) > Date: Sun, 2 Mar 2025 04:06:15 +0000 > Author: root (root@demo.dsiprouter.net) > Committer: root (root@demo.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 4859f366f289a1e8aa03d19fcb902cedba2129d7) [//]: # (START_SECTION e8f8f8c07902241cd86428ab860dde834ba2977a) ### Resolves #633 > Commit: [e8f8f8c07902241cd86428ab860dde834ba2977a](https://github.com/dOpensource/dsiprouter/commit/e8f8f8c07902241cd86428ab860dde834ba2977a) > Date: Fri, 28 Feb 2025 20:14:40 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION e8f8f8c07902241cd86428ab860dde834ba2977a) [//]: # (START_SECTION 8c3d1ec2aec7952602f6c19af5d94648d272257a) ### PIDF-LO Support: - Fixed the Kamailio configuration so that SIP packets with PIDF-LO messages are properly re-written > Commit: [8c3d1ec2aec7952602f6c19af5d94648d272257a](https://github.com/dOpensource/dsiprouter/commit/8c3d1ec2aec7952602f6c19af5d94648d272257a) > Date: Fri, 28 Feb 2025 14:12:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8c3d1ec2aec7952602f6c19af5d94648d272257a) [//]: # (START_SECTION ce9efc694149c243243e366d9303f12a6ff21090) ### Improved CDR Handling > Commit: [ce9efc694149c243243e366d9303f12a6ff21090](https://github.com/dOpensource/dsiprouter/commit/ce9efc694149c243243e366d9303f12a6ff21090) > Date: Thu, 27 Feb 2025 16:37:35 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add selector for billable calls - move pagination to serverside - move searching to serverside - improve performance of queries - update changelog --- [//]: # (END_SECTION ce9efc694149c243243e366d9303f12a6ff21090) [//]: # (START_SECTION a6690af4543a9458baf971404c7d21d03ec19bb3) ### Upgrade from 0.76 to 0.77 (#631) > Commit: [a6690af4543a9458baf971404c7d21d03ec19bb3](https://github.com/dOpensource/dsiprouter/commit/a6690af4543a9458baf971404c7d21d03ec19bb3) > Date: Wed, 26 Feb 2025 17:08:49 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a6690af4543a9458baf971404c7d21d03ec19bb3) [//]: # (START_SECTION a89c77066094932562cb1aee1ade444c1335b02b) ### Fixes: - Removed the Microsoft Teams Subscriptions Link from Dashboard - Fixed the Teleblock Screen so that the Enable/Disable button works properly > Commit: [a89c77066094932562cb1aee1ade444c1335b02b](https://github.com/dOpensource/dsiprouter/commit/a89c77066094932562cb1aee1ade444c1335b02b) > Date: Wed, 26 Feb 2025 11:48:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a89c77066094932562cb1aee1ade444c1335b02b) [//]: # (START_SECTION 16e7d16b9aaf5a230f1b4faa2ad032127befbe6e) ### Fixes: - Fixed BYE on non-MSTeams Calls - Fixed BYE on MSTeams to Carrier calls with the Carrier hanging up > Commit: [16e7d16b9aaf5a230f1b4faa2ad032127befbe6e](https://github.com/dOpensource/dsiprouter/commit/16e7d16b9aaf5a230f1b4faa2ad032127befbe6e) > Date: Wed, 26 Feb 2025 11:44:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 16e7d16b9aaf5a230f1b4faa2ad032127befbe6e) [//]: # (START_SECTION db26338c391f2b39463967f03ec2f123cc4c1f56) ### Upgrade from 0.76 to 0.77 > Commit: [db26338c391f2b39463967f03ec2f123cc4c1f56](https://github.com/dOpensource/dsiprouter/commit/db26338c391f2b39463967f03ec2f123cc4c1f56) > Date: Mon, 24 Feb 2025 10:54:49 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION db26338c391f2b39463967f03ec2f123cc4c1f56) [//]: # (START_SECTION ce3312c9020a8c1e208143ba51ae6ab19fb75fec) ### Fixed issue with the media type being copied to the other leg of the call when using MSTeams > Commit: [ce3312c9020a8c1e208143ba51ae6ab19fb75fec](https://github.com/dOpensource/dsiprouter/commit/ce3312c9020a8c1e208143ba51ae6ab19fb75fec) > Date: Mon, 24 Feb 2025 23:02:53 +0000 > Author: root (root@sbc1.customers.dsiprouter.net) > Committer: root (root@sbc1.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION ce3312c9020a8c1e208143ba51ae6ab19fb75fec) [//]: # (START_SECTION c7d98a3c26f298b4416fe27bb9ed6145a3eed3c3) ### Revert 02207a7 > Commit: [c7d98a3c26f298b4416fe27bb9ed6145a3eed3c3](https://github.com/dOpensource/dsiprouter/commit/c7d98a3c26f298b4416fe27bb9ed6145a3eed3c3) > Date: Mon, 24 Feb 2025 07:39:33 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - this was resolved in 2302d89 --- [//]: # (END_SECTION c7d98a3c26f298b4416fe27bb9ed6145a3eed3c3) [//]: # (START_SECTION 77b0d03bf0c0ca0dcc9be4730aff129aaf2a0fca) ### Fixes: - Fixed the BYE for initial request from MSTeams to Carrier and from Carrier to MSTeams - Removed 2 Flowroute gateways from the DRouting Gateway List > Commit: [77b0d03bf0c0ca0dcc9be4730aff129aaf2a0fca](https://github.com/dOpensource/dsiprouter/commit/77b0d03bf0c0ca0dcc9be4730aff129aaf2a0fca) > Date: Mon, 24 Feb 2025 04:45:10 +0000 > Author: root (root@sbc1.customers.dsiprouter.net) > Committer: root (root@sbc1.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 77b0d03bf0c0ca0dcc9be4730aff129aaf2a0fca) [//]: # (START_SECTION dd4564e01ccc4d5bd3ef101a1d1abf368771ce1e) ### Updated Flowroute POP's and configured the address table to allow inbound traffic from all Flowroute PoP's > Commit: [dd4564e01ccc4d5bd3ef101a1d1abf368771ce1e](https://github.com/dOpensource/dsiprouter/commit/dd4564e01ccc4d5bd3ef101a1d1abf368771ce1e) > Date: Sun, 23 Feb 2025 22:54:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION dd4564e01ccc4d5bd3ef101a1d1abf368771ce1e) [//]: # (START_SECTION 02207a75314811582d3895d35c2eabab968422e7) ### Added a fix to allow DigitalOcean Dropets to ignore the Internal IP address because the DO Networking doesn't NAT the address, which makes it not routable from the Internet > Commit: [02207a75314811582d3895d35c2eabab968422e7](https://github.com/dOpensource/dsiprouter/commit/02207a75314811582d3895d35c2eabab968422e7) > Date: Sun, 23 Feb 2025 22:40:23 +0000 > Author: root (root@sbc1.customers.dsiprouter.net) > Committer: root (root@sbc1.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 02207a75314811582d3895d35c2eabab968422e7) [//]: # (START_SECTION 2302d89fd3752bbc6231f9e8131105a04a9f14ac) ### Revert dsip_lib.sh Internal Network Resolution > Commit: [2302d89fd3752bbc6231f9e8131105a04a9f14ac](https://github.com/dOpensource/dsiprouter/commit/2302d89fd3752bbc6231f9e8131105a04a9f14ac) > Date: Fri, 21 Feb 2025 15:29:33 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2302d89fd3752bbc6231f9e8131105a04a9f14ac) [//]: # (START_SECTION f429ed2052552e1d8ea2c8182ce4825b1432a8e0) ### Revert "Feature/sbcsignalhandling" > Commit: [f429ed2052552e1d8ea2c8182ce4825b1432a8e0](https://github.com/dOpensource/dsiprouter/commit/f429ed2052552e1d8ea2c8182ce4825b1432a8e0) > Date: Fri, 21 Feb 2025 16:16:54 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f429ed2052552e1d8ea2c8182ce4825b1432a8e0) [//]: # (START_SECTION 40eac0dbad1c95998ca0b5346187b47abceb1f95) ### Fixed issue(s) with MSTeams, which include: - Adding logic to improve REINVITES for MSTeam - Improved signal handling > Commit: [40eac0dbad1c95998ca0b5346187b47abceb1f95](https://github.com/dOpensource/dsiprouter/commit/40eac0dbad1c95998ca0b5346187b47abceb1f95) > Date: Fri, 21 Feb 2025 21:12:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 40eac0dbad1c95998ca0b5346187b47abceb1f95) [//]: # (START_SECTION 5a5f7558d67477c8b69dd1e23ac4f2ebc65ea2a3) ### Update DIDs When Exist On Import > Commit: [5a5f7558d67477c8b69dd1e23ac4f2ebc65ea2a3](https://github.com/dOpensource/dsiprouter/commit/5a5f7558d67477c8b69dd1e23ac4f2ebc65ea2a3) > Date: Fri, 21 Feb 2025 11:10:17 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 5a5f7558d67477c8b69dd1e23ac4f2ebc65ea2a3) [//]: # (START_SECTION 84d1f13d647cc2d7eeab523587435e9fb36b1688) ### Added logic to fix BYES > Commit: [84d1f13d647cc2d7eeab523587435e9fb36b1688](https://github.com/dOpensource/dsiprouter/commit/84d1f13d647cc2d7eeab523587435e9fb36b1688) > Date: Fri, 21 Feb 2025 10:24:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 84d1f13d647cc2d7eeab523587435e9fb36b1688) [//]: # (START_SECTION 1c0b86cc905d6c6a1aa4a11b89eb9361fdd97907) ### Added port-latch parameter to RTPEngine flags > Commit: [1c0b86cc905d6c6a1aa4a11b89eb9361fdd97907](https://github.com/dOpensource/dsiprouter/commit/1c0b86cc905d6c6a1aa4a11b89eb9361fdd97907) > Date: Thu, 20 Feb 2025 22:17:10 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1c0b86cc905d6c6a1aa4a11b89eb9361fdd97907) [//]: # (START_SECTION 2fab7686d38b88730594a8cd57d25eaf88076cbb) ### Fix Typo WITH_CDRS > Commit: [2fab7686d38b88730594a8cd57d25eaf88076cbb](https://github.com/dOpensource/dsiprouter/commit/2fab7686d38b88730594a8cd57d25eaf88076cbb) > Date: Wed, 12 Feb 2025 12:17:14 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2fab7686d38b88730594a8cd57d25eaf88076cbb) [//]: # (START_SECTION e1c2bd0628d34c0e32c48d7645810d29d9296718) ### Latest Progress on Translations > Commit: [e1c2bd0628d34c0e32c48d7645810d29d9296718](https://github.com/dOpensource/dsiprouter/commit/e1c2bd0628d34c0e32c48d7645810d29d9296718) > Date: Fri, 31 Jan 2025 12:51:08 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e1c2bd0628d34c0e32c48d7645810d29d9296718) [//]: # (START_SECTION db746a1ef367383edbc0896f52f31ef82f3e0439) ### Fix Missing ftag > Commit: [db746a1ef367383edbc0896f52f31ef82f3e0439](https://github.com/dOpensource/dsiprouter/commit/db746a1ef367383edbc0896f52f31ef82f3e0439) > Date: Fri, 27 Sep 2024 10:15:47 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - fix missing tag in From after SBC translations --- [//]: # (END_SECTION db746a1ef367383edbc0896f52f31ef82f3e0439) [//]: # (START_SECTION ad5f92b9896911a020caa3971dd9b0a01ea56164) ### Better Call Teardown > Commit: [ad5f92b9896911a020caa3971dd9b0a01ea56164](https://github.com/dOpensource/dsiprouter/commit/ad5f92b9896911a020caa3971dd9b0a01ea56164) > Date: Thu, 19 Sep 2024 17:36:29 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - update cfg to ensure rtpengine sessions are always torn down immediately - fix dlg_ctx parameters not being applied to dialog --- [//]: # (END_SECTION ad5f92b9896911a020caa3971dd9b0a01ea56164) [//]: # (START_SECTION 9c19f242e8a7ad0196f88a2078e0be061ca16cbf) ### Fix Endpoint Group Call Limit > Commit: [9c19f242e8a7ad0196f88a2078e0be061ca16cbf](https://github.com/dOpensource/dsiprouter/commit/9c19f242e8a7ad0196f88a2078e0be061ca16cbf) > Date: Wed, 18 Sep 2024 16:22:18 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - fix in dialog requests forgetting gwid/gwgroupid --- [//]: # (END_SECTION 9c19f242e8a7ad0196f88a2078e0be061ca16cbf) [//]: # (START_SECTION 4ce5a4a0d4125defd1fe11046d72efe8e10b42f0) ### Handle Null Request Username > Commit: [4ce5a4a0d4125defd1fe11046d72efe8e10b42f0](https://github.com/dOpensource/dsiprouter/commit/4ce5a4a0d4125defd1fe11046d72efe8e10b42f0) > Date: Wed, 11 Sep 2024 10:45:09 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - default null req username to To username to support non-compliant UACs --- [//]: # (END_SECTION 4ce5a4a0d4125defd1fe11046d72efe8e10b42f0) [//]: # (START_SECTION db53233a086170ff69b829ab1a203ce78d404a36) ### Added MSTeams Fixes and SBC Signaling Handling - Cherry Picked 52e5931 - Added Logic to Fix MSTeams > Commit: [db53233a086170ff69b829ab1a203ce78d404a36](https://github.com/dOpensource/dsiprouter/commit/db53233a086170ff69b829ab1a203ce78d404a36) > Date: Sun, 16 Feb 2025 22:01:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION db53233a086170ff69b829ab1a203ce78d404a36) [//]: # (START_SECTION 582aa4c755a129dbaf30fa951232bcfc6b174c48) ### Improved SBC Signal Handling > Commit: [582aa4c755a129dbaf30fa951232bcfc6b174c48](https://github.com/dOpensource/dsiprouter/commit/582aa4c755a129dbaf30fa951232bcfc6b174c48) > Date: Wed, 28 Aug 2024 09:45:33 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - handle the translation between A and B leg signalling within every dialog - update in dialog requests back to UA to use the Contact they initiated call with - fix dialog tracking not matching properly - fix To number not reformatted when drouting strips from the request URI --- [//]: # (END_SECTION 582aa4c755a129dbaf30fa951232bcfc6b174c48) [//]: # (START_SECTION 029833e37ba7c808c64a2062836edfffb0bf87af) ### Fixed typo > Commit: [029833e37ba7c808c64a2062836edfffb0bf87af](https://github.com/dOpensource/dsiprouter/commit/029833e37ba7c808c64a2062836edfffb0bf87af) > Date: Wed, 12 Feb 2025 20:53:34 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 029833e37ba7c808c64a2062836edfffb0bf87af) [//]: # (START_SECTION 96ae8e0ad6b59ffcfa5eb5e22071ef01cda3b386) ### Fixed Carrier to MSTeams call flow with audio working properly. Hanging the call up from the carrier sends a proper BYE to MSTeams > Commit: [96ae8e0ad6b59ffcfa5eb5e22071ef01cda3b386](https://github.com/dOpensource/dsiprouter/commit/96ae8e0ad6b59ffcfa5eb5e22071ef01cda3b386) > Date: Wed, 12 Feb 2025 05:14:37 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 96ae8e0ad6b59ffcfa5eb5e22071ef01cda3b386) [//]: # (START_SECTION 4c603fe4465e0777bc9ab83f138b24bdc790857c) ### Fix Backup and Restore Feature > Commit: [4c603fe4465e0777bc9ab83f138b24bdc790857c](https://github.com/dOpensource/dsiprouter/commit/4c603fe4465e0777bc9ab83f138b24bdc790857c) > Date: Mon, 3 Feb 2025 06:10:13 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - use root DB creds for backup/restore - add backup/restore subcmmands to CLI - add new subcommands to sudoers - add CLI completion for new subcommands - add docs for new subcommands - allow backup / restore with DB proxy auth - update API to use CLI interface for backup/restore - fixup browser history / links in backup code path - add loading spinner to backup/restore features --- [//]: # (END_SECTION 4c603fe4465e0777bc9ab83f138b24bdc790857c) [//]: # (START_SECTION 9e3fba57211e2d32b1d4cc2abca9d0212e23cc6f) ### Fixed #591 - Fixed the backup so that it excluded database views - Added logic to propagate mysql errors when trying to perform a restore > Commit: [9e3fba57211e2d32b1d4cc2abca9d0212e23cc6f](https://github.com/dOpensource/dsiprouter/commit/9e3fba57211e2d32b1d4cc2abca9d0212e23cc6f) > Date: Mon, 2 Sep 2024 03:47:32 +0000 > Author: root (root@ip-172-31-13-77) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 9e3fba57211e2d32b1d4cc2abca9d0212e23cc6f) [//]: # (START_SECTION 4fadad93e3aa6411e567a424759a451d053f9a58) ### Resolves #602 > Commit: [4fadad93e3aa6411e567a424759a451d053f9a58](https://github.com/dOpensource/dsiprouter/commit/4fadad93e3aa6411e567a424759a451d053f9a58) > Date: Thu, 30 Jan 2025 11:08:07 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix fall through condition when building regex for epg dispatcher query --- [//]: # (END_SECTION 4fadad93e3aa6411e567a424759a451d053f9a58) [//]: # (START_SECTION b4c5011cba0f5d39566336fd926158c4c0bcfa3a) ### Bump jinja2 from 3.1.2 to 3.1.3 in /docs > Commit: [b4c5011cba0f5d39566336fd926158c4c0bcfa3a](https://github.com/dOpensource/dsiprouter/commit/b4c5011cba0f5d39566336fd926158c4c0bcfa3a) > Date: Thu, 11 Jan 2024 21:44:15 +0000 > Author: dependabot[bot] (49699333+dependabot[bot]@users.noreply.github.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. - - [Release notes](https://github.com/pallets/jinja/releases) - - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) - --- - updated-dependencies: - - dependency-name: jinja2 - dependency-type: indirect - ... - Signed-off-by: dependabot[bot] --- [//]: # (END_SECTION b4c5011cba0f5d39566336fd926158c4c0bcfa3a) [//]: # (START_SECTION 9d1a14335d324a834625d1e922c8de2d4057b649) ### Bump jinja2 from 3.0.3 to 3.1.3 in /gui > Commit: [9d1a14335d324a834625d1e922c8de2d4057b649](https://github.com/dOpensource/dsiprouter/commit/9d1a14335d324a834625d1e922c8de2d4057b649) > Date: Thu, 11 Jan 2024 19:21:16 +0000 > Author: dependabot[bot] (49699333+dependabot[bot]@users.noreply.github.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Bumps [jinja2](https://github.com/pallets/jinja) from 3.0.3 to 3.1.3. - - [Release notes](https://github.com/pallets/jinja/releases) - - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - - [Commits](https://github.com/pallets/jinja/compare/3.0.3...3.1.3) - --- - updated-dependencies: - - dependency-name: jinja2 - dependency-type: direct:production - ... - Signed-off-by: dependabot[bot] --- [//]: # (END_SECTION 9d1a14335d324a834625d1e922c8de2d4057b649) [//]: # (START_SECTION 103bc6e4a3c22b37c1f52bdfa79e48d90dd86e2e) ### Fix Standalone Run of generateCDRS() > Commit: [103bc6e4a3c22b37c1f52bdfa79e48d90dd86e2e](https://github.com/dOpensource/dsiprouter/commit/103bc6e4a3c22b37c1f52bdfa79e48d90dd86e2e) > Date: Mon, 20 Jan 2025 09:23:26 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 103bc6e4a3c22b37c1f52bdfa79e48d90dd86e2e) [//]: # (START_SECTION ba663bd3cc3c16f7c33c8f7977e1b4d5051efc0d) ### Cronjob Edge Cases > Commit: [ba663bd3cc3c16f7c33c8f7977e1b4d5051efc0d](https://github.com/dOpensource/dsiprouter/commit/ba663bd3cc3c16f7c33c8f7977e1b4d5051efc0d) > Date: Wed, 28 Aug 2024 09:10:25 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - detect if CDR cron entry missing on update and add it - update default python paths to venv on shebang lines --- [//]: # (END_SECTION ba663bd3cc3c16f7c33c8f7977e1b4d5051efc0d) [//]: # (START_SECTION 41a1a512aba83310e57d6c2c4d6e19a35a633900) ### Set Defaults For restart > Commit: [41a1a512aba83310e57d6c2c4d6e19a35a633900](https://github.com/dOpensource/dsiprouter/commit/41a1a512aba83310e57d6c2c4d6e19a35a633900) > Date: Fri, 17 Jan 2025 11:27:41 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - give `RESTART_DAEMONIZE` a default value so the func is more reusable --- [//]: # (END_SECTION 41a1a512aba83310e57d6c2c4d6e19a35a633900) [//]: # (START_SECTION 325b4c18db320c491021681490d1bc364524b000) ### Networking Updates > Commit: [325b4c18db320c491021681490d1bc364524b000](https://github.com/dOpensource/dsiprouter/commit/325b4c18db320c491021681490d1bc364524b000) > Date: Wed, 15 Jan 2025 14:40:57 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix dsip-net-cfg reverted when system network mgmt service reloads - improve output / parsing of `-debug` options - fix swapfile not written in fstab - fix null returned when no default route in `getInternalIP()` - fix comment typo in debian install script --- [//]: # (END_SECTION 325b4c18db320c491021681490d1bc364524b000) [//]: # (START_SECTION 5edf7567a52854a403c21b66c785c4a529b8dd7e) ### Fix Some Dependent Sequencing > Commit: [5edf7567a52854a403c21b66c785c4a529b8dd7e](https://github.com/dOpensource/dsiprouter/commit/5edf7567a52854a403c21b66c785c4a529b8dd7e) > Date: Tue, 14 Jan 2025 11:40:39 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - move system path config to `install` / `uninstall` subcommands - move `setStaticScriptSettings()` up before funcs expecting dsip config dir --- [//]: # (END_SECTION 5edf7567a52854a403c21b66c785c4a529b8dd7e) [//]: # (START_SECTION 472c343c957240dcebe500f405e3d818fd65f6fc) ### Dynamic Network Mode Improvements > Commit: [472c343c957240dcebe500f405e3d818fd65f6fc](https://github.com/dOpensource/dsiprouter/commit/472c343c957240dcebe500f405e3d818fd65f6fc) > Date: Wed, 8 Jan 2025 12:05:33 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - improve IP selection algo for internal addresses - make floating IP the default route on supported deployments - add hook in dsip-init for network updates prior cfg updates --- [//]: # (END_SECTION 472c343c957240dcebe500f405e3d818fd65f6fc) [//]: # (START_SECTION 3400b7d680e252f1b193294c24385740e0ea4cf9) ### Fix Call Settings Htable > Commit: [3400b7d680e252f1b193294c24385740e0ea4cf9](https://github.com/dOpensource/dsiprouter/commit/3400b7d680e252f1b193294c24385740e0ea4cf9) > Date: Wed, 11 Sep 2024 07:30:08 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix issue where call settings htable would not load - remove unused drouting htable and replace with prefix_to_route --- [//]: # (END_SECTION 3400b7d680e252f1b193294c24385740e0ea4cf9) [//]: # (START_SECTION 6bfcbbe3ccc56fef2378e7d1305751318ac508e6) ### Fix Concurrent Call Limits > Commit: [6bfcbbe3ccc56fef2378e7d1305751318ac508e6](https://github.com/dOpensource/dsiprouter/commit/6bfcbbe3ccc56fef2378e7d1305751318ac508e6) > Date: Mon, 6 Jan 2025 15:04:59 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - use dialog var so dialog event routes can access the src/dst gwgroupid --- [//]: # (END_SECTION 6bfcbbe3ccc56fef2378e7d1305751318ac508e6) [//]: # (START_SECTION 8a9eefe4814db0d20e6d5030f6e9865f47711513) ### Fix CloudInit Networking > Commit: [8a9eefe4814db0d20e6d5030f6e9865f47711513](https://github.com/dOpensource/dsiprouter/commit/8a9eefe4814db0d20e6d5030f6e9865f47711513) > Date: Thu, 3 Oct 2024 09:24:55 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix images created from instances failing to configure new ifaces --- [//]: # (END_SECTION 8a9eefe4814db0d20e6d5030f6e9865f47711513) [//]: # (START_SECTION 5737d1f33c99de90e0ef9188ca6c840292896567) ### Fix Apt Variable Sequencing > Commit: [5737d1f33c99de90e0ef9188ca6c840292896567](https://github.com/dOpensource/dsiprouter/commit/5737d1f33c99de90e0ef9188ca6c840292896567) > Date: Tue, 14 Jan 2025 10:42:59 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - move the APT config variables into `setStaticScriptSettings()` --- [//]: # (END_SECTION 5737d1f33c99de90e0ef9188ca6c840292896567) [//]: # (START_SECTION cced1d2cd835ca6d546a22b78dcc6d46f2c1a874) ### Fix TLS Cert Renewal Check > Commit: [cced1d2cd835ca6d546a22b78dcc6d46f2c1a874](https://github.com/dOpensource/dsiprouter/commit/cced1d2cd835ca6d546a22b78dcc6d46f2c1a874) > Date: Thu, 21 Nov 2024 12:22:11 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix typo in cli function --- [//]: # (END_SECTION cced1d2cd835ca6d546a22b78dcc6d46f2c1a874) [//]: # (START_SECTION 357eca71bf3767d10f36b6e2db1249048a399c54) ### Backport UltraDict Build Fix to Debian-based OS > Commit: [357eca71bf3767d10f36b6e2db1249048a399c54](https://github.com/dOpensource/dsiprouter/commit/357eca71bf3767d10f36b6e2db1249048a399c54) > Date: Mon, 13 Jan 2025 10:25:40 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 357eca71bf3767d10f36b6e2db1249048a399c54) [//]: # (START_SECTION cd0db1a1f6bf45e09e31ff56e6e3541bd617ea70) ### Debian12 UltraDict Dependency Fix > Commit: [cd0db1a1f6bf45e09e31ff56e6e3541bd617ea70](https://github.com/dOpensource/dsiprouter/commit/cd0db1a1f6bf45e09e31ff56e6e3541bd617ea70) > Date: Fri, 8 Nov 2024 13:50:00 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - run UltraDict install separate to workaround hanging install --- [//]: # (END_SECTION cd0db1a1f6bf45e09e31ff56e6e3541bd617ea70) [//]: # (START_SECTION 8b79cd5e57ee370fe39d8d2234b995c7a0c3667f) ### Backport UltraDict Build Fix to Debian-based OS > Commit: [8b79cd5e57ee370fe39d8d2234b995c7a0c3667f](https://github.com/dOpensource/dsiprouter/commit/8b79cd5e57ee370fe39d8d2234b995c7a0c3667f) > Date: Mon, 13 Jan 2025 10:25:40 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 8b79cd5e57ee370fe39d8d2234b995c7a0c3667f) [//]: # (START_SECTION 435b3cadb5cdc837343d12c04199ef58ecc88123) ### Fix Libjwt / Libstirshaken Builds > Commit: [435b3cadb5cdc837343d12c04199ef58ecc88123](https://github.com/dOpensource/dsiprouter/commit/435b3cadb5cdc837343d12c04199ef58ecc88123) > Date: Mon, 13 Jan 2025 09:57:12 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix build for debian-based systems - pin libjwt to 2.1.1 --- [//]: # (END_SECTION 435b3cadb5cdc837343d12c04199ef58ecc88123) [//]: # (START_SECTION ec09a74859312f30a48ec11dd57ed682f4da3868) ### OS Support Updates > Commit: [ec09a74859312f30a48ec11dd57ed682f4da3868](https://github.com/dOpensource/dsiprouter/commit/ec09a74859312f30a48ec11dd57ed682f4da3868) > Date: Tue, 19 Nov 2024 14:23:43 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - centos7 --> deprecated - centos9 stability fixes - rhel9 --> alpha - rhel8 --> beta - alma8 --> alpha - alma8 --> beta - rocky9 --> alpha - rocky8 --> beta - ubuntu 24.04 --> beta - debian 10/11/12 pinned kamailio to 5.8.3 - debian 9 pinned kamailio to 5.5.7 - centos 8/9 pinned kamailio to 5.8.3 - centos7 pinned kamailio to 5.7.6 - amazn2 pinned kamailo to 5.7.6 - ubuntu 24.04 pinned kamailio to 5.8.4 / rtpengine to mr11.5.1.11 - ubuntu 22.04 pinned kamailio to 5.8.3 / rtpengine to mr11.5.1.11 - ubuntu 20.04 pinned kamailio to 5.8.3 - rhel8/9 pinned kamailio to 5.8.3 - alma 8/9 pinned kamalio to 5.8.3 / rtpengine to mr11.5.1.11 - rocky 8/9 pinned kamalio to 5.8.3 / rtpengine to mr11.5.1.11 - add back in swap file for low memory systems (2GB) - update OS support in docs - fix `RTPENGINE_URI` missing from `dsip_settings` python interfaces --- [//]: # (END_SECTION ec09a74859312f30a48ec11dd57ed682f4da3868) [//]: # (START_SECTION edc1122b046b2948c5ccff28940693c0dcae2821) ### Debian12 UltraDict Dependency Fix > Commit: [edc1122b046b2948c5ccff28940693c0dcae2821](https://github.com/dOpensource/dsiprouter/commit/edc1122b046b2948c5ccff28940693c0dcae2821) > Date: Fri, 8 Nov 2024 13:50:00 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - run UltraDict install separate to workaround hanging install --- [//]: # (END_SECTION edc1122b046b2948c5ccff28940693c0dcae2821) [//]: # (START_SECTION deb523e80b4f468c47b0f56e89480b8d19b2c899) ### Fix isHostLocal Matching Pattern > Commit: [deb523e80b4f468c47b0f56e89480b8d19b2c899](https://github.com/dOpensource/dsiprouter/commit/deb523e80b4f468c47b0f56e89480b8d19b2c899) > Date: Thu, 31 Oct 2024 14:15:02 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION deb523e80b4f468c47b0f56e89480b8d19b2c899) [//]: # (START_SECTION 2e52b3173dc0bfec031da450adc0bc06ff51dab2) ### Support Remote RTPEngine Media Server > Commit: [2e52b3173dc0bfec031da450adc0bc06ff51dab2](https://github.com/dOpensource/dsiprouter/commit/2e52b3173dc0bfec031da450adc0bc06ff51dab2) > Date: Tue, 24 Sep 2024 18:48:30 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for media proxy through a remote RTPEngine instance - add capability for rtpengine service to be dynamically disabled - update RHEL-based distros rtpengine installs - add CLI option `--rtpengine-uri=` to `install` subcommand - improve local host check for services that can be either remote or local --- [//]: # (END_SECTION 2e52b3173dc0bfec031da450adc0bc06ff51dab2) [//]: # (START_SECTION 2d2491605e0912a54a66a6e141b5f093120f6ee6) ### Fix HEP Port Not Set In Some Cases > Commit: [2d2491605e0912a54a66a6e141b5f093120f6ee6](https://github.com/dOpensource/dsiprouter/commit/2d2491605e0912a54a66a6e141b5f093120f6ee6) > Date: Fri, 20 Sep 2024 13:12:18 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - remove HEP port from static variables in CLI - make dynamic lookups for homer variables more reliable --- [//]: # (END_SECTION 2d2491605e0912a54a66a6e141b5f093120f6ee6) [//]: # (START_SECTION abc7359613387ce22dc82a1f8bf9a336835f505e) ### Fix Homer Updates in RTPEngine Config > Commit: [abc7359613387ce22dc82a1f8bf9a336835f505e](https://github.com/dOpensource/dsiprouter/commit/abc7359613387ce22dc82a1f8bf9a336835f505e) > Date: Wed, 11 Sep 2024 16:04:29 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - get default values from `settings.py` when running `dsiprouter updatertpconfig` --- [//]: # (END_SECTION abc7359613387ce22dc82a1f8bf9a336835f505e) [//]: # (START_SECTION fe0127248147c98388771c02900afa45c17eb565) ### Compartmentalize Mysql Portion of Installation > Commit: [fe0127248147c98388771c02900afa45c17eb565](https://github.com/dOpensource/dsiprouter/commit/fe0127248147c98388771c02900afa45c17eb565) > Date: Sun, 11 Aug 2024 11:41:39 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - mariadb server now installs with `-all` or explicitly with `-mysql` - move dev/lib package installs to the rtpengine installation scripts --- [//]: # (END_SECTION fe0127248147c98388771c02900afa45c17eb565) [//]: # (START_SECTION f8aecea6bd3c217b2073234c9f6ba3fae4c4e5f8) ### Allow External RTPEngine to be Configured > Commit: [f8aecea6bd3c217b2073234c9f6ba3fae4c4e5f8](https://github.com/dOpensource/dsiprouter/commit/f8aecea6bd3c217b2073234c9f6ba3fae4c4e5f8) > Date: Sun, 11 Aug 2024 13:16:48 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for specifying remote rtpengine uri during install - add settings to allow changing the above later on - fix typos in `dsip_lib.sh` - update DB URI parsing func to not rely on system python --- [//]: # (END_SECTION f8aecea6bd3c217b2073234c9f6ba3fae4c4e5f8) [//]: # (START_SECTION 4bae4f3d0153a244c1441213327a83884547ea96) ### Fix HA Script Not Removing init Commands > Commit: [4bae4f3d0153a244c1441213327a83884547ea96](https://github.com/dOpensource/dsiprouter/commit/4bae4f3d0153a244c1441213327a83884547ea96) > Date: Sun, 11 Aug 2024 13:23:43 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 4bae4f3d0153a244c1441213327a83884547ea96) [//]: # (START_SECTION f833e8374efecbc4a9319251db9941f68f331617) ### Allow Python Selection for hashCreds() > Commit: [f833e8374efecbc4a9319251db9941f68f331617](https://github.com/dOpensource/dsiprouter/commit/f833e8374efecbc4a9319251db9941f68f331617) > Date: Mon, 12 Aug 2024 07:10:46 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - this will allow us to remove the system python package after installation --- [//]: # (END_SECTION f833e8374efecbc4a9319251db9941f68f331617) [//]: # (START_SECTION 17084e4bb069807f065f847086b5b002ff4d54ed) ### Fixed LibJWT: - Missed adding install as a command to make > Commit: [17084e4bb069807f065f847086b5b002ff4d54ed](https://github.com/dOpensource/dsiprouter/commit/17084e4bb069807f065f847086b5b002ff4d54ed) > Date: Sat, 4 Jan 2025 01:32:21 +0000 > Author: root (root@demo.dsiprouter.net) > Committer: root (root@demo.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 17084e4bb069807f065f847086b5b002ff4d54ed) [//]: # (START_SECTION fea919d0b4f5184c4ae2d319ed39cfdfb1e3a1dd) ### Fix LibJWT: The LibJWT build system switched over to cmake from autoconf so Kamailio install script had to be updated > Commit: [fea919d0b4f5184c4ae2d319ed39cfdfb1e3a1dd](https://github.com/dOpensource/dsiprouter/commit/fea919d0b4f5184c4ae2d319ed39cfdfb1e3a1dd) > Date: Fri, 3 Jan 2025 23:52:26 +0000 > Author: root (root@demo.dsiprouter.net) > Committer: root (root@demo.dsiprouter.net) > Signed: --- [//]: # (END_SECTION fea919d0b4f5184c4ae2d319ed39cfdfb1e3a1dd) [//]: # (START_SECTION 351dc8ef45d4f09072584b1b906bc0b04973df8d) ### Fix libjwt Not Compiling > Commit: [351dc8ef45d4f09072584b1b906bc0b04973df8d](https://github.com/dOpensource/dsiprouter/commit/351dc8ef45d4f09072584b1b906bc0b04973df8d) > Date: Wed, 18 Dec 2024 12:56:49 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 351dc8ef45d4f09072584b1b906bc0b04973df8d) [//]: # (START_SECTION bda08eb49639f20e85563131cc2356770eaea61f) ### Stabilize Flux Capacitors > Commit: [bda08eb49639f20e85563131cc2356770eaea61f](https://github.com/dOpensource/dsiprouter/commit/bda08eb49639f20e85563131cc2356770eaea61f) > Date: Mon, 25 Nov 2024 17:05:43 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - rhel9 --> STABLE - rhel9: bump rtpengine version to mr11.5.1.11 - alma9 --> STABLE - rocky9 --> STABLE - ubuntu 24.04 --> STABLE - rhel9/alma9/rocky9/centos9 update to use dsiprouter-repo instead of rpmfusion --- [//]: # (END_SECTION bda08eb49639f20e85563131cc2356770eaea61f) [//]: # (START_SECTION 3408b9a6c8d68a473bb2b7a53ec31ceff6900f91) ### OS Support Updates > Commit: [3408b9a6c8d68a473bb2b7a53ec31ceff6900f91](https://github.com/dOpensource/dsiprouter/commit/3408b9a6c8d68a473bb2b7a53ec31ceff6900f91) > Date: Tue, 19 Nov 2024 14:23:43 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - centos7 --> deprecated - centos9 stability fixes - rhel9 --> alpha - rhel8 --> beta - alma8 --> alpha - alma8 --> beta - rocky9 --> alpha - rocky8 --> beta - ubuntu 24.04 --> beta - debian 10/11/12 pinned kamailio to 5.8.3 - debian 9 pinned kamailio to 5.5.7 - centos 8/9 pinned kamailio to 5.8.3 - centos7 pinned kamailio to 5.7.6 - amazn2 pinned kamailo to 5.7.6 - ubuntu 24.04 pinned kamailio to 5.8.4 / rtpengine to mr11.5.1.11 - ubuntu 22.04 pinned kamailio to 5.8.3 / rtpengine to mr11.5.1.11 - ubuntu 20.04 pinned kamailio to 5.8.3 - rhel8/9 pinned kamailio to 5.8.3 - alma 8/9 pinned kamalio to 5.8.3 / rtpengine to mr11.5.1.11 - rocky 8/9 pinned kamalio to 5.8.3 / rtpengine to mr11.5.1.11 - add back in swap file for low memory systems (2GB) - update OS support in docs - fix `RTPENGINE_URI` missing from `dsip_settings` python interfaces --- [//]: # (END_SECTION 3408b9a6c8d68a473bb2b7a53ec31ceff6900f91) [//]: # (START_SECTION 014c30de6ac92ab9ca51e758b74c8563a875d98b) ### Debian12 UltraDict Dependency Fix > Commit: [014c30de6ac92ab9ca51e758b74c8563a875d98b](https://github.com/dOpensource/dsiprouter/commit/014c30de6ac92ab9ca51e758b74c8563a875d98b) > Date: Fri, 8 Nov 2024 13:50:00 -0700 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - run UltraDict install separate to workaround hanging install --- [//]: # (END_SECTION 014c30de6ac92ab9ca51e758b74c8563a875d98b) [//]: # (START_SECTION b46462ea5d8160b69a7999de6116f0b2aa0c42ed) ### Fix isHostLocal Matching Pattern > Commit: [b46462ea5d8160b69a7999de6116f0b2aa0c42ed](https://github.com/dOpensource/dsiprouter/commit/b46462ea5d8160b69a7999de6116f0b2aa0c42ed) > Date: Thu, 31 Oct 2024 14:15:02 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION b46462ea5d8160b69a7999de6116f0b2aa0c42ed) [//]: # (START_SECTION 1c46e8cd1d1cf82cced662b3e6af94b667c77ac9) ### Support Remote RTPEngine Media Server > Commit: [1c46e8cd1d1cf82cced662b3e6af94b667c77ac9](https://github.com/dOpensource/dsiprouter/commit/1c46e8cd1d1cf82cced662b3e6af94b667c77ac9) > Date: Tue, 24 Sep 2024 18:48:30 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add support for media proxy through a remote RTPEngine instance - add capability for rtpengine service to be dynamically disabled - update RHEL-based distros rtpengine installs - add CLI option `--rtpengine-uri=` to `install` subcommand - improve local host check for services that can be either remote or local --- [//]: # (END_SECTION 1c46e8cd1d1cf82cced662b3e6af94b667c77ac9) [//]: # (START_SECTION dcf2b437e94bf987e60d1c16bf85b331de1ff592) ### Fix HEP Port Not Set In Some Cases > Commit: [dcf2b437e94bf987e60d1c16bf85b331de1ff592](https://github.com/dOpensource/dsiprouter/commit/dcf2b437e94bf987e60d1c16bf85b331de1ff592) > Date: Fri, 20 Sep 2024 13:12:18 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - remove HEP port from static variables in CLI - make dynamic lookups for homer variables more reliable --- [//]: # (END_SECTION dcf2b437e94bf987e60d1c16bf85b331de1ff592) [//]: # (START_SECTION f0ac44f8041b10a3675f5a2accfe29199a795990) ### Fix Homer Updates in RTPEngine Config > Commit: [f0ac44f8041b10a3675f5a2accfe29199a795990](https://github.com/dOpensource/dsiprouter/commit/f0ac44f8041b10a3675f5a2accfe29199a795990) > Date: Wed, 11 Sep 2024 16:04:29 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - get default values from `settings.py` when running `dsiprouter updatertpconfig` --- [//]: # (END_SECTION f0ac44f8041b10a3675f5a2accfe29199a795990) [//]: # (START_SECTION 482bd41f6d5bc599e632c76095c3f0ea09ee7712) ### Compartmentalize Mysql Portion of Installation > Commit: [482bd41f6d5bc599e632c76095c3f0ea09ee7712](https://github.com/dOpensource/dsiprouter/commit/482bd41f6d5bc599e632c76095c3f0ea09ee7712) > Date: Sun, 11 Aug 2024 11:41:39 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - mariadb server now installs with `-all` or explicitly with `-mysql` - move dev/lib package installs to the rtpengine installation scripts --- [//]: # (END_SECTION 482bd41f6d5bc599e632c76095c3f0ea09ee7712) [//]: # (START_SECTION 559fec30e82c5553b483acfe7c59d801d65cfd5d) ### Allow External RTPEngine to be Configured > Commit: [559fec30e82c5553b483acfe7c59d801d65cfd5d](https://github.com/dOpensource/dsiprouter/commit/559fec30e82c5553b483acfe7c59d801d65cfd5d) > Date: Sun, 11 Aug 2024 13:16:48 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add support for specifying remote rtpengine uri during install - add settings to allow changing the above later on - fix typos in `dsip_lib.sh` - update DB URI parsing func to not rely on system python --- [//]: # (END_SECTION 559fec30e82c5553b483acfe7c59d801d65cfd5d) [//]: # (START_SECTION d2f71046f6eb8b7dabc13cbb88b74d530d686367) ### Fix HA Script Not Removing init Commands > Commit: [d2f71046f6eb8b7dabc13cbb88b74d530d686367](https://github.com/dOpensource/dsiprouter/commit/d2f71046f6eb8b7dabc13cbb88b74d530d686367) > Date: Sun, 11 Aug 2024 13:23:43 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION d2f71046f6eb8b7dabc13cbb88b74d530d686367) [//]: # (START_SECTION 1b8064c9760fd98037e0471373fdc6288f8c2910) ### Allow Python Selection for hashCreds() > Commit: [1b8064c9760fd98037e0471373fdc6288f8c2910](https://github.com/dOpensource/dsiprouter/commit/1b8064c9760fd98037e0471373fdc6288f8c2910) > Date: Mon, 12 Aug 2024 07:10:46 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - this will allow us to remove the system python package after installation --- [//]: # (END_SECTION 1b8064c9760fd98037e0471373fdc6288f8c2910) [//]: # (START_SECTION 313906939ddd7d64a3ef9562c3e86c7101bdff8c) ### Explictily defined the Kamailio modules that will be installed. This overcomes the bug in the lastest version of Kamailio, which has ims_icscf as a default extra module to install, but the corresponding ims_icscf-create.sql file is not part of the Debian package > Commit: [313906939ddd7d64a3ef9562c3e86c7101bdff8c](https://github.com/dOpensource/dsiprouter/commit/313906939ddd7d64a3ef9562c3e86c7101bdff8c) > Date: Mon, 18 Nov 2024 03:34:53 +0000 > Author: root (root@mack.test.dsiprouter.net) > Committer: root (root@mack.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 313906939ddd7d64a3ef9562c3e86c7101bdff8c) [//]: # (START_SECTION 1d7bc2d6a526458a943b79cc23b49169cdf4974e) ### Made Terrafrom Config more generic > Commit: [1d7bc2d6a526458a943b79cc23b49169cdf4974e](https://github.com/dOpensource/dsiprouter/commit/1d7bc2d6a526458a943b79cc23b49169cdf4974e) > Date: Mon, 11 Nov 2024 01:43:22 +0000 > Author: root (root@chelsea.test.dsiprouter.net) > Committer: root (root@chelsea.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 1d7bc2d6a526458a943b79cc23b49169cdf4974e) [//]: # (START_SECTION 9b5afa06020b84b598576c56edc09f3fa98be14c) ### Increased the default size of DigitalOcean Images from 1gb to 2gb > Commit: [9b5afa06020b84b598576c56edc09f3fa98be14c](https://github.com/dOpensource/dsiprouter/commit/9b5afa06020b84b598576c56edc09f3fa98be14c) > Date: Sun, 10 Nov 2024 23:12:26 +0000 > Author: root (root@chelsea.test.dsiprouter.net) > Committer: root (root@chelsea.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 9b5afa06020b84b598576c56edc09f3fa98be14c) [//]: # (START_SECTION 3da760ed578537f9f39475bb6404d3931af22dfc) ### Made UltraDict the first python package to be installed > Commit: [3da760ed578537f9f39475bb6404d3931af22dfc](https://github.com/dOpensource/dsiprouter/commit/3da760ed578537f9f39475bb6404d3931af22dfc) > Date: Sun, 10 Nov 2024 22:47:22 +0000 > Author: root (root@mack.test.dsiprouter.net) > Committer: root (root@mack.test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 3da760ed578537f9f39475bb6404d3931af22dfc) [//]: # (START_SECTION 92eefb44fc4b1cb17ad9908a77b7587f5a408baa) ### Terraform PR Build > Commit: [92eefb44fc4b1cb17ad9908a77b7587f5a408baa](https://github.com/dOpensource/dsiprouter/commit/92eefb44fc4b1cb17ad9908a77b7587f5a408baa) > Date: Thu, 3 Oct 2024 17:02:42 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for building from a PR in terraform builds --- [//]: # (END_SECTION 92eefb44fc4b1cb17ad9908a77b7587f5a408baa) [//]: # (START_SECTION e19f0de2ab42f6630ca65aff48a7cea1b2f656c8) ### Fix UltraDict Build on Debian12 > Commit: [e19f0de2ab42f6630ca65aff48a7cea1b2f656c8](https://github.com/dOpensource/dsiprouter/commit/e19f0de2ab42f6630ca65aff48a7cea1b2f656c8) > Date: Wed, 11 Sep 2024 11:24:35 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add libffi-dev dependency to fix UltraDict wheel build --- [//]: # (END_SECTION e19f0de2ab42f6630ca65aff48a7cea1b2f656c8) [//]: # (START_SECTION 9560aab94b63edc79181c8e973a226cffaf89aa3) ### Ensure Python venv Built From New System Deps on Debian11 > Commit: [9560aab94b63edc79181c8e973a226cffaf89aa3](https://github.com/dOpensource/dsiprouter/commit/9560aab94b63edc79181c8e973a226cffaf89aa3) > Date: Tue, 3 Sep 2024 15:53:38 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 9560aab94b63edc79181c8e973a226cffaf89aa3) [//]: # (START_SECTION 59a745237cd9586f8dc2224a423365724a90ad21) ### Update dsiprouter.sh > Commit: [59a745237cd9586f8dc2224a423365724a90ad21](https://github.com/dOpensource/dsiprouter/commit/59a745237cd9586f8dc2224a423365724a90ad21) > Date: Tue, 13 Aug 2024 06:07:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Removed the creation of swap and removal of swap from the installer until we review the effects on different operating systems. --- [//]: # (END_SECTION 59a745237cd9586f8dc2224a423365724a90ad21) [//]: # (START_SECTION 8bc13858f70633ec2ec2ade2b878759a3d6715b0) ### Fix Stubborn Woocommerce "Delivered" Status Check > Commit: [8bc13858f70633ec2ec2ade2b878759a3d6715b0](https://github.com/dOpensource/dsiprouter/commit/8bc13858f70633ec2ec2ade2b878759a3d6715b0) > Date: Thu, 8 Aug 2024 11:56:32 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 8bc13858f70633ec2ec2ade2b878759a3d6715b0) [//]: # (START_SECTION 6c73f8c46eb1477fcbaee6014e2a861e80f04c81) ### Updated Terraform scripts to work with Jenkins Parameters > Commit: [6c73f8c46eb1477fcbaee6014e2a861e80f04c81](https://github.com/dOpensource/dsiprouter/commit/6c73f8c46eb1477fcbaee6014e2a861e80f04c81) > Date: Thu, 8 Aug 2024 05:25:00 +0000 > Author: Mack Hendricks (mack.hendricks@gmail.com) > Committer: Mack Hendricks (mack.hendricks@gmail.com) > Signed: --- [//]: # (END_SECTION 6c73f8c46eb1477fcbaee6014e2a861e80f04c81) [//]: # (START_SECTION 073aae22bea1191cd548d7ee676d571df4487891) ### Added logic to handle adding and updating carriers from the UI > Commit: [073aae22bea1191cd548d7ee676d571df4487891](https://github.com/dOpensource/dsiprouter/commit/073aae22bea1191cd548d7ee676d571df4487891) > Date: Thu, 8 Aug 2024 03:50:53 +0000 > Author: Mack Hendricks (mack.hendricks@gmail.com) > Committer: Mack Hendricks (mack.hendricks@gmail.com) > Signed: --- [//]: # (END_SECTION 073aae22bea1191cd548d7ee676d571df4487891) [//]: # (START_SECTION 0a9b0322870cd277aa41e0abb7f95ed026909d4f) ### Fixed Carriergroups Post API > Commit: [0a9b0322870cd277aa41e0abb7f95ed026909d4f](https://github.com/dOpensource/dsiprouter/commit/0a9b0322870cd277aa41e0abb7f95ed026909d4f) > Date: Wed, 7 Aug 2024 23:50:21 +0000 > Author: root (root@demo-dsip-v0.760) > Committer: root (root@demo-dsip-v0.760) > Signed: --- [//]: # (END_SECTION 0a9b0322870cd277aa41e0abb7f95ed026909d4f) [//]: # (START_SECTION 2f42fe9d97523d96b226f74ad9c25f60ff3a992f) ### Update Version in Settings File > Commit: [2f42fe9d97523d96b226f74ad9c25f60ff3a992f](https://github.com/dOpensource/dsiprouter/commit/2f42fe9d97523d96b226f74ad9c25f60ff3a992f) > Date: Wed, 7 Aug 2024 13:43:25 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2f42fe9d97523d96b226f74ad9c25f60ff3a992f) [//]: # (START_SECTION 4ef9068ca819def5eb7df6abdd9f799c4baaedd4) ### Add Support for Upgrading to v0.76 > Commit: [4ef9068ca819def5eb7df6abdd9f799c4baaedd4](https://github.com/dOpensource/dsiprouter/commit/4ef9068ca819def5eb7df6abdd9f799c4baaedd4) > Date: Sun, 4 Aug 2024 14:28:00 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 4ef9068ca819def5eb7df6abdd9f799c4baaedd4) [//]: # (START_SECTION a5513488ce466250a812748d7d757af95acdff8e) ### Better Handling ufw Removal > Commit: [a5513488ce466250a812748d7d757af95acdff8e](https://github.com/dOpensource/dsiprouter/commit/a5513488ce466250a812748d7d757af95acdff8e) > Date: Sun, 4 Aug 2024 13:24:11 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION a5513488ce466250a812748d7d757af95acdff8e) [//]: # (START_SECTION a10cce22b9a632e0b4a1540b0fc06ed42a4412a3) ### Fix DMZ Feature Listening Sockets (#584) > Commit: [a10cce22b9a632e0b4a1540b0fc06ed42a4412a3](https://github.com/dOpensource/dsiprouter/commit/a10cce22b9a632e0b4a1540b0fc06ed42a4412a3) > Date: Sun, 4 Aug 2024 13:02:05 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - * Better Naming for Sending Media Directly to Endpoint - change naming from "bypass media" to "direct media" - * Domain Routing Translations - add support for signalling / media features on domain pass thru - * Carrier Group Regression Hotfix - fix regression in attributes query for carriers - * Fix CarrierGroup Load Balancing Icon - fix load balancing icon not shown on toggle button - * Fix CarrierGroup lb_enabled Not Forwarded - add `lb_enabled` to data forwarded to the backend - * Fix Carrier Update Via API Out of Sync - updated the API logic for carriers to match the GUI logic - * Fix SQL Munging on CSV Field Queries - fixed a class of bugs where the description and tag fields could return the wrong rows - * Fix Dangling Comma When Adding Carriers - handle case when `split()` is called on empty `gwlist` - * Dispatcher Fixes - fix dispatcher relations and related queries - update defaults to match new formats of table associations - * Remove Duplicate Chcek for validateFields() - * Fix Empty Domain Prevents Kamailio Reload - * CarrierGroup Load Balancing Missing Disable - fix the DB query that disables load balancing for carrier groups - * Fix DMZ Feature Listening Sockets - fix logic bug in ifdefs causing kam to not listen on ext iface --- [//]: # (END_SECTION a10cce22b9a632e0b4a1540b0fc06ed42a4412a3) [//]: # (START_SECTION 52cfbb1f29bb83db1dc2f4d4454d1da048f2a467) ### License Manager CLI > Commit: [52cfbb1f29bb83db1dc2f4d4454d1da048f2a467](https://github.com/dOpensource/dsiprouter/commit/52cfbb1f29bb83db1dc2f4d4454d1da048f2a467) > Date: Sun, 4 Aug 2024 12:56:07 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for `licensemanager` command in the dsiprouter CLI - add documentation for new command - add CLI completion for new command --- [//]: # (END_SECTION 52cfbb1f29bb83db1dc2f4d4454d1da048f2a467) [//]: # (START_SECTION f1d9e0098908132570596a1fa9fd2ceb17978d14) ### LIcense Manager Function Improvements > Commit: [f1d9e0098908132570596a1fa9fd2ceb17978d14](https://github.com/dOpensource/dsiprouter/commit/f1d9e0098908132570596a1fa9fd2ceb17978d14) > Date: Sun, 4 Aug 2024 12:31:07 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - improve the function api's to be more useful - make check for shared memory loaded when upgrading more reliable --- [//]: # (END_SECTION f1d9e0098908132570596a1fa9fd2ceb17978d14) [//]: # (START_SECTION 1ab82314fab1f8521df7b2ec8a9954d92548958a) ### License Manager Fixes > Commit: [1ab82314fab1f8521df7b2ec8a9954d92548958a](https://github.com/dOpensource/dsiprouter/commit/1ab82314fab1f8521df7b2ec8a9954d92548958a) > Date: Sun, 4 Aug 2024 12:25:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix error message formatting on woocommerce error - fix timestamp check for licenses without an end date --- [//]: # (END_SECTION 1ab82314fab1f8521df7b2ec8a9954d92548958a) [//]: # (START_SECTION ee088949886ce13d66ddea732d23b51b90ee4ffb) ### Support Licensing Via the API > Commit: [ee088949886ce13d66ddea732d23b51b90ee4ffb](https://github.com/dOpensource/dsiprouter/commit/ee088949886ce13d66ddea732d23b51b90ee4ffb) > Date: Fri, 2 Aug 2024 07:41:29 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION ee088949886ce13d66ddea732d23b51b90ee4ffb) [//]: # (START_SECTION c3eb4d56b294866513087636aa96f6c3a748982a) ### Better Validation in chown Subcommand > Commit: [c3eb4d56b294866513087636aa96f6c3a748982a](https://github.com/dOpensource/dsiprouter/commit/c3eb4d56b294866513087636aa96f6c3a748982a) > Date: Fri, 2 Aug 2024 07:39:43 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add check for configured services in default `chown` CLI command --- [//]: # (END_SECTION c3eb4d56b294866513087636aa96f6c3a748982a) [//]: # (START_SECTION f1800b77e276cc28469c6f11bf2cd315271d6d17) ### CarrierGroup Load Balancing Missing Disable > Commit: [f1800b77e276cc28469c6f11bf2cd315271d6d17](https://github.com/dOpensource/dsiprouter/commit/f1800b77e276cc28469c6f11bf2cd315271d6d17) > Date: Mon, 22 Jul 2024 12:40:16 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix the DB query that disables load balancing for carrier groups --- [//]: # (END_SECTION f1800b77e276cc28469c6f11bf2cd315271d6d17) [//]: # (START_SECTION a183a8c60c6774bb8823b8f73be8a414066ab7fb) ### Fix Empty Domain Prevents Kamailio Reload > Commit: [a183a8c60c6774bb8823b8f73be8a414066ab7fb](https://github.com/dOpensource/dsiprouter/commit/a183a8c60c6774bb8823b8f73be8a414066ab7fb) > Date: Mon, 22 Jul 2024 09:35:14 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION a183a8c60c6774bb8823b8f73be8a414066ab7fb) [//]: # (START_SECTION 7309bf1dffba660067e70977ec9f636eee86a510) ### Remove Duplicate Chcek for validateFields() > Commit: [7309bf1dffba660067e70977ec9f636eee86a510](https://github.com/dOpensource/dsiprouter/commit/7309bf1dffba660067e70977ec9f636eee86a510) > Date: Mon, 22 Jul 2024 08:31:21 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 7309bf1dffba660067e70977ec9f636eee86a510) [//]: # (START_SECTION 8a2675034dd8293dcb3b1aa0efdb3641f5c1005b) ### Dispatcher Fixes > Commit: [8a2675034dd8293dcb3b1aa0efdb3641f5c1005b](https://github.com/dOpensource/dsiprouter/commit/8a2675034dd8293dcb3b1aa0efdb3641f5c1005b) > Date: Mon, 22 Jul 2024 07:55:14 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix dispatcher relations and related queries - update defaults to match new formats of table associations --- [//]: # (END_SECTION 8a2675034dd8293dcb3b1aa0efdb3641f5c1005b) [//]: # (START_SECTION 5ec677df29e26d2d78251e8d9582202d50665ac5) ### Fix Dangling Comma When Adding Carriers > Commit: [5ec677df29e26d2d78251e8d9582202d50665ac5](https://github.com/dOpensource/dsiprouter/commit/5ec677df29e26d2d78251e8d9582202d50665ac5) > Date: Fri, 19 Jul 2024 16:26:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - handle case when `split()` is called on empty `gwlist` --- [//]: # (END_SECTION 5ec677df29e26d2d78251e8d9582202d50665ac5) [//]: # (START_SECTION 8759e869034f9410f2622fed0ab40e5dbb107d97) ### Fix SQL Munging on CSV Field Queries > Commit: [8759e869034f9410f2622fed0ab40e5dbb107d97](https://github.com/dOpensource/dsiprouter/commit/8759e869034f9410f2622fed0ab40e5dbb107d97) > Date: Fri, 19 Jul 2024 16:04:07 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed a class of bugs where the description and tag fields could return the wrong rows --- [//]: # (END_SECTION 8759e869034f9410f2622fed0ab40e5dbb107d97) [//]: # (START_SECTION 75b3c327bbb19116894e0703ca417b8b1654cb4a) ### Fix Carrier Update Via API Out of Sync > Commit: [75b3c327bbb19116894e0703ca417b8b1654cb4a](https://github.com/dOpensource/dsiprouter/commit/75b3c327bbb19116894e0703ca417b8b1654cb4a) > Date: Fri, 19 Jul 2024 11:56:57 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - updated the API logic for carriers to match the GUI logic --- [//]: # (END_SECTION 75b3c327bbb19116894e0703ca417b8b1654cb4a) [//]: # (START_SECTION 426a4c2b0a5bacc8f4727b2f35127d94237cac40) ### Fix CarrierGroup lb_enabled Not Forwarded > Commit: [426a4c2b0a5bacc8f4727b2f35127d94237cac40](https://github.com/dOpensource/dsiprouter/commit/426a4c2b0a5bacc8f4727b2f35127d94237cac40) > Date: Fri, 19 Jul 2024 10:11:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add `lb_enabled` to data forwarded to the backend --- [//]: # (END_SECTION 426a4c2b0a5bacc8f4727b2f35127d94237cac40) [//]: # (START_SECTION 6b3ea5bb772a6e0d75eb91368d9a0f0a1e15c9e6) ### Fix CarrierGroup Load Balancing Icon > Commit: [6b3ea5bb772a6e0d75eb91368d9a0f0a1e15c9e6](https://github.com/dOpensource/dsiprouter/commit/6b3ea5bb772a6e0d75eb91368d9a0f0a1e15c9e6) > Date: Fri, 19 Jul 2024 10:10:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load balancing icon not shown on toggle button --- [//]: # (END_SECTION 6b3ea5bb772a6e0d75eb91368d9a0f0a1e15c9e6) [//]: # (START_SECTION f13aec39adf65e0d750820e4f7851da645eae6e7) ### Carrier Group Regression Hotfix > Commit: [f13aec39adf65e0d750820e4f7851da645eae6e7](https://github.com/dOpensource/dsiprouter/commit/f13aec39adf65e0d750820e4f7851da645eae6e7) > Date: Tue, 16 Jul 2024 13:28:57 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix regression in attributes query for carriers --- [//]: # (END_SECTION f13aec39adf65e0d750820e4f7851da645eae6e7) [//]: # (START_SECTION 5a824fe4be28edbef8103b3cd1a8013f91e25492) ### Domain Routing Translations > Commit: [5a824fe4be28edbef8103b3cd1a8013f91e25492](https://github.com/dOpensource/dsiprouter/commit/5a824fe4be28edbef8103b3cd1a8013f91e25492) > Date: Thu, 11 Jul 2024 15:52:08 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for signalling / media features on domain pass thru --- [//]: # (END_SECTION 5a824fe4be28edbef8103b3cd1a8013f91e25492) [//]: # (START_SECTION 9eb470f8a4dd79a2562395324c7d2e60583315bc) ### Better Naming for Sending Media Directly to Endpoint > Commit: [9eb470f8a4dd79a2562395324c7d2e60583315bc](https://github.com/dOpensource/dsiprouter/commit/9eb470f8a4dd79a2562395324c7d2e60583315bc) > Date: Thu, 11 Jul 2024 15:50:41 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change naming from "bypass media" to "direct media" --- [//]: # (END_SECTION 9eb470f8a4dd79a2562395324c7d2e60583315bc) [//]: # (START_SECTION 543e2f3a85e2256879d22f7651cd924b6fc47a95) ### Cleanup Artifacts From c529665 > Commit: [543e2f3a85e2256879d22f7651cd924b6fc47a95](https://github.com/dOpensource/dsiprouter/commit/543e2f3a85e2256879d22f7651cd924b6fc47a95) > Date: Fri, 5 Jul 2024 12:21:42 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 543e2f3a85e2256879d22f7651cd924b6fc47a95) [//]: # (START_SECTION 4e748d9225c0471535e529d428247ec110caaf0d) ### Fix DB Name not Set in withKamDB() > Commit: [4e748d9225c0471535e529d428247ec110caaf0d](https://github.com/dOpensource/dsiprouter/commit/4e748d9225c0471535e529d428247ec110caaf0d) > Date: Wed, 3 Jul 2024 15:26:21 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 4e748d9225c0471535e529d428247ec110caaf0d) [//]: # (START_SECTION 05d8a3db546cddf462cbe5ba7072f940acfededd) ### configuresslcert enhancement - Added the ability to overview the FQDN by using the -o or --override option with the FQDN that you want to use. Otherwise, dSIP was using reverse lookup to obtain the hostname, which in somecases isn't what the user wanted > Commit: [05d8a3db546cddf462cbe5ba7072f940acfededd](https://github.com/dOpensource/dsiprouter/commit/05d8a3db546cddf462cbe5ba7072f940acfededd) > Date: Tue, 2 Jul 2024 01:06:49 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 05d8a3db546cddf462cbe5ba7072f940acfededd) [//]: # (START_SECTION 7816850f83fad98085c3fec4fe1470c014f6b244) ### MSTeams UI Fixes - Fixes that prevented a MSTeams Domain from being added > Commit: [7816850f83fad98085c3fec4fe1470c014f6b244](https://github.com/dOpensource/dsiprouter/commit/7816850f83fad98085c3fec4fe1470c014f6b244) > Date: Sun, 30 Jun 2024 22:08:39 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7816850f83fad98085c3fec4fe1470c014f6b244) [//]: # (START_SECTION 39fd67f26e204be7f0389d16f7f51bb51411bdda) ### Fixed routing to endpoints when a 401 or 407 is received from a PBX using dispatcher > Commit: [39fd67f26e204be7f0389d16f7f51bb51411bdda](https://github.com/dOpensource/dsiprouter/commit/39fd67f26e204be7f0389d16f7f51bb51411bdda) > Date: Sun, 30 Jun 2024 13:46:09 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 39fd67f26e204be7f0389d16f7f51bb51411bdda) [//]: # (START_SECTION a0e020d98ca138243813a25931ce2bfd44f856f7) ### Fixed routing to endpoints when a 401 or 407 is received from a PBX using dispatcher > Commit: [a0e020d98ca138243813a25931ce2bfd44f856f7](https://github.com/dOpensource/dsiprouter/commit/a0e020d98ca138243813a25931ce2bfd44f856f7) > Date: Sun, 30 Jun 2024 04:16:11 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a0e020d98ca138243813a25931ce2bfd44f856f7) [//]: # (START_SECTION 87a29f49f7e0ef6e456aadfeb58d4c2155665999) ### Fixed endpoint group failover - The next gateway in the endpoint group will be tried if a failure is detected > Commit: [87a29f49f7e0ef6e456aadfeb58d4c2155665999](https://github.com/dOpensource/dsiprouter/commit/87a29f49f7e0ef6e456aadfeb58d4c2155665999) > Date: Sat, 29 Jun 2024 19:33:12 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 87a29f49f7e0ef6e456aadfeb58d4c2155665999) [//]: # (START_SECTION 53e56fa76503e703d7fa27170ed403927d33d741) ### Inbound Routing Fixes > Commit: [53e56fa76503e703d7fa27170ed403927d33d741](https://github.com/dOpensource/dsiprouter/commit/53e56fa76503e703d7fa27170ed403927d33d741) > Date: Fri, 28 Jun 2024 14:15:52 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix inbound failover via drouting -> drouting epg - fix inbound failover via dispatcher -> drouting epg - fix forwarded DID from hardfwd not reset on failover - cleanup the main routing config --- [//]: # (END_SECTION 53e56fa76503e703d7fa27170ed403927d33d741) [//]: # (START_SECTION ca380a7b55e3dcb5f1baf08c70dd734b917188d9) ### Fixes for v0.75 - Fixed CDR's so that API works correctly - Removed TLS reload from Kamailio reload due to delay in reloading > Commit: [ca380a7b55e3dcb5f1baf08c70dd734b917188d9](https://github.com/dOpensource/dsiprouter/commit/ca380a7b55e3dcb5f1baf08c70dd734b917188d9) > Date: Fri, 28 Jun 2024 18:25:21 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ca380a7b55e3dcb5f1baf08c70dd734b917188d9) [//]: # (START_SECTION ca0d727353041c1fd1ac7a1054dcdeb9c927cb11) ### Load Balancing Bug Fixes > Commit: [ca0d727353041c1fd1ac7a1054dcdeb9c927cb11](https://github.com/dOpensource/dsiprouter/commit/ca0d727353041c1fd1ac7a1054dcdeb9c927cb11) > Date: Thu, 27 Jun 2024 16:46:13 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load balanacing failover - fix fusionpbx load balancing dispatcher updates - fix final dst in load balancing group not disabled when rweight=0 - add support for enabling/disabling keepalive on endpoints - update keepalive to be disabled on enpoints by default - update keepalive timeout to 60 seconds - fix hanging dispatcher entries in some cases - fix edge case where `gwgroup2lb` was not updated correctly --- [//]: # (END_SECTION ca0d727353041c1fd1ac7a1054dcdeb9c927cb11) [//]: # (START_SECTION 0bf4420aabbefb4d6d2e9e32a5f4ee641edfe683) ### Fixed an issue with auto-unregister that prevented gateways from being removed from te dr_gw_list > Commit: [0bf4420aabbefb4d6d2e9e32a5f4ee641edfe683](https://github.com/dOpensource/dsiprouter/commit/0bf4420aabbefb4d6d2e9e32a5f4ee641edfe683) > Date: Thu, 20 Jun 2024 06:03:03 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0bf4420aabbefb4d6d2e9e32a5f4ee641edfe683) [//]: # (START_SECTION 2e00a284992b45dbf5d75c4de87f4704fd78395c) ### Fix Services on Centos7 > Commit: [2e00a284992b45dbf5d75c4de87f4704fd78395c](https://github.com/dOpensource/dsiprouter/commit/2e00a284992b45dbf5d75c4de87f4704fd78395c) > Date: Tue, 18 Jun 2024 09:58:46 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - revert centos7 to older systemd service file versions --- [//]: # (END_SECTION 2e00a284992b45dbf5d75c4de87f4704fd78395c) [//]: # (START_SECTION 3bee7a4c7e837e0e8a70acf18a65964d6edf0600) ### Fix Sync with CLUSTER_SYNC Enabled > Commit: [3bee7a4c7e837e0e8a70acf18a65964d6edf0600](https://github.com/dOpensource/dsiprouter/commit/3bee7a4c7e837e0e8a70acf18a65964d6edf0600) > Date: Tue, 18 Jun 2024 08:32:50 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 3bee7a4c7e837e0e8a70acf18a65964d6edf0600) [//]: # (START_SECTION b7a5a67c73f390e26d5d6d912a1a48d41636382c) ### Fix dsiprouter.srevice Version on CetnOS7 > Commit: [b7a5a67c73f390e26d5d6d912a1a48d41636382c](https://github.com/dOpensource/dsiprouter/commit/b7a5a67c73f390e26d5d6d912a1a48d41636382c) > Date: Tue, 18 Jun 2024 07:59:54 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION b7a5a67c73f390e26d5d6d912a1a48d41636382c) [//]: # (START_SECTION d77f068ca08ee2c842a62cbcb58d7038bd138534) ### Add Support for Upgrading from 0.72x to 0.75 > Commit: [d77f068ca08ee2c842a62cbcb58d7038bd138534](https://github.com/dOpensource/dsiprouter/commit/d77f068ca08ee2c842a62cbcb58d7038bd138534) > Date: Mon, 17 Jun 2024 20:27:35 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION d77f068ca08ee2c842a62cbcb58d7038bd138534) [//]: # (START_SECTION 83d6af70e487b84ee55c3b9522410fe30db23ad8) ### Comment Out Cert Generation > Commit: [83d6af70e487b84ee55c3b9522410fe30db23ad8](https://github.com/dOpensource/dsiprouter/commit/83d6af70e487b84ee55c3b9522410fe30db23ad8) > Date: Wed, 12 Jun 2024 22:34:26 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - commented out the broken generate radio buttons --- [//]: # (END_SECTION 83d6af70e487b84ee55c3b9522410fe30db23ad8) [//]: # (START_SECTION 575cceede455b4809e2da145592ca78b5cbf6156) ### Fix GUI Docs Build > Commit: [575cceede455b4809e2da145592ca78b5cbf6156](https://github.com/dOpensource/dsiprouter/commit/575cceede455b4809e2da145592ca78b5cbf6156) > Date: Wed, 12 Jun 2024 22:12:06 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 575cceede455b4809e2da145592ca78b5cbf6156) [//]: # (START_SECTION fc2aa26bd5fc83eb74ab86f81dc9c630a777f989) ### Fix Ext-to-Ext Calling > Commit: [fc2aa26bd5fc83eb74ab86f81dc9c630a777f989](https://github.com/dOpensource/dsiprouter/commit/fc2aa26bd5fc83eb74ab86f81dc9c630a777f989) > Date: Wed, 12 Jun 2024 21:12:17 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix `FLB_SRC_PBX` not set properly - fix checks for same flag - make digest auth get checked before IP auth --- [//]: # (END_SECTION fc2aa26bd5fc83eb74ab86f81dc9c630a777f989) [//]: # (START_SECTION c0a47655b5a2b5739121efbc9a0e03c059a291b4) ### Carrier Group Load Balancing > Commit: [c0a47655b5a2b5739121efbc9a0e03c059a291b4](https://github.com/dOpensource/dsiprouter/commit/c0a47655b5a2b5739121efbc9a0e03c059a291b4) > Date: Wed, 12 Jun 2024 19:31:37 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load balancing selection for carrier groups - add support for toggling load balancing on carrier groups - fix registered endpoints missing default signal/media params --- [//]: # (END_SECTION c0a47655b5a2b5739121efbc9a0e03c059a291b4) [//]: # (START_SECTION 10523849ac49dfa56c99eadab438d1f576666966) ### Fix Teleblock Not Routing > Commit: [10523849ac49dfa56c99eadab438d1f576666966](https://github.com/dOpensource/dsiprouter/commit/10523849ac49dfa56c99eadab438d1f576666966) > Date: Wed, 12 Jun 2024 13:53:00 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make sure teleblock setting is handled from gui -> kamailio - fix resetting selected routes after routing to teleblock --- [//]: # (END_SECTION 10523849ac49dfa56c99eadab438d1f576666966) [//]: # (START_SECTION 7cf4ac77b3693c06d94f6a3ec3c96e44acbaf713) ### License Check Edge Case Fix > Commit: [7cf4ac77b3693c06d94f6a3ec3c96e44acbaf713](https://github.com/dOpensource/dsiprouter/commit/7cf4ac77b3693c06d94f6a3ec3c96e44acbaf713) > Date: Tue, 11 Jun 2024 17:48:44 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for license keys with `_valid_for` or no expiration to load --- [//]: # (END_SECTION 7cf4ac77b3693c06d94f6a3ec3c96e44acbaf713) [//]: # (START_SECTION 44ef8c01b6c02603d423425141e0557cf7602a3b) ### Fix Carrier Auth Encoding > Commit: [44ef8c01b6c02603d423425141e0557cf7602a3b](https://github.com/dOpensource/dsiprouter/commit/44ef8c01b6c02603d423425141e0557cf7602a3b) > Date: Mon, 10 Jun 2024 22:41:20 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - encode r_username per RFC 2396 in uacreg - update network library to encode username portion - remove uneeded user portion of auth_proxy by default --- [//]: # (END_SECTION 44ef8c01b6c02603d423425141e0557cf7602a3b) [//]: # (START_SECTION c1f16fd78645972af29cede0046df2412d0f1aac) ### Add Field Validation to Twilio Carrier Plugin > Commit: [c1f16fd78645972af29cede0046df2412d0f1aac](https://github.com/dOpensource/dsiprouter/commit/c1f16fd78645972af29cede0046df2412d0f1aac) > Date: Mon, 10 Jun 2024 14:39:26 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add field validation for twilio carrier plugin --- [//]: # (END_SECTION c1f16fd78645972af29cede0046df2412d0f1aac) [//]: # (START_SECTION 5cc5742704f45c6cffee27f1dda3dad5a4fd2da7) ### Fix FuxionPBX Endpoint Failure > Commit: [5cc5742704f45c6cffee27f1dda3dad5a4fd2da7](https://github.com/dOpensource/dsiprouter/commit/5cc5742704f45c6cffee27f1dda3dad5a4fd2da7) > Date: Mon, 10 Jun 2024 11:24:08 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix regression in denpoint creation for fusionpbx endpoint groups --- [//]: # (END_SECTION 5cc5742704f45c6cffee27f1dda3dad5a4fd2da7) [//]: # (START_SECTION 27dd5e3a87f3bac5942a454cd13dce5f5f60cfe4) ### RTPEngine Patches > Commit: [27dd5e3a87f3bac5942a454cd13dce5f5f60cfe4](https://github.com/dOpensource/dsiprouter/commit/27dd5e3a87f3bac5942a454cd13dce5f5f60cfe4) > Date: Sun, 9 Jun 2024 00:10:11 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - revert version on OS installs that have not been updated to support it - fix rtpengine kernel module build on centos - fix rtpengine config defaults on debian/centos --- [//]: # (END_SECTION 27dd5e3a87f3bac5942a454cd13dce5f5f60cfe4) [//]: # (START_SECTION 32face946d43cbc0df179550f6d612786808230a) ### Fix RTP Media Control Arguments > Commit: [32face946d43cbc0df179550f6d612786808230a](https://github.com/dOpensource/dsiprouter/commit/32face946d43cbc0df179550f6d612786808230a) > Date: Sat, 8 Jun 2024 21:52:54 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix control-ng args for UDTPL - fix control-ng args for OSRTP - make display names for media/signalling easier to understand --- [//]: # (END_SECTION 32face946d43cbc0df179550f6d612786808230a) [//]: # (START_SECTION 6b6e1b6dc489867566e398ca50913ef4be733c59) ### Fix RTPEngine Reload > Commit: [6b6e1b6dc489867566e398ca50913ef4be733c59](https://github.com/dOpensource/dsiprouter/commit/6b6e1b6dc489867566e398ca50913ef4be733c59) > Date: Sat, 8 Jun 2024 20:03:38 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug where rtpengine instances disabled on reload --- [//]: # (END_SECTION 6b6e1b6dc489867566e398ca50913ef4be733c59) [//]: # (START_SECTION 8804887bf59e6317ccea7e4e64d95d092825d9fc) ### Fix RTPEngine Disabled on Boot > Commit: [8804887bf59e6317ccea7e4e64d95d092825d9fc](https://github.com/dOpensource/dsiprouter/commit/8804887bf59e6317ccea7e4e64d95d092825d9fc) > Date: Fri, 7 Jun 2024 15:55:44 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update `kamailio.service` to be ordered after `rtpengine.service` --- [//]: # (END_SECTION 8804887bf59e6317ccea7e4e64d95d092825d9fc) [//]: # (START_SECTION bca5ab613fbb009b4b835e2903290888ba7f3574) ### Fix Missing BYE on Upstream > Commit: [bca5ab613fbb009b4b835e2903290888ba7f3574](https://github.com/dOpensource/dsiprouter/commit/bca5ab613fbb009b4b835e2903290888ba7f3574) > Date: Fri, 7 Jun 2024 13:35:22 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make in-dialog requests to upstream route to source from initial setup --- [//]: # (END_SECTION bca5ab613fbb009b4b835e2903290888ba7f3574) [//]: # (START_SECTION 17f6768ba9b4d3f58f20a2239f88b8728c67eaef) ### Auth Plugin Updates > Commit: [17f6768ba9b4d3f58f20a2239f88b8728c67eaef](https://github.com/dOpensource/dsiprouter/commit/17f6768ba9b4d3f58f20a2239f88b8728c67eaef) > Date: Thu, 6 Jun 2024 14:26:35 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make the auth module format easier to extend - reset the default to no extra auth modules --- [//]: # (END_SECTION 17f6768ba9b4d3f58f20a2239f88b8728c67eaef) [//]: # (START_SECTION 1d13139952f73f9ff9dfca79789c0ad1e36a2577) ### Update Dependency Requirements for CLI > Commit: [1d13139952f73f9ff9dfca79789c0ad1e36a2577](https://github.com/dOpensource/dsiprouter/commit/1d13139952f73f9ff9dfca79789c0ad1e36a2577) > Date: Wed, 5 Jun 2024 14:19:49 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make sure dependencies for CLI are installed --- [//]: # (END_SECTION 1d13139952f73f9ff9dfca79789c0ad1e36a2577) [//]: # (START_SECTION 3882f40b0177622d09e4b1267a007c3911cae25d) ### Fix CentOS SELinux Permissions > Commit: [3882f40b0177622d09e4b1267a007c3911cae25d](https://github.com/dOpensource/dsiprouter/commit/3882f40b0177622d09e4b1267a007c3911cae25d) > Date: Mon, 3 Jun 2024 17:49:36 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update permissions to allow nginx acces to dsiprouter.sock --- [//]: # (END_SECTION 3882f40b0177622d09e4b1267a007c3911cae25d) [//]: # (START_SECTION bcdcf178a97f27ddcb129d830f794c5a3fe0c717) ### CentOS Installation Updates > Commit: [bcdcf178a97f27ddcb129d830f794c5a3fe0c717](https://github.com/dOpensource/dsiprouter/commit/bcdcf178a97f27ddcb129d830f794c5a3fe0c717) > Date: Fri, 31 May 2024 14:14:10 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add stable support back to centos7 - fix various dependency issues - bump kamailio/rtpengine versino on centos7 - fix dnsmasq support --- [//]: # (END_SECTION bcdcf178a97f27ddcb129d830f794c5a3fe0c717) [//]: # (START_SECTION 692157482f7971a2ce2c90fe75184b3e51f23734) ### Amazon Linux 2 Patches > Commit: [692157482f7971a2ce2c90fe75184b3e51f23734](https://github.com/dOpensource/dsiprouter/commit/692157482f7971a2ce2c90fe75184b3e51f23734) > Date: Fri, 31 May 2024 14:04:11 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - bump rtpengine version - downgrade kamailio version - add patch for htable coldelim/colnull in kam5.7 - add support for ldap --- [//]: # (END_SECTION 692157482f7971a2ce2c90fe75184b3e51f23734) [//]: # (START_SECTION b1f784d5484b7a53e64ffc94200149500529d3b9) ### Cluster / Install Fixes > Commit: [b1f784d5484b7a53e64ffc94200149500529d3b9](https://github.com/dOpensource/dsiprouter/commit/b1f784d5484b7a53e64ffc94200149500529d3b9) > Date: Fri, 31 May 2024 13:57:41 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix `clusterinstall` when running with cluster sync enabled - add support for re-running `clusterinstall` after partial completion - add support for changing key used in `dsip_lib.sh` encryption funcs - fix ordering of module installs - updated CDR feature to be conditionally loaded --- [//]: # (END_SECTION b1f784d5484b7a53e64ffc94200149500529d3b9) [//]: # (START_SECTION 64640ba1e2bf8ad5a2ffefba4ff9c00682954af8) ### Updated Privelege Escalation in HA Scripts > Commit: [64640ba1e2bf8ad5a2ffefba4ff9c00682954af8](https://github.com/dOpensource/dsiprouter/commit/64640ba1e2bf8ad5a2ffefba4ff9c00682954af8) > Date: Wed, 29 May 2024 21:10:04 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for `su` in pacemaker cluster install - add support for `sudo` or `su` escalation using password --- [//]: # (END_SECTION 64640ba1e2bf8ad5a2ffefba4ff9c00682954af8) [//]: # (START_SECTION c0f2604cbe4bed4a9681348ea7cd2ce4067fbc06) ### Improve Privilege Escalation on Galera Install > Commit: [c0f2604cbe4bed4a9681348ea7cd2ce4067fbc06](https://github.com/dOpensource/dsiprouter/commit/c0f2604cbe4bed4a9681348ea7cd2ce4067fbc06) > Date: Tue, 28 May 2024 15:31:58 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for `su` privilege escalation - add helper function for selecting program for escalation --- [//]: # (END_SECTION c0f2604cbe4bed4a9681348ea7cd2ce4067fbc06) [//]: # (START_SECTION 8630f8cee5b28b0ac9eaebf5751c1df3b17409fb) ### Make DNSMasq Optional > Commit: [8630f8cee5b28b0ac9eaebf5751c1df3b17409fb](https://github.com/dOpensource/dsiprouter/commit/8630f8cee5b28b0ac9eaebf5751c1df3b17409fb) > Date: Thu, 23 May 2024 13:22:58 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move dnsmasq / dsiprouter network stack to an install option - add documentation for new `-dns`/`--dnsmasq` options - make `clusterinstall` require dnsmasq install --- [//]: # (END_SECTION 8630f8cee5b28b0ac9eaebf5751c1df3b17409fb) [//]: # (START_SECTION 6fc903b9086178ddd02322d17b5999719df733bc) ### Updated dSIPRouter Service files to have Kamailio Service start first > Commit: [6fc903b9086178ddd02322d17b5999719df733bc](https://github.com/dOpensource/dsiprouter/commit/6fc903b9086178ddd02322d17b5999719df733bc) > Date: Thu, 23 May 2024 15:10:12 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6fc903b9086178ddd02322d17b5999719df733bc) [//]: # (START_SECTION 13989cbf1e16ee377db7ab6a4d1138aad614417b) ### Removed DnsMasq from the install > Commit: [13989cbf1e16ee377db7ab6a4d1138aad614417b](https://github.com/dOpensource/dsiprouter/commit/13989cbf1e16ee377db7ab6a4d1138aad614417b) > Date: Thu, 23 May 2024 05:17:23 +0000 > Author: root (root@075test.dsiprouter.net) > Committer: root (root@075test.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 13989cbf1e16ee377db7ab6a4d1138aad614417b) [//]: # (START_SECTION a7f092254269a5a33732667ab0cae56c4c562bcc) ### Networking Edge Cases > Commit: [a7f092254269a5a33732667ab0cae56c4c562bcc](https://github.com/dOpensource/dsiprouter/commit/a7f092254269a5a33732667ab0cae56c4c562bcc) > Date: Tue, 21 May 2024 14:06:19 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix edge cases where network stack was failing - fix edge cases where apt prompted during installs --- [//]: # (END_SECTION a7f092254269a5a33732667ab0cae56c4c562bcc) [//]: # (START_SECTION c041cfd3e048e48df24d927680e38d383da9c126) ### Bug Fixes > Commit: [c041cfd3e048e48df24d927680e38d383da9c126](https://github.com/dOpensource/dsiprouter/commit/c041cfd3e048e48df24d927680e38d383da9c126) > Date: Mon, 20 May 2024 18:42:16 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update uninstall files for debian dnsmasq - conditionally enable sctp on boot if module loaded --- [//]: # (END_SECTION c041cfd3e048e48df24d927680e38d383da9c126) [//]: # (START_SECTION 14e8619eab2e549ea5522cc70f65ffb629d4dfc5) ### Upgrade Fixes > Commit: [14e8619eab2e549ea5522cc70f65ffb629d4dfc5](https://github.com/dOpensource/dsiprouter/commit/14e8619eab2e549ea5522cc70f65ffb629d4dfc5) > Date: Mon, 20 May 2024 16:49:53 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixup upgrade script issues - fix dsip_settings migration filter - make uac patch error handling more robust --- [//]: # (END_SECTION 14e8619eab2e549ea5522cc70f65ffb629d4dfc5) [//]: # (START_SECTION 17bad27719f91cc0ba496a31453be46816224546) ### Fixup Debian12 Networking > Commit: [17bad27719f91cc0ba496a31453be46816224546](https://github.com/dOpensource/dsiprouter/commit/17bad27719f91cc0ba496a31453be46816224546) > Date: Mon, 20 May 2024 15:27:49 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make services skip interfaces managed by other services --- [//]: # (END_SECTION 17bad27719f91cc0ba496a31453be46816224546) [//]: # (START_SECTION fbf026c7e76dad7a0f6e17947c9200b73b98c985) ### Networking Fixes > Commit: [fbf026c7e76dad7a0f6e17947c9200b73b98c985](https://github.com/dOpensource/dsiprouter/commit/fbf026c7e76dad7a0f6e17947c9200b73b98c985) > Date: Mon, 20 May 2024 12:01:28 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix networking on centos - fix docker interface management --- [//]: # (END_SECTION fbf026c7e76dad7a0f6e17947c9200b73b98c985) [//]: # (START_SECTION f87aa6653d56da4c29bef3c80aa1b94100ae46ab) ### Fix Latest Release Default > Commit: [f87aa6653d56da4c29bef3c80aa1b94100ae46ab](https://github.com/dOpensource/dsiprouter/commit/f87aa6653d56da4c29bef3c80aa1b94100ae46ab) > Date: Mon, 20 May 2024 11:53:25 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix default selection for latest release --- [//]: # (END_SECTION f87aa6653d56da4c29bef3c80aa1b94100ae46ab) [//]: # (START_SECTION de0aa6126df9eccf60d9cb1d921d7454f90e4aec) ### Fix DNSMasq Config Path > Commit: [de0aa6126df9eccf60d9cb1d921d7454f90e4aec](https://github.com/dOpensource/dsiprouter/commit/de0aa6126df9eccf60d9cb1d921d7454f90e4aec) > Date: Mon, 20 May 2024 08:46:34 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix updated config paths for other OS --- [//]: # (END_SECTION de0aa6126df9eccf60d9cb1d921d7454f90e4aec) [//]: # (START_SECTION 9e1a4649f51c94ff9f187a3f7ac31e2a8f616d64) ### Upgrade Improvements > Commit: [9e1a4649f51c94ff9f187a3f7ac31e2a8f616d64](https://github.com/dOpensource/dsiprouter/commit/9e1a4649f51c94ff9f187a3f7ac31e2a8f616d64) > Date: Fri, 17 May 2024 17:28:04 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for upgrading from v0.73/v0.74 -> v0.75 - add support for migrating licenses - fix a few typos in the v0.74 upgrade script --- [//]: # (END_SECTION 9e1a4649f51c94ff9f187a3f7ac31e2a8f616d64) [//]: # (START_SECTION 7652a2c8b52da1097b5b2bf736f655547e13c175) ### Gateway Update Bug Fix > Commit: [7652a2c8b52da1097b5b2bf736f655547e13c175](https://github.com/dOpensource/dsiprouter/commit/7652a2c8b52da1097b5b2bf736f655547e13c175) > Date: Fri, 17 May 2024 17:27:31 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix index error on new `attrs` field format --- [//]: # (END_SECTION 7652a2c8b52da1097b5b2bf736f655547e13c175) [//]: # (START_SECTION d2d63c7bfc9a96a447e3d3d6e293be630d117985) ### Fix Merge Conflicts > Commit: [d2d63c7bfc9a96a447e3d3d6e293be630d117985](https://github.com/dOpensource/dsiprouter/commit/d2d63c7bfc9a96a447e3d3d6e293be630d117985) > Date: Fri, 17 May 2024 12:26:22 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION d2d63c7bfc9a96a447e3d3d6e293be630d117985) [//]: # (START_SECTION 1fe2f3ee3f8bfb2bfc1bed9a2a60e4b0c1b40b83) ### Added Python LDAP to the requirements file > Commit: [1fe2f3ee3f8bfb2bfc1bed9a2a60e4b0c1b40b83](https://github.com/dOpensource/dsiprouter/commit/1fe2f3ee3f8bfb2bfc1bed9a2a60e4b0c1b40b83) > Date: Fri, 17 May 2024 01:37:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1fe2f3ee3f8bfb2bfc1bed9a2a60e4b0c1b40b83) [//]: # (START_SECTION bd478e44de1b6a3a53018b761db19fa2b3de4e9f) ### Fixed dSIPRouter installer so that libraries that support LDAP works properly > Commit: [bd478e44de1b6a3a53018b761db19fa2b3de4e9f](https://github.com/dOpensource/dsiprouter/commit/bd478e44de1b6a3a53018b761db19fa2b3de4e9f) > Date: Fri, 17 May 2024 01:20:33 +0000 > Author: root (root@075testing.dsiprouter.net) > Committer: root (root@075testing.dsiprouter.net) > Signed: --- [//]: # (END_SECTION bd478e44de1b6a3a53018b761db19fa2b3de4e9f) [//]: # (START_SECTION 5eac4d78b45be6c85cba5bcb4b0e18a3434be119) ### LDAP Support: - Added the ability to authenticate using LDAP - Settings for LDAP group membership has been defined - A user must be in a defined LDAP group in order to access the UI > Commit: [5eac4d78b45be6c85cba5bcb4b0e18a3434be119](https://github.com/dOpensource/dsiprouter/commit/5eac4d78b45be6c85cba5bcb4b0e18a3434be119) > Date: Wed, 15 May 2024 16:04:21 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5eac4d78b45be6c85cba5bcb4b0e18a3434be119) [//]: # (START_SECTION d755ccb6bde29d678adcc0b5373be9e8b65866d1) ### RHEL SCTP Support > Commit: [d755ccb6bde29d678adcc0b5373be9e8b65866d1](https://github.com/dOpensource/dsiprouter/commit/d755ccb6bde29d678adcc0b5373be9e8b65866d1) > Date: Tue, 14 May 2024 14:06:17 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - install/enable SCTP on RHEL-based distros --- [//]: # (END_SECTION d755ccb6bde29d678adcc0b5373be9e8b65866d1) [//]: # (START_SECTION d906755d15830244286b5c7ae84168777e57b7da) ### Bug Fixes > Commit: [d906755d15830244286b5c7ae84168777e57b7da](https://github.com/dOpensource/dsiprouter/commit/d906755d15830244286b5c7ae84168777e57b7da) > Date: Tue, 14 May 2024 12:35:21 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix debian network manager integration - improve network service support - remove unsupported ws uac transports - fix duplicate swap entries on reinstall --- [//]: # (END_SECTION d906755d15830244286b5c7ae84168777e57b7da) [//]: # (START_SECTION 65eb074c0d896d2fee33ff5424763187d4219cde) ### Bump Version to v0.75 > Commit: [65eb074c0d896d2fee33ff5424763187d4219cde](https://github.com/dOpensource/dsiprouter/commit/65eb074c0d896d2fee33ff5424763187d4219cde) > Date: Thu, 9 May 2024 09:43:33 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 65eb074c0d896d2fee33ff5424763187d4219cde) [//]: # (START_SECTION 0a1d91c4ba6061fd95f1218556e5c8bd6bee8b2d) ### Fix RTPEngine Stale Hash Entries > Commit: [0a1d91c4ba6061fd95f1218556e5c8bd6bee8b2d](https://github.com/dOpensource/dsiprouter/commit/0a1d91c4ba6061fd95f1218556e5c8bd6bee8b2d) > Date: Mon, 6 May 2024 12:46:25 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make hash entries expire on same timeout as rtpengine application --- [//]: # (END_SECTION 0a1d91c4ba6061fd95f1218556e5c8bd6bee8b2d) [//]: # (START_SECTION d4e003b62a7e28af4047926dc5c01019e7564167) ### Signalling & Media Translation Support > Commit: [d4e003b62a7e28af4047926dc5c01019e7564167](https://github.com/dOpensource/dsiprouter/commit/d4e003b62a7e28af4047926dc5c01019e7564167) > Date: Sun, 5 May 2024 18:31:37 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #555 - add support for SCTP as an UAS - add support for SCTP as an UAC - add support for translating media per endpoint - add support for translating signalling per endpoint - add tooltips to endpoint configuration options - switch to rewight and relative load balancing as the default - disable WITH_PUSH by default (causing issues with routing) - make record routing more reliable (stateful selection instead) - fix call limiting pv conversions incorrect in some cases - fix typo in dialog timeout - allow media bypass as an option for endpoints - make modal popups relative to viewport size --- [//]: # (END_SECTION d4e003b62a7e28af4047926dc5c01019e7564167) [//]: # (START_SECTION 70a9734b9d723d8384bf483c509c5d911f6a1688) ### Fixup Styling on Reload Buttons > Commit: [70a9734b9d723d8384bf483c509c5d911f6a1688](https://github.com/dOpensource/dsiprouter/commit/70a9734b9d723d8384bf483c509c5d911f6a1688) > Date: Wed, 1 May 2024 13:33:36 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make clicks on "reload" open the dropdown - make styling more consistent with other dropdowns --- [//]: # (END_SECTION 70a9734b9d723d8384bf483c509c5d911f6a1688) [//]: # (START_SECTION 25ebab6b83380c069e94c322049135363073bd05) ### Fix Dialog Call Settings > Commit: [25ebab6b83380c069e94c322049135363073bd05](https://github.com/dOpensource/dsiprouter/commit/25ebab6b83380c069e94c322049135363073bd05) > Date: Wed, 1 May 2024 11:44:02 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - bump kamailio version to 5.8.x - fix null checks when setting dialog call settings --- [//]: # (END_SECTION 25ebab6b83380c069e94c322049135363073bd05) [//]: # (START_SECTION d6af461097c8d15b052bcd478c618b6ff5e10c6a) ### Fix Improper Htable Config > Commit: [d6af461097c8d15b052bcd478c618b6ff5e10c6a](https://github.com/dOpensource/dsiprouter/commit/d6af461097c8d15b052bcd478c618b6ff5e10c6a) > Date: Wed, 1 May 2024 11:17:45 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - rename route2lb lookup to gwgroup2lb --- [//]: # (END_SECTION d6af461097c8d15b052bcd478c618b6ff5e10c6a) [//]: # (START_SECTION 61917b2d0fa7da63d671d9ca81d0c23953af5427) ### Fix Cron and JsonRPC Bugs > Commit: [61917b2d0fa7da63d671d9ca81d0c23953af5427](https://github.com/dOpensource/dsiprouter/commit/61917b2d0fa7da63d671d9ca81d0c23953af5427) > Date: Fri, 26 Apr 2024 12:00:39 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug in kamailio reload where some tuples were in wrong format - refactor above tuples to lists for easier debugging - fix license checks fail after cron job runs (wrong permissions) - refactor cron jobs to use least privilege needed - refactor fusionpbx certs into the standard configuration dir - refactor fusion sync to use standard RPC reloads instead of kamcmd --- [//]: # (END_SECTION 61917b2d0fa7da63d671d9ca81d0c23953af5427) [//]: # (START_SECTION 400837f00c4758b2344aec005c93f44d7819d3d6) ### Improve Error Messages for HTTP Errors > Commit: [400837f00c4758b2344aec005c93f44d7819d3d6](https://github.com/dOpensource/dsiprouter/commit/400837f00c4758b2344aec005c93f44d7819d3d6) > Date: Wed, 24 Apr 2024 14:49:13 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #276 --- [//]: # (END_SECTION 400837f00c4758b2344aec005c93f44d7819d3d6) [//]: # (START_SECTION 1108bb9f9266eacb3b49a9ac3d733c76731d68af) ### Stability Improvements > Commit: [1108bb9f9266eacb3b49a9ac3d733c76731d68af](https://github.com/dOpensource/dsiprouter/commit/1108bb9f9266eacb3b49a9ac3d733c76731d68af) > Date: Wed, 24 Apr 2024 13:46:58 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update RPC reload deltas in kamailio - add support for reload_delta in uac module - fix license machine validation false positives - fix license state not preloaded into template context - fix shared memory not cleaned up on startup failure --- [//]: # (END_SECTION 1108bb9f9266eacb3b49a9ac3d733c76731d68af) [//]: # (START_SECTION 8df5e8f8dcaacb911e03448982ec10f470dbd18c) ### Fix Subcriber Update API > Commit: [8df5e8f8dcaacb911e03448982ec10f470dbd18c](https://github.com/dOpensource/dsiprouter/commit/8df5e8f8dcaacb911e03448982ec10f470dbd18c) > Date: Mon, 22 Apr 2024 15:37:18 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #566 --- [//]: # (END_SECTION 8df5e8f8dcaacb911e03448982ec10f470dbd18c) [//]: # (START_SECTION fbc8e958f4c431dc26b91f95196f77013a74f4b3) ### v0.75 Bug Fixes > Commit: [fbc8e958f4c431dc26b91f95196f77013a74f4b3](https://github.com/dOpensource/dsiprouter/commit/fbc8e958f4c431dc26b91f95196f77013a74f4b3) > Date: Mon, 22 Apr 2024 15:11:46 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix uninstall for dnsmasq on debian not updated - fix password not set in kamailio.cfg on install --- [//]: # (END_SECTION fbc8e958f4c431dc26b91f95196f77013a74f4b3) [//]: # (START_SECTION 80f143450a09cd7eae6e081d62a96d7eca14b3b3) ### fix NetworkManager integration on debian > Commit: [80f143450a09cd7eae6e081d62a96d7eca14b3b3](https://github.com/dOpensource/dsiprouter/commit/80f143450a09cd7eae6e081d62a96d7eca14b3b3) > Date: Mon, 22 Apr 2024 13:28:47 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 80f143450a09cd7eae6e081d62a96d7eca14b3b3) [//]: # (START_SECTION da8e89e81c280e09035b82e7dfa349522c7b8e85) ### Update Transnexus GUI Settings > Commit: [da8e89e81c280e09035b82e7dfa349522c7b8e85](https://github.com/dOpensource/dsiprouter/commit/da8e89e81c280e09035b82e7dfa349522c7b8e85) > Date: Thu, 4 Apr 2024 15:37:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for configuring verify service in the GUI --- [//]: # (END_SECTION da8e89e81c280e09035b82e7dfa349522c7b8e85) [//]: # (START_SECTION c66b16684b0206e1bd71907882bb64cfa95195b2) ### v0.75 Bug Fixes > Commit: [c66b16684b0206e1bd71907882bb64cfa95195b2](https://github.com/dOpensource/dsiprouter/commit/c66b16684b0206e1bd71907882bb64cfa95195b2) > Date: Wed, 3 Apr 2024 15:52:39 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix FQDNs with IP in name not parsed by routing config - fix htable syntax errors in routing config - fix firewalld / ufw package conflict on debian - fix systemd-resolved not configured correctly on vultr --- [//]: # (END_SECTION c66b16684b0206e1bd71907882bb64cfa95195b2) [//]: # (START_SECTION a53cd57604f817365dbbccde3741dbd83e87e722) ### Merge pull request #561 from dOpensource/bugfix/560 > Commit: [a53cd57604f817365dbbccde3741dbd83e87e722](https://github.com/dOpensource/dsiprouter/commit/a53cd57604f817365dbbccde3741dbd83e87e722) > Date: Tue, 27 Feb 2024 14:36:07 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Fixes 560 - Enables the OpenSSL Default Provider --- [//]: # (END_SECTION a53cd57604f817365dbbccde3741dbd83e87e722) [//]: # (START_SECTION 78920dded1b5cd7afe0c8dd916b780b18e284374) ### Add Max Time Limit For Endpoint Groups > Commit: [78920dded1b5cd7afe0c8dd916b780b18e284374](https://github.com/dOpensource/dsiprouter/commit/78920dded1b5cd7afe0c8dd916b780b18e284374) > Date: Fri, 29 Mar 2024 17:17:28 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #177 - add support for limiting call durations on endpoint groups - reformat call limit into call_settings schema --- [//]: # (END_SECTION 78920dded1b5cd7afe0c8dd916b780b18e284374) [//]: # (START_SECTION bb168ab0f564235717dece05cd43a625e5da85b0) ### Added configuration settings for the LDAP auth plugin > Commit: [bb168ab0f564235717dece05cd43a625e5da85b0](https://github.com/dOpensource/dsiprouter/commit/bb168ab0f564235717dece05cd43a625e5da85b0) > Date: Mon, 22 Apr 2024 18:03:34 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bb168ab0f564235717dece05cd43a625e5da85b0) [//]: # (START_SECTION ac530a50b2f7c2e3e4c7135dbaef019ca00645bd) ### Initial commit of adding LDAP Support for dSIPRouter authentication > Commit: [ac530a50b2f7c2e3e4c7135dbaef019ca00645bd](https://github.com/dOpensource/dsiprouter/commit/ac530a50b2f7c2e3e4c7135dbaef019ca00645bd) > Date: Mon, 22 Apr 2024 04:28:03 +0000 > Author: root (root@ldapsupport.dsiprouter.net) > Committer: root (root@ldapsupport.dsiprouter.net) > Signed: --- [//]: # (END_SECTION ac530a50b2f7c2e3e4c7135dbaef019ca00645bd) [//]: # (START_SECTION 09d219da3dfa0c44ab928953c3a88bb92a4fc19c) ### Fixes #564 - Added logic to convert a LCR outbound route to a simple outbound route once the from prefix is removed > Commit: [09d219da3dfa0c44ab928953c3a88bb92a4fc19c](https://github.com/dOpensource/dsiprouter/commit/09d219da3dfa0c44ab928953c3a88bb92a4fc19c) > Date: Mon, 15 Apr 2024 22:39:46 +0000 > Author: root (root@demo-dsip-master0) > Committer: root (root@demo-dsip-master0) > Signed: --- [//]: # (END_SECTION 09d219da3dfa0c44ab928953c3a88bb92a4fc19c) [//]: # (START_SECTION 5515922c7c178bdabb4d159ed167f21c63615b5c) ### Update Transnexus GUI Settings > Commit: [5515922c7c178bdabb4d159ed167f21c63615b5c](https://github.com/dOpensource/dsiprouter/commit/5515922c7c178bdabb4d159ed167f21c63615b5c) > Date: Thu, 4 Apr 2024 15:37:23 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for configuring verify service in the GUI --- [//]: # (END_SECTION 5515922c7c178bdabb4d159ed167f21c63615b5c) [//]: # (START_SECTION 60cbe341aaec7bc0447dc667a569bc205a1f06aa) ### v0.75 Bug Fixes > Commit: [60cbe341aaec7bc0447dc667a569bc205a1f06aa](https://github.com/dOpensource/dsiprouter/commit/60cbe341aaec7bc0447dc667a569bc205a1f06aa) > Date: Wed, 3 Apr 2024 15:52:39 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix FQDNs with IP in name not parsed by routing config - fix htable syntax errors in routing config - fix firewalld / ufw package conflict on debian - fix systemd-resolved not configured correctly on vultr --- [//]: # (END_SECTION 60cbe341aaec7bc0447dc667a569bc205a1f06aa) [//]: # (START_SECTION aa2789a6cb688d9e9bfa30f4972a883dbd92145d) ### Moved the removal of ufw up in the script before Kamailio dependencies > Commit: [aa2789a6cb688d9e9bfa30f4972a883dbd92145d](https://github.com/dOpensource/dsiprouter/commit/aa2789a6cb688d9e9bfa30f4972a883dbd92145d) > Date: Wed, 3 Apr 2024 12:53:40 +0000 > Author: root (root@test1.dsiprouter.net) > Committer: root (root@test1.dsiprouter.net) > Signed: --- [//]: # (END_SECTION aa2789a6cb688d9e9bfa30f4972a883dbd92145d) [//]: # (START_SECTION 529685e23ec84d6da5fa8b945b87b17386cadfbb) ### Moved the removal of ufw up in the script > Commit: [529685e23ec84d6da5fa8b945b87b17386cadfbb](https://github.com/dOpensource/dsiprouter/commit/529685e23ec84d6da5fa8b945b87b17386cadfbb) > Date: Wed, 3 Apr 2024 12:35:17 +0000 > Author: root (root@test1.dsiprouter.net) > Committer: root (root@test1.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 529685e23ec84d6da5fa8b945b87b17386cadfbb) [//]: # (START_SECTION 0e272fbbd64c5ab1855e8d9ffa500205da93cf99) ### Fixes for dnsmasq and Vultr - Added logic to handle both resolvconf and systemd-resolved DNS stacks - dnsmasq has a specific configuration file for Debian 12 - Added logic to set the EXTERNAL_FQDN to the INTERNAL_FQDN if the vultrusercontent.com domain is detected, which uses the ip address in the hostname portion of the FQDN. Kamailio doesn't like FQDN's in this format > Commit: [0e272fbbd64c5ab1855e8d9ffa500205da93cf99](https://github.com/dOpensource/dsiprouter/commit/0e272fbbd64c5ab1855e8d9ffa500205da93cf99) > Date: Wed, 3 Apr 2024 04:39:00 +0000 > Author: root (root@test1.dsiprouter.net) > Committer: root (root@test1.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 0e272fbbd64c5ab1855e8d9ffa500205da93cf99) [//]: # (START_SECTION 64b1947c7da99eecfb02530cf75c211ddc74c2b0) ### Merge pull request #561 from dOpensource/bugfix/560 > Commit: [64b1947c7da99eecfb02530cf75c211ddc74c2b0](https://github.com/dOpensource/dsiprouter/commit/64b1947c7da99eecfb02530cf75c211ddc74c2b0) > Date: Tue, 27 Feb 2024 14:36:07 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Fixes 560 - Enables the OpenSSL Default Provider --- [//]: # (END_SECTION 64b1947c7da99eecfb02530cf75c211ddc74c2b0) [//]: # (START_SECTION be105c9f5058d5b57470e15136dde89363353156) ### Add Max Time Limit For Endpoint Groups > Commit: [be105c9f5058d5b57470e15136dde89363353156](https://github.com/dOpensource/dsiprouter/commit/be105c9f5058d5b57470e15136dde89363353156) > Date: Fri, 29 Mar 2024 17:17:28 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #177 - add support for limiting call durations on endpoint groups - reformat call limit into call_settings schema --- [//]: # (END_SECTION be105c9f5058d5b57470e15136dde89363353156) [//]: # (START_SECTION c1d7e509bedfbf6a8e4814ce021bbccf58a5b705) ### Fix dSipRouter Startup Limits > Commit: [c1d7e509bedfbf6a8e4814ce021bbccf58a5b705](https://github.com/dOpensource/dsiprouter/commit/c1d7e509bedfbf6a8e4814ce021bbccf58a5b705) > Date: Thu, 28 Mar 2024 12:05:45 -0600 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - limit startup failures to 3 attempts when launched via systemd --- [//]: # (END_SECTION c1d7e509bedfbf6a8e4814ce021bbccf58a5b705) [//]: # (START_SECTION 4dc7317a42efd4e33f341cd1992ea2249841116e) ### Fix Kamailio Reloading Errors > Commit: [4dc7317a42efd4e33f341cd1992ea2249841116e](https://github.com/dOpensource/dsiprouter/commit/4dc7317a42efd4e33f341cd1992ea2249841116e) > Date: Thu, 21 Mar 2024 18:07:30 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix jsonrpc reload failing when features enabled/disabled - fix kamailio not starting when WITH_TLS disabled --- [//]: # (END_SECTION 4dc7317a42efd4e33f341cd1992ea2249841116e) [//]: # (START_SECTION addbc3dea61239991370950c3f106d1a73a3ff55) ### Update TLS Cert Renewal Checks > Commit: [addbc3dea61239991370950c3f106d1a73a3ff55](https://github.com/dOpensource/dsiprouter/commit/addbc3dea61239991370950c3f106d1a73a3ff55) > Date: Fri, 15 Mar 2024 10:44:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for renewing self signed cert - update wildcard check to instead check for LE issued cert - improve error handling during renewal failures --- [//]: # (END_SECTION addbc3dea61239991370950c3f106d1a73a3ff55) [//]: # (START_SECTION 618f43763704f8e5782258a50fd974ebec02cad1) ### Add AWS Pacemaker Support > Commit: [618f43763704f8e5782258a50fd974ebec02cad1](https://github.com/dOpensource/dsiprouter/commit/618f43763704f8e5782258a50fd974ebec02cad1) > Date: Thu, 14 Mar 2024 14:18:52 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for AWS elastic IPs in pacemaker - fix cloud-init hosts template not applying --- [//]: # (END_SECTION 618f43763704f8e5782258a50fd974ebec02cad1) [//]: # (START_SECTION 1e603d96e885d05dd7c6a8008f34f7cf051e0756) ### Central DB Config Updates > Commit: [1e603d96e885d05dd7c6a8008f34f7cf051e0756](https://github.com/dOpensource/dsiprouter/commit/1e603d96e885d05dd7c6a8008f34f7cf051e0756) > Date: Thu, 29 Feb 2024 18:38:09 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix credentials not being set properly when `-db` given - fix DB data not synced properly for fresh install with `-db` --- [//]: # (END_SECTION 1e603d96e885d05dd7c6a8008f34f7cf051e0756) [//]: # (START_SECTION 13b4377ee5658eede8dca625e1a07705a3d6ecfc) ### Add Support For License Tagging > Commit: [13b4377ee5658eede8dca625e1a07705a3d6ecfc](https://github.com/dOpensource/dsiprouter/commit/13b4377ee5658eede8dca625e1a07705a3d6ecfc) > Date: Thu, 29 Feb 2024 18:36:17 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - adds support for tagged licenses and license combos --- [//]: # (END_SECTION 13b4377ee5658eede8dca625e1a07705a3d6ecfc) [//]: # (START_SECTION a89a3bff193bb6c0b69807082fe475fae043fafe) ### Fixes 560 - Enables the OpenSSL Default Provider > Commit: [a89a3bff193bb6c0b69807082fe475fae043fafe](https://github.com/dOpensource/dsiprouter/commit/a89a3bff193bb6c0b69807082fe475fae043fafe) > Date: Tue, 27 Feb 2024 18:25:17 +0000 > Author: root (root@dsip-debian12-074) > Committer: root (root@dsip-debian12-074) > Signed: --- [//]: # (END_SECTION a89a3bff193bb6c0b69807082fe475fae043fafe) [//]: # (START_SECTION 8bd961101b57c5fa7254c6be4f81dc49977a3555) ### Fix Debian Net ISO Install > Commit: [8bd961101b57c5fa7254c6be4f81dc49977a3555](https://github.com/dOpensource/dsiprouter/commit/8bd961101b57c5fa7254c6be4f81dc49977a3555) > Date: Mon, 26 Feb 2024 08:44:30 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix dnsmasq installation for debian net ISO installs --- [//]: # (END_SECTION 8bd961101b57c5fa7254c6be4f81dc49977a3555) [//]: # (START_SECTION 03a79b1b952fc523b208c381315d58a001fcc69f) ### Hotfix For Debian10/11 dnsmasq Configuration > Commit: [03a79b1b952fc523b208c381315d58a001fcc69f](https://github.com/dOpensource/dsiprouter/commit/03a79b1b952fc523b208c381315d58a001fcc69f) > Date: Wed, 14 Feb 2024 10:28:44 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for resolvconf integration on debian versions < 12 --- [//]: # (END_SECTION 03a79b1b952fc523b208c381315d58a001fcc69f) [//]: # (START_SECTION b7e435108650eb13d77f95ced6e01fd4d9a3c871) ### merge changes in master into v0.75 (#558) > Commit: [b7e435108650eb13d77f95ced6e01fd4d9a3c871](https://github.com/dOpensource/dsiprouter/commit/b7e435108650eb13d77f95ced6e01fd4d9a3c871) > Date: Thu, 1 Feb 2024 09:50:11 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - * Fixed regression that prevented Python based crontab processes from executing - * Fix Carrier Prefix Regression - Resolves #548 - * Added an additional sed statement to handle the ignoreip statement being commented out - * Added an additional sed statement to the add screen to handle the ignoreip statement being commented out - * Make FusionPBX Regex More Reliable - * Fix FusionPBX Toggle Button - --------- - Co-authored-by: Mack Hendricks - Co-authored-by: root --- [//]: # (END_SECTION b7e435108650eb13d77f95ced6e01fd4d9a3c871) [//]: # (START_SECTION a593cfa496a38bc552e047eba9b61174ea2647e1) ### Fix FusionPBX Toggle Button > Commit: [a593cfa496a38bc552e047eba9b61174ea2647e1](https://github.com/dOpensource/dsiprouter/commit/a593cfa496a38bc552e047eba9b61174ea2647e1) > Date: Wed, 31 Jan 2024 17:43:54 +0000 > Author: root (root@ip-172-31-17-44) > Committer: root (root@ip-172-31-17-44) > Signed: --- [//]: # (END_SECTION a593cfa496a38bc552e047eba9b61174ea2647e1) [//]: # (START_SECTION 0b6e53cda06ab5496ab7e9197c28cc9311d34b87) ### Make FusionPBX Regex More Reliable > Commit: [0b6e53cda06ab5496ab7e9197c28cc9311d34b87](https://github.com/dOpensource/dsiprouter/commit/0b6e53cda06ab5496ab7e9197c28cc9311d34b87) > Date: Wed, 31 Jan 2024 16:00:44 +0000 > Author: root (root@ip-172-31-17-44) > Committer: root (root@ip-172-31-17-44) > Signed: --- [//]: # (END_SECTION 0b6e53cda06ab5496ab7e9197c28cc9311d34b87) [//]: # (START_SECTION 86f0006ee519a0e7a3a5863a7c781b824bfb9b19) ### Added an additional sed statement to the add screen to handle the ignoreip statement being commented out > Commit: [86f0006ee519a0e7a3a5863a7c781b824bfb9b19](https://github.com/dOpensource/dsiprouter/commit/86f0006ee519a0e7a3a5863a7c781b824bfb9b19) > Date: Wed, 31 Jan 2024 15:43:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 86f0006ee519a0e7a3a5863a7c781b824bfb9b19) [//]: # (START_SECTION 8d4b1cc918e6efe4543fe693002c4f31a2d47fb6) ### Added an additional sed statement to handle the ignoreip statement being commented out > Commit: [8d4b1cc918e6efe4543fe693002c4f31a2d47fb6](https://github.com/dOpensource/dsiprouter/commit/8d4b1cc918e6efe4543fe693002c4f31a2d47fb6) > Date: Wed, 31 Jan 2024 15:09:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8d4b1cc918e6efe4543fe693002c4f31a2d47fb6) [//]: # (START_SECTION c529665900eccccc6cb0eeaa76f12e13a95e7d7a) ### Fix Carrier Prefix Regression > Commit: [c529665900eccccc6cb0eeaa76f12e13a95e7d7a](https://github.com/dOpensource/dsiprouter/commit/c529665900eccccc6cb0eeaa76f12e13a95e7d7a) > Date: Thu, 25 Jan 2024 09:16:15 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - Resolves #548 --- [//]: # (END_SECTION c529665900eccccc6cb0eeaa76f12e13a95e7d7a) [//]: # (START_SECTION 0dfe64bd87d43fb425da70b86be312e0f59fa332) ### Fixed regression that prevented Python based crontab processes from executing > Commit: [0dfe64bd87d43fb425da70b86be312e0f59fa332](https://github.com/dOpensource/dsiprouter/commit/0dfe64bd87d43fb425da70b86be312e0f59fa332) > Date: Fri, 26 Jan 2024 13:40:47 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0dfe64bd87d43fb425da70b86be312e0f59fa332) [//]: # (START_SECTION 532f85a25b4611e2b3bc98a1a2358891ed04efcb) ### Fixed regression that prevented Python based crontab processes from executing > Commit: [532f85a25b4611e2b3bc98a1a2358891ed04efcb](https://github.com/dOpensource/dsiprouter/commit/532f85a25b4611e2b3bc98a1a2358891ed04efcb) > Date: Fri, 26 Jan 2024 13:40:47 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 532f85a25b4611e2b3bc98a1a2358891ed04efcb) [//]: # (START_SECTION 1c255d5935685ba5d4ec28e4be3b67da7f993183) ### Fix Carrier Prefix Regression > Commit: [1c255d5935685ba5d4ec28e4be3b67da7f993183](https://github.com/dOpensource/dsiprouter/commit/1c255d5935685ba5d4ec28e4be3b67da7f993183) > Date: Thu, 25 Jan 2024 09:16:15 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #548 --- [//]: # (END_SECTION 1c255d5935685ba5d4ec28e4be3b67da7f993183) [//]: # (START_SECTION 66001fadffe3bb7f231a2f2c9968f190293418f5) ### Hotfix Load Balancing > Commit: [66001fadffe3bb7f231a2f2c9968f190293418f5](https://github.com/dOpensource/dsiprouter/commit/66001fadffe3bb7f231a2f2c9968f190293418f5) > Date: Wed, 24 Jan 2024 09:49:20 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load balancing to work in current drouting limitations --- [//]: # (END_SECTION 66001fadffe3bb7f231a2f2c9968f190293418f5) [//]: # (START_SECTION 39180322dde955544c3ab31b8e0e9689c366ef78) ### Hotfix Gateway Digest Auth > Commit: [39180322dde955544c3ab31b8e0e9689c366ef78](https://github.com/dOpensource/dsiprouter/commit/39180322dde955544c3ab31b8e0e9689c366ef78) > Date: Wed, 24 Jan 2024 09:48:00 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix gateways not updated in epg in clustered environment --- [//]: # (END_SECTION 39180322dde955544c3ab31b8e0e9689c366ef78) [//]: # (START_SECTION 58c7da52f023c6b5f4e6392c602158d1fe8a073c) ### Add Support For v0.74 Upgrade > Commit: [58c7da52f023c6b5f4e6392c602158d1fe8a073c](https://github.com/dOpensource/dsiprouter/commit/58c7da52f023c6b5f4e6392c602158d1fe8a073c) > Date: Fri, 22 Dec 2023 13:47:55 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - - add license check to CLI upgrade command - - add supoprt for v0.73 -> v0.74 upgrade - - fix upgrade git url not defaulting to settings.py --- [//]: # (END_SECTION 58c7da52f023c6b5f4e6392c602158d1fe8a073c) [//]: # (START_SECTION 8eebafba384a4604c3a754a2b9c708eae462b3ea) ### DNSmasq and SElinux Fixes > Commit: [8eebafba384a4604c3a754a2b9c708eae462b3ea](https://github.com/dOpensource/dsiprouter/commit/8eebafba384a4604c3a754a2b9c708eae462b3ea) > Date: Thu, 21 Dec 2023 10:05:49 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - - fix DNSmasq integration with systemdresolved on debian - - fix DNSmasq integration with dhclient on amazon linux - - fix DNSmasq integration with NetworkManager on centos - - fix SElinux support on centos - - fix default route resolution when multiple default routes available - - fix typo in v0.73 upgrade script - - fix centos missing hosts template for cloud-init --- [//]: # (END_SECTION 8eebafba384a4604c3a754a2b9c708eae462b3ea) [//]: # (START_SECTION e856b11068c25bd8a5bfe8ec53629d94aba1c5c7) ### Fixed permissions to allow Nginx to access the the dSIPRouter UI via a UNIX socket > Commit: [e856b11068c25bd8a5bfe8ec53629d94aba1c5c7](https://github.com/dOpensource/dsiprouter/commit/e856b11068c25bd8a5bfe8ec53629d94aba1c5c7) > Date: Mon, 18 Dec 2023 04:09:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e856b11068c25bd8a5bfe8ec53629d94aba1c5c7) [//]: # (START_SECTION c6c4bb239aa4870dd8e24d24e68950ec76ece921) ### OS And Cloud Bug Fixes > Commit: [c6c4bb239aa4870dd8e24d24e68950ec76ece921](https://github.com/dOpensource/dsiprouter/commit/c6c4bb239aa4870dd8e24d24e68950ec76ece921) > Date: Fri, 8 Dec 2023 14:24:42 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - - fix amzn2 issue with kernel headers when installing rtpengine - - fix debian10 dependency issue when installing kamailio - - fix missing cron dependency on AWS debian images - - fix amazn2 libwebsockets compilation failing (bump openssl version) - - fix AWS metadata API changed - - fix testing regressions - - update references to `dsiprouter.sh` to use the binary path instead - - fix update* commands may return bad exit status to systemd - - fix run permissions for services to work with SELinux enabled - - fix nginx systemd service not loading on older versions - - fix selinux DMQ port definition incorrect - - fix license activation regression - - update debian/ubuntu to swap out dns stack for dnsmasq/resolvconf - - refactor dnsmasq install to use separate scripts for each OS --- [//]: # (END_SECTION c6c4bb239aa4870dd8e24d24e68950ec76ece921) [//]: # (START_SECTION 3df4ea7af6b656a233208f36b3b7558e07d7094e) ### Fixed bugs: - The dispatcher options were not being used - Gateways without a weight could not be deleted > Commit: [3df4ea7af6b656a233208f36b3b7558e07d7094e](https://github.com/dOpensource/dsiprouter/commit/3df4ea7af6b656a233208f36b3b7558e07d7094e) > Date: Fri, 17 Nov 2023 02:30:50 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3df4ea7af6b656a233208f36b3b7558e07d7094e) [//]: # (START_SECTION b803e61f8f862aaee163bfdeecd587de319761a5) ### Added support for weighted load balancing for carrier groups. It will be used if at least one weight is specified for a carrier within a carrier group. > Commit: [b803e61f8f862aaee163bfdeecd587de319761a5](https://github.com/dOpensource/dsiprouter/commit/b803e61f8f862aaee163bfdeecd587de319761a5) > Date: Wed, 15 Nov 2023 07:08:19 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b803e61f8f862aaee163bfdeecd587de319761a5) [//]: # (START_SECTION c38834a439f34e16c6a8370a1609f1c501513826) ### Fix Callee BYE Regression > Commit: [c38834a439f34e16c6a8370a1609f1c501513826](https://github.com/dOpensource/dsiprouter/commit/c38834a439f34e16c6a8370a1609f1c501513826) > Date: Fri, 10 Nov 2023 15:38:51 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix callee BYE not routed to caller - fix rtp ports exhausted by updating timeouts --- [//]: # (END_SECTION c38834a439f34e16c6a8370a1609f1c501513826) [//]: # (START_SECTION b1bb9d026fa0a2c5de1a28ef73f2fbeebdd39a58) ### Fix ReadTheDocs > Commit: [b1bb9d026fa0a2c5de1a28ef73f2fbeebdd39a58](https://github.com/dOpensource/dsiprouter/commit/b1bb9d026fa0a2c5de1a28ef73f2fbeebdd39a58) > Date: Wed, 1 Nov 2023 13:22:47 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix missing python dependencies for readthedocs --- [//]: # (END_SECTION b1bb9d026fa0a2c5de1a28ef73f2fbeebdd39a58) [//]: # (START_SECTION 9e71bfb6ef03e5ae7573adabf44578691a1b0402) ### Update ReadTheDocs Config > Commit: [9e71bfb6ef03e5ae7573adabf44578691a1b0402](https://github.com/dOpensource/dsiprouter/commit/9e71bfb6ef03e5ae7573adabf44578691a1b0402) > Date: Wed, 1 Nov 2023 09:16:06 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update rtd config to [v2](https://docs.readthedocs.io/en/stable/config-file/v2.html) --- [//]: # (END_SECTION 9e71bfb6ef03e5ae7573adabf44578691a1b0402) [//]: # (START_SECTION 239a79a38937b5844b47c2351118155642419320) ### Fix CentOS 8/9 LibKS > Commit: [239a79a38937b5844b47c2351118155642419320](https://github.com/dOpensource/dsiprouter/commit/239a79a38937b5844b47c2351118155642419320) > Date: Wed, 1 Nov 2023 01:00:49 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix compiling libks/libstirshaken in centos 8/9 --- [//]: # (END_SECTION 239a79a38937b5844b47c2351118155642419320) [//]: # (START_SECTION fad1ddc79f5567760d323634fe09adb5ba8cf27d) ### Upgrade Fixes > Commit: [fad1ddc79f5567760d323634fe09adb5ba8cf27d](https://github.com/dOpensource/dsiprouter/commit/fad1ddc79f5567760d323634fe09adb5ba8cf27d) > Date: Tue, 31 Oct 2023 21:29:48 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug in bash native credential parsing functions - fix resetting of credentials in v0.73 upgrade scripts - fix bug where multiple scripts could clobber `apt.conf.d` settings - add dpkg lock timeout to allow other processes to release apt lock - fix kamailio config not regenerated on upgrade - add more robust resetting of config files on upgrade failure - fix centos7 /etc/default/kamailio.conf in wrong place - fix upgrade log is not cleared when clicking show previous log - fix typo in `RESTART_DAEMONIZE` variable - fix cursor not a pointer on new reload buttons - fix reload lib functions to use new reload buttons - fix upgrade from gui hanging --- [//]: # (END_SECTION fad1ddc79f5567760d323634fe09adb5ba8cf27d) [//]: # (START_SECTION 5cee36cfb362c6fceb14348494749a93cdb1a2d2) ### Stability Improvements > Commit: [5cee36cfb362c6fceb14348494749a93cdb1a2d2](https://github.com/dOpensource/dsiprouter/commit/5cee36cfb362c6fceb14348494749a93cdb1a2d2) > Date: Sat, 28 Oct 2023 17:01:03 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for debian 12 - deprecate debian 9 support - drop support for debian7-8 and ubuntu 16.04 - update support for centos 8-9 to stable - deprecate centos 7 support - add support for mariadb ver >= 10.6.1 - update openssl native decrpytion/encrption in dsip_lib.sh - update AES CTR implementation to match openssl standard implemenation - improve decrypt function API in security.py - update project to use virtual env for python dependencies - bump kamailio version to 5.7.x for debian10-12/amzn2/centos8-9/ubuntu20-22/rhel8/rocky8/alma8 - bump rtpengine version to 11.5.1.11 for debian10-12/centos8-9/ubuntu20-22/rhel8/rocky8/alma8 - bump openssl version on amzn2 to 1.1.1q - bump python version on amzn2 to 3.9.18 - bump python version on debian10 to 3.9.2 - bump maridb version on debian10 to 10.5.21 - improve compilation times for debian and amazon linux - improve startup times of configured systemd services - decouple nginx and dsiprouter service again (speed improvements) - add exec internal command for calling into main script from systemd - update bug report template - revise DB engine loading as other globals are done - add python version check to install command - allow root DB connection host/port to be set separate from kam DB - fix dnsmasq startup issue on debian12 - add fix for low memory systems failing to compile large libraries - update CLI help message - imporove performance of help/version CLI commands - update dsiprouter manpage - fix bug in settings credentials when DB connection changes - reset default verbosity level in scripts - fix issue with main script project root resolution (when PWD is other git repo) - fix misconfigured RTP fw rules when rtpengine is not installed - fix systemd inhibitor locking error on centos7/amzn2 - add initial support for selinux in centos8-9 - update upgrade script to handle all the above changes --- [//]: # (END_SECTION 5cee36cfb362c6fceb14348494749a93cdb1a2d2) [//]: # (START_SECTION 28bf73e59979e32ba4952f8923db4aee3f25f87c) ### Changed Reload Button to a Split Dropdown Button > Commit: [28bf73e59979e32ba4952f8923db4aee3f25f87c](https://github.com/dOpensource/dsiprouter/commit/28bf73e59979e32ba4952f8923db4aee3f25f87c) > Date: Thu, 19 Oct 2023 22:30:02 +0000 > Author: root (root@demo-dsip-v0.730) > Committer: root (root@demo-dsip-v0.730) > Signed: --- [//]: # (END_SECTION 28bf73e59979e32ba4952f8923db4aee3f25f87c) [//]: # (START_SECTION d6c45d81e09759f98a897d36fe73754c261f9d4a) ### Update Shared Memory Manager > Commit: [d6c45d81e09759f98a897d36fe73754c261f9d4a](https://github.com/dOpensource/dsiprouter/commit/d6c45d81e09759f98a897d36fe73754c261f9d4a) > Date: Mon, 16 Oct 2023 12:38:19 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move state manager to shared memory instead of domain sockets - move license checks to startup, improving load times drastically --- [//]: # (END_SECTION d6c45d81e09759f98a897d36fe73754c261f9d4a) [//]: # (START_SECTION 29d9de5df30bdb2dbe0fb1d1de74c3e654d6920b) ### Make Docs Great Again > Commit: [29d9de5df30bdb2dbe0fb1d1de74c3e654d6920b](https://github.com/dOpensource/dsiprouter/commit/29d9de5df30bdb2dbe0fb1d1de74c3e654d6920b) > Date: Fri, 13 Oct 2023 12:46:16 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update theming on local generated documentation - improve navigation from main TOC - update upgrading insctructions - add more developer documentation --- [//]: # (END_SECTION 29d9de5df30bdb2dbe0fb1d1de74c3e654d6920b) [//]: # (START_SECTION 4b835bbc8546269a75c79fe7b81a6313af2536b1) ### V0.73 Release Feature Improvements > Commit: [4b835bbc8546269a75c79fe7b81a6313af2536b1](https://github.com/dOpensource/dsiprouter/commit/4b835bbc8546269a75c79fe7b81a6313af2536b1) > Date: Wed, 11 Oct 2023 19:54:05 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix annoying "detached head" message from dependent repos on install - fix GUI reload process killed by systemd - add persistent storage to globals - update flask session key to be generated on install - update CLI to allow setting the flask session key - improve import efficiency by removing duplicate paths - create standardized response payload and tooling for API - implement standardized responses on majority of the API routes - add ability to reload GUI/web server from within the GUI - add ability to reload GUI/web server from the API - fix upgrade release search functions - fix edge cases in upgrade merging logic for settings - add an overlay when user is waiting on reloads - improve look and feel of upgrade page - improve performance when showing previous upgrade log - add formatted streaming log feature to upgrade page - remove unused and confusing error handler overrides - update CLI documentation/help/CLI completion - update CHANGELOG --- [//]: # (END_SECTION 4b835bbc8546269a75c79fe7b81a6313af2536b1) [//]: # (START_SECTION 5caff2334b4dbc19e315b98e9e3b5978d1a60a30) ### API Change > Commit: [5caff2334b4dbc19e315b98e9e3b5978d1a60a30](https://github.com/dOpensource/dsiprouter/commit/5caff2334b4dbc19e315b98e9e3b5978d1a60a30) > Date: Mon, 9 Oct 2023 03:18:26 +0000 > Author: root (root@demo-dsip-v0.730) > Committer: root (root@demo-dsip-v0.730) > Signed: - - Moved API to only be available via paid subscription --- [//]: # (END_SECTION 5caff2334b4dbc19e315b98e9e3b5978d1a60a30) [//]: # (START_SECTION 8796289a7fbb2a88a44883baae68259b944de22c) ### Allow Changing Upgrade Repo > Commit: [8796289a7fbb2a88a44883baae68259b944de22c](https://github.com/dOpensource/dsiprouter/commit/8796289a7fbb2a88a44883baae68259b944de22c) > Date: Fri, 6 Oct 2023 17:50:03 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add ability to change upgrade repo --- [//]: # (END_SECTION 8796289a7fbb2a88a44883baae68259b944de22c) [//]: # (START_SECTION 91223e1f6fac5d29ffc064745080839dc6a0892b) ### Fix Upgrade Feature > Commit: [91223e1f6fac5d29ffc064745080839dc6a0892b](https://github.com/dOpensource/dsiprouter/commit/91223e1f6fac5d29ffc064745080839dc6a0892b) > Date: Fri, 6 Oct 2023 16:47:22 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix GUI upgrade permissions - add bootstrap for v0.73 - fix transaction handler could not handle routines in `dsip_lib.sh` - add `-rel` and `-url` options to `upgrade` command - improve update process logic - fix issues with v0.73 migrate script - add sudo dependency --- [//]: # (END_SECTION 91223e1f6fac5d29ffc064745080839dc6a0892b) [//]: # (START_SECTION 89ab6283a42c62d625f9a0cf4d48fe76c8c0049a) ### Fix Load Balancing Feature > Commit: [89ab6283a42c62d625f9a0cf4d48fe76c8c0049a](https://github.com/dOpensource/dsiprouter/commit/89ab6283a42c62d625f9a0cf4d48fe76c8c0049a) > Date: Fri, 6 Oct 2023 11:01:31 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix dispatcher lookups and routing when lb enabled on epgroup - fix missing htable reloads - update timeouts for inbound calls to be more sane - update pike defaults to be more sane - fix servernat / rtpengine detection in serverside NAT scenarios - update record routing checks to work in more NAT scenarios - fix clientside NAT handling for UAs that do not support STUN - update carrier registrations to be more carrier agnostic by default --- [//]: # (END_SECTION 89ab6283a42c62d625f9a0cf4d48fe76c8c0049a) [//]: # (START_SECTION c9596877cfa5d559efe756718bb24370605c5b67) ### HotFix For CLI Upgrade > Commit: [c9596877cfa5d559efe756718bb24370605c5b67](https://github.com/dOpensource/dsiprouter/commit/c9596877cfa5d559efe756718bb24370605c5b67) > Date: Wed, 27 Sep 2023 18:24:28 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add the DB procedure changes to upgrade script --- [//]: # (END_SECTION c9596877cfa5d559efe756718bb24370605c5b67) [//]: # (START_SECTION bed7432a0883791f9cca6913ab03b708f10f0b85) ### Updated README > Commit: [bed7432a0883791f9cca6913ab03b708f10f0b85](https://github.com/dOpensource/dsiprouter/commit/bed7432a0883791f9cca6913ab03b708f10f0b85) > Date: Tue, 26 Sep 2023 07:52:42 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bed7432a0883791f9cca6913ab03b708f10f0b85) [//]: # (START_SECTION 0f94ab7184a1e81feab9d5154226630de54a720c) ### Updated README to make it easier to read > Commit: [0f94ab7184a1e81feab9d5154226630de54a720c](https://github.com/dOpensource/dsiprouter/commit/0f94ab7184a1e81feab9d5154226630de54a720c) > Date: Tue, 26 Sep 2023 07:50:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0f94ab7184a1e81feab9d5154226630de54a720c) [//]: # (START_SECTION e32711d41e34c6e07ef5bfdf48eb3b14cc2f5e4b) ### Hotfix For DB License Storage Mismatch > Commit: [e32711d41e34c6e07ef5bfdf48eb3b14cc2f5e4b](https://github.com/dOpensource/dsiprouter/commit/e32711d41e34c6e07ef5bfdf48eb3b14cc2f5e4b) > Date: Mon, 25 Sep 2023 14:02:47 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix `update_dsip_settings()` procedure input sizes for licenses --- [//]: # (END_SECTION e32711d41e34c6e07ef5bfdf48eb3b14cc2f5e4b) [//]: # (START_SECTION ec3d3696c2c77ef8c2715c32a3cb001422e5ecfa) ### Inbound Route Load Balancing Fix > Commit: [ec3d3696c2c77ef8c2715c32a3cb001422e5ecfa](https://github.com/dOpensource/dsiprouter/commit/ec3d3696c2c77ef8c2715c32a3cb001422e5ecfa) > Date: Mon, 25 Sep 2023 17:11:29 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Added logic to correctly select the dispatcher set, which hands load balancing of requests --- [//]: # (END_SECTION ec3d3696c2c77ef8c2715c32a3cb001422e5ecfa) [//]: # (START_SECTION 9d11a5062fafabc3f39573c583552e6caee2aa51) ### API Updates > Commit: [9d11a5062fafabc3f39573c583552e6caee2aa51](https://github.com/dOpensource/dsiprouter/commit/9d11a5062fafabc3f39573c583552e6caee2aa51) > Date: Mon, 25 Sep 2023 02:23:21 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Configured the EndpointGroup API to send an error with the hostname/ip that's maltformed or not working --- [//]: # (END_SECTION 9d11a5062fafabc3f39573c583552e6caee2aa51) [//]: # (START_SECTION 10f4deb3e10de44df76acb637d7002fb00907231) ### Core Networks Changes > Commit: [10f4deb3e10de44df76acb637d7002fb00907231](https://github.com/dOpensource/dsiprouter/commit/10f4deb3e10de44df76acb637d7002fb00907231) > Date: Fri, 22 Sep 2023 20:28:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Raise an exception with a readable message when the hostname or ip is not routeable --- [//]: # (END_SECTION 10f4deb3e10de44df76acb637d7002fb00907231) [//]: # (START_SECTION 1fa981a268f0a17f1d3ec33ad05739130dbf3315) ### Changes - Added Slack link to the dSIP Dashboard - Fixed issue with the nameserver line in /etc/resolv.conf not being set correctly > Commit: [1fa981a268f0a17f1d3ec33ad05739130dbf3315](https://github.com/dOpensource/dsiprouter/commit/1fa981a268f0a17f1d3ec33ad05739130dbf3315) > Date: Fri, 22 Sep 2023 02:10:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1fa981a268f0a17f1d3ec33ad05739130dbf3315) [//]: # (START_SECTION 5b9cbccd2d522d713434ec0da90e10c9b26b665e) ### NAT Updates - Removed logic that re-wrote the contact address coming from Carriers and SIP Endpoints > Commit: [5b9cbccd2d522d713434ec0da90e10c9b26b665e](https://github.com/dOpensource/dsiprouter/commit/5b9cbccd2d522d713434ec0da90e10c9b26b665e) > Date: Sat, 16 Sep 2023 02:03:52 +0000 > Author: root (root@demo2.dsiprouter.net) > Committer: root (root@demo2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 5b9cbccd2d522d713434ec0da90e10c9b26b665e) [//]: # (START_SECTION 8c5e8907aba41c1b92adbc0f19c6084e0502a08d) ### Change Default UAC Registration Address > Commit: [8c5e8907aba41c1b92adbc0f19c6084e0502a08d](https://github.com/dOpensource/dsiprouter/commit/8c5e8907aba41c1b92adbc0f19c6084e0502a08d) > Date: Thu, 14 Sep 2023 09:41:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make the uac reg addr default to the external IP for dynamic networking modes --- [//]: # (END_SECTION 8c5e8907aba41c1b92adbc0f19c6084e0502a08d) [//]: # (START_SECTION cb9112446174e67633d26ec6b71956060a8531ab) ### Upgrade Scripts Update > Commit: [cb9112446174e67633d26ec6b71956060a8531ab](https://github.com/dOpensource/dsiprouter/commit/cb9112446174e67633d26ec6b71956060a8531ab) > Date: Wed, 6 Sep 2023 11:40:34 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - bump dsiprouter version - reset dsip/kam settings defaults - add upgrade path from v0.7x to v0.73 - fix versioning issue with upgrade scripts - update CHANGELOG --- [//]: # (END_SECTION cb9112446174e67633d26ec6b71956060a8531ab) [//]: # (START_SECTION 8b1ccf2e53f1da403341957b57bbb4c318f64330) ### HotFix For Large Domain Names > Commit: [8b1ccf2e53f1da403341957b57bbb4c318f64330](https://github.com/dOpensource/dsiprouter/commit/8b1ccf2e53f1da403341957b57bbb4c318f64330) > Date: Mon, 28 Aug 2023 21:59:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix large domain names not fitting in DB storage --- [//]: # (END_SECTION 8b1ccf2e53f1da403341957b57bbb4c318f64330) [//]: # (START_SECTION ca9e5da8c80b3f9fb09752ebd8e1d2662a55f59a) ### Cluster Deployment Fixes > Commit: [ca9e5da8c80b3f9fb09752ebd8e1d2662a55f59a](https://github.com/dOpensource/dsiprouter/commit/ca9e5da8c80b3f9fb09752ebd8e1d2662a55f59a) > Date: Mon, 28 Aug 2023 21:58:49 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add check for missing commands in clusterinstall command - add support for choosing ssh key in clusterinstall command - add support for choosing ssh key in pacemaker cluster install - add support for choosing ssh key in galera cluster install - add support for digital ocean virtual IPs in pacemaker cluster install - fix galera cluster install service names wrong on debian 11 - fix local project directory check in clusterinstall command - fix pacemaker cluster install invalid hostname resolution in cluster - fix pacemaker cluster install missing / conflicting hostname - fix pipe checks in shell library functions - fix clusterinstall command eating extra argument - update pacemaker cluster install to support corosync ver >= 0.10 - update pacemaker cluster install to manage dsiprouter service as well - update pacemaker cluster install to use firewalld by default - update pacemaker cluster install to add cluster nodes by internal IP - update galera cluster install to use firewalld by default - update galera cluster install to add cluster nodes by internal IP - update clusterinstall command to use rsync instead of scp - update clusterinstall command to reuse credentials when cluster sync enabled --- [//]: # (END_SECTION ca9e5da8c80b3f9fb09752ebd8e1d2662a55f59a) [//]: # (START_SECTION d663e2342ac65b1d559376fe3e7db6582648efec) ### Hotfix For Dnsmasq > Commit: [d663e2342ac65b1d559376fe3e7db6582648efec](https://github.com/dOpensource/dsiprouter/commit/d663e2342ac65b1d559376fe3e7db6582648efec) > Date: Fri, 18 Aug 2023 12:02:03 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix integration with resolvconf - fix ordering of dsiprouter module install --- [//]: # (END_SECTION d663e2342ac65b1d559376fe3e7db6582648efec) [//]: # (START_SECTION 71f2577294ad7dddf7206fe30a362d29880a124d) ### Hotfix For UAC Registration > Commit: [71f2577294ad7dddf7206fe30a362d29880a124d](https://github.com/dOpensource/dsiprouter/commit/71f2577294ad7dddf7206fe30a362d29880a124d) > Date: Mon, 14 Aug 2023 09:14:52 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix uac registration when external fqdn is non-routable --- [//]: # (END_SECTION 71f2577294ad7dddf7206fe30a362d29880a124d) [//]: # (START_SECTION 768793973e59ce600a909a29d744e5edaee3f934) ### Hotfix For Flood Detection > Commit: [768793973e59ce600a909a29d744e5edaee3f934](https://github.com/dOpensource/dsiprouter/commit/768793973e59ce600a909a29d744e5edaee3f934) > Date: Thu, 10 Aug 2023 15:13:17 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update flood detection to check any source other than self --- [//]: # (END_SECTION 768793973e59ce600a909a29d744e5edaee3f934) [//]: # (START_SECTION 0e7e43b0471528a7dc9175c0939bb93bf9f34c85) ### HotFix For CDR Generation > Commit: [0e7e43b0471528a7dc9175c0939bb93bf9f34c85](https://github.com/dOpensource/dsiprouter/commit/0e7e43b0471528a7dc9175c0939bb93bf9f34c85) > Date: Fri, 4 Aug 2023 07:27:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #514 --- [//]: # (END_SECTION 0e7e43b0471528a7dc9175c0939bb93bf9f34c85) [//]: # (START_SECTION 997f911ecce2f4df036a30bed2e488ca983c3fba) ### HotFix For Settings Update in Cluster Mode > Commit: [997f911ecce2f4df036a30bed2e488ca983c3fba](https://github.com/dOpensource/dsiprouter/commit/997f911ecce2f4df036a30bed2e488ca983c3fba) > Date: Thu, 3 Aug 2023 13:02:30 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed (temp) dsiprouter settings not synced in cluster mode --- [//]: # (END_SECTION 997f911ecce2f4df036a30bed2e488ca983c3fba) [//]: # (START_SECTION e1f402ac7faf5604be924bc693cd389fd4ebd3fb) ### Kamailio Config HotFixes > Commit: [e1f402ac7faf5604be924bc693cd389fd4ebd3fb](https://github.com/dOpensource/dsiprouter/commit/e1f402ac7faf5604be924bc693cd389fd4ebd3fb) > Date: Wed, 2 Aug 2023 08:58:45 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix sdp only updated on INVITE - fix IP Address ban not refreshed on next request from banned IP --- [//]: # (END_SECTION e1f402ac7faf5604be924bc693cd389fd4ebd3fb) [//]: # (START_SECTION 6d2aa297487b38829ba832aec10ddc1e3b3d7f10) ### Cluster Mode HotFixes > Commit: [6d2aa297487b38829ba832aec10ddc1e3b3d7f10](https://github.com/dOpensource/dsiprouter/commit/6d2aa297487b38829ba832aec10ddc1e3b3d7f10) > Date: Wed, 2 Aug 2023 08:56:00 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix charset mismatches with mysql enterprise - fix DSIP_UNIX_SOCK and DSIP_IPC_SOCK swapped on DB update in cluster mode - fix DB update broken in cluster mode from sqlalchemy version update --- [//]: # (END_SECTION 6d2aa297487b38829ba832aec10ddc1e3b3d7f10) [//]: # (START_SECTION 8adead794dcb8fcea85b07819c2738ada08771ae) ### Update use-cases.rst > Commit: [8adead794dcb8fcea85b07819c2738ada08771ae](https://github.com/dOpensource/dsiprouter/commit/8adead794dcb8fcea85b07819c2738ada08771ae) > Date: Thu, 20 Jul 2023 17:04:13 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8adead794dcb8fcea85b07819c2738ada08771ae) [//]: # (START_SECTION 55ac4519606a16f54c383c6de10d34725261c8a7) ### Update 22.sh > Commit: [55ac4519606a16f54c383c6de10d34725261c8a7](https://github.com/dOpensource/dsiprouter/commit/55ac4519606a16f54c383c6de10d34725261c8a7) > Date: Wed, 19 Jul 2023 13:52:13 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 55ac4519606a16f54c383c6de10d34725261c8a7) [//]: # (START_SECTION d4a6a915bc7a4ef12c14899214df01a840c4fe01) ### Update 20.sh > Commit: [d4a6a915bc7a4ef12c14899214df01a840c4fe01](https://github.com/dOpensource/dsiprouter/commit/d4a6a915bc7a4ef12c14899214df01a840c4fe01) > Date: Wed, 19 Jul 2023 13:51:08 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d4a6a915bc7a4ef12c14899214df01a840c4fe01) [//]: # (START_SECTION 51eb9e2614dae7f49dca658e10c268433cf19702) ### Update 10.sh > Commit: [51eb9e2614dae7f49dca658e10c268433cf19702](https://github.com/dOpensource/dsiprouter/commit/51eb9e2614dae7f49dca658e10c268433cf19702) > Date: Wed, 19 Jul 2023 13:49:52 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 51eb9e2614dae7f49dca658e10c268433cf19702) [//]: # (START_SECTION 350e777e101b3e0b70134f8571a598348ede7d44) ### Fixed issue with StirShaken not able to be disbaled > Commit: [350e777e101b3e0b70134f8571a598348ede7d44](https://github.com/dOpensource/dsiprouter/commit/350e777e101b3e0b70134f8571a598348ede7d44) > Date: Tue, 18 Jul 2023 21:26:55 +0000 > Author: root (root@mack-dsip-v0.7210) > Committer: root (root@mack-dsip-v0.7210) > Signed: --- [//]: # (END_SECTION 350e777e101b3e0b70134f8571a598348ede7d44) [//]: # (START_SECTION 904879953e2217394b364f7464101dafcdce5e98) ### Update 11.sh > Commit: [904879953e2217394b364f7464101dafcdce5e98](https://github.com/dOpensource/dsiprouter/commit/904879953e2217394b364f7464101dafcdce5e98) > Date: Tue, 18 Jul 2023 06:54:50 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 904879953e2217394b364f7464101dafcdce5e98) [//]: # (START_SECTION 94f1469f2ed8b7412f8bf2f8b4d6e644415ffefd) ### Fixed bug with Transnexus UI not able to be enabled > Commit: [94f1469f2ed8b7412f8bf2f8b4d6e644415ffefd](https://github.com/dOpensource/dsiprouter/commit/94f1469f2ed8b7412f8bf2f8b4d6e644415ffefd) > Date: Mon, 10 Jul 2023 00:33:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 94f1469f2ed8b7412f8bf2f8b4d6e644415ffefd) [//]: # (START_SECTION b69eb35a5b634101080a43c52ab323fd1ac3faa0) ### Fix for the gateway duplication > Commit: [b69eb35a5b634101080a43c52ab323fd1ac3faa0](https://github.com/dOpensource/dsiprouter/commit/b69eb35a5b634101080a43c52ab323fd1ac3faa0) > Date: Fri, 9 Jun 2023 08:12:01 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION b69eb35a5b634101080a43c52ab323fd1ac3faa0) [//]: # (START_SECTION e4e43e45f6d1c02c0ffa91a77719a2436dab66d5) ### Critical Fix - Fixed MSTeams Endpoint Group so that it used 5061 > Commit: [e4e43e45f6d1c02c0ffa91a77719a2436dab66d5](https://github.com/dOpensource/dsiprouter/commit/e4e43e45f6d1c02c0ffa91a77719a2436dab66d5) > Date: Mon, 15 May 2023 10:56:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e4e43e45f6d1c02c0ffa91a77719a2436dab66d5) [//]: # (START_SECTION 5f3fc87b08437f11bcd55d92618f5cc8129c8931) ### Critical Fixes - Deleting an Endpoint in CarrierGroup Causes dSIPRouter to become unavailable, Fixed #511 - Endpoint Group Weights are not being retrieved properly, Fixed #512 > Commit: [5f3fc87b08437f11bcd55d92618f5cc8129c8931](https://github.com/dOpensource/dsiprouter/commit/5f3fc87b08437f11bcd55d92618f5cc8129c8931) > Date: Sat, 13 May 2023 00:24:36 +0000 > Author: root (root@sbc4-dsip-v0.7210) > Committer: root (root@sbc4-dsip-v0.7210) > Signed: --- [//]: # (END_SECTION 5f3fc87b08437f11bcd55d92618f5cc8129c8931) [//]: # (START_SECTION 8e0fae1a415b8e13427bf66489fd33f362054785) ### UI Updated pack ported > Commit: [8e0fae1a415b8e13427bf66489fd33f362054785](https://github.com/dOpensource/dsiprouter/commit/8e0fae1a415b8e13427bf66489fd33f362054785) > Date: Mon, 1 May 2023 18:34:35 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 8e0fae1a415b8e13427bf66489fd33f362054785) [//]: # (START_SECTION e188ee0611c594e582f7b3c18adb2c65ce2fe3a9) ### Hotfixes for v0.72 > Commit: [e188ee0611c594e582f7b3c18adb2c65ce2fe3a9](https://github.com/dOpensource/dsiprouter/commit/e188ee0611c594e582f7b3c18adb2c65ce2fe3a9) > Date: Thu, 27 Apr 2023 13:18:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - pin Flask version to 2.2.x - fix tagging format - update upgrade documentation - add support for migrating trnasnexus licenses - various bug fixes in migration script - fix certbot overwrite on reinstall - # Please enter the commit message for your changes. Lines starting - # with '#' will be ignored, and an empty message aborts the commit. - # - # Date: Thu Apr 27 13:03:19 2023 -0400 - # - # On branch v0.721 - # Your branch is ahead of 'origin/v0.721' by 1 commit. - # (use "git push" to publish your local commits) - # - # Changes to be committed: - # modified: docs/source/user/upgrade.rst - # modified: dsiprouter.sh - # modified: gui/dsiprouter.py - # modified: gui/modules/api/licensemanager/routes.py - # modified: gui/requirements.txt - # modified: gui/settings.py - # modified: kamailio/almalinux/8.sh - # modified: kamailio/amzn/2.sh - # modified: kamailio/configs/kamailio.cfg - # modified: kamailio/debian/10.sh - # modified: kamailio/debian/11.sh - # modified: kamailio/debian/9.sh - # modified: kamailio/rhel/8.sh - # modified: kamailio/rocky/8.sh - # modified: kamailio/ubuntu/20.sh - # modified: kamailio/ubuntu/22.sh - # modified: resources/git/hooks/pre-commit - # modified: resources/upgrade/v0.721/scripts/bootstrap.sh - # modified: resources/upgrade/v0.721/scripts/migrate.sh - # --- [//]: # (END_SECTION e188ee0611c594e582f7b3c18adb2c65ce2fe3a9) [//]: # (START_SECTION 5d5c37dd97e4337a71782b42878114c1ba391dd7) ### Update upgrade.rst > Commit: [5d5c37dd97e4337a71782b42878114c1ba391dd7](https://github.com/dOpensource/dsiprouter/commit/5d5c37dd97e4337a71782b42878114c1ba391dd7) > Date: Wed, 26 Apr 2023 02:01:57 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Added the steps to migrate from 0.70 to 0.721 --- [//]: # (END_SECTION 5d5c37dd97e4337a71782b42878114c1ba391dd7) [//]: # (START_SECTION 49c5034d79504c2558b57d751ad1bc63006ad2d2) ### Fixed issue with upgrade script > Commit: [49c5034d79504c2558b57d751ad1bc63006ad2d2](https://github.com/dOpensource/dsiprouter/commit/49c5034d79504c2558b57d751ad1bc63006ad2d2) > Date: Wed, 26 Apr 2023 05:27:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 49c5034d79504c2558b57d751ad1bc63006ad2d2) [//]: # (START_SECTION fc1fc199a1859d5f09454010c6e5d9e91d6ad68c) ### Updated migration scripts to handle upgrading the shared memory > Commit: [fc1fc199a1859d5f09454010c6e5d9e91d6ad68c](https://github.com/dOpensource/dsiprouter/commit/fc1fc199a1859d5f09454010c6e5d9e91d6ad68c) > Date: Wed, 26 Apr 2023 03:40:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fc1fc199a1859d5f09454010c6e5d9e91d6ad68c) [//]: # (START_SECTION 84c412b8cd2e945e630120f567402f311defd46e) ### Added Upgrade to v0.721 logic > Commit: [84c412b8cd2e945e630120f567402f311defd46e](https://github.com/dOpensource/dsiprouter/commit/84c412b8cd2e945e630120f567402f311defd46e) > Date: Wed, 26 Apr 2023 02:36:22 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 84c412b8cd2e945e630120f567402f311defd46e) [//]: # (START_SECTION 473fb813f9d5ccd80270d63d67a02d31a95ce124) ### Bug Fixes > Commit: [473fb813f9d5ccd80270d63d67a02d31a95ce124](https://github.com/dOpensource/dsiprouter/commit/473fb813f9d5ccd80270d63d67a02d31a95ce124) > Date: Tue, 25 Apr 2023 14:33:57 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #504 - fix credential updates broken - fix DNS resolution for local DNS providers - update login info display to include domain - update gitignore to exclude generated files in repo dir --- [//]: # (END_SECTION 473fb813f9d5ccd80270d63d67a02d31a95ce124) [//]: # (START_SECTION b51c7a8db53f623a2dff1f84ac004da9de06b4d8) ### MS Teams Address Table Fix > Commit: [b51c7a8db53f623a2dff1f84ac004da9de06b4d8](https://github.com/dOpensource/dsiprouter/commit/b51c7a8db53f623a2dff1f84ac004da9de06b4d8) > Date: Tue, 25 Apr 2023 12:29:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix address entries for default ms teams endpoints --- [//]: # (END_SECTION b51c7a8db53f623a2dff1f84ac004da9de06b4d8) [//]: # (START_SECTION e2a6e5fc12e9ce7015a7e73c4a93627a24fa93ce) ### Update dsip_settings.sql > Commit: [e2a6e5fc12e9ce7015a7e73c4a93627a24fa93ce](https://github.com/dOpensource/dsiprouter/commit/e2a6e5fc12e9ce7015a7e73c4a93627a24fa93ce) > Date: Fri, 21 Apr 2023 13:14:52 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Changed HOMER_ID type from INT to BIGINT --- [//]: # (END_SECTION e2a6e5fc12e9ce7015a7e73c4a93627a24fa93ce) [//]: # (START_SECTION 709d07e8f693bd7fb2899735f3ac0a37024f211b) ### MSTeams Fix > Commit: [709d07e8f693bd7fb2899735f3ac0a37024f211b](https://github.com/dOpensource/dsiprouter/commit/709d07e8f693bd7fb2899735f3ac0a37024f211b) > Date: Fri, 21 Apr 2023 11:36:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Fixed the UI so that domains of type MSTeams shows up correctly - - Updated the Kamailio configurtion to use 3 as the MSTeams domain type - - Changed the MSTEAMS Endpoints back to using TLS and port 5061 --- [//]: # (END_SECTION 709d07e8f693bd7fb2899735f3ac0a37024f211b) [//]: # (START_SECTION 035438eb210cc122a233eb1c1543946f0bb890f6) ### Credentials Update Hotfix > Commit: [035438eb210cc122a233eb1c1543946f0bb890f6](https://github.com/dOpensource/dsiprouter/commit/035438eb210cc122a233eb1c1543946f0bb890f6) > Date: Wed, 19 Apr 2023 11:08:04 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix formatting issue with new version of `DSIP_ID` in `setCredentials()` --- [//]: # (END_SECTION 035438eb210cc122a233eb1c1543946f0bb890f6) [//]: # (START_SECTION 242f6999566658e2cc385639da0fd5d169bd1bac) ### Better Coverage For DB Update HotFix > Commit: [242f6999566658e2cc385639da0fd5d169bd1bac](https://github.com/dOpensource/dsiprouter/commit/242f6999566658e2cc385639da0fd5d169bd1bac) > Date: Tue, 18 Apr 2023 16:15:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix extra test cases found in 816f78bd4adc634bd31ff95086f27813b78dcc41 --- [//]: # (END_SECTION 242f6999566658e2cc385639da0fd5d169bd1bac) [//]: # (START_SECTION 626da8c3d4947651afcbb7061e6cf07e2bd3739c) ### Domain Pass-thru Fixes - Fixed the PBX_TO_ENDPOINT_LOOKUP to lookup the location of an endpoint only if the request is coming from a PBX and the IP Address is private > Commit: [626da8c3d4947651afcbb7061e6cf07e2bd3739c](https://github.com/dOpensource/dsiprouter/commit/626da8c3d4947651afcbb7061e6cf07e2bd3739c) > Date: Tue, 18 Apr 2023 19:17:58 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 626da8c3d4947651afcbb7061e6cf07e2bd3739c) [//]: # (START_SECTION 816f78bd4adc634bd31ff95086f27813b78dcc41) ### DB Sync and Certbot Hotfix > Commit: [816f78bd4adc634bd31ff95086f27813b78dcc41](https://github.com/dOpensource/dsiprouter/commit/816f78bd4adc634bd31ff95086f27813b78dcc41) > Date: Tue, 18 Apr 2023 15:03:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix issue where `dsip_settings` table was not updating properly - fix missing python package for updated certbot version --- [//]: # (END_SECTION 816f78bd4adc634bd31ff95086f27813b78dcc41) [//]: # (START_SECTION 2a54a13ce90f9ec2b0907631e9995457d7f778ef) ### Update Default Settings > Commit: [2a54a13ce90f9ec2b0907631e9995457d7f778ef](https://github.com/dOpensource/dsiprouter/commit/2a54a13ce90f9ec2b0907631e9995457d7f778ef) > Date: Mon, 17 Apr 2023 09:41:32 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - reset default dsiprouter and kamailio settings - update dsiprouter version number --- [//]: # (END_SECTION 2a54a13ce90f9ec2b0907631e9995457d7f778ef) [//]: # (START_SECTION 3209ac195e1b47e42811a44ce5affb4764d4072b) ### Patch Dependencies and Backup Feature > Commit: [3209ac195e1b47e42811a44ce5affb4764d4072b](https://github.com/dOpensource/dsiprouter/commit/3209ac195e1b47e42811a44ce5affb4764d4072b) > Date: Mon, 17 Apr 2023 09:22:01 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #503 - fix `josepy` and `acme` dependencies after certbot version update - fix `send_file` function calls after Flask version update --- [//]: # (END_SECTION 3209ac195e1b47e42811a44ce5affb4764d4072b) [//]: # (START_SECTION 6758cb002b0bbc88e1e8c736f4a770fb190accc7) ### Cert / MS Teams HotFix > Commit: [6758cb002b0bbc88e1e8c736f4a770fb190accc7](https://github.com/dOpensource/dsiprouter/commit/6758cb002b0bbc88e1e8c736f4a770fb190accc7) > Date: Fri, 14 Apr 2023 16:14:53 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix DST_Root_CA_X3 expired in lets encrypt chain - update certbot install version - fix uploading certs only parses the first cert in the chain - fix MS Teams inbound routing - fix MS Teams outbound routing via IP address - add option to route MS Teams domain via FQDN - fix MS Teams default address settings typo - fix kamailio TLS reload bug - update default memory allocatoin for kamailio - fix kamailio does not update TLS cert when reloading --- [//]: # (END_SECTION 6758cb002b0bbc88e1e8c736f4a770fb190accc7) [//]: # (START_SECTION d8c889ecc66770e3d388c91860bad31a68fa0803) ### handle a bug where multiple addresses are returned from the db. > Commit: [d8c889ecc66770e3d388c91860bad31a68fa0803](https://github.com/dOpensource/dsiprouter/commit/d8c889ecc66770e3d388c91860bad31a68fa0803) > Date: Thu, 13 Apr 2023 08:50:04 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION d8c889ecc66770e3d388c91860bad31a68fa0803) [//]: # (START_SECTION 25b049f577776529d1e6eb1b651c895eeb8363e2) ### MSTeams fix - The load balancing attribute will not be set when creating an endpoint group of type Microsoft Teams > Commit: [25b049f577776529d1e6eb1b651c895eeb8363e2](https://github.com/dOpensource/dsiprouter/commit/25b049f577776529d1e6eb1b651c895eeb8363e2) > Date: Thu, 13 Apr 2023 14:04:41 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 25b049f577776529d1e6eb1b651c895eeb8363e2) [//]: # (START_SECTION eeb1eaad119a1ee5809f88b9af203956fc7f02eb) ### MSTeams fix - A dispatcher load balancing gorup will not be created when a MSTeams domain is created > Commit: [eeb1eaad119a1ee5809f88b9af203956fc7f02eb](https://github.com/dOpensource/dsiprouter/commit/eeb1eaad119a1ee5809f88b9af203956fc7f02eb) > Date: Thu, 13 Apr 2023 13:26:45 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eeb1eaad119a1ee5809f88b9af203956fc7f02eb) [//]: # (START_SECTION 86cb8df0f93f1a6cd07f560396de031e4a7abe08) ### Licensing Patch > Commit: [86cb8df0f93f1a6cd07f560396de031e4a7abe08](https://github.com/dOpensource/dsiprouter/commit/86cb8df0f93f1a6cd07f560396de031e4a7abe08) > Date: Fri, 7 Apr 2023 14:54:27 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for variable length license keys - fix error handling in license manager - update error message for license check failures - fix `stop` command scope issue in CLI --- [//]: # (END_SECTION 86cb8df0f93f1a6cd07f560396de031e4a7abe08) [//]: # (START_SECTION 91d62644a07fc9b3693fffbb3ccfdcbddb4db794) ### Added a fix to select the Correct load balance ID in the UI of the inbound route page > Commit: [91d62644a07fc9b3693fffbb3ccfdcbddb4db794](https://github.com/dOpensource/dsiprouter/commit/91d62644a07fc9b3693fffbb3ccfdcbddb4db794) > Date: Tue, 4 Apr 2023 07:06:23 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 91d62644a07fc9b3693fffbb3ccfdcbddb4db794) [//]: # (START_SECTION 7ccb9c615e62c349a67e2509eed9741d5d064cae) ### Update Documentation for v0.72 > Commit: [7ccb9c615e62c349a67e2509eed9741d5d064cae](https://github.com/dOpensource/dsiprouter/commit/7ccb9c615e62c349a67e2509eed9741d5d064cae) > Date: Mon, 3 Apr 2023 15:52:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update boostrap command - update changelog --- [//]: # (END_SECTION 7ccb9c615e62c349a67e2509eed9741d5d064cae) [//]: # (START_SECTION 47c73b18c4d1eed30af136cdcb36f44fa8f0229f) ### Update Documentation for v0.72 > Commit: [47c73b18c4d1eed30af136cdcb36f44fa8f0229f](https://github.com/dOpensource/dsiprouter/commit/47c73b18c4d1eed30af136cdcb36f44fa8f0229f) > Date: Mon, 3 Apr 2023 15:52:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update boostrap command - update changelog --- [//]: # (END_SECTION 47c73b18c4d1eed30af136cdcb36f44fa8f0229f) [//]: # (START_SECTION 28309c1a2d37d8ec3b3e3bfdbce1afda1c33ca28) ### v0.72 Release Bug Fix > Commit: [28309c1a2d37d8ec3b3e3bfdbce1afda1c33ca28](https://github.com/dOpensource/dsiprouter/commit/28309c1a2d37d8ec3b3e3bfdbce1afda1c33ca28) > Date: Mon, 3 Apr 2023 14:09:23 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bad hash algorithm implementation in the CLI functions - fix schema migration issues on `dsip_settings` table - fix edge cases where bootstrapping failed - # Please enter the commit message for your changes. Lines starting - # with '#' will be ignored, and an empty message aborts the commit. - # - # On branch v0.72 - # Your branch is up to date with 'origin/v0.72'. - # - # Changes to be committed: - # modified: dsiprouter/dsip_lib.sh - # modified: resources/upgrade/v0.72/scripts/bootstrap.sh - # modified: resources/upgrade/v0.72/scripts/migrate.sh - # --- [//]: # (END_SECTION 28309c1a2d37d8ec3b3e3bfdbce1afda1c33ca28) [//]: # (START_SECTION e0bbbfa860e57f9549736149142c9df8be72433b) ### Bootstrap Fixes > Commit: [e0bbbfa860e57f9549736149142c9df8be72433b](https://github.com/dOpensource/dsiprouter/commit/e0bbbfa860e57f9549736149142c9df8be72433b) > Date: Mon, 3 Apr 2023 10:32:08 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change bootstrap to use tag instead of branch - fix python versioning - fix boostrap variable scoping - # Please enter the commit message for your changes. Lines starting - # with '#' will be ignored, and an empty message aborts the commit. - # - # Date: Mon Apr 3 10:32:08 2023 -0400 - # - # On branch v0.72 - # Your branch is up to date with 'origin/v0.72'. - # - # Changes to be committed: - # modified: dsiprouter.sh - # modified: resources/upgrade/v0.72/scripts/bootstrap.sh - # modified: resources/upgrade/v0.72/scripts/migrate.sh - # --- [//]: # (END_SECTION e0bbbfa860e57f9549736149142c9df8be72433b) [//]: # (START_SECTION a659fedb44ef2a844282d3de0d5503c567837948) ### Merge Changes From Master (#500) > Commit: [a659fedb44ef2a844282d3de0d5503c567837948](https://github.com/dOpensource/dsiprouter/commit/a659fedb44ef2a844282d3de0d5503c567837948) > Date: Mon, 3 Apr 2023 10:10:11 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - Co-authored-by: Mack Hendricks --- [//]: # (END_SECTION a659fedb44ef2a844282d3de0d5503c567837948) [//]: # (START_SECTION baa6415f1d630b695cd28de742ed609eb65f1399) ### Update The Changelog > Commit: [baa6415f1d630b695cd28de742ed609eb65f1399](https://github.com/dOpensource/dsiprouter/commit/baa6415f1d630b695cd28de742ed609eb65f1399) > Date: Mon, 3 Apr 2023 09:12:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update `CHANGELOG.md` --- [//]: # (END_SECTION baa6415f1d630b695cd28de742ed609eb65f1399) [//]: # (START_SECTION bc560f0fd419cbe271e22c0801e64e92a9469397) ### Fixed permissions again > Commit: [bc560f0fd419cbe271e22c0801e64e92a9469397](https://github.com/dOpensource/dsiprouter/commit/bc560f0fd419cbe271e22c0801e64e92a9469397) > Date: Sat, 1 Apr 2023 21:09:50 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bc560f0fd419cbe271e22c0801e64e92a9469397) [//]: # (START_SECTION 0209c81fa9302517ac674725cefc1f4799d70d28) ### Added execute permissions > Commit: [0209c81fa9302517ac674725cefc1f4799d70d28](https://github.com/dOpensource/dsiprouter/commit/0209c81fa9302517ac674725cefc1f4799d70d28) > Date: Sat, 1 Apr 2023 20:29:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0209c81fa9302517ac674725cefc1f4799d70d28) [//]: # (START_SECTION c850b78988eed29591fcacbc01d4c26f2f49c22a) ### Fixed permissions > Commit: [c850b78988eed29591fcacbc01d4c26f2f49c22a](https://github.com/dOpensource/dsiprouter/commit/c850b78988eed29591fcacbc01d4c26f2f49c22a) > Date: Sat, 1 Apr 2023 20:25:34 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c850b78988eed29591fcacbc01d4c26f2f49c22a) [//]: # (START_SECTION 36297d60599a1f214e8d183f978d2688236b4479) ### Stir Shaken Fixes - Fixed an issue with the certificate URL not being saved - Added logic to copy the self-signed certs to the proper location > Commit: [36297d60599a1f214e8d183f978d2688236b4479](https://github.com/dOpensource/dsiprouter/commit/36297d60599a1f214e8d183f978d2688236b4479) > Date: Sat, 1 Apr 2023 20:12:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 36297d60599a1f214e8d183f978d2688236b4479) [//]: # (START_SECTION fbeeb172c70957d7397f66a612811e75fd31dd6c) ### reverting change > Commit: [fbeeb172c70957d7397f66a612811e75fd31dd6c](https://github.com/dOpensource/dsiprouter/commit/fbeeb172c70957d7397f66a612811e75fd31dd6c) > Date: Fri, 31 Mar 2023 14:42:14 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION fbeeb172c70957d7397f66a612811e75fd31dd6c) [//]: # (START_SECTION 2ec6da12a319bb378df586332c56261484ae1ace) ### fixed a bug that caused password encoding issues > Commit: [2ec6da12a319bb378df586332c56261484ae1ace](https://github.com/dOpensource/dsiprouter/commit/2ec6da12a319bb378df586332c56261484ae1ace) > Date: Fri, 31 Mar 2023 14:21:37 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 2ec6da12a319bb378df586332c56261484ae1ace) [//]: # (START_SECTION d2b1e55b3a9ea5743f68389a1ef864b4c06642a5) ### Update migrate.sh > Commit: [d2b1e55b3a9ea5743f68389a1ef864b4c06642a5](https://github.com/dOpensource/dsiprouter/commit/d2b1e55b3a9ea5743f68389a1ef864b4c06642a5) > Date: Fri, 31 Mar 2023 15:38:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d2b1e55b3a9ea5743f68389a1ef864b4c06642a5) [//]: # (START_SECTION 4e717ae72b7d49bd35dbb9d6bef4b37b3fdb66ae) ### Update migrate.sh > Commit: [4e717ae72b7d49bd35dbb9d6bef4b37b3fdb66ae](https://github.com/dOpensource/dsiprouter/commit/4e717ae72b7d49bd35dbb9d6bef4b37b3fdb66ae) > Date: Fri, 31 Mar 2023 15:15:23 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4e717ae72b7d49bd35dbb9d6bef4b37b3fdb66ae) [//]: # (START_SECTION 1c939ac8a7fc954a21354672fab2cb0e8f877ce4) ### Update upgrade.rst > Commit: [1c939ac8a7fc954a21354672fab2cb0e8f877ce4](https://github.com/dOpensource/dsiprouter/commit/1c939ac8a7fc954a21354672fab2cb0e8f877ce4) > Date: Fri, 31 Mar 2023 12:18:01 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c939ac8a7fc954a21354672fab2cb0e8f877ce4) [//]: # (START_SECTION 8351d90c77289f126aad66c145eef2dc753591e9) ### Update upgrade.rst > Commit: [8351d90c77289f126aad66c145eef2dc753591e9](https://github.com/dOpensource/dsiprouter/commit/8351d90c77289f126aad66c145eef2dc753591e9) > Date: Fri, 31 Mar 2023 12:12:01 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8351d90c77289f126aad66c145eef2dc753591e9) [//]: # (START_SECTION cbea100db0f75e72d39ae6eeeaaf6164221cdc4e) ### Update upgrade.rst > Commit: [cbea100db0f75e72d39ae6eeeaaf6164221cdc4e](https://github.com/dOpensource/dsiprouter/commit/cbea100db0f75e72d39ae6eeeaaf6164221cdc4e) > Date: Fri, 31 Mar 2023 12:09:29 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cbea100db0f75e72d39ae6eeeaaf6164221cdc4e) [//]: # (START_SECTION 7b883cf29bc9ae40ad5cc09a58f9830a4d7a8942) ### Update upgrade.rst > Commit: [7b883cf29bc9ae40ad5cc09a58f9830a4d7a8942](https://github.com/dOpensource/dsiprouter/commit/7b883cf29bc9ae40ad5cc09a58f9830a4d7a8942) > Date: Fri, 31 Mar 2023 12:05:36 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7b883cf29bc9ae40ad5cc09a58f9830a4d7a8942) [//]: # (START_SECTION 5674c7f6191b002b43d9f2e89736b26c890e1ea7) ### Update upgrade.rst > Commit: [5674c7f6191b002b43d9f2e89736b26c890e1ea7](https://github.com/dOpensource/dsiprouter/commit/5674c7f6191b002b43d9f2e89736b26c890e1ea7) > Date: Fri, 31 Mar 2023 12:03:58 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5674c7f6191b002b43d9f2e89736b26c890e1ea7) [//]: # (START_SECTION 852f7412f7ad9c0502eaab76579a6e0f71f51f60) ### Update upgrade.rst > Commit: [852f7412f7ad9c0502eaab76579a6e0f71f51f60](https://github.com/dOpensource/dsiprouter/commit/852f7412f7ad9c0502eaab76579a6e0f71f51f60) > Date: Fri, 31 Mar 2023 12:02:52 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 852f7412f7ad9c0502eaab76579a6e0f71f51f60) [//]: # (START_SECTION 80ae06ac5d83a3d219cbcca7432cae4de6403fc5) ### Add files via upload > Commit: [80ae06ac5d83a3d219cbcca7432cae4de6403fc5](https://github.com/dOpensource/dsiprouter/commit/80ae06ac5d83a3d219cbcca7432cae4de6403fc5) > Date: Fri, 31 Mar 2023 07:57:31 -0700 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 80ae06ac5d83a3d219cbcca7432cae4de6403fc5) [//]: # (START_SECTION 669a4e115aad6efc4027cf9bb989e0fc2ea52729) ### Upgrade Feature CLI Completion > Commit: [669a4e115aad6efc4027cf9bb989e0fc2ea52729](https://github.com/dOpensource/dsiprouter/commit/669a4e115aad6efc4027cf9bb989e0fc2ea52729) > Date: Fri, 31 Mar 2023 10:31:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 669a4e115aad6efc4027cf9bb989e0fc2ea52729) [//]: # (START_SECTION 898f6dc5d884c92a049e06bbf4b3aac1f2a01f01) ### WIP Upgrade Feature > Commit: [898f6dc5d884c92a049e06bbf4b3aac1f2a01f01](https://github.com/dOpensource/dsiprouter/commit/898f6dc5d884c92a049e06bbf4b3aac1f2a01f01) > Date: Fri, 31 Mar 2023 09:05:55 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 898f6dc5d884c92a049e06bbf4b3aac1f2a01f01) [//]: # (START_SECTION 5f7bec3e9b4802429701f3cdbc159ac379370f48) ### Update api.rst > Commit: [5f7bec3e9b4802429701f3cdbc159ac379370f48](https://github.com/dOpensource/dsiprouter/commit/5f7bec3e9b4802429701f3cdbc159ac379370f48) > Date: Thu, 30 Mar 2023 23:58:55 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5f7bec3e9b4802429701f3cdbc159ac379370f48) [//]: # (START_SECTION dda4c22b944c3a1972fff24cd112a377b92f8d64) ### Update api.rst > Commit: [dda4c22b944c3a1972fff24cd112a377b92f8d64](https://github.com/dOpensource/dsiprouter/commit/dda4c22b944c3a1972fff24cd112a377b92f8d64) > Date: Thu, 30 Mar 2023 23:57:44 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dda4c22b944c3a1972fff24cd112a377b92f8d64) [//]: # (START_SECTION 9ee06fb3c6c2f1eed7b2db7bcd750ed152bc8895) ### Update api.rst > Commit: [9ee06fb3c6c2f1eed7b2db7bcd750ed152bc8895](https://github.com/dOpensource/dsiprouter/commit/9ee06fb3c6c2f1eed7b2db7bcd750ed152bc8895) > Date: Thu, 30 Mar 2023 23:56:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9ee06fb3c6c2f1eed7b2db7bcd750ed152bc8895) [//]: # (START_SECTION 5522370d4d6f461c03c1caf31d6c7bebb90d9712) ### Update api.rst > Commit: [5522370d4d6f461c03c1caf31d6c7bebb90d9712](https://github.com/dOpensource/dsiprouter/commit/5522370d4d6f461c03c1caf31d6c7bebb90d9712) > Date: Thu, 30 Mar 2023 23:55:22 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5522370d4d6f461c03c1caf31d6c7bebb90d9712) [//]: # (START_SECTION 98e5622375544c0eb7ae3f8233dc676f01de80e2) ### Update Bootstrap Process > Commit: [98e5622375544c0eb7ae3f8233dc676f01de80e2](https://github.com/dOpensource/dsiprouter/commit/98e5622375544c0eb7ae3f8233dc676f01de80e2) > Date: Thu, 30 Mar 2023 23:45:48 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 98e5622375544c0eb7ae3f8233dc676f01de80e2) [//]: # (START_SECTION d43dd36b6fcd625fcf80b9ce7cd380cc699c4666) ### Update api.rst > Commit: [d43dd36b6fcd625fcf80b9ce7cd380cc699c4666](https://github.com/dOpensource/dsiprouter/commit/d43dd36b6fcd625fcf80b9ce7cd380cc699c4666) > Date: Thu, 30 Mar 2023 23:24:04 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d43dd36b6fcd625fcf80b9ce7cd380cc699c4666) [//]: # (START_SECTION b599ffed2123ac4a8db6e572d71c540e44031ca3) ### Update api.rst > Commit: [b599ffed2123ac4a8db6e572d71c540e44031ca3](https://github.com/dOpensource/dsiprouter/commit/b599ffed2123ac4a8db6e572d71c540e44031ca3) > Date: Thu, 30 Mar 2023 23:19:27 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b599ffed2123ac4a8db6e572d71c540e44031ca3) [//]: # (START_SECTION 4b5f72a3b6d30d2bc660e947bcb2f924785979b7) ### Update api.rst > Commit: [4b5f72a3b6d30d2bc660e947bcb2f924785979b7](https://github.com/dOpensource/dsiprouter/commit/4b5f72a3b6d30d2bc660e947bcb2f924785979b7) > Date: Thu, 30 Mar 2023 23:17:51 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4b5f72a3b6d30d2bc660e947bcb2f924785979b7) [//]: # (START_SECTION 8258df375bc73cfc17b19cc92cc1e371295b00e9) ### Update api.rst > Commit: [8258df375bc73cfc17b19cc92cc1e371295b00e9](https://github.com/dOpensource/dsiprouter/commit/8258df375bc73cfc17b19cc92cc1e371295b00e9) > Date: Thu, 30 Mar 2023 23:16:23 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8258df375bc73cfc17b19cc92cc1e371295b00e9) [//]: # (START_SECTION fb34d3bc071c54e92d5840c5c818bdb79f8209cc) ### Update use-cases.rst > Commit: [fb34d3bc071c54e92d5840c5c818bdb79f8209cc](https://github.com/dOpensource/dsiprouter/commit/fb34d3bc071c54e92d5840c5c818bdb79f8209cc) > Date: Thu, 30 Mar 2023 23:06:12 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fb34d3bc071c54e92d5840c5c818bdb79f8209cc) [//]: # (START_SECTION 1d46d255fed53ec1511b103af5a99d64edf82edb) ### Update use-cases.rst > Commit: [1d46d255fed53ec1511b103af5a99d64edf82edb](https://github.com/dOpensource/dsiprouter/commit/1d46d255fed53ec1511b103af5a99d64edf82edb) > Date: Thu, 30 Mar 2023 23:00:45 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1d46d255fed53ec1511b103af5a99d64edf82edb) [//]: # (START_SECTION a1738ef6ecf934e6775afcc7045adac7b1e31819) ### Update use-cases.rst > Commit: [a1738ef6ecf934e6775afcc7045adac7b1e31819](https://github.com/dOpensource/dsiprouter/commit/a1738ef6ecf934e6775afcc7045adac7b1e31819) > Date: Thu, 30 Mar 2023 22:44:33 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a1738ef6ecf934e6775afcc7045adac7b1e31819) [//]: # (START_SECTION 34cd79c63c171b3bcb8b37a6857bb386b0aad127) ### Update carrier_groups.rst > Commit: [34cd79c63c171b3bcb8b37a6857bb386b0aad127](https://github.com/dOpensource/dsiprouter/commit/34cd79c63c171b3bcb8b37a6857bb386b0aad127) > Date: Thu, 30 Mar 2023 22:34:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 34cd79c63c171b3bcb8b37a6857bb386b0aad127) [//]: # (START_SECTION a54465a970ab50e701cf5a8c44ddb2fd55f6acee) ### Allow v0.70 Upgrade Bootstrapping > Commit: [a54465a970ab50e701cf5a8c44ddb2fd55f6acee](https://github.com/dOpensource/dsiprouter/commit/a54465a970ab50e701cf5a8c44ddb2fd55f6acee) > Date: Thu, 30 Mar 2023 22:00:53 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION a54465a970ab50e701cf5a8c44ddb2fd55f6acee) [//]: # (START_SECTION 2953f326f02116afc1dc4fb5acbb458c63dba834) ### Update use-cases.rst > Commit: [2953f326f02116afc1dc4fb5acbb458c63dba834](https://github.com/dOpensource/dsiprouter/commit/2953f326f02116afc1dc4fb5acbb458c63dba834) > Date: Thu, 30 Mar 2023 20:01:21 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2953f326f02116afc1dc4fb5acbb458c63dba834) [//]: # (START_SECTION 6843ead09ca062c856a7c199a247aaf817b88306) ### Update use-cases.rst > Commit: [6843ead09ca062c856a7c199a247aaf817b88306](https://github.com/dOpensource/dsiprouter/commit/6843ead09ca062c856a7c199a247aaf817b88306) > Date: Thu, 30 Mar 2023 19:58:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6843ead09ca062c856a7c199a247aaf817b88306) [//]: # (START_SECTION b935436bb85ffc97b3abb14458ff6d8de657f204) ### Update use-cases.rst > Commit: [b935436bb85ffc97b3abb14458ff6d8de657f204](https://github.com/dOpensource/dsiprouter/commit/b935436bb85ffc97b3abb14458ff6d8de657f204) > Date: Thu, 30 Mar 2023 19:55:33 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b935436bb85ffc97b3abb14458ff6d8de657f204) [//]: # (START_SECTION 96799b3245092cc24ed082212edd023802ad60b8) ### Update use-cases.rst > Commit: [96799b3245092cc24ed082212edd023802ad60b8](https://github.com/dOpensource/dsiprouter/commit/96799b3245092cc24ed082212edd023802ad60b8) > Date: Thu, 30 Mar 2023 19:43:08 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 96799b3245092cc24ed082212edd023802ad60b8) [//]: # (START_SECTION c08a7f914caf43d37ffeff09bb8f9bf75dd442dd) ### Update use-cases.rst > Commit: [c08a7f914caf43d37ffeff09bb8f9bf75dd442dd](https://github.com/dOpensource/dsiprouter/commit/c08a7f914caf43d37ffeff09bb8f9bf75dd442dd) > Date: Thu, 30 Mar 2023 19:40:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c08a7f914caf43d37ffeff09bb8f9bf75dd442dd) [//]: # (START_SECTION 05f1eca4837d1676905d882a44d112de5c92562b) ### Add files via upload > Commit: [05f1eca4837d1676905d882a44d112de5c92562b](https://github.com/dOpensource/dsiprouter/commit/05f1eca4837d1676905d882a44d112de5c92562b) > Date: Thu, 30 Mar 2023 16:36:42 -0700 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 05f1eca4837d1676905d882a44d112de5c92562b) [//]: # (START_SECTION 6e931ce7092aecb7ce511059b3e40d4f6b1c9fda) ### Update use-cases.rst > Commit: [6e931ce7092aecb7ce511059b3e40d4f6b1c9fda](https://github.com/dOpensource/dsiprouter/commit/6e931ce7092aecb7ce511059b3e40d4f6b1c9fda) > Date: Thu, 30 Mar 2023 19:35:45 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6e931ce7092aecb7ce511059b3e40d4f6b1c9fda) [//]: # (START_SECTION aaf31fce475ee60b5dccce765c34d9208a3af280) ### Update use-cases.rst > Commit: [aaf31fce475ee60b5dccce765c34d9208a3af280](https://github.com/dOpensource/dsiprouter/commit/aaf31fce475ee60b5dccce765c34d9208a3af280) > Date: Thu, 30 Mar 2023 17:42:03 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aaf31fce475ee60b5dccce765c34d9208a3af280) [//]: # (START_SECTION 3b341f4168e65f653fdf7191a08978e967e86237) ### Update use-cases.rst > Commit: [3b341f4168e65f653fdf7191a08978e967e86237](https://github.com/dOpensource/dsiprouter/commit/3b341f4168e65f653fdf7191a08978e967e86237) > Date: Thu, 30 Mar 2023 15:35:55 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3b341f4168e65f653fdf7191a08978e967e86237) [//]: # (START_SECTION b926ea7f70d26818a04a8c75b68f295cdb8f4978) ### Fix Upgrade Feature Entrypoints > Commit: [b926ea7f70d26818a04a8c75b68f295cdb8f4978](https://github.com/dOpensource/dsiprouter/commit/b926ea7f70d26818a04a8c75b68f295cdb8f4978) > Date: Thu, 30 Mar 2023 15:13:41 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION b926ea7f70d26818a04a8c75b68f295cdb8f4978) [//]: # (START_SECTION 02ea31c21261e6c9c374cf51e67484b0d358409f) ### Update rhel_install.rst > Commit: [02ea31c21261e6c9c374cf51e67484b0d358409f](https://github.com/dOpensource/dsiprouter/commit/02ea31c21261e6c9c374cf51e67484b0d358409f) > Date: Thu, 30 Mar 2023 15:01:51 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 02ea31c21261e6c9c374cf51e67484b0d358409f) [//]: # (START_SECTION 3c29315838afcc70648170e0cd12d8bcde3cb6ae) ### Update debian_install.rst > Commit: [3c29315838afcc70648170e0cd12d8bcde3cb6ae](https://github.com/dOpensource/dsiprouter/commit/3c29315838afcc70648170e0cd12d8bcde3cb6ae) > Date: Thu, 30 Mar 2023 15:01:19 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3c29315838afcc70648170e0cd12d8bcde3cb6ae) [//]: # (START_SECTION cce7d0412b19159e65436c667241a7580b384263) ### Upgrade Feature Updates > Commit: [cce7d0412b19159e65436c667241a7580b384263](https://github.com/dOpensource/dsiprouter/commit/cce7d0412b19159e65436c667241a7580b384263) > Date: Thu, 30 Mar 2023 14:55:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION cce7d0412b19159e65436c667241a7580b384263) [//]: # (START_SECTION 89450c0d2b9cbff0f9c5360b3301811bde4a17c1) ### Update installing.rst > Commit: [89450c0d2b9cbff0f9c5360b3301811bde4a17c1](https://github.com/dOpensource/dsiprouter/commit/89450c0d2b9cbff0f9c5360b3301811bde4a17c1) > Date: Thu, 30 Mar 2023 14:44:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 89450c0d2b9cbff0f9c5360b3301811bde4a17c1) [//]: # (START_SECTION 41736eb05ed2c9469622d49d9f41b7bc5b8125d0) ### Update installing.rst > Commit: [41736eb05ed2c9469622d49d9f41b7bc5b8125d0](https://github.com/dOpensource/dsiprouter/commit/41736eb05ed2c9469622d49d9f41b7bc5b8125d0) > Date: Thu, 30 Mar 2023 14:36:36 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 41736eb05ed2c9469622d49d9f41b7bc5b8125d0) [//]: # (START_SECTION a7b22b34d458ba0b53af1bf452064caa61897078) ### Standalong Script checkpoint > Commit: [a7b22b34d458ba0b53af1bf452064caa61897078](https://github.com/dOpensource/dsiprouter/commit/a7b22b34d458ba0b53af1bf452064caa61897078) > Date: Thu, 30 Mar 2023 10:12:26 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION a7b22b34d458ba0b53af1bf452064caa61897078) [//]: # (START_SECTION e415705f275a8aa4ec19f595c156338ecbed1075) ### WIP Changes for Upgrade Feature > Commit: [e415705f275a8aa4ec19f595c156338ecbed1075](https://github.com/dOpensource/dsiprouter/commit/e415705f275a8aa4ec19f595c156338ecbed1075) > Date: Thu, 30 Mar 2023 11:50:17 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION e415705f275a8aa4ec19f595c156338ecbed1075) [//]: # (START_SECTION e24d53894f2f613e8d76d61b0065fce3fe63adcb) ### Upgrade script tweaks > Commit: [e24d53894f2f613e8d76d61b0065fce3fe63adcb](https://github.com/dOpensource/dsiprouter/commit/e24d53894f2f613e8d76d61b0065fce3fe63adcb) > Date: Thu, 30 Mar 2023 07:43:15 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION e24d53894f2f613e8d76d61b0065fce3fe63adcb) [//]: # (START_SECTION 2c80df409e6cb9e0782d6f7544096041ebfa225b) ### Bug Fixes And Cleanup For v0.72 > Commit: [2c80df409e6cb9e0782d6f7544096041ebfa225b](https://github.com/dOpensource/dsiprouter/commit/2c80df409e6cb9e0782d6f7544096041ebfa225b) > Date: Thu, 30 Mar 2023 09:34:52 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add flask gui deprecation comments - add additional documenting comments - silence superfluous output when sources.pref missing - fix systemd services bugfix not working - fix updating existing key/cert pair fails --- [//]: # (END_SECTION 2c80df409e6cb9e0782d6f7544096041ebfa225b) [//]: # (START_SECTION 69b735d9bf020d111d5251e70281767fb4e94faa) ### Bug Fixes And Cleanup For v0.72 > Commit: [69b735d9bf020d111d5251e70281767fb4e94faa](https://github.com/dOpensource/dsiprouter/commit/69b735d9bf020d111d5251e70281767fb4e94faa) > Date: Wed, 29 Mar 2023 16:02:15 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - allow notification message animation to be interrupted by another request - fix carrier defaults misaligned - fix msteams gwgroupid confilct - fix description field storage requirements for other DB tables --- [//]: # (END_SECTION 69b735d9bf020d111d5251e70281767fb4e94faa) [//]: # (START_SECTION 7dbe2558f6c045311ef043f2c5859c711345491b) ### Update index.rst > Commit: [7dbe2558f6c045311ef043f2c5859c711345491b](https://github.com/dOpensource/dsiprouter/commit/7dbe2558f6c045311ef043f2c5859c711345491b) > Date: Wed, 29 Mar 2023 14:27:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7dbe2558f6c045311ef043f2c5859c711345491b) [//]: # (START_SECTION 33c3bee02578082f7c3192d3496b0067af992f9c) ### Update index.rst > Commit: [33c3bee02578082f7c3192d3496b0067af992f9c](https://github.com/dOpensource/dsiprouter/commit/33c3bee02578082f7c3192d3496b0067af992f9c) > Date: Wed, 29 Mar 2023 14:26:15 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 33c3bee02578082f7c3192d3496b0067af992f9c) [//]: # (START_SECTION f0f02584af775a85e723c6ccdb09db2c6141a145) ### Update index.rst > Commit: [f0f02584af775a85e723c6ccdb09db2c6141a145](https://github.com/dOpensource/dsiprouter/commit/f0f02584af775a85e723c6ccdb09db2c6141a145) > Date: Wed, 29 Mar 2023 14:17:44 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f0f02584af775a85e723c6ccdb09db2c6141a145) [//]: # (START_SECTION d7f7da68512e560bd9bc5f2dbb35b07e6630b7f9) ### Stir-Shaken Fixes - Added a missing parameter to the Stir-Shaken outbound logic - Added a script that will generate a self-signed certificate for testing out Stir-Shaken > Commit: [d7f7da68512e560bd9bc5f2dbb35b07e6630b7f9](https://github.com/dOpensource/dsiprouter/commit/d7f7da68512e560bd9bc5f2dbb35b07e6630b7f9) > Date: Wed, 29 Mar 2023 16:34:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d7f7da68512e560bd9bc5f2dbb35b07e6630b7f9) [//]: # (START_SECTION eb720e2961d73a826fbd6fae55a0b7e93eef25bf) ### Updated SQL in Call Detail Records so that it loads properly > Commit: [eb720e2961d73a826fbd6fae55a0b7e93eef25bf](https://github.com/dOpensource/dsiprouter/commit/eb720e2961d73a826fbd6fae55a0b7e93eef25bf) > Date: Wed, 29 Mar 2023 01:56:45 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eb720e2961d73a826fbd6fae55a0b7e93eef25bf) [//]: # (START_SECTION bbcfaeeb0d88324f3dc8a9a85b3875e7ff3e76dd) ### Bug Fixes And Cleanup For v0.72 > Commit: [bbcfaeeb0d88324f3dc8a9a85b3875e7ff3e76dd](https://github.com/dOpensource/dsiprouter/commit/bbcfaeeb0d88324f3dc8a9a85b3875e7ff3e76dd) > Date: Tue, 28 Mar 2023 15:09:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - remove superfluous cache files - fix "nginx not reloaded when default SSL cert updated" - update systemd services to use `dsiprouter chown` for preparation - fix `dsiprouter restart -debug` fails when started with `DEBUG=False` - remove deprecations noted for removal in v0.72 --- [//]: # (END_SECTION bbcfaeeb0d88324f3dc8a9a85b3875e7ff3e76dd) [//]: # (START_SECTION 050f5fbb39bdb1e2073fa98fb471acf01777f0f1) ### Fixed Issue #493 > Commit: [050f5fbb39bdb1e2073fa98fb471acf01777f0f1](https://github.com/dOpensource/dsiprouter/commit/050f5fbb39bdb1e2073fa98fb471acf01777f0f1) > Date: Mon, 27 Mar 2023 17:13:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 050f5fbb39bdb1e2073fa98fb471acf01777f0f1) [//]: # (START_SECTION 94bfc589ae9777163855e52b511fc90f3f4adf1d) ### Fixed a bug that prevented domain_attrs from being deleted > Commit: [94bfc589ae9777163855e52b511fc90f3f4adf1d](https://github.com/dOpensource/dsiprouter/commit/94bfc589ae9777163855e52b511fc90f3f4adf1d) > Date: Sat, 25 Mar 2023 05:46:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 94bfc589ae9777163855e52b511fc90f3f4adf1d) [//]: # (START_SECTION 13fcb33a140bba7391d9ad8b52a1aa7f25a09955) ### Fixed the WooCommerce Hashing Len > Commit: [13fcb33a140bba7391d9ad8b52a1aa7f25a09955](https://github.com/dOpensource/dsiprouter/commit/13fcb33a140bba7391d9ad8b52a1aa7f25a09955) > Date: Fri, 17 Mar 2023 17:27:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 13fcb33a140bba7391d9ad8b52a1aa7f25a09955) [//]: # (START_SECTION 72cd4261677d9de449577489edb50fafce56b662) ### Bug Fixes For v0.72 > Commit: [72cd4261677d9de449577489edb50fafce56b662](https://github.com/dOpensource/dsiprouter/commit/72cd4261677d9de449577489edb50fafce56b662) > Date: Tue, 14 Mar 2023 12:08:39 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - msteams address records missing - fix typos in dsiprouter.sh - fix `FLT_MSTEAMS` value in dsiprouter.sh - fix address records tag field too long --- [//]: # (END_SECTION 72cd4261677d9de449577489edb50fafce56b662) [//]: # (START_SECTION b7d9a30838189b0e240a590f610b66505d85d0ec) ### Updated the SQL logic for Inbound Routes to work with the text() function > Commit: [b7d9a30838189b0e240a590f610b66505d85d0ec](https://github.com/dOpensource/dsiprouter/commit/b7d9a30838189b0e240a590f610b66505d85d0ec) > Date: Tue, 14 Mar 2023 15:05:10 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b7d9a30838189b0e240a590f610b66505d85d0ec) [//]: # (START_SECTION 8e19d470d3fff85329e78239c84e54998f39f247) ### Suggested Changes and Comments > Commit: [8e19d470d3fff85329e78239c84e54998f39f247](https://github.com/dOpensource/dsiprouter/commit/8e19d470d3fff85329e78239c84e54998f39f247) > Date: Mon, 13 Mar 2023 14:33:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 8e19d470d3fff85329e78239c84e54998f39f247) [//]: # (START_SECTION a1a55ec01f46433fb7ac64eae58089cc8e506a19) ### Updated the database logic to use the new SQLAlchemy 2.0 result-object > Commit: [a1a55ec01f46433fb7ac64eae58089cc8e506a19](https://github.com/dOpensource/dsiprouter/commit/a1a55ec01f46433fb7ac64eae58089cc8e506a19) > Date: Sun, 12 Mar 2023 00:44:26 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION a1a55ec01f46433fb7ac64eae58089cc8e506a19) [//]: # (START_SECTION 5a34993875502fc1cd9f21dec686a3035a8dcb92) ### Allow Static Networking > Commit: [5a34993875502fc1cd9f21dec686a3035a8dcb92](https://github.com/dOpensource/dsiprouter/commit/5a34993875502fc1cd9f21dec686a3035a8dcb92) > Date: Tue, 7 Mar 2023 15:39:52 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#473](https://github.com/dOpensource/dsiprouter/issues/473) --- [//]: # (END_SECTION 5a34993875502fc1cd9f21dec686a3035a8dcb92) [//]: # (START_SECTION 8cac48d464f70bd5da4c8227abfc7025a23a82a0) ### Resolve Merge Conflicts > Commit: [8cac48d464f70bd5da4c8227abfc7025a23a82a0](https://github.com/dOpensource/dsiprouter/commit/8cac48d464f70bd5da4c8227abfc7025a23a82a0) > Date: Mon, 6 Mar 2023 15:06:24 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 8cac48d464f70bd5da4c8227abfc7025a23a82a0) [//]: # (START_SECTION 63ac499d92fac90f8bb1c3fabc7c03136abd505f) ### Process Updates > Commit: [63ac499d92fac90f8bb1c3fabc7c03136abd505f](https://github.com/dOpensource/dsiprouter/commit/63ac499d92fac90f8bb1c3fabc7c03136abd505f) > Date: Fri, 3 Mar 2023 08:24:03 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 63ac499d92fac90f8bb1c3fabc7c03136abd505f) [//]: # (START_SECTION 2bba54fc6e7061893b205149d7c9172c97239cbf) ### Security Updates For WSGI Dependencies > Commit: [2bba54fc6e7061893b205149d7c9172c97239cbf](https://github.com/dOpensource/dsiprouter/commit/2bba54fc6e7061893b205149d7c9172c97239cbf) > Date: Wed, 1 Mar 2023 14:31:56 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#484](https://github.com/dOpensource/dsiprouter/pull/484) - remove unused imports - update WSGI and Flask dependencies --- [//]: # (END_SECTION 2bba54fc6e7061893b205149d7c9172c97239cbf) [//]: # (START_SECTION 50348be3339f4d3f9be532dcd4164964992bdf17) ### Fix Default Dispatcher Attributes > Commit: [50348be3339f4d3f9be532dcd4164964992bdf17](https://github.com/dOpensource/dsiprouter/commit/50348be3339f4d3f9be532dcd4164964992bdf17) > Date: Wed, 1 Mar 2023 14:22:23 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#468](https://github.com/dOpensource/dsiprouter/issues/468) --- [//]: # (END_SECTION 50348be3339f4d3f9be532dcd4164964992bdf17) [//]: # (START_SECTION a1174d1e922ffd85eb767b7047fb84e3e3e0745e) ### Update Installation Documentation > Commit: [a1174d1e922ffd85eb767b7047fb84e3e3e0745e](https://github.com/dOpensource/dsiprouter/commit/a1174d1e922ffd85eb767b7047fb84e3e3e0745e) > Date: Wed, 1 Mar 2023 11:59:41 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#470](https://github.com/dOpensource/dsiprouter/issues/470) --- [//]: # (END_SECTION a1174d1e922ffd85eb767b7047fb84e3e3e0745e) [//]: # (START_SECTION da72adbd503e5fce324c8a801d90c1e0b38ca054) ### Require Issue Template Usage > Commit: [da72adbd503e5fce324c8a801d90c1e0b38ca054](https://github.com/dOpensource/dsiprouter/commit/da72adbd503e5fce324c8a801d90c1e0b38ca054) > Date: Wed, 15 Feb 2023 09:50:31 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION da72adbd503e5fce324c8a801d90c1e0b38ca054) [//]: # (START_SECTION 2a19a64551c0a191adc60a956111f25b2a6a340c) ### Update Installation Documentation > Commit: [2a19a64551c0a191adc60a956111f25b2a6a340c](https://github.com/dOpensource/dsiprouter/commit/2a19a64551c0a191adc60a956111f25b2a6a340c) > Date: Wed, 1 Mar 2023 11:59:41 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#470](https://github.com/dOpensource/dsiprouter/issues/470) --- [//]: # (END_SECTION 2a19a64551c0a191adc60a956111f25b2a6a340c) [//]: # (START_SECTION 6dfb127bd0e7f504522e80a4ad60ac01118f2c6f) ### Add PR Template > Commit: [6dfb127bd0e7f504522e80a4ad60ac01118f2c6f](https://github.com/dOpensource/dsiprouter/commit/6dfb127bd0e7f504522e80a4ad60ac01118f2c6f) > Date: Wed, 15 Feb 2023 11:32:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 6dfb127bd0e7f504522e80a4ad60ac01118f2c6f) [//]: # (START_SECTION bb58600d9294fbb5ce39c2e273155602d7981638) ### Require Issue Template Usage > Commit: [bb58600d9294fbb5ce39c2e273155602d7981638](https://github.com/dOpensource/dsiprouter/commit/bb58600d9294fbb5ce39c2e273155602d7981638) > Date: Wed, 15 Feb 2023 09:50:31 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION bb58600d9294fbb5ce39c2e273155602d7981638) [//]: # (START_SECTION cc8a46218083c85894e090228f3cd55eb5a238c7) ### Update issue templates (#481) > Commit: [cc8a46218083c85894e090228f3cd55eb5a238c7](https://github.com/dOpensource/dsiprouter/commit/cc8a46218083c85894e090228f3cd55eb5a238c7) > Date: Wed, 15 Feb 2023 09:27:23 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - Merging --- [//]: # (END_SECTION cc8a46218083c85894e090228f3cd55eb5a238c7) [//]: # (START_SECTION 681c2c432b735a3f67d6732264f50638b47b1552) ### Update issue templates > Commit: [681c2c432b735a3f67d6732264f50638b47b1552](https://github.com/dOpensource/dsiprouter/commit/681c2c432b735a3f67d6732264f50638b47b1552) > Date: Wed, 15 Feb 2023 09:26:05 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 681c2c432b735a3f67d6732264f50638b47b1552) [//]: # (START_SECTION 799d68b0365eeda8c2d17811575b925a0023ae2a) ### Merge PR435 > Commit: [799d68b0365eeda8c2d17811575b925a0023ae2a](https://github.com/dOpensource/dsiprouter/commit/799d68b0365eeda8c2d17811575b925a0023ae2a) > Date: Tue, 14 Feb 2023 11:36:04 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - resolves #435 --- [//]: # (END_SECTION 799d68b0365eeda8c2d17811575b925a0023ae2a) [//]: # (START_SECTION 9a82a5efab8b2d6a3bed6c0611888b0a191119c5) ### Correction of CDR fields (#465) > Commit: [9a82a5efab8b2d6a3bed6c0611888b0a191119c5](https://github.com/dOpensource/dsiprouter/commit/9a82a5efab8b2d6a3bed6c0611888b0a191119c5) > Date: Tue, 14 Feb 2023 07:53:03 -0800 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: - * Update cdrs.sql - Adjusted field types to address bug [#464](https://github.com/dOpensource/dsiprouter/issues/464) - --------- - Co-authored-by: Tyler Moore --- [//]: # (END_SECTION 9a82a5efab8b2d6a3bed6c0611888b0a191119c5) [//]: # (START_SECTION e354fbeb2467854770650aec659d98b27dc8d464) ### Integration Fixes > Commit: [e354fbeb2467854770650aec659d98b27dc8d464](https://github.com/dOpensource/dsiprouter/commit/e354fbeb2467854770650aec659d98b27dc8d464) > Date: Thu, 9 Feb 2023 11:25:05 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - updated license manager UI table rows to scale properly - fix span and button resizing in license manager UI - fix redirection links to license manager UI - fix typos in domain route - fix `dsip_settings.sql` syntax highlighting - add deprecation notices where missing - fix typoes in `dsiprouter.sh` - update default character length in `security.py` to match `dsip_lib.sh` - fix typo in `dsip_lib.sh` - deprecate dsiprouter module compilation in debian, rhel, and amzn --- [//]: # (END_SECTION e354fbeb2467854770650aec659d98b27dc8d464) [//]: # (START_SECTION 98270d267c77f991b3aecf572d38c097fecac68b) ### Cleanup Branch / Final Fixes > Commit: [98270d267c77f991b3aecf572d38c097fecac68b](https://github.com/dOpensource/dsiprouter/commit/98270d267c77f991b3aecf572d38c097fecac68b) > Date: Wed, 8 Feb 2023 11:56:33 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix hashing functions in `dsip_lib.sh` - fix `homer-id` format (based on machine-id) - fix outbound clearip default hostname - add WIP PBKDF2 bash-native implementation - cleanup branch for merging --- [//]: # (END_SECTION 98270d267c77f991b3aecf572d38c097fecac68b) [//]: # (START_SECTION 16a5a16cf1ef8a7aad44ac3d9da1e4c8706fac35) ### API Updates > Commit: [16a5a16cf1ef8a7aad44ac3d9da1e4c8706fac35](https://github.com/dOpensource/dsiprouter/commit/16a5a16cf1ef8a7aad44ac3d9da1e4c8706fac35) > Date: Tue, 7 Feb 2023 13:52:19 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 16a5a16cf1ef8a7aad44ac3d9da1e4c8706fac35) [//]: # (START_SECTION 4082e01348c3279f71685ff9eeaccaee25a6e53d) ### Upgrade Checkpoint > Commit: [4082e01348c3279f71685ff9eeaccaee25a6e53d](https://github.com/dOpensource/dsiprouter/commit/4082e01348c3279f71685ff9eeaccaee25a6e53d) > Date: Tue, 7 Feb 2023 13:06:59 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 4082e01348c3279f71685ff9eeaccaee25a6e53d) [//]: # (START_SECTION 92d323dc7d6d82645ae7c25a4134679009dba4a6) ### Update installing.rst > Commit: [92d323dc7d6d82645ae7c25a4134679009dba4a6](https://github.com/dOpensource/dsiprouter/commit/92d323dc7d6d82645ae7c25a4134679009dba4a6) > Date: Sun, 5 Feb 2023 14:16:38 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92d323dc7d6d82645ae7c25a4134679009dba4a6) [//]: # (START_SECTION 0b3ed50bbf81577558e79b77dd8b2c65f61ad034) ### Update installing.rst > Commit: [0b3ed50bbf81577558e79b77dd8b2c65f61ad034](https://github.com/dOpensource/dsiprouter/commit/0b3ed50bbf81577558e79b77dd8b2c65f61ad034) > Date: Sun, 5 Feb 2023 14:10:11 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0b3ed50bbf81577558e79b77dd8b2c65f61ad034) [//]: # (START_SECTION 203f8ea5953044b05b047a5565c36c23155cff43) ### Update installing.rst > Commit: [203f8ea5953044b05b047a5565c36c23155cff43](https://github.com/dOpensource/dsiprouter/commit/203f8ea5953044b05b047a5565c36c23155cff43) > Date: Sun, 5 Feb 2023 14:09:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 203f8ea5953044b05b047a5565c36c23155cff43) [//]: # (START_SECTION d2144a88b8ccba4dba32265d69782e65635b4997) ### Update debian_install.rst > Commit: [d2144a88b8ccba4dba32265d69782e65635b4997](https://github.com/dOpensource/dsiprouter/commit/d2144a88b8ccba4dba32265d69782e65635b4997) > Date: Sun, 5 Feb 2023 14:04:35 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Removing documentation about Debian 9 and combining the install documentation for Deb 10 and Deb 11 --- [//]: # (END_SECTION d2144a88b8ccba4dba32265d69782e65635b4997) [//]: # (START_SECTION af39173450198616f9e1f959f233f980a65a4d47) ### Pinned the version of SQLAlchemy and Requests to 1.4.46 and 2.28.1, respectively > Commit: [af39173450198616f9e1f959f233f980a65a4d47](https://github.com/dOpensource/dsiprouter/commit/af39173450198616f9e1f959f233f980a65a4d47) > Date: Sun, 5 Feb 2023 18:33:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION af39173450198616f9e1f959f233f980a65a4d47) [//]: # (START_SECTION 2f31989a6b34be6d213a891c3725dde6c168f2fb) ### Add Licensing Manager > Commit: [2f31989a6b34be6d213a891c3725dde6c168f2fb](https://github.com/dOpensource/dsiprouter/commit/2f31989a6b34be6d213a891c3725dde6c168f2fb) > Date: Wed, 1 Feb 2023 15:41:03 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add license management REST api - add license management GUI routes - update sqlalchemy to version 2.0.0 - update license checking logic to use new api - fix slow reload in systemd services - remove deprecated sqlalchemy settings - update credential hashing to use standard salt lengths - fix edge cases where credential hashing failed - add native openssl hashing in bash scripts - update all raw sql queries to be parameterized - update secrets checking to combat padding attacks - add various utility functions - update exceptions to show full stack trace by default - improve request data handling to handle edge cases - update `DSIP_ID` to be machine specific - add useful debug output to signal handlers - add safeguards against improper app teardown - fix missing port and charset in DB connection URI --- [//]: # (END_SECTION 2f31989a6b34be6d213a891c3725dde6c168f2fb) [//]: # (START_SECTION 581d8ed65958db142c9c64e3d6f973327a626fcc) ### Fix Missing Kamailio Config Subst > Commit: [581d8ed65958db142c9c64e3d6f973327a626fcc](https://github.com/dOpensource/dsiprouter/commit/581d8ed65958db142c9c64e3d6f973327a626fcc) > Date: Sat, 10 Dec 2022 13:17:06 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add missing subst defines to kamailio config --- [//]: # (END_SECTION 581d8ed65958db142c9c64e3d6f973327a626fcc) [//]: # (START_SECTION 3b02dc02eeaee8d4e8ca13741489c2e6f72de230) ### Increase CDR Field Sizes > Commit: [3b02dc02eeaee8d4e8ca13741489c2e6f72de230](https://github.com/dOpensource/dsiprouter/commit/3b02dc02eeaee8d4e8ca13741489c2e6f72de230) > Date: Sat, 10 Dec 2022 13:12:31 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #472 - increase varchar sizes to handle edge cases --- [//]: # (END_SECTION 3b02dc02eeaee8d4e8ca13741489c2e6f72de230) [//]: # (START_SECTION 63dfa5ae605542cb998a8b03155bdddb75bb1472) ### Added teh script to generate the self signed cert > Commit: [63dfa5ae605542cb998a8b03155bdddb75bb1472](https://github.com/dOpensource/dsiprouter/commit/63dfa5ae605542cb998a8b03155bdddb75bb1472) > Date: Thu, 8 Dec 2022 11:53:58 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 63dfa5ae605542cb998a8b03155bdddb75bb1472) [//]: # (START_SECTION a787ec5760bf147c755950116a9e440d181a2e9d) ### Removed Rabbit MQ logic > Commit: [a787ec5760bf147c755950116a9e440d181a2e9d](https://github.com/dOpensource/dsiprouter/commit/a787ec5760bf147c755950116a9e440d181a2e9d) > Date: Sun, 4 Dec 2022 22:37:46 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a787ec5760bf147c755950116a9e440d181a2e9d) [//]: # (START_SECTION 35b4d7233eeda44587c59f0593f5599f82898f65) ### Added support for spinning up a demo server > Commit: [35b4d7233eeda44587c59f0593f5599f82898f65](https://github.com/dOpensource/dsiprouter/commit/35b4d7233eeda44587c59f0593f5599f82898f65) > Date: Sun, 4 Dec 2022 22:33:35 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 35b4d7233eeda44587c59f0593f5599f82898f65) [//]: # (START_SECTION 1fc9ae71deb1b5191c68a3171bf9b5269e198b80) ### Added push notification updates > Commit: [1fc9ae71deb1b5191c68a3171bf9b5269e198b80](https://github.com/dOpensource/dsiprouter/commit/1fc9ae71deb1b5191c68a3171bf9b5269e198b80) > Date: Wed, 30 Nov 2022 09:34:52 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 1fc9ae71deb1b5191c68a3171bf9b5269e198b80) [//]: # (START_SECTION 200cd34e29349b99b6da38065cee4102e916d98c) ### Terraform - Added support for adding a hostname record to DNS > Commit: [200cd34e29349b99b6da38065cee4102e916d98c](https://github.com/dOpensource/dsiprouter/commit/200cd34e29349b99b6da38065cee4102e916d98c) > Date: Fri, 18 Nov 2022 12:03:37 -0600 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 200cd34e29349b99b6da38065cee4102e916d98c) [//]: # (START_SECTION cf5c5200f68b97d88ad2dec4331d04db3e189a51) ### Updated getInternalCIDR with support for DMZ > Commit: [cf5c5200f68b97d88ad2dec4331d04db3e189a51](https://github.com/dOpensource/dsiprouter/commit/cf5c5200f68b97d88ad2dec4331d04db3e189a51) > Date: Wed, 2 Nov 2022 00:02:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cf5c5200f68b97d88ad2dec4331d04db3e189a51) [//]: # (START_SECTION ee072c3650e9a008900cf00132804e83af3fbf56) ### Fixed issues with install scripts > Commit: [ee072c3650e9a008900cf00132804e83af3fbf56](https://github.com/dOpensource/dsiprouter/commit/ee072c3650e9a008900cf00132804e83af3fbf56) > Date: Tue, 1 Nov 2022 02:28:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ee072c3650e9a008900cf00132804e83af3fbf56) [//]: # (START_SECTION 90affe1dbac937b1fd09e2d5ba90ae07491948e6) ### Fixed issues with install scripts > Commit: [90affe1dbac937b1fd09e2d5ba90ae07491948e6](https://github.com/dOpensource/dsiprouter/commit/90affe1dbac937b1fd09e2d5ba90ae07491948e6) > Date: Tue, 1 Nov 2022 02:27:26 +0000 > Author: root (root@mack-dsip-v0.710) > Committer: root (root@mack-dsip-v0.710) > Signed: --- [//]: # (END_SECTION 90affe1dbac937b1fd09e2d5ba90ae07491948e6) [//]: # (START_SECTION 0a4cf6a2b0a8352659dfd8145692ed6147ba3694) ### Fixed issue with Kamailio.cfg > Commit: [0a4cf6a2b0a8352659dfd8145692ed6147ba3694](https://github.com/dOpensource/dsiprouter/commit/0a4cf6a2b0a8352659dfd8145692ed6147ba3694) > Date: Mon, 31 Oct 2022 23:30:26 +0000 > Author: root (root@mack-dsip-v0.710) > Committer: root (root@mack-dsip-v0.710) > Signed: --- [//]: # (END_SECTION 0a4cf6a2b0a8352659dfd8145692ed6147ba3694) [//]: # (START_SECTION b26b91e04a541685d0d5a78277c4ab7bf564669d) ### Added DMZ Support > Commit: [b26b91e04a541685d0d5a78277c4ab7bf564669d](https://github.com/dOpensource/dsiprouter/commit/b26b91e04a541685d0d5a78277c4ab7bf564669d) > Date: Mon, 31 Oct 2022 23:03:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b26b91e04a541685d0d5a78277c4ab7bf564669d) [//]: # (START_SECTION f321abf3809cc8b6696be3b6423a073a76f81365) ### Updated Pass-Thru Authentication - Added support to handle pass-thru when the hostname of the media server is used as the endpoint. > Commit: [f321abf3809cc8b6696be3b6423a073a76f81365](https://github.com/dOpensource/dsiprouter/commit/f321abf3809cc8b6696be3b6423a073a76f81365) > Date: Tue, 18 Oct 2022 10:38:56 +0000 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f321abf3809cc8b6696be3b6423a073a76f81365) [//]: # (START_SECTION 84bd75cd3f05b01b765ad8be4d4a096681ac681d) ### Fixed issue with ACK on Domain Routing: - Changed VALIDATE_ROUTE_HEADERS to CUSTOM_WITHINDLG - Changed logic to not remove the first Route header - this causes loose_route to functionaly incorrectly > Commit: [84bd75cd3f05b01b765ad8be4d4a096681ac681d](https://github.com/dOpensource/dsiprouter/commit/84bd75cd3f05b01b765ad8be4d4a096681ac681d) > Date: Tue, 11 Oct 2022 04:06:58 +0000 > Author: root (root@dsiptest.dsiprouter.net) > Committer: root (root@dsiptest.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 84bd75cd3f05b01b765ad8be4d4a096681ac681d) [//]: # (START_SECTION 4452960d95d0bf8e6c9e9c8d60b51b376f6551d9) ### Added a sleep after the update to give apt time to release the locks > Commit: [4452960d95d0bf8e6c9e9c8d60b51b376f6551d9](https://github.com/dOpensource/dsiprouter/commit/4452960d95d0bf8e6c9e9c8d60b51b376f6551d9) > Date: Tue, 4 Oct 2022 08:02:09 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 4452960d95d0bf8e6c9e9c8d60b51b376f6551d9) [//]: # (START_SECTION 3341101669b42a7341643d13c284f696da4a0caf) ### updated the install files to create the directory for serving up manual certificates > Commit: [3341101669b42a7341643d13c284f696da4a0caf](https://github.com/dOpensource/dsiprouter/commit/3341101669b42a7341643d13c284f696da4a0caf) > Date: Tue, 4 Oct 2022 07:26:45 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 3341101669b42a7341643d13c284f696da4a0caf) [//]: # (START_SECTION f53bfa21e447f53f22ff20a2a64b7b859835e8e6) ### Fix Failover Routing > Commit: [f53bfa21e447f53f22ff20a2a64b7b859835e8e6](https://github.com/dOpensource/dsiprouter/commit/f53bfa21e447f53f22ff20a2a64b7b859835e8e6) > Date: Fri, 30 Sep 2022 10:49:17 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change sort algorithm for drouting - fix call limit out of scope error - fix failover routing on no reply - fix failover routing on dropped packets - update failover timeouts to be more realistic - fix route header variable out of scope error - make debugger more readable in debug mode - fix usrloc variable typo --- [//]: # (END_SECTION f53bfa21e447f53f22ff20a2a64b7b859835e8e6) [//]: # (START_SECTION 591aeeb225c1f542bb49a078d6353a7083ee8fc5) ### FusionPBX Sync Fixes: - Added logic to exclude the default FusionPBX domain from being added during sync's. This fixes an ACK loop - Deprecated logic for using the Docker instance of Nginx > Commit: [591aeeb225c1f542bb49a078d6353a7083ee8fc5](https://github.com/dOpensource/dsiprouter/commit/591aeeb225c1f542bb49a078d6353a7083ee8fc5) > Date: Fri, 30 Sep 2022 11:03:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 591aeeb225c1f542bb49a078d6353a7083ee8fc5) [//]: # (START_SECTION c1e0be12cf158786064bc1f1d1c4bdf3db2159f3) ### Installation Bug Fixes > Commit: [c1e0be12cf158786064bc1f1d1c4bdf3db2159f3](https://github.com/dOpensource/dsiprouter/commit/c1e0be12cf158786064bc1f1d1c4bdf3db2159f3) > Date: Thu, 29 Sep 2022 09:37:20 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix unpatched `tls.reload` commands - fix typo in `reconfigureMysqlSystemdService()` - fix cloud-init edge case where DB passwords not updated --- [//]: # (END_SECTION c1e0be12cf158786064bc1f1d1c4bdf3db2159f3) [//]: # (START_SECTION b3a9b851f5b0c095eede8d5913f102b4f62b04f0) ### Installation Bug Fixes > Commit: [b3a9b851f5b0c095eede8d5913f102b4f62b04f0](https://github.com/dOpensource/dsiprouter/commit/b3a9b851f5b0c095eede8d5913f102b4f62b04f0) > Date: Wed, 28 Sep 2022 14:31:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix credential setting functions edge case not setting DB pass - add workaround for TLS module not reloading --- [//]: # (END_SECTION b3a9b851f5b0c095eede8d5913f102b4f62b04f0) [//]: # (START_SECTION cdd8aac9f751a212fcca7f01e7a5159d6ab749f2) ### Installation Bug Fixes > Commit: [cdd8aac9f751a212fcca7f01e7a5159d6ab749f2](https://github.com/dOpensource/dsiprouter/commit/cdd8aac9f751a212fcca7f01e7a5159d6ab749f2) > Date: Wed, 28 Sep 2022 08:21:37 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix openssl version conflict on amazon linux - fix openssl missing rnd file on amazon linux - fix missing wheel package on amazon linux --- [//]: # (END_SECTION cdd8aac9f751a212fcca7f01e7a5159d6ab749f2) [//]: # (START_SECTION a1cba27cee11cd0de0a46ebec89c65eeb86a302a) ### Installation Bug Fixes > Commit: [a1cba27cee11cd0de0a46ebec89c65eeb86a302a](https://github.com/dOpensource/dsiprouter/commit/a1cba27cee11cd0de0a46ebec89c65eeb86a302a) > Date: Tue, 27 Sep 2022 15:45:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix permissions errors on install - silence false positive error crontab missing - fix ipv6 connection check for older iproute versions - fix systemd service formats for debian9 - fix missing uuid module for amazon linux 2 - fix kamailio TLS configurations - fix dupliacte init commands when re-running install - fix kamailio service for old systemd versions --- [//]: # (END_SECTION a1cba27cee11cd0de0a46ebec89c65eeb86a302a) [//]: # (START_SECTION c3fa6d356292be28d495bdd1a32973880bb31068) ### dSIPRouter Provisioning: - Changed the provisioning template to run on port 443 and SSL by default - Supports /provision and /app/provision URLs for obtaining provisioning profiles > Commit: [c3fa6d356292be28d495bdd1a32973880bb31068](https://github.com/dOpensource/dsiprouter/commit/c3fa6d356292be28d495bdd1a32973880bb31068) > Date: Tue, 27 Sep 2022 14:05:41 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c3fa6d356292be28d495bdd1a32973880bb31068) [//]: # (START_SECTION b3f6d0264ce15c28953af1f995738d0d4b335f8b) ### FusionPBX Sync: - Nginx will now do a reload when FusionPBX Sync is enabled vs a restart > Commit: [b3f6d0264ce15c28953af1f995738d0d4b335f8b](https://github.com/dOpensource/dsiprouter/commit/b3f6d0264ce15c28953af1f995738d0d4b335f8b) > Date: Tue, 27 Sep 2022 10:24:38 +0000 > Author: root (root@mack-dsip-v0.700) > Committer: root (root@mack-dsip-v0.700) > Signed: --- [//]: # (END_SECTION b3f6d0264ce15c28953af1f995738d0d4b335f8b) [//]: # (START_SECTION 3238a10fb81d182766c38caaee646e3f79e98e71) ### Misc Bug Fixes for v0.70 > Commit: [3238a10fb81d182766c38caaee646e3f79e98e71](https://github.com/dOpensource/dsiprouter/commit/3238a10fb81d182766c38caaee646e3f79e98e71) > Date: Fri, 23 Sep 2022 15:05:43 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix missing update for INTERNAL_FQDN in `kamailio.cfg` - fix formatting in `kamailio.cfg` --- [//]: # (END_SECTION 3238a10fb81d182766c38caaee646e3f79e98e71) [//]: # (START_SECTION a8185ebd21e1e7b26aeadda1e39c36c8d9278052) ### v0.70 Misc Fixes > Commit: [a8185ebd21e1e7b26aeadda1e39c36c8d9278052](https://github.com/dOpensource/dsiprouter/commit/a8185ebd21e1e7b26aeadda1e39c36c8d9278052) > Date: Fri, 23 Sep 2022 10:59:04 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix typo in `dsiprouter.py` - update kamailio log configs to use a standard prefix --- [//]: # (END_SECTION a8185ebd21e1e7b26aeadda1e39c36c8d9278052) [//]: # (START_SECTION 858d692c20fb15b7e897c58e428ed094423a136a) ### Update Precommit Checks > Commit: [858d692c20fb15b7e897c58e428ed094423a136a](https://github.com/dOpensource/dsiprouter/commit/858d692c20fb15b7e897c58e428ed094423a136a) > Date: Wed, 14 Sep 2022 20:25:21 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update syntax checker to add more use cases - fix bug in `dsip_lib.sh` set* regex functions --- [//]: # (END_SECTION 858d692c20fb15b7e897c58e428ed094423a136a) [//]: # (START_SECTION 53d99e58631b8304aeb76ca3e0b62143dd445f68) ### Updated CDR's: - Added support for nonCompletedCalls to the API - Exposed the Data Time Filter (dtfilter) to the CDR API - Fixed the csv option so that empty CDR's doesn't cause an error > Commit: [53d99e58631b8304aeb76ca3e0b62143dd445f68](https://github.com/dOpensource/dsiprouter/commit/53d99e58631b8304aeb76ca3e0b62143dd445f68) > Date: Wed, 14 Sep 2022 13:20:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 53d99e58631b8304aeb76ca3e0b62143dd445f68) [//]: # (START_SECTION 224667b7be2a2416e73475803fd164f99d1081da) ### Merge Homer Support > Commit: [224667b7be2a2416e73475803fd164f99d1081da](https://github.com/dOpensource/dsiprouter/commit/224667b7be2a2416e73475803fd164f99d1081da) > Date: Tue, 13 Sep 2022 15:28:04 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#456](https://github.com/dOpensource/dsiprouter/issues/456) - merge in Homer support from v0.65 - move rtpengine configs into seperate dir - add functions for managing rtpengine config to `dsip_lib.sh` - update `dsip_settings` table columns - move sections of `settings.py` to their proper locations - add / cleanup comments --- [//]: # (END_SECTION 224667b7be2a2416e73475803fd164f99d1081da) [//]: # (START_SECTION 6721e7478d45d27d4af231c34c270a87f6d98b75) ### Fix DMQ Error Messages When Not Enabled > Commit: [6721e7478d45d27d4af231c34c270a87f6d98b75](https://github.com/dOpensource/dsiprouter/commit/6721e7478d45d27d4af231c34c270a87f6d98b75) > Date: Tue, 13 Sep 2022 11:07:23 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#454](https://github.com/dOpensource/dsiprouter/issues/454) - ensure dmq relication option is disabled on htables when `WITH_DMQ` is disabled --- [//]: # (END_SECTION 6721e7478d45d27d4af231c34c270a87f6d98b75) [//]: # (START_SECTION f2a5fd1d4363af84a8bbe880bc807563bc00ca0a) ### Update CLI Documentation > Commit: [f2a5fd1d4363af84a8bbe880bc807563bc00ca0a](https://github.com/dOpensource/dsiprouter/commit/f2a5fd1d4363af84a8bbe880bc807563bc00ca0a) > Date: Mon, 12 Sep 2022 21:00:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #452 - update CLI documentation for current cmds/options - structured/re-usable cmdline documentation file to come in next release --- [//]: # (END_SECTION f2a5fd1d4363af84a8bbe880bc807563bc00ca0a) [//]: # (START_SECTION c854fb813aabca89cd859016f9b6c9d24d2988f4) ### FusionPBX Edge Case: - An Endpoing Group with FusionPBX support enabled can now be deleted properly if the FusionPBX setup doesn't complete properly during setup > Commit: [c854fb813aabca89cd859016f9b6c9d24d2988f4](https://github.com/dOpensource/dsiprouter/commit/c854fb813aabca89cd859016f9b6c9d24d2988f4) > Date: Sun, 11 Sep 2022 20:02:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c854fb813aabca89cd859016f9b6c9d24d2988f4) [//]: # (START_SECTION 7087d856b39143ba0c9ed14b4ce5c199152a9687) ### CDR Updates: - Fixed #312 - Fixed #387 - Added a CDR refresh button on the CDR page - The default soft order is based on the Call DateTime in descending order > Commit: [7087d856b39143ba0c9ed14b4ce5c199152a9687](https://github.com/dOpensource/dsiprouter/commit/7087d856b39143ba0c9ed14b4ce5c199152a9687) > Date: Sun, 11 Sep 2022 01:52:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7087d856b39143ba0c9ed14b4ce5c199152a9687) [//]: # (START_SECTION 6e25bc7cd582d62cfa688c2203be577804624435) ### Removed unused imports that were preventing the app from starting > Commit: [6e25bc7cd582d62cfa688c2203be577804624435](https://github.com/dOpensource/dsiprouter/commit/6e25bc7cd582d62cfa688c2203be577804624435) > Date: Fri, 9 Sep 2022 22:14:33 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 6e25bc7cd582d62cfa688c2203be577804624435) [//]: # (START_SECTION bfdc34035cb07a2272672d964b855c64b81a63c7) ### Update dthe file with the latest code > Commit: [bfdc34035cb07a2272672d964b855c64b81a63c7](https://github.com/dOpensource/dsiprouter/commit/bfdc34035cb07a2272672d964b855c64b81a63c7) > Date: Fri, 9 Sep 2022 21:40:14 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION bfdc34035cb07a2272672d964b855c64b81a63c7) [//]: # (START_SECTION c121e97d6aba9f566a53f017f8695903bee03556) ### updating the routes file with the latest code > Commit: [c121e97d6aba9f566a53f017f8695903bee03556](https://github.com/dOpensource/dsiprouter/commit/c121e97d6aba9f566a53f017f8695903bee03556) > Date: Fri, 9 Sep 2022 21:12:05 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION c121e97d6aba9f566a53f017f8695903bee03556) [//]: # (START_SECTION a1738694639365fc09a7f4c49ea4f94bb45d5d2a) ### Reverted Accidental changes to main terraform file > Commit: [a1738694639365fc09a7f4c49ea4f94bb45d5d2a](https://github.com/dOpensource/dsiprouter/commit/a1738694639365fc09a7f4c49ea4f94bb45d5d2a) > Date: Fri, 9 Sep 2022 18:08:52 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION a1738694639365fc09a7f4c49ea4f94bb45d5d2a) [//]: # (START_SECTION fff6275b60335280698dbb6a609a1ab34229542e) ### Added extra logic to handle inbound calls with no identity header > Commit: [fff6275b60335280698dbb6a609a1ab34229542e](https://github.com/dOpensource/dsiprouter/commit/fff6275b60335280698dbb6a609a1ab34229542e) > Date: Fri, 9 Sep 2022 10:50:24 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION fff6275b60335280698dbb6a609a1ab34229542e) [//]: # (START_SECTION 2cec15e1f3d8d856511225cbd305a35ffc7d33ba) ### Add Instance Build Script for Cloud Installs > Commit: [2cec15e1f3d8d856511225cbd305a35ffc7d33ba](https://github.com/dOpensource/dsiprouter/commit/2cec15e1f3d8d856511225cbd305a35ffc7d33ba) > Date: Wed, 7 Sep 2022 12:38:33 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - added build script for instances making automation easier to script --- [//]: # (END_SECTION 2cec15e1f3d8d856511225cbd305a35ffc7d33ba) [//]: # (START_SECTION 921d13593df8d28e981991d9a5e4aabe5ca0fffa) ### Update Image Build Logic > Commit: [921d13593df8d28e981991d9a5e4aabe5ca0fffa](https://github.com/dOpensource/dsiprouter/commit/921d13593df8d28e981991d9a5e4aabe5ca0fffa) > Date: Wed, 7 Sep 2022 08:46:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - remove check for cloud-init completion allowing script execution from cloud-init - remove cloud-init cleanup logic to allow cloud-init to finish proper boot --- [//]: # (END_SECTION 921d13593df8d28e981991d9a5e4aabe5ca0fffa) [//]: # (START_SECTION c5e6aa71ce58895c10091db1c7acfac7bb53cdef) ### FusionPBX Provisioning Update: - Added support for proxying phone provisioning profiles from FusionPBX using the native Nginx server > Commit: [c5e6aa71ce58895c10091db1c7acfac7bb53cdef](https://github.com/dOpensource/dsiprouter/commit/c5e6aa71ce58895c10091db1c7acfac7bb53cdef) > Date: Wed, 7 Sep 2022 11:59:57 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c5e6aa71ce58895c10091db1c7acfac7bb53cdef) [//]: # (START_SECTION 273915422552702005f6ad070f3c726ef4836335) ### Fix Default Network Sync Settings > Commit: [273915422552702005f6ad070f3c726ef4836335](https://github.com/dOpensource/dsiprouter/commit/273915422552702005f6ad070f3c726ef4836335) > Date: Fri, 2 Sep 2022 11:22:52 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - set network settings to empty string when resolution fails - make teardown code more reliable - fix sqlalchemy debug settings not being set --- [//]: # (END_SECTION 273915422552702005f6ad070f3c726ef4836335) [//]: # (START_SECTION 0b07c0332e0153766bc273f29979cf2beec337e8) ### IPv6 Serverside NAT Features > Commit: [0b07c0332e0153766bc273f29979cf2beec337e8](https://github.com/dOpensource/dsiprouter/commit/0b07c0332e0153766bc273f29979cf2beec337e8) > Date: Thu, 1 Sep 2022 14:34:48 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add support for isolated enablement of serverside NAT on ipv4 or ipv6 - add support for rtpengine interface switching on ipv6 w/ or w/o serverside NAT - fix a few typos - remove `-servernat` option, serverside NAT is now automatically detected/configured --- [//]: # (END_SECTION 0b07c0332e0153766bc273f29979cf2beec337e8) [//]: # (START_SECTION ae07741723edb6b01165ece997a1da2d172fb50e) ### Updated module to use new dSIP networking library > Commit: [ae07741723edb6b01165ece997a1da2d172fb50e](https://github.com/dOpensource/dsiprouter/commit/ae07741723edb6b01165ece997a1da2d172fb50e) > Date: Wed, 31 Aug 2022 01:10:09 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ae07741723edb6b01165ece997a1da2d172fb50e) [//]: # (START_SECTION 3e684eef5ec045020461fbafe4ce58245a096de2) ### Replace main.tf after removing it by accident > Commit: [3e684eef5ec045020461fbafe4ce58245a096de2](https://github.com/dOpensource/dsiprouter/commit/3e684eef5ec045020461fbafe4ce58245a096de2) > Date: Tue, 30 Aug 2022 19:32:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3e684eef5ec045020461fbafe4ce58245a096de2) [//]: # (START_SECTION bab65db299f3412abc5fa28b2dffc16aff574c55) ### User API Checkpoint > Commit: [bab65db299f3412abc5fa28b2dffc16aff574c55](https://github.com/dOpensource/dsiprouter/commit/bab65db299f3412abc5fa28b2dffc16aff574c55) > Date: Tue, 30 Aug 2022 19:25:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bab65db299f3412abc5fa28b2dffc16aff574c55) [//]: # (START_SECTION c0478fe2ab8142e746a703956b89a3f5ec69f524) ### Fix IPv6 Enabled Check > Commit: [c0478fe2ab8142e746a703956b89a3f5ec69f524](https://github.com/dOpensource/dsiprouter/commit/c0478fe2ab8142e746a703956b89a3f5ec69f524) > Date: Tue, 30 Aug 2022 08:28:45 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add check for routable IPv6 address before enabling IPv6 in kamailio --- [//]: # (END_SECTION c0478fe2ab8142e746a703956b89a3f5ec69f524) [//]: # (START_SECTION fe84f3a2e63bc5190493b469fa2524ddbc585cb7) ### General Bug Fixes for Release > Commit: [fe84f3a2e63bc5190493b469fa2524ddbc585cb7](https://github.com/dOpensource/dsiprouter/commit/fe84f3a2e63bc5190493b469fa2524ddbc585cb7) > Date: Mon, 29 Aug 2022 13:14:19 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix IPv6 address resolution issues on AWS - fix IPv6 TLS conditional configuration - add conditional checks for IPv6 logic - fix missing cloud platform in `dsip_settings` table constraint - update STIR/SHAKEN configs to utilize new flags - add `IPV6_ENABLED` setting to DB and python settings - reset defaults and remove unwanted testing variables - fix SSH banner logic - fix FQDN aliasing in kamailio config --- [//]: # (END_SECTION fe84f3a2e63bc5190493b469fa2524ddbc585cb7) [//]: # (START_SECTION a1d72b29d902e17d92a80db3eeb00b7d0e2d7123) ### Disabled IPV6 and TLS IPV6 > Commit: [a1d72b29d902e17d92a80db3eeb00b7d0e2d7123](https://github.com/dOpensource/dsiprouter/commit/a1d72b29d902e17d92a80db3eeb00b7d0e2d7123) > Date: Sun, 28 Aug 2022 23:28:40 +0000 > Author: root (root@dev-070-dsip-v0.700) > Committer: root (root@dev-070-dsip-v0.700) > Signed: --- [//]: # (END_SECTION a1d72b29d902e17d92a80db3eeb00b7d0e2d7123) [//]: # (START_SECTION 1c4ffe41812941046a1cf1c1cb869b5021273c88) ### Pushed a workaround to disable IPV6 support > Commit: [1c4ffe41812941046a1cf1c1cb869b5021273c88](https://github.com/dOpensource/dsiprouter/commit/1c4ffe41812941046a1cf1c1cb869b5021273c88) > Date: Sun, 28 Aug 2022 23:02:59 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1c4ffe41812941046a1cf1c1cb869b5021273c88) [//]: # (START_SECTION 94b70f189ccb0199130e2abfc9a87cf9c9b193a9) ### Added IPV6 Enabled Logic to Kamailio > Commit: [94b70f189ccb0199130e2abfc9a87cf9c9b193a9](https://github.com/dOpensource/dsiprouter/commit/94b70f189ccb0199130e2abfc9a87cf9c9b193a9) > Date: Sun, 28 Aug 2022 20:06:57 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 94b70f189ccb0199130e2abfc9a87cf9c9b193a9) [//]: # (START_SECTION f1bd6c453827140e70170b7cd86de21bf5e8aeed) ### IPV6 Installer Detection: --Added logic to test if traffic can be routed to an IPV6 address - if not disables it > Commit: [f1bd6c453827140e70170b7cd86de21bf5e8aeed](https://github.com/dOpensource/dsiprouter/commit/f1bd6c453827140e70170b7cd86de21bf5e8aeed) > Date: Sun, 28 Aug 2022 18:20:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f1bd6c453827140e70170b7cd86de21bf5e8aeed) [//]: # (START_SECTION fe91ef431b330c83f063b8251cbd3fc0bcbb7d4a) ### Fix Refactoring Bugs > Commit: [fe91ef431b330c83f063b8251cbd3fc0bcbb7d4a](https://github.com/dOpensource/dsiprouter/commit/fe91ef431b330c83f063b8251cbd3fc0bcbb7d4a) > Date: Fri, 26 Aug 2022 13:32:13 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix a few bugs from code refactoring in [287e9f6](https://github.com/dOpensource/dsiprouter/commit/287e9f6) --- [//]: # (END_SECTION fe91ef431b330c83f063b8251cbd3fc0bcbb7d4a) [//]: # (START_SECTION 287e9f6759b3d18f48dbf6ca170fa48be2b00c82) ### Enhanced IPv6 Support > Commit: [287e9f6759b3d18f48dbf6ca170fa48be2b00c82](https://github.com/dOpensource/dsiprouter/commit/287e9f6759b3d18f48dbf6ca170fa48be2b00c82) > Date: Fri, 26 Aug 2022 11:22:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#192](https://github.com/dOpensource/dsiprouter/issues/192) - add support for ipv6 routing - add support for ipv6 NAT/SERVERNAT environments - add support for TLS over ipv6 - add support for ipv4 <-> ipv6 translation and routing - add support for forwarding media over ipv6 - add support for transcoding over ipv6 - cleanup kamailio configs and bad formatting in code - fix ifdef checks in pre-commit hook - add some commenting to areas of codebase - add/move networking utility functions - refactor mulitiprocessing namespace - fix bug in `security.py` preventing credentials being set in some edge cases - update networking functions in `dsip_lib.sh` - update GUI auth entry functions to use fqdn instead of external ipv4 address - add support for ipv6 addresses in domain entries from GUI - update signal handling logic to handle SIGINT and SIGQUIT --- [//]: # (END_SECTION 287e9f6759b3d18f48dbf6ca170fa48be2b00c82) [//]: # (START_SECTION 460f0c26cd2bb10b4e44f1f62024525d7b1eadf6) ### Update Credential Setting SQL Statements > Commit: [460f0c26cd2bb10b4e44f1f62024525d7b1eadf6](https://github.com/dOpensource/dsiprouter/commit/460f0c26cd2bb10b4e44f1f62024525d7b1eadf6) > Date: Tue, 23 Aug 2022 12:51:28 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update SQL statements in `setCredentials()` to use the newer API - fix bug in password reset preventing cloud instance pw reset - fix rtpengine kernel module loaded check on install --- [//]: # (END_SECTION 460f0c26cd2bb10b4e44f1f62024525d7b1eadf6) [//]: # (START_SECTION bb710c9d8412c556055e4f49a406bab60142dee9) ### Fix Amazon Linux Cloud Image Password Reset > Commit: [bb710c9d8412c556055e4f49a406bab60142dee9](https://github.com/dOpensource/dsiprouter/commit/bb710c9d8412c556055e4f49a406bab60142dee9) > Date: Mon, 22 Aug 2022 15:53:05 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix a bug where cloud-init would hang on instance first boot - add `-q|--quiet` option to `resetpassword` dsiprouter command - make cloud instance initial password reset silent - refactor IPC function `sendSyncSettingsSignal()` - refactor code from `resetPassword()` into `setCredentials()` function --- [//]: # (END_SECTION bb710c9d8412c556055e4f49a406bab60142dee9) [//]: # (START_SECTION 93ae11d1afb8dc6b94739a56bcf278ae3ba8c112) ### Fix Cloud Image Password Reset > Commit: [93ae11d1afb8dc6b94739a56bcf278ae3ba8c112](https://github.com/dOpensource/dsiprouter/commit/93ae11d1afb8dc6b94739a56bcf278ae3ba8c112) > Date: Fri, 19 Aug 2022 17:20:48 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make password resetting more reliable by hooking into cloud-init --- [//]: # (END_SECTION 93ae11d1afb8dc6b94739a56bcf278ae3ba8c112) [//]: # (START_SECTION d5b61a29a7bf53a7ad91810072e4737d8939c642) ### Added back in the logic to block incoming calls if the flag is set > Commit: [d5b61a29a7bf53a7ad91810072e4737d8939c642](https://github.com/dOpensource/dsiprouter/commit/d5b61a29a7bf53a7ad91810072e4737d8939c642) > Date: Tue, 16 Aug 2022 22:12:48 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION d5b61a29a7bf53a7ad91810072e4737d8939c642) [//]: # (START_SECTION 3323aabfeea77579cf71ce92bc6e615332e5390b) ### Reload the Key Path from the UI as Well > Commit: [3323aabfeea77579cf71ce92bc6e615332e5390b](https://github.com/dOpensource/dsiprouter/commit/3323aabfeea77579cf71ce92bc6e615332e5390b) > Date: Tue, 16 Aug 2022 21:50:14 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 3323aabfeea77579cf71ce92bc6e615332e5390b) [//]: # (START_SECTION d32faad6427e6fe36d3f643fdf8585d01f6452ea) ### Used the specified signing key to sign the outbound calls > Commit: [d32faad6427e6fe36d3f643fdf8585d01f6452ea](https://github.com/dOpensource/dsiprouter/commit/d32faad6427e6fe36d3f643fdf8585d01f6452ea) > Date: Tue, 16 Aug 2022 21:19:43 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION d32faad6427e6fe36d3f643fdf8585d01f6452ea) [//]: # (START_SECTION c028ac3999a88df3c2c62a342e1918f31ea9f0b8) ### Added an entry for the certificate key path > Commit: [c028ac3999a88df3c2c62a342e1918f31ea9f0b8](https://github.com/dOpensource/dsiprouter/commit/c028ac3999a88df3c2c62a342e1918f31ea9f0b8) > Date: Tue, 16 Aug 2022 20:33:30 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION c028ac3999a88df3c2c62a342e1918f31ea9f0b8) [//]: # (START_SECTION 6b1eec58bb6b77641f782b5414f0ba2980fb4317) ### update kamalio log messages > Commit: [6b1eec58bb6b77641f782b5414f0ba2980fb4317](https://github.com/dOpensource/dsiprouter/commit/6b1eec58bb6b77641f782b5414f0ba2980fb4317) > Date: Tue, 16 Aug 2022 19:11:12 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 6b1eec58bb6b77641f782b5414f0ba2980fb4317) [//]: # (START_SECTION 6fa0a5b3ae7d5ac1d267cfbb7937a283ec3c3598) ### Improve RTPEngine Kernel Module Compilation > Commit: [6fa0a5b3ae7d5ac1d267cfbb7937a283ec3c3598](https://github.com/dOpensource/dsiprouter/commit/6fa0a5b3ae7d5ac1d267cfbb7937a283ec3c3598) > Date: Tue, 16 Aug 2022 13:24:51 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - installer will now compile `xt_RTPENGINE.ko` for all installed kernels --- [//]: # (END_SECTION 6fa0a5b3ae7d5ac1d267cfbb7937a283ec3c3598) [//]: # (START_SECTION e61d6d6e9ff125381d4813a5db0782eff5066e66) ### Fix Password Resetting Bug > Commit: [e61d6d6e9ff125381d4813a5db0782eff5066e66](https://github.com/dOpensource/dsiprouter/commit/e61d6d6e9ff125381d4813a5db0782eff5066e66) > Date: Tue, 16 Aug 2022 10:50:45 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed bug where `resetpassword` would not default to gui password with `-fid` option --- [//]: # (END_SECTION e61d6d6e9ff125381d4813a5db0782eff5066e66) [//]: # (START_SECTION 466bdfbc9a916580d2e70950af6fb54ef4cf8bb5) ### Fix Cloud Platform Password Reset Boot Hang > Commit: [466bdfbc9a916580d2e70950af6fb54ef4cf8bb5](https://github.com/dOpensource/dsiprouter/commit/466bdfbc9a916580d2e70950af6fb54ef4cf8bb5) > Date: Tue, 16 Aug 2022 08:51:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - reverted password resetting for cloud platforms to use cron instead of systemd --- [//]: # (END_SECTION 466bdfbc9a916580d2e70950af6fb54ef4cf8bb5) [//]: # (START_SECTION b5e8df8a4d4b82994d6be2b6a0dbbd26dcb34e5a) ### Cloud Deployment Fixes > Commit: [b5e8df8a4d4b82994d6be2b6a0dbbd26dcb34e5a](https://github.com/dOpensource/dsiprouter/commit/b5e8df8a4d4b82994d6be2b6a0dbbd26dcb34e5a) > Date: Mon, 15 Aug 2022 17:49:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed dependency ordering issue on rhel-based distro's - fixed bug in current version of debian-based `cloud-init.service` - updated `pre-snapshot.sh` to be slightly faster - fixed dnsmasq/cloud-init host resolution conflicts - fixed dhclient/cloud-init DNS resolution conflicts - standardized `kamailio.service` unit file - added STIR/SHAKEN support in debian10/debian9 installation scripts --- [//]: # (END_SECTION b5e8df8a4d4b82994d6be2b6a0dbbd26dcb34e5a) [//]: # (START_SECTION 431f37ffd6cb1daba38e2a6f654da35dbd81c3a2) ### Fix Ordering of Default Carriers > Commit: [431f37ffd6cb1daba38e2a6f654da35dbd81c3a2](https://github.com/dOpensource/dsiprouter/commit/431f37ffd6cb1daba38e2a6f654da35dbd81c3a2) > Date: Thu, 11 Aug 2022 13:37:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 431f37ffd6cb1daba38e2a6f654da35dbd81c3a2) [//]: # (START_SECTION d50f15b4c4eb65069dbaae31e3bc5b86075bd57f) ### Fix pre-commit and requirements.txt > Commit: [d50f15b4c4eb65069dbaae31e3bc5b86075bd57f](https://github.com/dOpensource/dsiprouter/commit/d50f15b4c4eb65069dbaae31e3bc5b86075bd57f) > Date: Tue, 9 Aug 2022 09:40:24 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION d50f15b4c4eb65069dbaae31e3bc5b86075bd57f) [//]: # (START_SECTION 56c39e111ed66326b50447bb6ed12826e5cd74cd) ### Update pre-commit > Commit: [56c39e111ed66326b50447bb6ed12826e5cd74cd](https://github.com/dOpensource/dsiprouter/commit/56c39e111ed66326b50447bb6ed12826e5cd74cd) > Date: Mon, 8 Aug 2022 22:22:57 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 56c39e111ed66326b50447bb6ed12826e5cd74cd) [//]: # (START_SECTION 96221e574830aea41cfc23c9237cbc6a336a8e95) ### Bug Fixes for Updated OS Support > Commit: [96221e574830aea41cfc23c9237cbc6a336a8e95](https://github.com/dOpensource/dsiprouter/commit/96221e574830aea41cfc23c9237cbc6a336a8e95) > Date: Mon, 8 Aug 2022 15:36:23 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update `dsiprouter.service` to stop/restart `nginx.service` - move nginx install into seperate directory - normalize nginx service for better support across OS - fix typo in mysql install - remove unsupported debian/ubuntu scripts - fix `requirements.txt` regression and update pre-commit hook --- [//]: # (END_SECTION 96221e574830aea41cfc23c9237cbc6a336a8e95) [//]: # (START_SECTION ccea19047f2b1959fe0bbc0e6cf70a66576e5d15) ### Update OS Installation Support > Commit: [ccea19047f2b1959fe0bbc0e6cf70a66576e5d15](https://github.com/dOpensource/dsiprouter/commit/ccea19047f2b1959fe0bbc0e6cf70a66576e5d15) > Date: Wed, 3 Aug 2022 11:39:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update debian11 support to STABLE - add STABLE support for amazon linux 2 - add ALPHA support for redhat linux 8 - add ALPHA support for alma linux 8 - add ALPHA support for rocky linux 8 - add ALPHA support for ubuntu 22.04 - add ALPHA support for ubuntu 20.04 - deprecate support for debian 8 - deprecate support for centos 8 - deprecate support for centos 7 - deprecate support for ubuntu 16.04 - fix cloud-init/dsip-init service integration - fix dsiprouter /etc/hosts management - fix dsiprouter apt repo management - fix dnsmasq installation - add WIP for dsiprouter /etc/resolv.conf management - add cached DNS name updates to dsip-init and cron - add backwards compatibility for systemd v219 - fix kernel image/header conflicts in image creation - move permissions changes to new `chown` dsiprouter command - fix `clusterinstall` not copying project files on second run - update source installs to reuse source files on additional install attempts - add back in support for configuring homer on install - fix `-dsipkey` arg parsing when setting dsip private key - add comments and deprecation notices - fix missing kamailio config settings update on install - refactor kamailio config locations - move dsiprouter services to `/lib/systemd/system` *aligning with most installers* - move dsiprouter script to `/usr/bin` *aligning with most installers* - add automatic servernat detection `-servernat` now overrides this - update rtpengine version - update ip / hostname resolution bash functions to align with python ones - add networking functions needed for IPv6 support - update `build_image.sh` to support passing args via any cloud provisioner - fix locale not set on some cloud images - fix dependency loop on `dsip-init` with subsequent install attempts - fix vulter cloud install check - add WIP libstirshaken openssl 3.x.x support - fix `setCredentials` not setting password in some cases --- [//]: # (END_SECTION ccea19047f2b1959fe0bbc0e6cf70a66576e5d15) [//]: # (START_SECTION 1e029d26dcbbc3ce204cfe49d25502d1ec23355c) ### user CRUD checkpoint > Commit: [1e029d26dcbbc3ce204cfe49d25502d1ec23355c](https://github.com/dOpensource/dsiprouter/commit/1e029d26dcbbc3ce204cfe49d25502d1ec23355c) > Date: Thu, 28 Jul 2022 13:07:23 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 1e029d26dcbbc3ce204cfe49d25502d1ec23355c) [//]: # (START_SECTION 37b20891477f13d5f7d4bd166cf461329d733e32) ### Login logic working > Commit: [37b20891477f13d5f7d4bd166cf461329d733e32](https://github.com/dOpensource/dsiprouter/commit/37b20891477f13d5f7d4bd166cf461329d733e32) > Date: Wed, 27 Jul 2022 23:48:29 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 37b20891477f13d5f7d4bd166cf461329d733e32) [//]: # (START_SECTION 781eff6363f2efb44e47ddb863651be491421106) ### User API Checkpoint > Commit: [781eff6363f2efb44e47ddb863651be491421106](https://github.com/dOpensource/dsiprouter/commit/781eff6363f2efb44e47ddb863651be491421106) > Date: Wed, 27 Jul 2022 11:36:06 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 781eff6363f2efb44e47ddb863651be491421106) [//]: # (START_SECTION 9f3515967f6a05789cce7aba8b03bb107ae5582f) ### Updated Terraform Scripts > Commit: [9f3515967f6a05789cce7aba8b03bb107ae5582f](https://github.com/dOpensource/dsiprouter/commit/9f3515967f6a05789cce7aba8b03bb107ae5582f) > Date: Tue, 26 Jul 2022 23:13:10 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9f3515967f6a05789cce7aba8b03bb107ae5582f) [//]: # (START_SECTION 99fed4a8840a27dfe1e4de27b95374787874f71f) ### Added the file keyword > Commit: [99fed4a8840a27dfe1e4de27b95374787874f71f](https://github.com/dOpensource/dsiprouter/commit/99fed4a8840a27dfe1e4de27b95374787874f71f) > Date: Mon, 25 Jul 2022 11:03:13 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 99fed4a8840a27dfe1e4de27b95374787874f71f) [//]: # (START_SECTION dffdbab53a21c0b96b6fca16f5bf772f0921dcf8) ### Tweaks to the stir shaken setup > Commit: [dffdbab53a21c0b96b6fca16f5bf772f0921dcf8](https://github.com/dOpensource/dsiprouter/commit/dffdbab53a21c0b96b6fca16f5bf772f0921dcf8) > Date: Sun, 10 Jul 2022 09:31:22 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION dffdbab53a21c0b96b6fca16f5bf772f0921dcf8) [//]: # (START_SECTION 000e370c48da86b40a2d68259c8f42238ca83df7) ### relocated apt command for libavcodec-extra > Commit: [000e370c48da86b40a2d68259c8f42238ca83df7](https://github.com/dOpensource/dsiprouter/commit/000e370c48da86b40a2d68259c8f42238ca83df7) > Date: Thu, 7 Jul 2022 10:34:53 -0700 > Author: TuxPowered (vw.jetta.dude@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: - Relocaed install of libavcodec-extra to main block to be included in all debian builds. --- [//]: # (END_SECTION 000e370c48da86b40a2d68259c8f42238ca83df7) [//]: # (START_SECTION d3b28a7ce77894f343e51cd6a9bbd78cea91c1b5) ### Added Build dependency for RTPEngine > Commit: [d3b28a7ce77894f343e51cd6a9bbd78cea91c1b5](https://github.com/dOpensource/dsiprouter/commit/d3b28a7ce77894f343e51cd6a9bbd78cea91c1b5) > Date: Thu, 7 Jul 2022 10:27:49 -0700 > Author: TuxPowered (vw.jetta.dude@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: - RTPEngine fails to build on Debian 10. - Added cmake dependency and libavcodec-extra. - libavcodec-extra - is recommended by RTPEngine github on debian installs to support wider codecs - You can not build using - `dsiprouter.sh install -rtp` or `dsiprouter.sh install -all -servernat` without getting the RTPEngine failed to install error. --- [//]: # (END_SECTION d3b28a7ce77894f343e51cd6a9bbd78cea91c1b5) [//]: # (START_SECTION 62080422649deb5cda3c5ea671c1deb34e256b1a) ### Create FUNDING.yml > Commit: [62080422649deb5cda3c5ea671c1deb34e256b1a](https://github.com/dOpensource/dsiprouter/commit/62080422649deb5cda3c5ea671c1deb34e256b1a) > Date: Thu, 30 Jun 2022 14:38:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62080422649deb5cda3c5ea671c1deb34e256b1a) [//]: # (START_SECTION 0391b278289365f96fc6508b6c7da7e49e01706d) ### Added a tag to the bcg729 repo which forces a specific version > Commit: [0391b278289365f96fc6508b6c7da7e49e01706d](https://github.com/dOpensource/dsiprouter/commit/0391b278289365f96fc6508b6c7da7e49e01706d) > Date: Wed, 29 Jun 2022 06:30:51 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0391b278289365f96fc6508b6c7da7e49e01706d) [//]: # (START_SECTION 48cd1ec6288a0ff4eee7a80b8008f2cd9cd05aa2) ### Added support for installing the json module > Commit: [48cd1ec6288a0ff4eee7a80b8008f2cd9cd05aa2](https://github.com/dOpensource/dsiprouter/commit/48cd1ec6288a0ff4eee7a80b8008f2cd9cd05aa2) > Date: Wed, 29 Jun 2022 04:48:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 48cd1ec6288a0ff4eee7a80b8008f2cd9cd05aa2) [//]: # (START_SECTION 06ca6ce25a2c753d46405b198407cc7db62d2b03) ### Added Kamailio logic to block invalidated calls > Commit: [06ca6ce25a2c753d46405b198407cc7db62d2b03](https://github.com/dOpensource/dsiprouter/commit/06ca6ce25a2c753d46405b198407cc7db62d2b03) > Date: Mon, 27 Jun 2022 16:55:44 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 06ca6ce25a2c753d46405b198407cc7db62d2b03) [//]: # (START_SECTION 1e1bf37e1dafaf7c366702da190a778a0736ea02) ### Use the libbcg729 library on Debian Bullseyee > Commit: [1e1bf37e1dafaf7c366702da190a778a0736ea02](https://github.com/dOpensource/dsiprouter/commit/1e1bf37e1dafaf7c366702da190a778a0736ea02) > Date: Mon, 27 Jun 2022 05:14:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1e1bf37e1dafaf7c366702da190a778a0736ea02) [//]: # (START_SECTION 11bc68b1196f2232e074eb965496c77182d2155a) ### Update 11.sh > Commit: [11bc68b1196f2232e074eb965496c77182d2155a](https://github.com/dOpensource/dsiprouter/commit/11bc68b1196f2232e074eb965496c77182d2155a) > Date: Sun, 26 Jun 2022 23:53:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Added support for json --- [//]: # (END_SECTION 11bc68b1196f2232e074eb965496c77182d2155a) [//]: # (START_SECTION 383a57bb4bc9ded9ad28942670dcb7a2022fdaf5) ### Update 11.sh > Commit: [383a57bb4bc9ded9ad28942670dcb7a2022fdaf5](https://github.com/dOpensource/dsiprouter/commit/383a57bb4bc9ded9ad28942670dcb7a2022fdaf5) > Date: Sun, 26 Jun 2022 23:35:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 383a57bb4bc9ded9ad28942670dcb7a2022fdaf5) [//]: # (START_SECTION d71a50bd712c066bbd538ecee5a3b089d1e4d829) ### Refactoring the Transnexus custom logic include > Commit: [d71a50bd712c066bbd538ecee5a3b089d1e4d829](https://github.com/dOpensource/dsiprouter/commit/d71a50bd712c066bbd538ecee5a3b089d1e4d829) > Date: Thu, 23 Jun 2022 21:00:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d71a50bd712c066bbd538ecee5a3b089d1e4d829) [//]: # (START_SECTION b4b5cc7f8f826c07956bff054d30c84ff5e8de49) ### Changed logic that updates the TLS address in tls.cfg > Commit: [b4b5cc7f8f826c07956bff054d30c84ff5e8de49](https://github.com/dOpensource/dsiprouter/commit/b4b5cc7f8f826c07956bff054d30c84ff5e8de49) > Date: Wed, 22 Jun 2022 19:27:50 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b4b5cc7f8f826c07956bff054d30c84ff5e8de49) [//]: # (START_SECTION 9c62c9ed74006af75e9c679498f5e91a39cf9200) ### Made STIR/SHAKEN and TransNexus STIR/SHAKEN module off by default > Commit: [9c62c9ed74006af75e9c679498f5e91a39cf9200](https://github.com/dOpensource/dsiprouter/commit/9c62c9ed74006af75e9c679498f5e91a39cf9200) > Date: Wed, 15 Jun 2022 14:12:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9c62c9ed74006af75e9c679498f5e91a39cf9200) [//]: # (START_SECTION 6f44e913e57a62e8f4d4ecca83fa84cbf35cbd60) ### Fixed Stir/Shaken: - Fixed the UI - The proper orgination and destination number will be set in the identity header on outbound calls - Fixed the Kamailio Reload > Commit: [6f44e913e57a62e8f4d4ecca83fa84cbf35cbd60](https://github.com/dOpensource/dsiprouter/commit/6f44e913e57a62e8f4d4ecca83fa84cbf35cbd60) > Date: Wed, 15 Jun 2022 12:56:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6f44e913e57a62e8f4d4ecca83fa84cbf35cbd60) [//]: # (START_SECTION 5e305d87a1466106c6658c64d1454a0b1e44ad36) ### Fixed issues with installing the stirshaken libaries > Commit: [5e305d87a1466106c6658c64d1454a0b1e44ad36](https://github.com/dOpensource/dsiprouter/commit/5e305d87a1466106c6658c64d1454a0b1e44ad36) > Date: Wed, 15 Jun 2022 01:46:44 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5e305d87a1466106c6658c64d1454a0b1e44ad36) [//]: # (START_SECTION 912aa73b0dc8f3c8bc684b22e92f0af878ba1a85) ### Added the VerifyService to TransNexus module > Commit: [912aa73b0dc8f3c8bc684b22e92f0af878ba1a85](https://github.com/dOpensource/dsiprouter/commit/912aa73b0dc8f3c8bc684b22e92f0af878ba1a85) > Date: Tue, 14 Jun 2022 18:18:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 912aa73b0dc8f3c8bc684b22e92f0af878ba1a85) [//]: # (START_SECTION 362c144d2ab056c0bfd03d8221ace09767ba5841) ### Fixed an issue with LRN not selecting the first contact to try > Commit: [362c144d2ab056c0bfd03d8221ace09767ba5841](https://github.com/dOpensource/dsiprouter/commit/362c144d2ab056c0bfd03d8221ace09767ba5841) > Date: Mon, 6 Jun 2022 14:09:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 362c144d2ab056c0bfd03d8221ace09767ba5841) [//]: # (START_SECTION 08458ff1258fa668ed48edf0e00b9bf6aa38f07a) ### Changed LRN support to sequential routing versus parallel routing > Commit: [08458ff1258fa668ed48edf0e00b9bf6aa38f07a](https://github.com/dOpensource/dsiprouter/commit/08458ff1258fa668ed48edf0e00b9bf6aa38f07a) > Date: Sun, 5 Jun 2022 19:00:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 08458ff1258fa668ed48edf0e00b9bf6aa38f07a) [//]: # (START_SECTION 42681d1ec76c6b4b64acec9fa44c1c1c8821be56) ### Fixed a typo that prevented the UI from loading teh config values > Commit: [42681d1ec76c6b4b64acec9fa44c1c1c8821be56](https://github.com/dOpensource/dsiprouter/commit/42681d1ec76c6b4b64acec9fa44c1c1c8821be56) > Date: Tue, 17 May 2022 09:24:25 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 42681d1ec76c6b4b64acec9fa44c1c1c8821be56) [//]: # (START_SECTION c40702e443e6c3fece31cc66338d051527067d88) ### UI Updates > Commit: [c40702e443e6c3fece31cc66338d051527067d88](https://github.com/dOpensource/dsiprouter/commit/c40702e443e6c3fece31cc66338d051527067d88) > Date: Tue, 17 May 2022 08:42:19 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION c40702e443e6c3fece31cc66338d051527067d88) [//]: # (START_SECTION 6fa6d008767b45244978e9b0c53998f5cb3116d8) ### Integrated the UI with the Settings reloading > Commit: [6fa6d008767b45244978e9b0c53998f5cb3116d8](https://github.com/dOpensource/dsiprouter/commit/6fa6d008767b45244978e9b0c53998f5cb3116d8) > Date: Tue, 17 May 2022 07:44:09 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 6fa6d008767b45244978e9b0c53998f5cb3116d8) [//]: # (START_SECTION 92689753326c9a96f642d5cda16b08378ea96c7d) ### Added support to bypass TransNexus STIR/SHAKEN when calling emergency numbers > Commit: [92689753326c9a96f642d5cda16b08378ea96c7d](https://github.com/dOpensource/dsiprouter/commit/92689753326c9a96f642d5cda16b08378ea96c7d) > Date: Mon, 16 May 2022 06:29:17 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 92689753326c9a96f642d5cda16b08378ea96c7d) [//]: # (START_SECTION 7d76b585c66154d3542e92329c53ae313b6d0ae2) ### STIR SHAKEN inbound call checkpoint > Commit: [7d76b585c66154d3542e92329c53ae313b6d0ae2](https://github.com/dOpensource/dsiprouter/commit/7d76b585c66154d3542e92329c53ae313b6d0ae2) > Date: Fri, 13 May 2022 18:01:17 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 7d76b585c66154d3542e92329c53ae313b6d0ae2) [//]: # (START_SECTION 2466ccf4a9638b5428a9e515aa47b52e476b1840) ### Load the stirshaken module > Commit: [2466ccf4a9638b5428a9e515aa47b52e476b1840](https://github.com/dOpensource/dsiprouter/commit/2466ccf4a9638b5428a9e515aa47b52e476b1840) > Date: Fri, 13 May 2022 12:54:34 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 2466ccf4a9638b5428a9e515aa47b52e476b1840) [//]: # (START_SECTION 4a3ad21a37a98251a2412d22294faf1f81df6b1b) ### Removed unused code > Commit: [4a3ad21a37a98251a2412d22294faf1f81df6b1b](https://github.com/dOpensource/dsiprouter/commit/4a3ad21a37a98251a2412d22294faf1f81df6b1b) > Date: Fri, 13 May 2022 12:46:24 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 4a3ad21a37a98251a2412d22294faf1f81df6b1b) [//]: # (START_SECTION 9c2fbcae2aec2174783e03b53e978e7a9fa62a2f) ### Testing the incoming calls > Commit: [9c2fbcae2aec2174783e03b53e978e7a9fa62a2f](https://github.com/dOpensource/dsiprouter/commit/9c2fbcae2aec2174783e03b53e978e7a9fa62a2f) > Date: Fri, 13 May 2022 12:31:01 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 9c2fbcae2aec2174783e03b53e978e7a9fa62a2f) [//]: # (START_SECTION 5cb96769844687f19c809c8b25b37e595256a4bf) ### Added Support for Native Kamailio STIR/Shaken Support > Commit: [5cb96769844687f19c809c8b25b37e595256a4bf](https://github.com/dOpensource/dsiprouter/commit/5cb96769844687f19c809c8b25b37e595256a4bf) > Date: Fri, 13 May 2022 06:21:37 -0600 > Author: Maurice Rogers (cruzer45@gmail.com) > Committer: Maurice Rogers (cruzer45@gmail.com) > Signed: --- [//]: # (END_SECTION 5cb96769844687f19c809c8b25b37e595256a4bf) [//]: # (START_SECTION 43da59b8384e66f889ae00548fbbca76c55c6bc6) ### Added toggle switch to enable or disable transnexus LRN > Commit: [43da59b8384e66f889ae00548fbbca76c55c6bc6](https://github.com/dOpensource/dsiprouter/commit/43da59b8384e66f889ae00548fbbca76c55c6bc6) > Date: Thu, 12 May 2022 18:08:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 43da59b8384e66f889ae00548fbbca76c55c6bc6) [//]: # (START_SECTION 56d29640a2d9e59628534fc959b7f66c4eae6673) ### Fixed Mariadb systemctl service script > Commit: [56d29640a2d9e59628534fc959b7f66c4eae6673](https://github.com/dOpensource/dsiprouter/commit/56d29640a2d9e59628534fc959b7f66c4eae6673) > Date: Tue, 10 May 2022 13:47:49 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 56d29640a2d9e59628534fc959b7f66c4eae6673) [//]: # (START_SECTION 34e30b6dc3e50e80f8cfa229416d4c409fc49832) ### Fixed an issue that caused the BYE from the endpoints not to work properly > Commit: [34e30b6dc3e50e80f8cfa229416d4c409fc49832](https://github.com/dOpensource/dsiprouter/commit/34e30b6dc3e50e80f8cfa229416d4c409fc49832) > Date: Sat, 7 May 2022 01:43:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 34e30b6dc3e50e80f8cfa229416d4c409fc49832) [//]: # (START_SECTION 2a362b9331c4ee2e7b5473712ca54d98643586b2) ### Transnexus Module Updates: - Now supports LCR - Contacts are followed if they are outside of the clearip domain - Fixed an issue that prevented the dSIPRouter carrier route from being followed - Fixed a bug that stopped the Transnexus module from being enabled during dSIPRouter startup > Commit: [2a362b9331c4ee2e7b5473712ca54d98643586b2](https://github.com/dOpensource/dsiprouter/commit/2a362b9331c4ee2e7b5473712ca54d98643586b2) > Date: Thu, 5 May 2022 02:23:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2a362b9331c4ee2e7b5473712ca54d98643586b2) [//]: # (START_SECTION 3ce7eca6f9e31f5351260e085a4e720ede16d066) ### Will automatically setup up the realm properly for Twilio Programmable Voice (sip.twilio.com) > Commit: [3ce7eca6f9e31f5351260e085a4e720ede16d066](https://github.com/dOpensource/dsiprouter/commit/3ce7eca6f9e31f5351260e085a4e720ede16d066) > Date: Fri, 29 Apr 2022 05:36:59 +0000 > Author: root (root@ip-172-31-19-223.ec2.internal) > Committer: root (root@ip-172-31-19-223.ec2.internal) > Signed: --- [//]: # (END_SECTION 3ce7eca6f9e31f5351260e085a4e720ede16d066) [//]: # (START_SECTION 1ba6465cfffd6c80d1e0f07314cda1eee1dd25ed) ### Added clarity to debian10 set-hostname > Commit: [1ba6465cfffd6c80d1e0f07314cda1eee1dd25ed](https://github.com/dOpensource/dsiprouter/commit/1ba6465cfffd6c80d1e0f07314cda1eee1dd25ed) > Date: Wed, 27 Apr 2022 23:43:52 -0700 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1ba6465cfffd6c80d1e0f07314cda1eee1dd25ed) [//]: # (START_SECTION 6a9f9b2de498ad93c7d84e92f7be8c43d7fb146b) ### Added additional details to outbound routes. > Commit: [6a9f9b2de498ad93c7d84e92f7be8c43d7fb146b](https://github.com/dOpensource/dsiprouter/commit/6a9f9b2de498ad93c7d84e92f7be8c43d7fb146b) > Date: Wed, 27 Apr 2022 23:20:50 -0700 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a9f9b2de498ad93c7d84e92f7be8c43d7fb146b) [//]: # (START_SECTION bc32b97a11ff4729a060685a30ae8a019a5491f0) ### Removed EOL Pops from Flowroute > Commit: [bc32b97a11ff4729a060685a30ae8a019a5491f0](https://github.com/dOpensource/dsiprouter/commit/bc32b97a11ff4729a060685a30ae8a019a5491f0) > Date: Mon, 25 Apr 2022 16:27:47 -0700 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bc32b97a11ff4729a060685a30ae8a019a5491f0) [//]: # (START_SECTION 6fe3c84fec77ae9ec554c2eea18eb74e1982bcff) ### Removed EOL PoPs from Flowroute > Commit: [6fe3c84fec77ae9ec554c2eea18eb74e1982bcff](https://github.com/dOpensource/dsiprouter/commit/6fe3c84fec77ae9ec554c2eea18eb74e1982bcff) > Date: Mon, 25 Apr 2022 16:27:12 -0700 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6fe3c84fec77ae9ec554c2eea18eb74e1982bcff) [//]: # (START_SECTION 2f6b076c27cdb5c324a9ccfeab9960c6b8952ab7) ### Removed EOL PoPs from Flowroute > Commit: [2f6b076c27cdb5c324a9ccfeab9960c6b8952ab7](https://github.com/dOpensource/dsiprouter/commit/2f6b076c27cdb5c324a9ccfeab9960c6b8952ab7) > Date: Mon, 25 Apr 2022 16:26:22 -0700 > Author: VOICE1 (voice1me@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2f6b076c27cdb5c324a9ccfeab9960c6b8952ab7) [//]: # (START_SECTION e9f3dd7059bd73e4a73ebee4e5eabd7bfaffbdc5) ### Added our v2 License Manager - Initial commit > Commit: [e9f3dd7059bd73e4a73ebee4e5eabd7bfaffbdc5](https://github.com/dOpensource/dsiprouter/commit/e9f3dd7059bd73e4a73ebee4e5eabd7bfaffbdc5) > Date: Mon, 25 Apr 2022 05:55:25 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e9f3dd7059bd73e4a73ebee4e5eabd7bfaffbdc5) [//]: # (START_SECTION 162e3ed4fdc9e48d0f5e2c061cbb1d44db317735) ### dSIPRouter Installer: - Silenced a warning message when installing Kamailio > Commit: [162e3ed4fdc9e48d0f5e2c061cbb1d44db317735](https://github.com/dOpensource/dsiprouter/commit/162e3ed4fdc9e48d0f5e2c061cbb1d44db317735) > Date: Sun, 24 Apr 2022 12:15:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 162e3ed4fdc9e48d0f5e2c061cbb1d44db317735) [//]: # (START_SECTION 34e29bcb6fef9fea67de067928e27aa861ee4063) ### dSIPRouter on Debian 11: - Added the python3-mysqldb to the Debian 11 installer so that dSIPRouter installs properly > Commit: [34e29bcb6fef9fea67de067928e27aa861ee4063](https://github.com/dOpensource/dsiprouter/commit/34e29bcb6fef9fea67de067928e27aa861ee4063) > Date: Sat, 23 Apr 2022 10:42:20 +0000 > Author: Mack Hendicks (mack.hendricks@gmail.com) > Committer: Mack Hendicks (mack.hendricks@gmail.com) > Signed: --- [//]: # (END_SECTION 34e29bcb6fef9fea67de067928e27aa861ee4063) [//]: # (START_SECTION c879639b5cd8d54da6e96ca9a3f474e734d14594) ### Fixes for RTPEngine for Debian 11 - Implemented an override for the rtpengine version to mr10.4.1.1 - Added required packages for mr10.4.1.1 - Added logic to ensure rtpengine is installed properly on Debian 11 > Commit: [c879639b5cd8d54da6e96ca9a3f474e734d14594](https://github.com/dOpensource/dsiprouter/commit/c879639b5cd8d54da6e96ca9a3f474e734d14594) > Date: Sat, 23 Apr 2022 09:08:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c879639b5cd8d54da6e96ca9a3f474e734d14594) [//]: # (START_SECTION c83d719784414c97bbf89b9d6f991582b82dfaa1) ### Fixed Install for Debian 11 - Changed the Kamailio version to 5.5 > Commit: [c83d719784414c97bbf89b9d6f991582b82dfaa1](https://github.com/dOpensource/dsiprouter/commit/c83d719784414c97bbf89b9d6f991582b82dfaa1) > Date: Sat, 23 Apr 2022 03:18:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c83d719784414c97bbf89b9d6f991582b82dfaa1) [//]: # (START_SECTION 6eb5efb7f4a3be4515745cdbe8951c5ee5a3b14a) ### Fixed issue with looping ACK's > Commit: [6eb5efb7f4a3be4515745cdbe8951c5ee5a3b14a](https://github.com/dOpensource/dsiprouter/commit/6eb5efb7f4a3be4515745cdbe8951c5ee5a3b14a) > Date: Thu, 21 Apr 2022 17:44:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6eb5efb7f4a3be4515745cdbe8951c5ee5a3b14a) [//]: # (START_SECTION c809094393142acd2ac025f641d920d7182136e5) ### Twilio Username/Password Auth Fixes: - Hardcoded logic that sets the realm to sip.twilio.com, which allows username/password auth to work - Fixed a bug that caused the password not to be saved when updating the CarrierGroup > Commit: [c809094393142acd2ac025f641d920d7182136e5](https://github.com/dOpensource/dsiprouter/commit/c809094393142acd2ac025f641d920d7182136e5) > Date: Thu, 21 Apr 2022 03:07:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c809094393142acd2ac025f641d920d7182136e5) [//]: # (START_SECTION eab2b7b5092a97bfa14369902d5701ac58313944) ### TransNexus Update - Changed the default outbound server address to sip.clearip.com > Commit: [eab2b7b5092a97bfa14369902d5701ac58313944](https://github.com/dOpensource/dsiprouter/commit/eab2b7b5092a97bfa14369902d5701ac58313944) > Date: Thu, 21 Apr 2022 03:03:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eab2b7b5092a97bfa14369902d5701ac58313944) [//]: # (START_SECTION 26294003c2dbb0f98ba0fbacd2498eb91fcbf0ec) ### Added suppot for TransNexus sending either X-Identity or Identity with the identity information > Commit: [26294003c2dbb0f98ba0fbacd2498eb91fcbf0ec](https://github.com/dOpensource/dsiprouter/commit/26294003c2dbb0f98ba0fbacd2498eb91fcbf0ec) > Date: Fri, 15 Apr 2022 23:33:16 +0000 > Author: root (root@guest.guest) > Committer: root (root@guest.guest) > Signed: --- [//]: # (END_SECTION 26294003c2dbb0f98ba0fbacd2498eb91fcbf0ec) [//]: # (START_SECTION f477820eb11f1af06e73b4f022c8059a4e6b78e3) ### Added logic to install dSIPRouter Kamailio module includes > Commit: [f477820eb11f1af06e73b4f022c8059a4e6b78e3](https://github.com/dOpensource/dsiprouter/commit/f477820eb11f1af06e73b4f022c8059a4e6b78e3) > Date: Wed, 13 Apr 2022 11:27:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f477820eb11f1af06e73b4f022c8059a4e6b78e3) [//]: # (START_SECTION c71a5ca053c4deecd0843da72208315964c45569) ### Added logic to handle updating Transnexus Settings via the UI and during startup of dSIPRouter > Commit: [c71a5ca053c4deecd0843da72208315964c45569](https://github.com/dOpensource/dsiprouter/commit/c71a5ca053c4deecd0843da72208315964c45569) > Date: Sun, 10 Apr 2022 19:52:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c71a5ca053c4deecd0843da72208315964c45569) [//]: # (START_SECTION 2a2878e14b3a04aefd74efd015a86ff7d4efacc0) ### Added support for Transnexus UI > Commit: [2a2878e14b3a04aefd74efd015a86ff7d4efacc0](https://github.com/dOpensource/dsiprouter/commit/2a2878e14b3a04aefd74efd015a86ff7d4efacc0) > Date: Sun, 10 Apr 2022 15:20:46 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2a2878e14b3a04aefd74efd015a86ff7d4efacc0) [//]: # (START_SECTION 6b90261785a05f79a7ef429d66e15302409f9d78) ### Updated CarrierGroups - Added logic to handle updating the Name of the carrier group - Updated CarrierGroup Javascript to handle updates from the UI - Configured CarrierGroup page so that it reloads after an update > Commit: [6b90261785a05f79a7ef429d66e15302409f9d78](https://github.com/dOpensource/dsiprouter/commit/6b90261785a05f79a7ef429d66e15302409f9d78) > Date: Sun, 10 Apr 2022 00:02:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6b90261785a05f79a7ef429d66e15302409f9d78) [//]: # (START_SECTION a5c9d5b01d592c3d619cf7eac6ac86978a2a159c) ### Fixes: - Pinned the version of Jinja2 and http server Fixes #429 - Debian Sources for the current version of the OS will be added to the system versus for all versions Fixes #406 > Commit: [a5c9d5b01d592c3d619cf7eac6ac86978a2a159c](https://github.com/dOpensource/dsiprouter/commit/a5c9d5b01d592c3d619cf7eac6ac86978a2a159c) > Date: Sun, 3 Apr 2022 00:30:45 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a5c9d5b01d592c3d619cf7eac6ac86978a2a159c) [//]: # (START_SECTION d7733ecb8cefd4f46b082145e7dad84b3d675389) ### Added libffi-dev to Debian 10 install file. Fixes #86 > Commit: [d7733ecb8cefd4f46b082145e7dad84b3d675389](https://github.com/dOpensource/dsiprouter/commit/d7733ecb8cefd4f46b082145e7dad84b3d675389) > Date: Sat, 2 Apr 2022 21:52:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d7733ecb8cefd4f46b082145e7dad84b3d675389) [//]: # (START_SECTION 4d4db52fab645f470698e36b2c3580dcd6f7ad8f) ### Update main.tf > Commit: [4d4db52fab645f470698e36b2c3580dcd6f7ad8f](https://github.com/dOpensource/dsiprouter/commit/4d4db52fab645f470698e36b2c3580dcd6f7ad8f) > Date: Fri, 1 Apr 2022 21:59:57 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4d4db52fab645f470698e36b2c3580dcd6f7ad8f) [//]: # (START_SECTION e2eb9514adda76bfa18b23a58038507d9030f5c5) ### Update main.tf > Commit: [e2eb9514adda76bfa18b23a58038507d9030f5c5](https://github.com/dOpensource/dsiprouter/commit/e2eb9514adda76bfa18b23a58038507d9030f5c5) > Date: Fri, 1 Apr 2022 21:59:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e2eb9514adda76bfa18b23a58038507d9030f5c5) [//]: # (START_SECTION c2a9e73dd7a7fb9a04aee38b259f189f5d0bc610) ### Update main.tf > Commit: [c2a9e73dd7a7fb9a04aee38b259f189f5d0bc610](https://github.com/dOpensource/dsiprouter/commit/c2a9e73dd7a7fb9a04aee38b259f189f5d0bc610) > Date: Fri, 1 Apr 2022 21:53:19 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c2a9e73dd7a7fb9a04aee38b259f189f5d0bc610) [//]: # (START_SECTION de7ec6b999347b86b156ef40014234372912dfac) ### Added support for Transnexus > Commit: [de7ec6b999347b86b156ef40014234372912dfac](https://github.com/dOpensource/dsiprouter/commit/de7ec6b999347b86b156ef40014234372912dfac) > Date: Tue, 29 Mar 2022 07:11:02 +0000 > Author: root (root@kam-test.us-central1-a.c.detection-calls.internal) > Committer: root (root@kam-test.us-central1-a.c.detection-calls.internal) > Signed: --- [//]: # (END_SECTION de7ec6b999347b86b156ef40014234372912dfac) [//]: # (START_SECTION f8fca5557f1734f828e5f8f876a4f200c7993d37) ### Updated the CarrierGroup API to support updates via PUT > Commit: [f8fca5557f1734f828e5f8f876a4f200c7993d37](https://github.com/dOpensource/dsiprouter/commit/f8fca5557f1734f828e5f8f876a4f200c7993d37) > Date: Tue, 29 Mar 2022 02:36:32 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f8fca5557f1734f828e5f8f876a4f200c7993d37) [//]: # (START_SECTION f09171cd444f0f5b0c5570d484f18526283e6cdc) ### Fixed the CarrierGroup Add model - The authentication for the CarrierGroup is delegated to the Plugin so the CarrierGroup creation is not responsible for setting up IP Auth of UserPassword auth > Commit: [f09171cd444f0f5b0c5570d484f18526283e6cdc](https://github.com/dOpensource/dsiprouter/commit/f09171cd444f0f5b0c5570d484f18526283e6cdc) > Date: Sun, 20 Mar 2022 22:18:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f09171cd444f0f5b0c5570d484f18526283e6cdc) [//]: # (START_SECTION 4c0d9220766f3bc7794972a5796ad898ec6ad879) ### Fixed a JavaScript issue with the CarrierGroup UI > Commit: [4c0d9220766f3bc7794972a5796ad898ec6ad879](https://github.com/dOpensource/dsiprouter/commit/4c0d9220766f3bc7794972a5796ad898ec6ad879) > Date: Sun, 20 Mar 2022 16:03:30 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4c0d9220766f3bc7794972a5796ad898ec6ad879) [//]: # (START_SECTION a3b873763dc004f2739099f55161d3e3a5ec671c) ### Fixed the CarrierGroup API - Endpoints can now be created during the inital creation of a CarrierGroup - A Global Strip and Prefix can be set and applied during the initial configuration of a CarrierGroup - Fixed a bug that prevented the Username/Password auth from working - Fixed an issue with the Plugin architecture that caused an error when the Plugin name was not specified when the plugin stanza was present > Commit: [a3b873763dc004f2739099f55161d3e3a5ec671c](https://github.com/dOpensource/dsiprouter/commit/a3b873763dc004f2739099f55161d3e3a5ec671c) > Date: Sat, 19 Mar 2022 23:58:32 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a3b873763dc004f2739099f55161d3e3a5ec671c) [//]: # (START_SECTION 6b717430ef4cc75169b4de8573ba9222dfa32d13) ### Backported PJSIP Fix > Commit: [6b717430ef4cc75169b4de8573ba9222dfa32d13](https://github.com/dOpensource/dsiprouter/commit/6b717430ef4cc75169b4de8573ba9222dfa32d13) > Date: Wed, 9 Mar 2022 08:37:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6b717430ef4cc75169b4de8573ba9222dfa32d13) [//]: # (START_SECTION 11659a3b1a6d9c951a61c812e63d73de4a264270) ### Backporting Fix for #421 Microsoft Teams Silent Ringback > Commit: [11659a3b1a6d9c951a61c812e63d73de4a264270](https://github.com/dOpensource/dsiprouter/commit/11659a3b1a6d9c951a61c812e63d73de4a264270) > Date: Tue, 22 Feb 2022 23:08:22 -0800 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION 11659a3b1a6d9c951a61c812e63d73de4a264270) [//]: # (START_SECTION 4ded45556e2c896781186e19c843cc298e0cf140) ### Fix for #421 Microsoft Teams Silent Ringback > Commit: [4ded45556e2c896781186e19c843cc298e0cf140](https://github.com/dOpensource/dsiprouter/commit/4ded45556e2c896781186e19c843cc298e0cf140) > Date: Tue, 22 Feb 2022 22:52:19 -0800 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION 4ded45556e2c896781186e19c843cc298e0cf140) [//]: # (START_SECTION a7a3355724e86989dbe1d985a63274ccf29251f2) ### Fix for #421 Microsoft Teams Silent Ringback > Commit: [a7a3355724e86989dbe1d985a63274ccf29251f2](https://github.com/dOpensource/dsiprouter/commit/a7a3355724e86989dbe1d985a63274ccf29251f2) > Date: Tue, 22 Feb 2022 22:42:04 -0800 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION a7a3355724e86989dbe1d985a63274ccf29251f2) [//]: # (START_SECTION de781ab923da6bea54d6e3998d63fd8ceac1291d) ### fix #418 Error itsdangerous== 2.1.0 version with Flask==1.1.2 > Commit: [de781ab923da6bea54d6e3998d63fd8ceac1291d](https://github.com/dOpensource/dsiprouter/commit/de781ab923da6bea54d6e3998d63fd8ceac1291d) > Date: Mon, 21 Feb 2022 13:39:03 -0300 > Author: Asiel Lara (asiel.lb@stic.cl) > Committer: Asiel Lara (asiel.lb@stic.cl) > Signed: --- [//]: # (END_SECTION de781ab923da6bea54d6e3998d63fd8ceac1291d) [//]: # (START_SECTION 74b38a9eaa123bb6e97aebf90d2d186f27bc6a4e) ### Fixed MSTeams Issue - The ACK from the PSTN was not reaching Microsoft Teams > Commit: [74b38a9eaa123bb6e97aebf90d2d186f27bc6a4e](https://github.com/dOpensource/dsiprouter/commit/74b38a9eaa123bb6e97aebf90d2d186f27bc6a4e) > Date: Mon, 21 Feb 2022 04:40:09 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 74b38a9eaa123bb6e97aebf90d2d186f27bc6a4e) [//]: # (START_SECTION 0303d05981d4e72ff831f582e0cf232ec804d2ae) ### Updated the version to reflect v0.643 > Commit: [0303d05981d4e72ff831f582e0cf232ec804d2ae](https://github.com/dOpensource/dsiprouter/commit/0303d05981d4e72ff831f582e0cf232ec804d2ae) > Date: Sat, 12 Feb 2022 02:53:20 +0000 > Author: root (root@dev-dsip-v0.6430) > Committer: root (root@dev-dsip-v0.6430) > Signed: --- [//]: # (END_SECTION 0303d05981d4e72ff831f582e0cf232ec804d2ae) [//]: # (START_SECTION 2d053086bfd08f11997514781f8949c9fb4a017b) ### Update api_routes.py > Commit: [2d053086bfd08f11997514781f8949c9fb4a017b](https://github.com/dOpensource/dsiprouter/commit/2d053086bfd08f11997514781f8949c9fb4a017b) > Date: Tue, 1 Feb 2022 19:02:17 +0100 > Author: TheGolg (98840856+TheGolg@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: - fix bug to upload certificates via post --- [//]: # (END_SECTION 2d053086bfd08f11997514781f8949c9fb4a017b) [//]: # (START_SECTION 467e0353d1ed92c2cdd297b4070e833f8f269313) ### Updates: - Changed the max_expires header so that the SIP UA and SIP UC controls this without having a max of 1 hour - Fixed the UI so that CarrierGroup UI works with the new CarrierGroup API > Commit: [467e0353d1ed92c2cdd297b4070e833f8f269313](https://github.com/dOpensource/dsiprouter/commit/467e0353d1ed92c2cdd297b4070e833f8f269313) > Date: Fri, 21 Jan 2022 18:34:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 467e0353d1ed92c2cdd297b4070e833f8f269313) [//]: # (START_SECTION 1398c9f37f6e2aee88fd6d62ab0a1b50ca4529d9) ### Update terraform.tfvars.sample > Commit: [1398c9f37f6e2aee88fd6d62ab0a1b50ca4529d9](https://github.com/dOpensource/dsiprouter/commit/1398c9f37f6e2aee88fd6d62ab0a1b50ca4529d9) > Date: Sat, 15 Jan 2022 07:47:37 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1398c9f37f6e2aee88fd6d62ab0a1b50ca4529d9) [//]: # (START_SECTION ffa050d4259d859128d75f34368e0430005967a0) ### Updated pvt_key_path variable > Commit: [ffa050d4259d859128d75f34368e0430005967a0](https://github.com/dOpensource/dsiprouter/commit/ffa050d4259d859128d75f34368e0430005967a0) > Date: Sun, 9 Jan 2022 23:49:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ffa050d4259d859128d75f34368e0430005967a0) [//]: # (START_SECTION 868444e2dcfad98ad93fba8bafced1bb0428ae39) ### Updated variables > Commit: [868444e2dcfad98ad93fba8bafced1bb0428ae39](https://github.com/dOpensource/dsiprouter/commit/868444e2dcfad98ad93fba8bafced1bb0428ae39) > Date: Sun, 9 Jan 2022 23:42:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 868444e2dcfad98ad93fba8bafced1bb0428ae39) [//]: # (START_SECTION 6f7e10ef918323380c8f2f1147c443608206d92d) ### Fixed the CarrierGroup UI to allow it to work properly when a carrier plugin is not used > Commit: [6f7e10ef918323380c8f2f1147c443608206d92d](https://github.com/dOpensource/dsiprouter/commit/6f7e10ef918323380c8f2f1147c443608206d92d) > Date: Sun, 9 Jan 2022 22:59:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6f7e10ef918323380c8f2f1147c443608206d92d) [//]: # (START_SECTION d098638cebee6123d756893963770e245dc96d6a) ### Fixed Carriergroup API - Added exception handling that prevents a carriergroup from being created if the plugin doesn't work > Commit: [d098638cebee6123d756893963770e245dc96d6a](https://github.com/dOpensource/dsiprouter/commit/d098638cebee6123d756893963770e245dc96d6a) > Date: Sun, 9 Jan 2022 03:40:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d098638cebee6123d756893963770e245dc96d6a) [//]: # (START_SECTION 60afad7298ae532c4d51a6214fe118b0c0442a0a) ### Added support for the Carriergroup API Issue #413 > Commit: [60afad7298ae532c4d51a6214fe118b0c0442a0a](https://github.com/dOpensource/dsiprouter/commit/60afad7298ae532c4d51a6214fe118b0c0442a0a) > Date: Sat, 8 Jan 2022 23:36:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 60afad7298ae532c4d51a6214fe118b0c0442a0a) [//]: # (START_SECTION dfafb9953c70e936ccb9a3c6772993d43c9be4cb) ### Added support to deal with Zoiper Softphone on Android Fixes #411 > Commit: [dfafb9953c70e936ccb9a3c6772993d43c9be4cb](https://github.com/dOpensource/dsiprouter/commit/dfafb9953c70e936ccb9a3c6772993d43c9be4cb) > Date: Thu, 30 Dec 2021 15:41:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION dfafb9953c70e936ccb9a3c6772993d43c9be4cb) [//]: # (START_SECTION f5ba6be88f6da2d66c52bb039f224b274cb68ca8) ### Update use-cases.rst > Commit: [f5ba6be88f6da2d66c52bb039f224b274cb68ca8](https://github.com/dOpensource/dsiprouter/commit/f5ba6be88f6da2d66c52bb039f224b274cb68ca8) > Date: Wed, 29 Dec 2021 17:05:24 +0300 > Author: James Peru Mmbono (jmsperu@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: - Corrected spelling from Authenication to Authentication --- [//]: # (END_SECTION f5ba6be88f6da2d66c52bb039f224b274cb68ca8) [//]: # (START_SECTION fe8e22a60b89b11d803880aba12a3bbfd43d37a7) ### Updated carriergroups UI to work with the new API > Commit: [fe8e22a60b89b11d803880aba12a3bbfd43d37a7](https://github.com/dOpensource/dsiprouter/commit/fe8e22a60b89b11d803880aba12a3bbfd43d37a7) > Date: Mon, 22 Nov 2021 02:41:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fe8e22a60b89b11d803880aba12a3bbfd43d37a7) [//]: # (START_SECTION 197eb7aa1b572796a9a1b9fc5d8e8bf52c85684b) ### Carriergroup Enhancements - Created a module to isolate logic for carriergroups - Added a plugin architecture for carriers - Added support for the carrier plugin architecture to the UI > Commit: [197eb7aa1b572796a9a1b9fc5d8e8bf52c85684b](https://github.com/dOpensource/dsiprouter/commit/197eb7aa1b572796a9a1b9fc5d8e8bf52c85684b) > Date: Fri, 19 Nov 2021 04:12:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 197eb7aa1b572796a9a1b9fc5d8e8bf52c85684b) [//]: # (START_SECTION 5f442e34c294153e1db463c432a0d73464aca3f2) ### Integrated new carriergroup API with Twilio Plugin > Commit: [5f442e34c294153e1db463c432a0d73464aca3f2](https://github.com/dOpensource/dsiprouter/commit/5f442e34c294153e1db463c432a0d73464aca3f2) > Date: Sun, 31 Oct 2021 17:30:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5f442e34c294153e1db463c432a0d73464aca3f2) [//]: # (START_SECTION 54d48156e8c9953a74121afa7173cde21f3f466c) ### Initial commit of the CarrierGroups API with Carrier Plugin Architecture > Commit: [54d48156e8c9953a74121afa7173cde21f3f466c](https://github.com/dOpensource/dsiprouter/commit/54d48156e8c9953a74121afa7173cde21f3f466c) > Date: Sun, 24 Oct 2021 22:54:42 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 54d48156e8c9953a74121afa7173cde21f3f466c) [//]: # (START_SECTION f00b06f414e6708f9381a16a2db46fe4e60e4ab8) ### Fixed an issue with route headers on ACK and BYE's > Commit: [f00b06f414e6708f9381a16a2db46fe4e60e4ab8](https://github.com/dOpensource/dsiprouter/commit/f00b06f414e6708f9381a16a2db46fe4e60e4ab8) > Date: Sun, 17 Oct 2021 15:44:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f00b06f414e6708f9381a16a2db46fe4e60e4ab8) [//]: # (START_SECTION d8fbb36687663b2b5fd474043b63732bae8980fd) ### Updated the version to v0.642 > Commit: [d8fbb36687663b2b5fd474043b63732bae8980fd](https://github.com/dOpensource/dsiprouter/commit/d8fbb36687663b2b5fd474043b63732bae8980fd) > Date: Mon, 27 Sep 2021 03:33:23 +0000 > Author: Mack hendricks (mack@dopensource.com) > Committer: Mack hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d8fbb36687663b2b5fd474043b63732bae8980fd) [//]: # (START_SECTION 28a6cec3232c02ad1a55b856fb667a17f16e157a) ### UI Fixes - Login screen looks correct on a mobile device - Dashboard screen displays the widgets vertically on a mobile device > Commit: [28a6cec3232c02ad1a55b856fb667a17f16e157a](https://github.com/dOpensource/dsiprouter/commit/28a6cec3232c02ad1a55b856fb667a17f16e157a) > Date: Mon, 27 Sep 2021 03:29:09 +0000 > Author: Mack hendricks (mack@dopensource.com) > Committer: Mack hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 28a6cec3232c02ad1a55b856fb667a17f16e157a) [//]: # (START_SECTION 660a32a135b8bac31aa59c495ae68f9467193bb4) ### Fixes - Looping ACK when in SERVERNAT mode - Added support for International Emergency Numbers and US National Suicide prevention - Created a generic test for removing the Route header when it matches the ip external ip address of dSIP > Commit: [660a32a135b8bac31aa59c495ae68f9467193bb4](https://github.com/dOpensource/dsiprouter/commit/660a32a135b8bac31aa59c495ae68f9467193bb4) > Date: Fri, 24 Sep 2021 15:03:57 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 660a32a135b8bac31aa59c495ae68f9467193bb4) [//]: # (START_SECTION 87d4aed54ceb44e31c22a0cf0e6902d3a09ec84e) ### Fixed Issues: - The SDP was not being written correctly when in a Servernat configuration - The Route header from the carrier in the WITHINDLG was configued to track the ip address of the selected media server and route the call back correctly > Commit: [87d4aed54ceb44e31c22a0cf0e6902d3a09ec84e](https://github.com/dOpensource/dsiprouter/commit/87d4aed54ceb44e31c22a0cf0e6902d3a09ec84e) > Date: Tue, 21 Sep 2021 06:38:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 87d4aed54ceb44e31c22a0cf0e6902d3a09ec84e) [//]: # (START_SECTION 323f38bf35f4078e9896a8f01a0535832c68ecaa) ### Fixes Issue #400 > Commit: [323f38bf35f4078e9896a8f01a0535832c68ecaa](https://github.com/dOpensource/dsiprouter/commit/323f38bf35f4078e9896a8f01a0535832c68ecaa) > Date: Sun, 29 Aug 2021 16:18:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 323f38bf35f4078e9896a8f01a0535832c68ecaa) [//]: # (START_SECTION e8f4ae98679f93c18759fb23153dcc61060356cc) ### Fix up formatting errors in MS Teams Docs, #388 and #395 > Commit: [e8f4ae98679f93c18759fb23153dcc61060356cc](https://github.com/dOpensource/dsiprouter/commit/e8f4ae98679f93c18759fb23153dcc61060356cc) > Date: Sat, 17 Jul 2021 22:00:51 -0700 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION e8f4ae98679f93c18759fb23153dcc61060356cc) [//]: # (START_SECTION cd7860138957fad20e630957d3352d31166c42b6) ### MediaServer API - A tab spacing caused the GET request not to be executed when not in DEBUG mode > Commit: [cd7860138957fad20e630957d3352d31166c42b6](https://github.com/dOpensource/dsiprouter/commit/cd7860138957fad20e630957d3352d31166c42b6) > Date: Fri, 25 Jun 2021 20:21:51 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION cd7860138957fad20e630957d3352d31166c42b6) [//]: # (START_SECTION 8049a7005ab453e7d10c9f5cdd1e0831ed5374b7) ### Update supported OS list and centos7 reference per pull request #388 > Commit: [8049a7005ab453e7d10c9f5cdd1e0831ed5374b7](https://github.com/dOpensource/dsiprouter/commit/8049a7005ab453e7d10c9f5cdd1e0831ed5374b7) > Date: Tue, 22 Jun 2021 14:11:03 -0700 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION 8049a7005ab453e7d10c9f5cdd1e0831ed5374b7) [//]: # (START_SECTION 9bd7986c4952c2f473c2fae2eee8ff0aaea06a4c) ### Add references to PowerShell Script sources > Commit: [9bd7986c4952c2f473c2fae2eee8ff0aaea06a4c](https://github.com/dOpensource/dsiprouter/commit/9bd7986c4952c2f473c2fae2eee8ff0aaea06a4c) > Date: Sun, 13 Jun 2021 15:05:20 -0700 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION 9bd7986c4952c2f473c2fae2eee8ff0aaea06a4c) [//]: # (START_SECTION 9355e5408640fa412254ed37ce3de0789cfcbade) ### Added Direct Routing use case > Commit: [9355e5408640fa412254ed37ce3de0789cfcbade](https://github.com/dOpensource/dsiprouter/commit/9355e5408640fa412254ed37ce3de0789cfcbade) > Date: Sun, 13 Jun 2021 15:02:10 -0700 > Author: Dan Ryan (dan@acceleratenetworks.com) > Committer: Dan Ryan (dan@acceleratenetworks.com) > Signed: --- [//]: # (END_SECTION 9355e5408640fa412254ed37ce3de0789cfcbade) [//]: # (START_SECTION 24f390f44aec775aceef7578ff75809c03142b39) ### Remove CentOS 7 Duplicate > Commit: [24f390f44aec775aceef7578ff75809c03142b39](https://github.com/dOpensource/dsiprouter/commit/24f390f44aec775aceef7578ff75809c03142b39) > Date: Sun, 13 Jun 2021 13:11:35 -0700 > Author: Dan (dan@acceleratenetworks.com) > Committer: GitHub (noreply@github.com) > Signed: - This page looks to be an old duplicate not used in the published ReadTheDocs --- [//]: # (END_SECTION 24f390f44aec775aceef7578ff75809c03142b39) [//]: # (START_SECTION f5017f5afa9e2f2bfea9fa9fb20ee8989a51ab2d) ### Add Debian 10 install section > Commit: [f5017f5afa9e2f2bfea9fa9fb20ee8989a51ab2d](https://github.com/dOpensource/dsiprouter/commit/f5017f5afa9e2f2bfea9fa9fb20ee8989a51ab2d) > Date: Sun, 13 Jun 2021 13:06:25 -0700 > Author: Dan (dan@acceleratenetworks.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f5017f5afa9e2f2bfea9fa9fb20ee8989a51ab2d) [//]: # (START_SECTION 1254e8e3d5cd29b3ae67ff993a0c110e76fac270) ### Updated installation documentation for 0.641 and master > Commit: [1254e8e3d5cd29b3ae67ff993a0c110e76fac270](https://github.com/dOpensource/dsiprouter/commit/1254e8e3d5cd29b3ae67ff993a0c110e76fac270) > Date: Sun, 13 Jun 2021 12:52:21 -0700 > Author: Dan (dan@acceleratenetworks.com) > Committer: GitHub (noreply@github.com) > Signed: - Added Debian 10.9 installation instructions reference --- [//]: # (END_SECTION 1254e8e3d5cd29b3ae67ff993a0c110e76fac270) [//]: # (START_SECTION a8c0496fa2525513d8cf81668c57d783eee299d7) ### Allow the installer to continue working if the repo public key is not available > Commit: [a8c0496fa2525513d8cf81668c57d783eee299d7](https://github.com/dOpensource/dsiprouter/commit/a8c0496fa2525513d8cf81668c57d783eee299d7) > Date: Thu, 27 May 2021 05:21:42 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION a8c0496fa2525513d8cf81668c57d783eee299d7) [//]: # (START_SECTION 5713f323069a50de8d0dce8bd2953e1d142b46bf) ### Pinned Flask to 1.1.2. Fixes #376 > Commit: [5713f323069a50de8d0dce8bd2953e1d142b46bf](https://github.com/dOpensource/dsiprouter/commit/5713f323069a50de8d0dce8bd2953e1d142b46bf) > Date: Thu, 13 May 2021 10:38:37 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 5713f323069a50de8d0dce8bd2953e1d142b46bf) [//]: # (START_SECTION cdecaf5d747f681ee6addf2b2d6e58479e36da91) ### Fixed issue with CDR's not working > Commit: [cdecaf5d747f681ee6addf2b2d6e58479e36da91](https://github.com/dOpensource/dsiprouter/commit/cdecaf5d747f681ee6addf2b2d6e58479e36da91) > Date: Wed, 31 Mar 2021 15:00:44 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cdecaf5d747f681ee6addf2b2d6e58479e36da91) [//]: # (START_SECTION ec4219de84f9d346f082fc668d5979aba519c047) ### Fixed issue that prevented CDR's from being created when loose routing is not being used > Commit: [ec4219de84f9d346f082fc668d5979aba519c047](https://github.com/dOpensource/dsiprouter/commit/ec4219de84f9d346f082fc668d5979aba519c047) > Date: Tue, 30 Mar 2021 23:59:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ec4219de84f9d346f082fc668d5979aba519c047) [//]: # (START_SECTION d7d03e0bf8481ce48fc6bc5c8d7c1b56fc46575f) ### Fixed Endpoint Group UI Bug - Removed entries from the dispatcher table when the weight was 0 > Commit: [d7d03e0bf8481ce48fc6bc5c8d7c1b56fc46575f](https://github.com/dOpensource/dsiprouter/commit/d7d03e0bf8481ce48fc6bc5c8d7c1b56fc46575f) > Date: Thu, 11 Mar 2021 19:38:16 +0000 > Author: root (root@ip-172-31-5-106.ec2.internal) > Committer: root (root@ip-172-31-5-106.ec2.internal) > Signed: --- [//]: # (END_SECTION d7d03e0bf8481ce48fc6bc5c8d7c1b56fc46575f) [//]: # (START_SECTION 6c7532ccec112d6faf8ce47f1d89151e4a46f316) ### Turned SETTINGS.DEBUG to False > Commit: [6c7532ccec112d6faf8ce47f1d89151e4a46f316](https://github.com/dOpensource/dsiprouter/commit/6c7532ccec112d6faf8ce47f1d89151e4a46f316) > Date: Sun, 21 Feb 2021 17:13:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6c7532ccec112d6faf8ce47f1d89151e4a46f316) [//]: # (START_SECTION 1fb969abeb8eefc633be50e11e3905d688526cdf) ### Fixed the carrier group endpoints - Some of the endpoints were not tied to the correct carrier group > Commit: [1fb969abeb8eefc633be50e11e3905d688526cdf](https://github.com/dOpensource/dsiprouter/commit/1fb969abeb8eefc633be50e11e3905d688526cdf) > Date: Sat, 20 Feb 2021 23:31:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1fb969abeb8eefc633be50e11e3905d688526cdf) [//]: # (START_SECTION 64f141ba6ced44e6a0b9f1b1680a8edb5755415f) ### Fixed the carrier group endpoints - Some of the endpoints were not tied to the correct carrier group > Commit: [64f141ba6ced44e6a0b9f1b1680a8edb5755415f](https://github.com/dOpensource/dsiprouter/commit/64f141ba6ced44e6a0b9f1b1680a8edb5755415f) > Date: Sat, 20 Feb 2021 23:28:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 64f141ba6ced44e6a0b9f1b1680a8edb5755415f) [//]: # (START_SECTION 813dcdc3199fa5157a3b4a83dd52580c5570e659) ### Updated the lease API > Commit: [813dcdc3199fa5157a3b4a83dd52580c5570e659](https://github.com/dOpensource/dsiprouter/commit/813dcdc3199fa5157a3b4a83dd52580c5570e659) > Date: Sat, 20 Feb 2021 19:27:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 813dcdc3199fa5157a3b4a83dd52580c5570e659) [//]: # (START_SECTION a77ffb5e2988d7ebb01f59464707cebf33a0adb4) ### Fixed issue with Record Routes so that a single NLB can be used > Commit: [a77ffb5e2988d7ebb01f59464707cebf33a0adb4](https://github.com/dOpensource/dsiprouter/commit/a77ffb5e2988d7ebb01f59464707cebf33a0adb4) > Date: Tue, 16 Feb 2021 19:38:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a77ffb5e2988d7ebb01f59464707cebf33a0adb4) [//]: # (START_SECTION 863343903605d610f5b42f72805f993c401cc862) ### Fixed the crontab > Commit: [863343903605d610f5b42f72805f993c401cc862](https://github.com/dOpensource/dsiprouter/commit/863343903605d610f5b42f72805f993c401cc862) > Date: Tue, 16 Feb 2021 19:26:31 +0000 > Author: root (root@ip-172-31-8-204.ec2.internal) > Committer: root (root@ip-172-31-8-204.ec2.internal) > Signed: --- [//]: # (END_SECTION 863343903605d610f5b42f72805f993c401cc862) [//]: # (START_SECTION 26f16646507d81a840cce049069872c431905ca9) ### Added support for writing multiple record routes for an INBOUND and OUTBOUND load balancer > Commit: [26f16646507d81a840cce049069872c431905ca9](https://github.com/dOpensource/dsiprouter/commit/26f16646507d81a840cce049069872c431905ca9) > Date: Fri, 12 Feb 2021 17:45:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 26f16646507d81a840cce049069872c431905ca9) [//]: # (START_SECTION 3b41901eb9baad1139d245b6220fb502c7c546ca) ### Added support for creating the Kamailio DB user > Commit: [3b41901eb9baad1139d245b6220fb502c7c546ca](https://github.com/dOpensource/dsiprouter/commit/3b41901eb9baad1139d245b6220fb502c7c546ca) > Date: Fri, 12 Feb 2021 00:21:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3b41901eb9baad1139d245b6220fb502c7c546ca) [//]: # (START_SECTION e44d74fb9848cb5812512da7e216e73c6bbbdad3) ### Added support for creating the Kamailio DB user > Commit: [e44d74fb9848cb5812512da7e216e73c6bbbdad3](https://github.com/dOpensource/dsiprouter/commit/e44d74fb9848cb5812512da7e216e73c6bbbdad3) > Date: Fri, 12 Feb 2021 00:07:27 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION e44d74fb9848cb5812512da7e216e73c6bbbdad3) [//]: # (START_SECTION 9203d051d69e9176f2bb659c70c29cceab90b084) ### Fixes: - Documentation generation - Moved the Message of The Day creation to after dSIP is started the first time > Commit: [9203d051d69e9176f2bb659c70c29cceab90b084](https://github.com/dOpensource/dsiprouter/commit/9203d051d69e9176f2bb659c70c29cceab90b084) > Date: Wed, 10 Feb 2021 17:31:40 +0000 > Author: root (root@ip-172-31-58-119.ec2.internal) > Committer: root (root@ip-172-31-58-119.ec2.internal) > Signed: --- [//]: # (END_SECTION 9203d051d69e9176f2bb659c70c29cceab90b084) [//]: # (START_SECTION 7e4b557bccfd166ac746c73e98e7c76e6c2c9d57) ### Fix Ordering Issues > Commit: [7e4b557bccfd166ac746c73e98e7c76e6c2c9d57](https://github.com/dOpensource/dsiprouter/commit/7e4b557bccfd166ac746c73e98e7c76e6c2c9d57) > Date: Wed, 10 Feb 2021 12:00:19 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix ssh banner update order to get current IP changes - re-add doc generation --- [//]: # (END_SECTION 7e4b557bccfd166ac746c73e98e7c76e6c2c9d57) [//]: # (START_SECTION 6757683347f947e730ff668bac4a3d578b8044db) ### Add Cryptography Conflict Resolution In Pre-Commit Hook > Commit: [6757683347f947e730ff668bac4a3d578b8044db](https://github.com/dOpensource/dsiprouter/commit/6757683347f947e730ff668bac4a3d578b8044db) > Date: Wed, 10 Feb 2021 11:35:43 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 6757683347f947e730ff668bac4a3d578b8044db) [//]: # (START_SECTION 1bf0cc47ceaa158aa24880be5e7f9b66dd67c956) ### Fixed issues with updatekamconfig > Commit: [1bf0cc47ceaa158aa24880be5e7f9b66dd67c956](https://github.com/dOpensource/dsiprouter/commit/1bf0cc47ceaa158aa24880be5e7f9b66dd67c956) > Date: Wed, 10 Feb 2021 16:25:57 +0000 > Author: root (root@ip-172-31-50-23.ec2.internal) > Committer: root (root@ip-172-31-50-23.ec2.internal) > Signed: --- [//]: # (END_SECTION 1bf0cc47ceaa158aa24880be5e7f9b66dd67c956) [//]: # (START_SECTION 063aa3646d4e6a39baeaa5fa26bc329139f05914) ### Fixes - Resolved error message during Kamailio install - Resolved issue with MOTD - Disabled Docs from being created for right now > Commit: [063aa3646d4e6a39baeaa5fa26bc329139f05914](https://github.com/dOpensource/dsiprouter/commit/063aa3646d4e6a39baeaa5fa26bc329139f05914) > Date: Wed, 10 Feb 2021 12:19:47 +0000 > Author: root (root@ip-172-31-14-13.ec2.internal) > Committer: root (root@ip-172-31-14-13.ec2.internal) > Signed: --- [//]: # (END_SECTION 063aa3646d4e6a39baeaa5fa26bc329139f05914) [//]: # (START_SECTION a1f4a720a824ed2e11d64e7fa22bfaa6a2da7eab) ### Changed the order in the requirements file so that the cryptograpy package is installed with a pinned version before the certbot package > Commit: [a1f4a720a824ed2e11d64e7fa22bfaa6a2da7eab](https://github.com/dOpensource/dsiprouter/commit/a1f4a720a824ed2e11d64e7fa22bfaa6a2da7eab) > Date: Wed, 10 Feb 2021 10:22:42 +0000 > Author: root (root@ip-172-31-15-70.ec2.internal) > Committer: root (root@ip-172-31-15-70.ec2.internal) > Signed: --- [//]: # (END_SECTION a1f4a720a824ed2e11d64e7fa22bfaa6a2da7eab) [//]: # (START_SECTION 01042e110ecc152ce807322aeb53baba5c06bc2f) ### Pinned the cryptography package to version >3.3 and <3.4 to deal with a new requirement for rust > Commit: [01042e110ecc152ce807322aeb53baba5c06bc2f](https://github.com/dOpensource/dsiprouter/commit/01042e110ecc152ce807322aeb53baba5c06bc2f) > Date: Wed, 10 Feb 2021 09:31:17 +0000 > Author: root (root@ip-172-31-8-5.ec2.internal) > Committer: root (root@ip-172-31-8-5.ec2.internal) > Signed: --- [//]: # (END_SECTION 01042e110ecc152ce807322aeb53baba5c06bc2f) [//]: # (START_SECTION a495613e9c6d52bdf921c9d005d4b7206ed6feb2) ### Fixed WebRTC Support - Added a TLS server certificate settings for the WebRTC port, which is 4443 - Update the Kam configuration script to update the settings > Commit: [a495613e9c6d52bdf921c9d005d4b7206ed6feb2](https://github.com/dOpensource/dsiprouter/commit/a495613e9c6d52bdf921c9d005d4b7206ed6feb2) > Date: Wed, 10 Feb 2021 02:23:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a495613e9c6d52bdf921c9d005d4b7206ed6feb2) [//]: # (START_SECTION f59ff1f05c8742ceabc0903bf453732976268ca2) ### Added support for setting the language for the domain > Commit: [f59ff1f05c8742ceabc0903bf453732976268ca2](https://github.com/dOpensource/dsiprouter/commit/f59ff1f05c8742ceabc0903bf453732976268ca2) > Date: Wed, 10 Feb 2021 01:30:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f59ff1f05c8742ceabc0903bf453732976268ca2) [//]: # (START_SECTION 500a9c4cbe3531658e7b6fad0c9298dc38e504ca) ### Fix RTPEngine Errors When Not Installed > Commit: [500a9c4cbe3531658e7b6fad0c9298dc38e504ca](https://github.com/dOpensource/dsiprouter/commit/500a9c4cbe3531658e7b6fad0c9298dc38e504ca) > Date: Mon, 8 Feb 2021 17:45:18 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - pre-process out rtpengine parts of kam config when not installed - add `WITH_RTPENGINE` generated param to install script --- [//]: # (END_SECTION 500a9c4cbe3531658e7b6fad0c9298dc38e504ca) [//]: # (START_SECTION da5a1a010e9c2950043daeca6766b5c1e5f01e9a) ### Fixed the permissions error when using backup and restore > Commit: [da5a1a010e9c2950043daeca6766b5c1e5f01e9a](https://github.com/dOpensource/dsiprouter/commit/da5a1a010e9c2950043daeca6766b5c1e5f01e9a) > Date: Mon, 8 Feb 2021 21:26:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION da5a1a010e9c2950043daeca6766b5c1e5f01e9a) [//]: # (START_SECTION 9bd78475c38ea4e4a2e91fbdd3d35e79ec292cf2) ### All domains and domain attributes will be deleted when deleting an EndPoint Group > Commit: [9bd78475c38ea4e4a2e91fbdd3d35e79ec292cf2](https://github.com/dOpensource/dsiprouter/commit/9bd78475c38ea4e4a2e91fbdd3d35e79ec292cf2) > Date: Mon, 8 Feb 2021 00:43:00 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9bd78475c38ea4e4a2e91fbdd3d35e79ec292cf2) [//]: # (START_SECTION 00512092012a8774bd3e8bb1d98da705f3ec6515) ### Fixed bug that was removing the inbound route each time an endpoint was updated > Commit: [00512092012a8774bd3e8bb1d98da705f3ec6515](https://github.com/dOpensource/dsiprouter/commit/00512092012a8774bd3e8bb1d98da705f3ec6515) > Date: Sun, 7 Feb 2021 16:45:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 00512092012a8774bd3e8bb1d98da705f3ec6515) [//]: # (START_SECTION fb295b01df70c24b149e0ba5e4dbe2078acd2c46) ### Fixed bug that was removing the inbound route each time an endpoint was updated > Commit: [fb295b01df70c24b149e0ba5e4dbe2078acd2c46](https://github.com/dOpensource/dsiprouter/commit/fb295b01df70c24b149e0ba5e4dbe2078acd2c46) > Date: Sun, 7 Feb 2021 16:41:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fb295b01df70c24b149e0ba5e4dbe2078acd2c46) [//]: # (START_SECTION 302f209612217053c796a452b904062542326532) ### Made the Network Load Balancer features more generic > Commit: [302f209612217053c796a452b904062542326532](https://github.com/dOpensource/dsiprouter/commit/302f209612217053c796a452b904062542326532) > Date: Sat, 6 Feb 2021 22:36:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 302f209612217053c796a452b904062542326532) [//]: # (START_SECTION 8ac9a1d9a2c1966591ab441bbc75958c09949a0c) ### Added a check to prevent the EXTERNAL_IP from not being set > Commit: [8ac9a1d9a2c1966591ab441bbc75958c09949a0c](https://github.com/dOpensource/dsiprouter/commit/8ac9a1d9a2c1966591ab441bbc75958c09949a0c) > Date: Sat, 6 Feb 2021 20:47:14 +0000 > Author: root (root@ip-172-31-37-179.ec2.internal) > Committer: root (root@ip-172-31-37-179.ec2.internal) > Signed: --- [//]: # (END_SECTION 8ac9a1d9a2c1966591ab441bbc75958c09949a0c) [//]: # (START_SECTION da57ae2e35af3f04ea35987b06bb5becc0801f9f) ### Bug fixes for setCredentials > Commit: [da57ae2e35af3f04ea35987b06bb5becc0801f9f](https://github.com/dOpensource/dsiprouter/commit/da57ae2e35af3f04ea35987b06bb5becc0801f9f) > Date: Wed, 3 Feb 2021 20:27:00 +0000 > Author: root (root@ip-172-31-54-216.ec2.internal) > Committer: root (root@ip-172-31-54-216.ec2.internal) > Signed: --- [//]: # (END_SECTION da57ae2e35af3f04ea35987b06bb5becc0801f9f) [//]: # (START_SECTION 55ad40f4723e2ac65f197f32e7f00983ee0a464a) ### Fixed Inbound Load Balancing > Commit: [55ad40f4723e2ac65f197f32e7f00983ee0a464a](https://github.com/dOpensource/dsiprouter/commit/55ad40f4723e2ac65f197f32e7f00983ee0a464a) > Date: Wed, 3 Feb 2021 12:52:21 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 55ad40f4723e2ac65f197f32e7f00983ee0a464a) [//]: # (START_SECTION 34b7d1942f56355684c8dc5b0cc3d0100c72184b) ### Fixed Inbound Load Balancing to use localhost > Commit: [34b7d1942f56355684c8dc5b0cc3d0100c72184b](https://github.com/dOpensource/dsiprouter/commit/34b7d1942f56355684c8dc5b0cc3d0100c72184b) > Date: Wed, 3 Feb 2021 12:51:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 34b7d1942f56355684c8dc5b0cc3d0100c72184b) [//]: # (START_SECTION e4539774b3e07c22694fc62b945868db06ce4458) ### Fixes issue #307 > Commit: [e4539774b3e07c22694fc62b945868db06ce4458](https://github.com/dOpensource/dsiprouter/commit/e4539774b3e07c22694fc62b945868db06ce4458) > Date: Wed, 3 Feb 2021 02:12:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e4539774b3e07c22694fc62b945868db06ce4458) [//]: # (START_SECTION eff0125c1711790734301fa41e4c4d9fa7bb9d85) ### Added support for an INBOUND and OUTBOUND AWS Network Load Balancers > Commit: [eff0125c1711790734301fa41e4c4d9fa7bb9d85](https://github.com/dOpensource/dsiprouter/commit/eff0125c1711790734301fa41e4c4d9fa7bb9d85) > Date: Tue, 2 Feb 2021 22:49:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eff0125c1711790734301fa41e4c4d9fa7bb9d85) [//]: # (START_SECTION b62c3850ef95ec00a9ee8b3fb27686002999c8fb) ### Added support for advertising the AWS NLB when Registering to a carrier > Commit: [b62c3850ef95ec00a9ee8b3fb27686002999c8fb](https://github.com/dOpensource/dsiprouter/commit/b62c3850ef95ec00a9ee8b3fb27686002999c8fb) > Date: Sat, 30 Jan 2021 20:43:48 +0000 > Author: root (root@ip-172-31-57-186.ec2.internal) > Committer: root (root@ip-172-31-57-186.ec2.internal) > Signed: --- [//]: # (END_SECTION b62c3850ef95ec00a9ee8b3fb27686002999c8fb) [//]: # (START_SECTION ab11ffdb7eaf4219bd35fc99e2c62029736991fc) ### Added a health endpoint > Commit: [ab11ffdb7eaf4219bd35fc99e2c62029736991fc](https://github.com/dOpensource/dsiprouter/commit/ab11ffdb7eaf4219bd35fc99e2c62029736991fc) > Date: Thu, 28 Jan 2021 21:53:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ab11ffdb7eaf4219bd35fc99e2c62029736991fc) [//]: # (START_SECTION 7105186a5f9a22d9a990f683ce7774ce2c585674) ### Added an additional check to get hostname > Commit: [7105186a5f9a22d9a990f683ce7774ce2c585674](https://github.com/dOpensource/dsiprouter/commit/7105186a5f9a22d9a990f683ce7774ce2c585674) > Date: Wed, 27 Jan 2021 13:52:54 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 7105186a5f9a22d9a990f683ce7774ce2c585674) [//]: # (START_SECTION c3f5eb7aaa55c037dc5b466cdd1aab3a6fd6b98f) ### Updated Kamailio > Commit: [c3f5eb7aaa55c037dc5b466cdd1aab3a6fd6b98f](https://github.com/dOpensource/dsiprouter/commit/c3f5eb7aaa55c037dc5b466cdd1aab3a6fd6b98f) > Date: Wed, 27 Jan 2021 13:10:34 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c3f5eb7aaa55c037dc5b466cdd1aab3a6fd6b98f) [//]: # (START_SECTION 2a2757d6a09801fd30a29af2e0b05bb8352c1f1b) ### Fixed issues: - LetsEncrypt certificate generation was fixed - Fixed the abililty to add MSTeams domain > Commit: [2a2757d6a09801fd30a29af2e0b05bb8352c1f1b](https://github.com/dOpensource/dsiprouter/commit/2a2757d6a09801fd30a29af2e0b05bb8352c1f1b) > Date: Fri, 22 Jan 2021 02:13:58 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2a2757d6a09801fd30a29af2e0b05bb8352c1f1b) [//]: # (START_SECTION 3c66751cd46caf6e8744767793d15ede88a9f5f2) ### Fixed an issue where Sync'd FusionPBX Domains were not deleted within dSIP when the Endpoint Group associated with the FusionPBX was deleted > Commit: [3c66751cd46caf6e8744767793d15ede88a9f5f2](https://github.com/dOpensource/dsiprouter/commit/3c66751cd46caf6e8744767793d15ede88a9f5f2) > Date: Thu, 21 Jan 2021 15:44:29 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3c66751cd46caf6e8744767793d15ede88a9f5f2) [//]: # (START_SECTION 046abc390bf18c6bb5fcbd178ed711d32e507e77) ### Renabled client side NAT Detection > Commit: [046abc390bf18c6bb5fcbd178ed711d32e507e77](https://github.com/dOpensource/dsiprouter/commit/046abc390bf18c6bb5fcbd178ed711d32e507e77) > Date: Thu, 21 Jan 2021 11:52:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 046abc390bf18c6bb5fcbd178ed711d32e507e77) [//]: # (START_SECTION bd4c682017cf089392a47c3f0976649c11ba39d4) ### Updated locatation of settings.py > Commit: [bd4c682017cf089392a47c3f0976649c11ba39d4](https://github.com/dOpensource/dsiprouter/commit/bd4c682017cf089392a47c3f0976649c11ba39d4) > Date: Wed, 20 Jan 2021 18:26:02 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bd4c682017cf089392a47c3f0976649c11ba39d4) [//]: # (START_SECTION 9d0f4034155f29da792dad6cfe72365fdca2ba1c) ### Changed the permissions to the backup directory > Commit: [9d0f4034155f29da792dad6cfe72365fdca2ba1c](https://github.com/dOpensource/dsiprouter/commit/9d0f4034155f29da792dad6cfe72365fdca2ba1c) > Date: Wed, 20 Jan 2021 19:16:42 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 9d0f4034155f29da792dad6cfe72365fdca2ba1c) [//]: # (START_SECTION 1c3fbd9772d7e68cf002a41646dc519343a180e0) ### Updating UI components > Commit: [1c3fbd9772d7e68cf002a41646dc519343a180e0](https://github.com/dOpensource/dsiprouter/commit/1c3fbd9772d7e68cf002a41646dc519343a180e0) > Date: Wed, 20 Jan 2021 18:34:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1c3fbd9772d7e68cf002a41646dc519343a180e0) [//]: # (START_SECTION a5ecbd575c741b310e885f2c83e4027a934e6f64) ### Fixed issue with settings.py not updating correctly > Commit: [a5ecbd575c741b310e885f2c83e4027a934e6f64](https://github.com/dOpensource/dsiprouter/commit/a5ecbd575c741b310e885f2c83e4027a934e6f64) > Date: Wed, 20 Jan 2021 12:19:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a5ecbd575c741b310e885f2c83e4027a934e6f64) [//]: # (START_SECTION 0bf43b396c17bffd42906533d61fdd0d4e8711fc) ### Fixed installer issue > Commit: [0bf43b396c17bffd42906533d61fdd0d4e8711fc](https://github.com/dOpensource/dsiprouter/commit/0bf43b396c17bffd42906533d61fdd0d4e8711fc) > Date: Wed, 20 Jan 2021 11:00:18 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 0bf43b396c17bffd42906533d61fdd0d4e8711fc) [//]: # (START_SECTION 6ad575e55af96adcf468272a6c6387db9f866505) ### Fixed installer issues > Commit: [6ad575e55af96adcf468272a6c6387db9f866505](https://github.com/dOpensource/dsiprouter/commit/6ad575e55af96adcf468272a6c6387db9f866505) > Date: Wed, 20 Jan 2021 04:00:10 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 6ad575e55af96adcf468272a6c6387db9f866505) [//]: # (START_SECTION 9be69cb27221c22a01dc51da7640edd5ccac0ef6) ### Updates - Fixed a bug that causes the motd to have the wrong external IP - Fixed an issue that prevented the Teleblock from being enabled at the Kamailio level > Commit: [9be69cb27221c22a01dc51da7640edd5ccac0ef6](https://github.com/dOpensource/dsiprouter/commit/9be69cb27221c22a01dc51da7640edd5ccac0ef6) > Date: Wed, 20 Jan 2021 03:16:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9be69cb27221c22a01dc51da7640edd5ccac0ef6) [//]: # (START_SECTION 8e471cae6e6bf94b9d4feb1675242458bade2b5a) ### Updated VI Carrier Lists > Commit: [8e471cae6e6bf94b9d4feb1675242458bade2b5a](https://github.com/dOpensource/dsiprouter/commit/8e471cae6e6bf94b9d4feb1675242458bade2b5a) > Date: Mon, 18 Jan 2021 14:27:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8e471cae6e6bf94b9d4feb1675242458bade2b5a) [//]: # (START_SECTION abf8f8e90d573342aa0e8ea3bb13d507fc31ee07) ### Fixed regression with FusionPBX Sync > Commit: [abf8f8e90d573342aa0e8ea3bb13d507fc31ee07](https://github.com/dOpensource/dsiprouter/commit/abf8f8e90d573342aa0e8ea3bb13d507fc31ee07) > Date: Sun, 17 Jan 2021 04:14:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION abf8f8e90d573342aa0e8ea3bb13d507fc31ee07) [//]: # (START_SECTION 9d65dde56b73b840e1091ecbcfd9c52d3905359d) ### Update api_routes.py > Commit: [9d65dde56b73b840e1091ecbcfd9c52d3905359d](https://github.com/dOpensource/dsiprouter/commit/9d65dde56b73b840e1091ecbcfd9c52d3905359d) > Date: Sat, 16 Jan 2021 15:13:07 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9d65dde56b73b840e1091ecbcfd9c52d3905359d) [//]: # (START_SECTION 63d11cedc8133bbc611e4943463ba8c8bff88951) ### GUI Updates: - Updated the sidebar so that it extends up to 2000px, which will make the UI look like it fills the screen - Fixed the idention on the backup and restore page > Commit: [63d11cedc8133bbc611e4943463ba8c8bff88951](https://github.com/dOpensource/dsiprouter/commit/63d11cedc8133bbc611e4943463ba8c8bff88951) > Date: Sat, 16 Jan 2021 13:37:42 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 63d11cedc8133bbc611e4943463ba8c8bff88951) [//]: # (START_SECTION 4f4a0f41d268ce510724e61ed4c9f77a1e23d666) ### Fixed resetpassword - Changed the SQL command so that it included the hostname from where the connection will be coming from > Commit: [4f4a0f41d268ce510724e61ed4c9f77a1e23d666](https://github.com/dOpensource/dsiprouter/commit/4f4a0f41d268ce510724e61ed4c9f77a1e23d666) > Date: Sat, 16 Jan 2021 02:28:59 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 4f4a0f41d268ce510724e61ed4c9f77a1e23d666) [//]: # (START_SECTION d6332da4c26f86cba02a24ba1cf9085a6ac49a77) ### Added support for deleting class of service when the domain is deleted > Commit: [d6332da4c26f86cba02a24ba1cf9085a6ac49a77](https://github.com/dOpensource/dsiprouter/commit/d6332da4c26f86cba02a24ba1cf9085a6ac49a77) > Date: Fri, 15 Jan 2021 20:29:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d6332da4c26f86cba02a24ba1cf9085a6ac49a77) [//]: # (START_SECTION 2f3505cebb8048a3ad9eb3aa238a4527ee8a354d) ### Added the ability to add a voicemail during the creation of an extension > Commit: [2f3505cebb8048a3ad9eb3aa238a4527ee8a354d](https://github.com/dOpensource/dsiprouter/commit/2f3505cebb8048a3ad9eb3aa238a4527ee8a354d) > Date: Fri, 15 Jan 2021 03:30:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2f3505cebb8048a3ad9eb3aa238a4527ee8a354d) [//]: # (START_SECTION eac246c60ddf8f940dc40da34fa8b458ede41004) ### V0.641 Bug Fixes > Commit: [eac246c60ddf8f940dc40da34fa8b458ede41004](https://github.com/dOpensource/dsiprouter/commit/eac246c60ddf8f940dc40da34fa8b458ede41004) > Date: Thu, 14 Jan 2021 17:47:38 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix various typos - fix defaults in kamcfg and settings.py - update settings.py to be generated on install - update documentation to use new settings.py path - update contributing guide to address settings change - update tests to use new settings location - force gzip to overwrite man pages if already created - add missing Vultr check for image build - fix reset passwords not being re-exported - fix error message on sync signal when dsiprouter not running - update rtpengine version (compilation issues on debian w/ current) - add rtpengine kernel module check to debian-based scripts --- [//]: # (END_SECTION eac246c60ddf8f940dc40da34fa8b458ede41004) [//]: # (START_SECTION 6b8cf1705666660973df8888803d5f0c132227c1) ### Update api_routes.py > Commit: [6b8cf1705666660973df8888803d5f0c132227c1](https://github.com/dOpensource/dsiprouter/commit/6b8cf1705666660973df8888803d5f0c132227c1) > Date: Thu, 14 Jan 2021 16:51:05 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6b8cf1705666660973df8888803d5f0c132227c1) [//]: # (START_SECTION 13ede23fedae0ca8a411d7adb75bffbe4d2e9dd0) ### Update api_routes.py > Commit: [13ede23fedae0ca8a411d7adb75bffbe4d2e9dd0](https://github.com/dOpensource/dsiprouter/commit/13ede23fedae0ca8a411d7adb75bffbe4d2e9dd0) > Date: Thu, 14 Jan 2021 15:48:20 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 13ede23fedae0ca8a411d7adb75bffbe4d2e9dd0) [//]: # (START_SECTION 8871255c04351ed846eda1ecbdada488c32b6d38) ### update to match some of our conventions > Commit: [8871255c04351ed846eda1ecbdada488c32b6d38](https://github.com/dOpensource/dsiprouter/commit/8871255c04351ed846eda1ecbdada488c32b6d38) > Date: Wed, 13 Jan 2021 17:49:00 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: GitHub (noreply@github.com) > Signed: - just some prep for merge --- [//]: # (END_SECTION 8871255c04351ed846eda1ecbdada488c32b6d38) [//]: # (START_SECTION 4d2d5345c335af336b00653a4b8f727bac942837) ### Update dsiprouter.sh > Commit: [4d2d5345c335af336b00653a4b8f727bac942837](https://github.com/dOpensource/dsiprouter/commit/4d2d5345c335af336b00653a4b8f727bac942837) > Date: Wed, 13 Jan 2021 16:59:30 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4d2d5345c335af336b00653a4b8f727bac942837) [//]: # (START_SECTION 4ffe5caab5d0835e7783d7c552b03c4b55d15524) ### Update dsiprouter.sh > Commit: [4ffe5caab5d0835e7783d7c552b03c4b55d15524](https://github.com/dOpensource/dsiprouter/commit/4ffe5caab5d0835e7783d7c552b03c4b55d15524) > Date: Wed, 13 Jan 2021 16:56:52 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4ffe5caab5d0835e7783d7c552b03c4b55d15524) [//]: # (START_SECTION fcd92513a0a2aee5c64764aa5caec08946459fdf) ### Finished implementing update and delete of extensions > Commit: [fcd92513a0a2aee5c64764aa5caec08946459fdf](https://github.com/dOpensource/dsiprouter/commit/fcd92513a0a2aee5c64764aa5caec08946459fdf) > Date: Tue, 12 Jan 2021 11:58:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fcd92513a0a2aee5c64764aa5caec08946459fdf) [//]: # (START_SECTION 1b213a4b393285804fb16547590ff56e8f573544) ### Update dsiprouter.sh > Commit: [1b213a4b393285804fb16547590ff56e8f573544](https://github.com/dOpensource/dsiprouter/commit/1b213a4b393285804fb16547590ff56e8f573544) > Date: Mon, 11 Jan 2021 20:33:11 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1b213a4b393285804fb16547590ff56e8f573544) [//]: # (START_SECTION f34291c8c07d1a11226e6d0d6261b32954cb4779) ### Fixed db host name character issue > Commit: [f34291c8c07d1a11226e6d0d6261b32954cb4779](https://github.com/dOpensource/dsiprouter/commit/f34291c8c07d1a11226e6d0d6261b32954cb4779) > Date: Mon, 11 Jan 2021 12:49:51 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f34291c8c07d1a11226e6d0d6261b32954cb4779) [//]: # (START_SECTION b2facf0143258ffabd83998501dc324ea0d70b2e) ### Update dsiprouter.sh > Commit: [b2facf0143258ffabd83998501dc324ea0d70b2e](https://github.com/dOpensource/dsiprouter/commit/b2facf0143258ffabd83998501dc324ea0d70b2e) > Date: Mon, 11 Jan 2021 11:30:17 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b2facf0143258ffabd83998501dc324ea0d70b2e) [//]: # (START_SECTION 056aa54a151d5dd84b0dbba14764dd9689db4bb4) ### Update dsiprouter.sh > Commit: [056aa54a151d5dd84b0dbba14764dd9689db4bb4](https://github.com/dOpensource/dsiprouter/commit/056aa54a151d5dd84b0dbba14764dd9689db4bb4) > Date: Mon, 11 Jan 2021 11:27:23 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 056aa54a151d5dd84b0dbba14764dd9689db4bb4) [//]: # (START_SECTION ef947db13678ba04d0871075388ce2ddbd29d3e8) ### Update dsiprouter.sh > Commit: [ef947db13678ba04d0871075388ce2ddbd29d3e8](https://github.com/dOpensource/dsiprouter/commit/ef947db13678ba04d0871075388ce2ddbd29d3e8) > Date: Mon, 11 Jan 2021 11:05:04 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ef947db13678ba04d0871075388ce2ddbd29d3e8) [//]: # (START_SECTION b94d77600ff45d7546888426cab34a326f0c4d45) ### Update dsiprouter.sh > Commit: [b94d77600ff45d7546888426cab34a326f0c4d45](https://github.com/dOpensource/dsiprouter/commit/b94d77600ff45d7546888426cab34a326f0c4d45) > Date: Mon, 11 Jan 2021 11:01:34 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b94d77600ff45d7546888426cab34a326f0c4d45) [//]: # (START_SECTION e36f27762b584aace9c899de266e8a9e9cfcd4a6) ### Update dsiprouter.sh > Commit: [e36f27762b584aace9c899de266e8a9e9cfcd4a6](https://github.com/dOpensource/dsiprouter/commit/e36f27762b584aace9c899de266e8a9e9cfcd4a6) > Date: Mon, 11 Jan 2021 10:54:19 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e36f27762b584aace9c899de266e8a9e9cfcd4a6) [//]: # (START_SECTION c5a6ef31fab26d2bd21351959a0527bde9540736) ### Update dsiprouter.1 > Commit: [c5a6ef31fab26d2bd21351959a0527bde9540736](https://github.com/dOpensource/dsiprouter/commit/c5a6ef31fab26d2bd21351959a0527bde9540736) > Date: Sat, 9 Jan 2021 12:38:13 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c5a6ef31fab26d2bd21351959a0527bde9540736) [//]: # (START_SECTION 257da44dfc069b910558e7fdc365ca204e44f9a0) ### Update dsiprouter.sh > Commit: [257da44dfc069b910558e7fdc365ca204e44f9a0](https://github.com/dOpensource/dsiprouter/commit/257da44dfc069b910558e7fdc365ca204e44f9a0) > Date: Sat, 9 Jan 2021 12:29:27 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 257da44dfc069b910558e7fdc365ca204e44f9a0) [//]: # (START_SECTION 1957202b3c57103ff6a2ecf9dba1b7caa59741ec) ### Create dsiprouter.1 > Commit: [1957202b3c57103ff6a2ecf9dba1b7caa59741ec](https://github.com/dOpensource/dsiprouter/commit/1957202b3c57103ff6a2ecf9dba1b7caa59741ec) > Date: Sat, 9 Jan 2021 12:25:23 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1957202b3c57103ff6a2ecf9dba1b7caa59741ec) [//]: # (START_SECTION 24d2dd3c20990f6c38362e96cd31b7905ed72c60) ### Set the setting.py file back to the defaults > Commit: [24d2dd3c20990f6c38362e96cd31b7905ed72c60](https://github.com/dOpensource/dsiprouter/commit/24d2dd3c20990f6c38362e96cd31b7905ed72c60) > Date: Tue, 5 Jan 2021 03:26:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 24d2dd3c20990f6c38362e96cd31b7905ed72c60) [//]: # (START_SECTION e7c81b1ddb71e72ae3f9fbe77c3447020240e308) ### Fixed an issue that prevented the credentials from being set when installing dSIP > Commit: [e7c81b1ddb71e72ae3f9fbe77c3447020240e308](https://github.com/dOpensource/dsiprouter/commit/e7c81b1ddb71e72ae3f9fbe77c3447020240e308) > Date: Tue, 5 Jan 2021 03:24:10 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION e7c81b1ddb71e72ae3f9fbe77c3447020240e308) [//]: # (START_SECTION 59d25e84e0d747cb7e8fd10cd60df6689fe0b3bc) ### Disabld Fraud Detection module > Commit: [59d25e84e0d747cb7e8fd10cd60df6689fe0b3bc](https://github.com/dOpensource/dsiprouter/commit/59d25e84e0d747cb7e8fd10cd60df6689fe0b3bc) > Date: Mon, 4 Jan 2021 22:14:44 +0000 > Author: root (root@sbc4.customers.dsiprouter.net) > Committer: root (root@sbc4.customers.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 59d25e84e0d747cb7e8fd10cd60df6689fe0b3bc) [//]: # (START_SECTION f80ef93a71dff7ae697bff7264a3cebf74c28163) ### Fixes For Remote DB Configuration > Commit: [f80ef93a71dff7ae697bff7264a3cebf74c28163](https://github.com/dOpensource/dsiprouter/commit/f80ef93a71dff7ae697bff7264a3cebf74c28163) > Date: Wed, 30 Dec 2020 23:58:05 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - # WIP for #326 - # WIP for #307 - add dsip version to kam Server header - remove `setkamdbconfig` command - update `setcredentials` command w/ root DB support - update `install` command w/ root DB support - change `DOMAIN` in `settings.py` to `DEFAULT_AUTH_DOMAIN` - fix root db access for dsip module installation - fix root db access on other CLI commands after install - move mysql install to before kamailio install - various command parsing improvements - renaming and better formatting for script variables - add rootdb credntial support to `security.setCreds()` - add some DB interfacing functions to `dsip_lib.sh` --- [//]: # (END_SECTION f80ef93a71dff7ae697bff7264a3cebf74c28163) [//]: # (START_SECTION f4a00e67519f6fbd9399db44a880634489676e5e) ### Fixed > Commit: [f4a00e67519f6fbd9399db44a880634489676e5e](https://github.com/dOpensource/dsiprouter/commit/f4a00e67519f6fbd9399db44a880634489676e5e) > Date: Wed, 30 Dec 2020 21:36:30 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f4a00e67519f6fbd9399db44a880634489676e5e) [//]: # (START_SECTION 537136db933b369ff0dadd0a35b0f8483fd1f5dc) ### Fixed Issue #292 - Changed the logic to check the status of MSTeams Option Messages using kamcmd to using the local Kamailio API endpoint > Commit: [537136db933b369ff0dadd0a35b0f8483fd1f5dc](https://github.com/dOpensource/dsiprouter/commit/537136db933b369ff0dadd0a35b0f8483fd1f5dc) > Date: Wed, 30 Dec 2020 21:20:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 537136db933b369ff0dadd0a35b0f8483fd1f5dc) [//]: # (START_SECTION e40a3e109cbaff6d5f4ae9b1cdae253f96b3d407) ### Updates - Added support for reading one or more extension from a FusionPBX domain > Commit: [e40a3e109cbaff6d5f4ae9b1cdae253f96b3d407](https://github.com/dOpensource/dsiprouter/commit/e40a3e109cbaff6d5f4ae9b1cdae253f96b3d407) > Date: Wed, 30 Dec 2020 12:20:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e40a3e109cbaff6d5f4ae9b1cdae253f96b3d407) [//]: # (START_SECTION d6d27ab8bb68fe69433a04eb5854656dc88e7ef3) ### Updating Media Service API Domains - Added support for updating and deleting domains > Commit: [d6d27ab8bb68fe69433a04eb5854656dc88e7ef3](https://github.com/dOpensource/dsiprouter/commit/d6d27ab8bb68fe69433a04eb5854656dc88e7ef3) > Date: Tue, 29 Dec 2020 05:09:21 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d6d27ab8bb68fe69433a04eb5854656dc88e7ef3) [//]: # (START_SECTION 5a193675fb7907390631c620e2f98a12266347e8) ### Added support for provisioning extensions > Commit: [5a193675fb7907390631c620e2f98a12266347e8](https://github.com/dOpensource/dsiprouter/commit/5a193675fb7907390631c620e2f98a12266347e8) > Date: Thu, 24 Dec 2020 02:00:37 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5a193675fb7907390631c620e2f98a12266347e8) [//]: # (START_SECTION 7ddc87fff9a6e124d2ccb1ec772352c4932446f9) ### Update Location Routing > Commit: [7ddc87fff9a6e124d2ccb1ec772352c4932446f9](https://github.com/dOpensource/dsiprouter/commit/7ddc87fff9a6e124d2ccb1ec772352c4932446f9) > Date: Wed, 23 Dec 2020 11:18:34 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add check for request domain for non-compliant UA's --- [//]: # (END_SECTION 7ddc87fff9a6e124d2ccb1ec772352c4932446f9) [//]: # (START_SECTION 48df26485429a3c1e785dfa82a1d64a4f3adda73) ### Fixed DO Name Error In Terraform > Commit: [48df26485429a3c1e785dfa82a1d64a4f3adda73](https://github.com/dOpensource/dsiprouter/commit/48df26485429a3c1e785dfa82a1d64a4f3adda73) > Date: Wed, 23 Dec 2020 10:25:20 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 48df26485429a3c1e785dfa82a1d64a4f3adda73) [//]: # (START_SECTION 2ffa80464ecb90ee77856939114833c074327cb0) ### Media Server API - Added support for creating a domain > Commit: [2ffa80464ecb90ee77856939114833c074327cb0](https://github.com/dOpensource/dsiprouter/commit/2ffa80464ecb90ee77856939114833c074327cb0) > Date: Wed, 23 Dec 2020 06:02:58 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2ffa80464ecb90ee77856939114833c074327cb0) [//]: # (START_SECTION 0dc523c62986c94ec14f59cf522b4d539420f5f5) ### Fix Typo In Location Route > Commit: [0dc523c62986c94ec14f59cf522b4d539420f5f5](https://github.com/dOpensource/dsiprouter/commit/0dc523c62986c94ec14f59cf522b4d539420f5f5) > Date: Tue, 22 Dec 2020 14:33:50 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change location routing to use from domain --- [//]: # (END_SECTION 0dc523c62986c94ec14f59cf522b4d539420f5f5) [//]: # (START_SECTION 646167fb8dc29f3b080ddcf112b5e75dfbdbb9f4) ### Fix Record Routing For SERVERNAT > Commit: [646167fb8dc29f3b080ddcf112b5e75dfbdbb9f4](https://github.com/dOpensource/dsiprouter/commit/646167fb8dc29f3b080ddcf112b5e75dfbdbb9f4) > Date: Tue, 22 Dec 2020 10:46:00 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix NAT translation in record routes - remove extraneous file `script.sh` --- [//]: # (END_SECTION 646167fb8dc29f3b080ddcf112b5e75dfbdbb9f4) [//]: # (START_SECTION 191cbc48d3813ccbf1d12681fe1084fab3c36696) ### Fixed issue with certificate permissions > Commit: [191cbc48d3813ccbf1d12681fe1084fab3c36696](https://github.com/dOpensource/dsiprouter/commit/191cbc48d3813ccbf1d12681fe1084fab3c36696) > Date: Mon, 21 Dec 2020 18:27:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 191cbc48d3813ccbf1d12681fe1084fab3c36696) [//]: # (START_SECTION d4dbff15c352ceded8459413be316c62bdcb58ad) ### FusionPBX Plugin for Media Server - Added logic to dynamically load the FusionPBX plugin - Added the basic interface for a MediaServer Plugin - Added specific logic for the FusionPBX MediaServer Plugin > Commit: [d4dbff15c352ceded8459413be316c62bdcb58ad](https://github.com/dOpensource/dsiprouter/commit/d4dbff15c352ceded8459413be316c62bdcb58ad) > Date: Sun, 20 Dec 2020 17:14:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - Issue #316 --- [//]: # (END_SECTION d4dbff15c352ceded8459413be316c62bdcb58ad) [//]: # (START_SECTION f8c73f821435298e7e6c388b26a98028b03d9ce3) ### Add to Media Server API - Added Configuration Checks - Added GET Action for Domain - Added the basis layout of the media server plugin architecture > Commit: [f8c73f821435298e7e6c388b26a98028b03d9ce3](https://github.com/dOpensource/dsiprouter/commit/f8c73f821435298e7e6c388b26a98028b03d9ce3) > Date: Sat, 19 Dec 2020 02:31:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f8c73f821435298e7e6c388b26a98028b03d9ce3) [//]: # (START_SECTION a77d93c7735658c3a04a7b6650607bb126ee2b53) ### Fixed an issue that prevented the Endpoint Group name from being presented in the Inbound Mapping page > Commit: [a77d93c7735658c3a04a7b6650607bb126ee2b53](https://github.com/dOpensource/dsiprouter/commit/a77d93c7735658c3a04a7b6650607bb126ee2b53) > Date: Wed, 16 Dec 2020 17:34:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a77d93c7735658c3a04a7b6650607bb126ee2b53) [//]: # (START_SECTION b72dc72adcf40fb62553db673ff70eef39220c96) ### Fixed logic for handling inbound calling thats routing to a DISPATCHER set > Commit: [b72dc72adcf40fb62553db673ff70eef39220c96](https://github.com/dOpensource/dsiprouter/commit/b72dc72adcf40fb62553db673ff70eef39220c96) > Date: Wed, 16 Dec 2020 11:25:34 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b72dc72adcf40fb62553db673ff70eef39220c96) [//]: # (START_SECTION 17f2e8702863666dd08403219099347711724310) ### FusionPBX Sync Support - Added cluster support to FusionPBX sync > Commit: [17f2e8702863666dd08403219099347711724310](https://github.com/dOpensource/dsiprouter/commit/17f2e8702863666dd08403219099347711724310) > Date: Tue, 15 Dec 2020 18:09:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 17f2e8702863666dd08403219099347711724310) [//]: # (START_SECTION 8f0d229dd89916ebe800228e94518a6ff2beda3f) ### Fixes issue #53 amd #297 > Commit: [8f0d229dd89916ebe800228e94518a6ff2beda3f](https://github.com/dOpensource/dsiprouter/commit/8f0d229dd89916ebe800228e94518a6ff2beda3f) > Date: Tue, 15 Dec 2020 12:08:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8f0d229dd89916ebe800228e94518a6ff2beda3f) [//]: # (START_SECTION 58df9f2d81b8094ff3f29a1c22b4de1b6d7f9eeb) ### Fixed syntax error > Commit: [58df9f2d81b8094ff3f29a1c22b4de1b6d7f9eeb](https://github.com/dOpensource/dsiprouter/commit/58df9f2d81b8094ff3f29a1c22b4de1b6d7f9eeb) > Date: Tue, 15 Dec 2020 11:34:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 58df9f2d81b8094ff3f29a1c22b4de1b6d7f9eeb) [//]: # (START_SECTION eeeb6cf0977ed3ada7ae47fd5607d0e22536b191) ### Media Server API - Inital commit for issue #316 - Moved the API security decorator to a shared function - Added instructions on how to add a new API and a sample API python script > Commit: [eeeb6cf0977ed3ada7ae47fd5607d0e22536b191](https://github.com/dOpensource/dsiprouter/commit/eeeb6cf0977ed3ada7ae47fd5607d0e22536b191) > Date: Sun, 13 Dec 2020 12:57:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eeeb6cf0977ed3ada7ae47fd5607d0e22536b191) [//]: # (START_SECTION c92729b89c5365414978f70453704d099e491eb9) ### Key / Certificate Handling Updates > Commit: [c92729b89c5365414978f70453704d099e491eb9](https://github.com/dOpensource/dsiprouter/commit/c92729b89c5365414978f70453704d099e491eb9) > Date: Wed, 9 Dec 2020 18:50:08 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #314 - fix fusionpbx sync not deleting old domains - fix host:port parsing on fusionpbx sync - add certificate detection on upload - add cert / key handling utility functions - add cert / key validation on upload - change default key / cert naming convention - add `DSIP_SSL_CA` parameter to modifiable settings - fix subscriber delete DB error on contact expiration - update default key lengths to be more secure - update cert generation to use correct permissions - fixup namespace clobbering in `api_routes.py` - remove option to disable SSL on install - fix nginx not starting on debug startup - fix some misc path names --- [//]: # (END_SECTION c92729b89c5365414978f70453704d099e491eb9) [//]: # (START_SECTION eff1a89624a28f73f080b92d3569a746933518b0) ### Fixed - Updated and deleting from Endpoint Groups that contain Weights > Commit: [eff1a89624a28f73f080b92d3569a746933518b0](https://github.com/dOpensource/dsiprouter/commit/eff1a89624a28f73f080b92d3569a746933518b0) > Date: Wed, 9 Dec 2020 22:56:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eff1a89624a28f73f080b92d3569a746933518b0) [//]: # (START_SECTION b52e1ef20efd0bae3bb2749d28ce01bc59f8c84d) ### Restructured Terraform Configuration > Commit: [b52e1ef20efd0bae3bb2749d28ce01bc59f8c84d](https://github.com/dOpensource/dsiprouter/commit/b52e1ef20efd0bae3bb2749d28ce01bc59f8c84d) > Date: Tue, 8 Dec 2020 09:49:27 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b52e1ef20efd0bae3bb2749d28ce01bc59f8c84d) [//]: # (START_SECTION 32581310a59093e773751cd4753a3e156e89e7e8) ### Fixed Inbound Mapping to allow FusionPBX Load Balancing > Commit: [32581310a59093e773751cd4753a3e156e89e7e8](https://github.com/dOpensource/dsiprouter/commit/32581310a59093e773751cd4753a3e156e89e7e8) > Date: Mon, 7 Dec 2020 19:38:19 +0000 > Author: root (root@mack-dsip-deb10.dsiprouter.org) > Committer: root (root@mack-dsip-deb10.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 32581310a59093e773751cd4753a3e156e89e7e8) [//]: # (START_SECTION 33535467399804add0ea69b5e7faf71c695014be) ### v0.641 Bug Fixes > Commit: [33535467399804add0ea69b5e7faf71c695014be](https://github.com/dOpensource/dsiprouter/commit/33535467399804add0ea69b5e7faf71c695014be) > Date: Mon, 7 Dec 2020 10:47:54 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Fixes [#310](https://github.com/dOpensource/dsiprouter/issues/310) - fix DNID enrichment enabled before tables installed - enable DMQ by default - increase external IP resolution timeout - add libwebsockets-dev dependency for next release of RTPENGINE - fix permissions issues with certificate upload --- [//]: # (END_SECTION 33535467399804add0ea69b5e7faf71c695014be) [//]: # (START_SECTION 21798511e6d33e40efea39d6ab68c640693e81cb) ### Updated the Endpoint Group and Inbound Mapping sections with more specific detail on how to use the new features > Commit: [21798511e6d33e40efea39d6ab68c640693e81cb](https://github.com/dOpensource/dsiprouter/commit/21798511e6d33e40efea39d6ab68c640693e81cb) > Date: Sat, 5 Dec 2020 09:04:26 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 21798511e6d33e40efea39d6ab68c640693e81cb) [//]: # (START_SECTION 886a9550c56d9317dd85494e1719a3229348fb3d) ### Updated doc's > Commit: [886a9550c56d9317dd85494e1719a3229348fb3d](https://github.com/dOpensource/dsiprouter/commit/886a9550c56d9317dd85494e1719a3229348fb3d) > Date: Fri, 4 Dec 2020 12:31:22 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 886a9550c56d9317dd85494e1719a3229348fb3d) [//]: # (START_SECTION d877f331b9bc248b9880c2edb099104cea420fac) ### Update conf.py > Commit: [d877f331b9bc248b9880c2edb099104cea420fac](https://github.com/dOpensource/dsiprouter/commit/d877f331b9bc248b9880c2edb099104cea420fac) > Date: Fri, 4 Dec 2020 08:31:30 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d877f331b9bc248b9880c2edb099104cea420fac) [//]: # (START_SECTION d585223c6a69512f4cb56d522d460296599a69e6) ### Update conf.py > Commit: [d585223c6a69512f4cb56d522d460296599a69e6](https://github.com/dOpensource/dsiprouter/commit/d585223c6a69512f4cb56d522d460296599a69e6) > Date: Fri, 4 Dec 2020 08:28:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d585223c6a69512f4cb56d522d460296599a69e6) [//]: # (START_SECTION 2a2131710df199e88189091ab16f307bb1089988) ### Update conf.py > Commit: [2a2131710df199e88189091ab16f307bb1089988](https://github.com/dOpensource/dsiprouter/commit/2a2131710df199e88189091ab16f307bb1089988) > Date: Fri, 4 Dec 2020 07:21:15 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2a2131710df199e88189091ab16f307bb1089988) [//]: # (START_SECTION 7da872d4edc18917deeedc4dde12f9bbe57a500d) ### Update .readthedocs.yml > Commit: [7da872d4edc18917deeedc4dde12f9bbe57a500d](https://github.com/dOpensource/dsiprouter/commit/7da872d4edc18917deeedc4dde12f9bbe57a500d) > Date: Fri, 4 Dec 2020 07:07:46 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7da872d4edc18917deeedc4dde12f9bbe57a500d) [//]: # (START_SECTION 025e5ec18575f08d2306a2c9ca99cc87981e9ee9) ### Create conf.py > Commit: [025e5ec18575f08d2306a2c9ca99cc87981e9ee9](https://github.com/dOpensource/dsiprouter/commit/025e5ec18575f08d2306a2c9ca99cc87981e9ee9) > Date: Thu, 3 Dec 2020 19:05:46 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 025e5ec18575f08d2306a2c9ca99cc87981e9ee9) [//]: # (START_SECTION e809c5c57ffdf627915db95e5f94e895f90291bb) ### Update .readthedocs.yml > Commit: [e809c5c57ffdf627915db95e5f94e895f90291bb](https://github.com/dOpensource/dsiprouter/commit/e809c5c57ffdf627915db95e5f94e895f90291bb) > Date: Thu, 3 Dec 2020 19:04:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e809c5c57ffdf627915db95e5f94e895f90291bb) [//]: # (START_SECTION 3db150d5c0f6dd107902b3656026e18711f7b959) ### Update .readthedocs.yml > Commit: [3db150d5c0f6dd107902b3656026e18711f7b959](https://github.com/dOpensource/dsiprouter/commit/3db150d5c0f6dd107902b3656026e18711f7b959) > Date: Thu, 3 Dec 2020 19:00:54 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3db150d5c0f6dd107902b3656026e18711f7b959) [//]: # (START_SECTION 2371850c27408aea29c0e98b792db5b548a32eb9) ### Update .readthedocs.yml > Commit: [2371850c27408aea29c0e98b792db5b548a32eb9](https://github.com/dOpensource/dsiprouter/commit/2371850c27408aea29c0e98b792db5b548a32eb9) > Date: Thu, 3 Dec 2020 18:40:44 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2371850c27408aea29c0e98b792db5b548a32eb9) [//]: # (START_SECTION 03493c243c12f606f1a5716841c2e7eb4185f32c) ### Delete requirements.txt > Commit: [03493c243c12f606f1a5716841c2e7eb4185f32c](https://github.com/dOpensource/dsiprouter/commit/03493c243c12f606f1a5716841c2e7eb4185f32c) > Date: Thu, 3 Dec 2020 18:38:59 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 03493c243c12f606f1a5716841c2e7eb4185f32c) [//]: # (START_SECTION 916e120ec3bbf7ea7089e93b8386b0facf00c4a0) ### Update requirements.txt > Commit: [916e120ec3bbf7ea7089e93b8386b0facf00c4a0](https://github.com/dOpensource/dsiprouter/commit/916e120ec3bbf7ea7089e93b8386b0facf00c4a0) > Date: Thu, 3 Dec 2020 18:32:22 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 916e120ec3bbf7ea7089e93b8386b0facf00c4a0) [//]: # (START_SECTION 76c7c679a947122e08cc5985623bbefbeb0681c7) ### Update .readthedocs.yml > Commit: [76c7c679a947122e08cc5985623bbefbeb0681c7](https://github.com/dOpensource/dsiprouter/commit/76c7c679a947122e08cc5985623bbefbeb0681c7) > Date: Thu, 3 Dec 2020 18:28:37 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 76c7c679a947122e08cc5985623bbefbeb0681c7) [//]: # (START_SECTION 085931328e9be893c65b674c9e491924afa88ca4) ### Update requirements.txt > Commit: [085931328e9be893c65b674c9e491924afa88ca4](https://github.com/dOpensource/dsiprouter/commit/085931328e9be893c65b674c9e491924afa88ca4) > Date: Thu, 3 Dec 2020 18:24:52 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 085931328e9be893c65b674c9e491924afa88ca4) [//]: # (START_SECTION c569928e35f25ab269b0aeab25423093f9db96b5) ### Delete requirements.txt > Commit: [c569928e35f25ab269b0aeab25423093f9db96b5](https://github.com/dOpensource/dsiprouter/commit/c569928e35f25ab269b0aeab25423093f9db96b5) > Date: Thu, 3 Dec 2020 18:24:16 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c569928e35f25ab269b0aeab25423093f9db96b5) [//]: # (START_SECTION 195bd8e7cc20468054182bb08fac2e80b80a0bea) ### Create requirements.txt > Commit: [195bd8e7cc20468054182bb08fac2e80b80a0bea](https://github.com/dOpensource/dsiprouter/commit/195bd8e7cc20468054182bb08fac2e80b80a0bea) > Date: Thu, 3 Dec 2020 18:23:23 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 195bd8e7cc20468054182bb08fac2e80b80a0bea) [//]: # (START_SECTION 14d26b51fa983c686b95a60afdfb145ea61c1bef) ### Update .readthedocs.yml > Commit: [14d26b51fa983c686b95a60afdfb145ea61c1bef](https://github.com/dOpensource/dsiprouter/commit/14d26b51fa983c686b95a60afdfb145ea61c1bef) > Date: Thu, 3 Dec 2020 12:13:55 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 14d26b51fa983c686b95a60afdfb145ea61c1bef) [//]: # (START_SECTION cff105fd47bada8ce5bafe07a5e6f85ac2b8579e) ### Create requirements.txt > Commit: [cff105fd47bada8ce5bafe07a5e6f85ac2b8579e](https://github.com/dOpensource/dsiprouter/commit/cff105fd47bada8ce5bafe07a5e6f85ac2b8579e) > Date: Thu, 3 Dec 2020 12:08:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cff105fd47bada8ce5bafe07a5e6f85ac2b8579e) [//]: # (START_SECTION 85874fade92d329fb0f6d7f824e06035f83eee97) ### Create .readthedocs.yml > Commit: [85874fade92d329fb0f6d7f824e06035f83eee97](https://github.com/dOpensource/dsiprouter/commit/85874fade92d329fb0f6d7f824e06035f83eee97) > Date: Thu, 3 Dec 2020 12:07:35 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 85874fade92d329fb0f6d7f824e06035f83eee97) [//]: # (START_SECTION 681a80c258d2011826c12d52874924843f17728e) ### Updates to FusionPBX Cluster Support: - Added support to inbound routes to map a DID to the Internal or External interface of a FusionPBX system > Commit: [681a80c258d2011826c12d52874924843f17728e](https://github.com/dOpensource/dsiprouter/commit/681a80c258d2011826c12d52874924843f17728e) > Date: Wed, 2 Dec 2020 12:15:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 681a80c258d2011826c12d52874924843f17728e) [//]: # (START_SECTION 9fc1b9c95e1c930caa9a711477e29f8ac94f8534) ### Merge Number Enrichment Into Current Build > Commit: [9fc1b9c95e1c930caa9a711477e29f8ac94f8534](https://github.com/dOpensource/dsiprouter/commit/9fc1b9c95e1c930caa9a711477e29f8ac94f8534) > Date: Tue, 1 Dec 2020 19:15:55 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#310](https://github.com/dOpensource/dsiprouter/issues/310) - merge in number enrichment feature - move teleblock settings to system settings menu - fix a few bash completions for dsiprouter commands - update upgrade command syntax to match others - misc CSS merges from enterprise repo - make module install scripts propagate return code - cleanup various scripts / configs / source files - update version in `settings.py` --- [//]: # (END_SECTION 9fc1b9c95e1c930caa9a711477e29f8ac94f8534) [//]: # (START_SECTION 78bb2e6ffb5d7e3d4d78ef3128bddb5f1f83104f) ### Updates to FusionPBX Cluster Support: - Added the ability to define an Endpoint Group as a FusionPBX Cluster versus a standalone FusionPBX Server - 2 dispatcher set's are created for each endpoint group, one for internal calls and another external calls on port 5080 > Commit: [78bb2e6ffb5d7e3d4d78ef3128bddb5f1f83104f](https://github.com/dOpensource/dsiprouter/commit/78bb2e6ffb5d7e3d4d78ef3128bddb5f1f83104f) > Date: Tue, 1 Dec 2020 23:14:51 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 78bb2e6ffb5d7e3d4d78ef3128bddb5f1f83104f) [//]: # (START_SECTION f74db4f6542d0f008fef614440e195ff724a7879) ### Allow TCP UAC SIP Connections By Default > Commit: [f74db4f6542d0f008fef614440e195ff724a7879](https://github.com/dOpensource/dsiprouter/commit/f74db4f6542d0f008fef614440e195ff724a7879) > Date: Tue, 1 Dec 2020 17:08:50 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#255](https://github.com/dOpensource/dsiprouter/issues/255) - add listen directives on tcp socket to kamailio config --- [//]: # (END_SECTION f74db4f6542d0f008fef614440e195ff724a7879) [//]: # (START_SECTION a409f4049744053e5958ea885b5d68524f15c6c4) ### Update NAT Handling > Commit: [a409f4049744053e5958ea885b5d68524f15c6c4](https://github.com/dOpensource/dsiprouter/commit/a409f4049744053e5958ea885b5d68524f15c6c4) > Date: Tue, 1 Dec 2020 17:04:19 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves [#311](https://github.com/dOpensource/dsiprouter/issues/311) - add client-side NAT translation for UAC Contact --- [//]: # (END_SECTION a409f4049744053e5958ea885b5d68524f15c6c4) [//]: # (START_SECTION d182417156ac4af142138abe951aefd6dea35259) ### FusionPBX Cluster Support Enhancements - Define a 30 second expiration to the pass_thru htable that keeps track of 401 and 407 auth request made by a media server and sends the request back to the same backend media server - Changed the address group that Kamailio uses to perform an internal route for DR_ROUTING to DISPATCHER use cases > Commit: [d182417156ac4af142138abe951aefd6dea35259](https://github.com/dOpensource/dsiprouter/commit/d182417156ac4af142138abe951aefd6dea35259) > Date: Mon, 30 Nov 2020 05:03:28 +0000 > Author: root (root@nightly-deb9.dsiprouter.org) > Committer: root (root@nightly-deb9.dsiprouter.org) > Signed: --- [//]: # (END_SECTION d182417156ac4af142138abe951aefd6dea35259) [//]: # (START_SECTION 9e45548eef592b97967607829e844913b6ced577) ### Added support for routing to multiple FusionPBX systems > Commit: [9e45548eef592b97967607829e844913b6ced577](https://github.com/dOpensource/dsiprouter/commit/9e45548eef592b97967607829e844913b6ced577) > Date: Mon, 23 Nov 2020 10:14:56 +0000 > Author: root (root@testing-dsiprouter0.b3paytcmeseudeqwsc2313xkvc.ex.internal.cloudapp.net) > Committer: root (root@nightly-deb9.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 9e45548eef592b97967607829e844913b6ced577) [//]: # (START_SECTION 86f9fc51b25f749858999ded032aebda17e7aa14) ### Added a restart of Kamailio after installing dSIPRouter to ensure it has the latest database connection info > Commit: [86f9fc51b25f749858999ded032aebda17e7aa14](https://github.com/dOpensource/dsiprouter/commit/86f9fc51b25f749858999ded032aebda17e7aa14) > Date: Fri, 27 Nov 2020 13:22:34 +0000 > Author: root (root@nightly-deb9.dsiprouter.org) > Committer: root (root@nightly-deb9.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 86f9fc51b25f749858999ded032aebda17e7aa14) [//]: # (START_SECTION 24a5c2115889e874b9c95a9ca1ffc59c80932d13) ### Added the default auth domain to the username/password auth screen > Commit: [24a5c2115889e874b9c95a9ca1ffc59c80932d13](https://github.com/dOpensource/dsiprouter/commit/24a5c2115889e874b9c95a9ca1ffc59c80932d13) > Date: Thu, 26 Nov 2020 14:22:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 24a5c2115889e874b9c95a9ca1ffc59c80932d13) [//]: # (START_SECTION 3333a6ec58d3c2530f33cf8132c18862c29d74ce) ### Improve Endpoint Group Address Entry Mapping > Commit: [3333a6ec58d3c2530f33cf8132c18862c29d74ce](https://github.com/dOpensource/dsiprouter/commit/3333a6ec58d3c2530f33cf8132c18862c29d74ce) > Date: Wed, 25 Nov 2020 16:59:23 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix address not deleted when endpoint group deleted - fix address not updated when changing from ip->user/pw auth and vice versa - fix bash auto completion for centos - fix dsiprouter auto completion not in effect until re-login --- [//]: # (END_SECTION 3333a6ec58d3c2530f33cf8132c18862c29d74ce) [//]: # (START_SECTION 702a0ac1811870197e64aa3dbaa04b9d9afea29f) ### Added ThinkTel as carrier > Commit: [702a0ac1811870197e64aa3dbaa04b9d9afea29f](https://github.com/dOpensource/dsiprouter/commit/702a0ac1811870197e64aa3dbaa04b9d9afea29f) > Date: Wed, 25 Nov 2020 17:37:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 702a0ac1811870197e64aa3dbaa04b9d9afea29f) [//]: # (START_SECTION 349e8b733e16eebb7ed693ff5bc0588a3b12cce4) ### Update CONTRIBUTORS.md > Commit: [349e8b733e16eebb7ed693ff5bc0588a3b12cce4](https://github.com/dOpensource/dsiprouter/commit/349e8b733e16eebb7ed693ff5bc0588a3b12cce4) > Date: Wed, 25 Nov 2020 08:35:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 349e8b733e16eebb7ed693ff5bc0588a3b12cce4) [//]: # (START_SECTION 04f2417b5cdc29bc08e217f8bf6f2a0cf07fa3a8) ### Fix Outbound Route Update Edge Case > Commit: [04f2417b5cdc29bc08e217f8bf6f2a0cf07fa3a8](https://github.com/dOpensource/dsiprouter/commit/04f2417b5cdc29bc08e217f8bf6f2a0cf07fa3a8) > Date: Tue, 24 Nov 2020 19:20:08 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug updating outbound routes when LCR routing entry exists - fix centos check for rtpengine recording RPM on rtpengine install - add centos fixes to amazon linux rtpengine install --- [//]: # (END_SECTION 04f2417b5cdc29bc08e217f8bf6f2a0cf07fa3a8) [//]: # (START_SECTION 5793453bc1f2476b4361c762611da08325fded96) ### Fix Fail2ban > Commit: [5793453bc1f2476b4361c762611da08325fded96](https://github.com/dOpensource/dsiprouter/commit/5793453bc1f2476b4361c762611da08325fded96) > Date: Tue, 24 Nov 2020 11:44:18 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: - -Used correct quotes --- [//]: # (END_SECTION 5793453bc1f2476b4361c762611da08325fded96) [//]: # (START_SECTION 62ba43bb6af91568e53e5ddcfe29f4740ae41788) ### fix broken rsyslog.service - only remove ; comment tags > Commit: [62ba43bb6af91568e53e5ddcfe29f4740ae41788](https://github.com/dOpensource/dsiprouter/commit/62ba43bb6af91568e53e5ddcfe29f4740ae41788) > Date: Tue, 24 Nov 2020 01:40:37 -0700 > Author: reqlez (6512602+reqlez@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: - Disable removing "#" comments tags and only remove ";" comment tags instead, otherwise, installing RTPENGINE under CentOS breaks rsyslog.service - By the way, dsiprouter logging is still broken even after this fix, I suspect there is another issue, possible with sysloginit.py but have not been able to figure out yet. --- [//]: # (END_SECTION 62ba43bb6af91568e53e5ddcfe29f4740ae41788) [//]: # (START_SECTION 9ef7eabc2075f31510565bf75226e46d30511cf6) ### Patch Old Host Variables > Commit: [9ef7eabc2075f31510565bf75226e46d30511cf6](https://github.com/dOpensource/dsiprouter/commit/9ef7eabc2075f31510565bf75226e46d30511cf6) > Date: Mon, 23 Nov 2020 16:25:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 9ef7eabc2075f31510565bf75226e46d30511cf6) [//]: # (START_SECTION ba3af5641e1244e52b148dd008e544d4631c86f5) ### dSIPRouter Install Fixes Patch > Commit: [ba3af5641e1244e52b148dd008e544d4631c86f5](https://github.com/dOpensource/dsiprouter/commit/ba3af5641e1244e52b148dd008e544d4631c86f5) > Date: Mon, 23 Nov 2020 13:38:16 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - patch systemd bug from [#295](https://github.com/dOpensource/dsiprouter/pull/295) - cleanup kamailio config - reset kamailio config defaults --- [//]: # (END_SECTION ba3af5641e1244e52b148dd008e544d4631c86f5) [//]: # (START_SECTION 24b32c7b053e73a0a3646854c998a2fc6c653ee4) ### VULTR Cloud Install Fixes > Commit: [24b32c7b053e73a0a3646854c998a2fc6c653ee4](https://github.com/dOpensource/dsiprouter/commit/24b32c7b053e73a0a3646854c998a2fc6c653ee4) > Date: Mon, 23 Nov 2020 12:25:42 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add vultr to cloud deployment checks - update cloud deployment checks - update `checkConn()` lib function - update same in `common` tests lib - fix external fqdn resolution for non-dns deployments - update `resetpassword()` cloud deployment check --- [//]: # (END_SECTION 24b32c7b053e73a0a3646854c998a2fc6c653ee4) [//]: # (START_SECTION a39fe323baf2b64f2b79c021b2767e05b2f98f7a) ### Update Pre-Push Git Hook > Commit: [a39fe323baf2b64f2b79c021b2767e05b2f98f7a](https://github.com/dOpensource/dsiprouter/commit/a39fe323baf2b64f2b79c021b2767e05b2f98f7a) > Date: Fri, 20 Nov 2020 17:09:25 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - lookup `REMOTE_NAME` when using default remote --- [//]: # (END_SECTION a39fe323baf2b64f2b79c021b2767e05b2f98f7a) [//]: # (START_SECTION 383bc1773a09bd9d2356e788fcf7bebadee489d8) ### dSIPRouter GUI Server Upgrade > Commit: [383bc1773a09bd9d2356e788fcf7bebadee489d8](https://github.com/dOpensource/dsiprouter/commit/383bc1773a09bd9d2356e788fcf7bebadee489d8) > Date: Fri, 20 Nov 2020 13:20:15 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix certbot not installed on RHEL-based OS - fix /var/run/dsiprouter/dsiprouter.pid not created on startup - fix hot reloading broken - fix http not redirected to https - fix install bug when `.git/` is not present - switch WSGI app server to bjoern - switch dsiprouter to using UNIX domain sockets - add various nginx performance improvements - add security improvements to flask app --- [//]: # (END_SECTION 383bc1773a09bd9d2356e788fcf7bebadee489d8) [//]: # (START_SECTION 0487fdf7ecc14fb4eea84f2ca8dd77d62191df0d) ### Nginx update: - Enabled Nginx after installation on CentOS 7 and CentOS 8 > Commit: [0487fdf7ecc14fb4eea84f2ca8dd77d62191df0d](https://github.com/dOpensource/dsiprouter/commit/0487fdf7ecc14fb4eea84f2ca8dd77d62191df0d) > Date: Fri, 20 Nov 2020 16:37:41 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0487fdf7ecc14fb4eea84f2ca8dd77d62191df0d) [//]: # (START_SECTION e29447636011bce47e75b8943840e87ef4db9ee3) ### Kamailio Module Load Ordering > Commit: [e29447636011bce47e75b8943840e87ef4db9ee3](https://github.com/dOpensource/dsiprouter/commit/e29447636011bce47e75b8943840e87ef4db9ee3) > Date: Thu, 19 Nov 2020 11:21:50 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix load order for tls module - make sipdump conditionally load --- [//]: # (END_SECTION e29447636011bce47e75b8943840e87ef4db9ee3) [//]: # (START_SECTION 442594103964d1441361071ae1f367dce714bda9) ### Add Missing Dependencies > Commit: [442594103964d1441361071ae1f367dce714bda9](https://github.com/dOpensource/dsiprouter/commit/442594103964d1441361071ae1f367dce714bda9) > Date: Wed, 18 Nov 2020 17:29:02 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add `sphinx-rtd-theme` and `uwsgi` dependency --- [//]: # (END_SECTION 442594103964d1441361071ae1f367dce714bda9) [//]: # (START_SECTION d39844eda0194d17dc47d205c5bc80ac1b4bb296) ### Fix RPM Search For CentOS Kernel Headers > Commit: [d39844eda0194d17dc47d205c5bc80ac1b4bb296](https://github.com/dOpensource/dsiprouter/commit/d39844eda0194d17dc47d205c5bc80ac1b4bb296) > Date: Wed, 18 Nov 2020 16:49:11 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add searching centos archives to `rpmSearch()` - add dnf support to `setVerbosityLevel()` --- [//]: # (END_SECTION d39844eda0194d17dc47d205c5bc80ac1b4bb296) [//]: # (START_SECTION a6c9c5a3fd93382c758002354e5e362516e70be7) ### Missing uwsgi and sphinx_rtd_theme python packages > Commit: [a6c9c5a3fd93382c758002354e5e362516e70be7](https://github.com/dOpensource/dsiprouter/commit/a6c9c5a3fd93382c758002354e5e362516e70be7) > Date: Wed, 18 Nov 2020 09:58:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a6c9c5a3fd93382c758002354e5e362516e70be7) [//]: # (START_SECTION b883b914b2ca6c0e6d9908c4611e9c4251c2eb0a) ### dsiprouter installation fixes > Commit: [b883b914b2ca6c0e6d9908c4611e9c4251c2eb0a](https://github.com/dOpensource/dsiprouter/commit/b883b914b2ca6c0e6d9908c4611e9c4251c2eb0a) > Date: Tue, 17 Nov 2020 19:40:41 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix psycopg2 dependency issue on centos - fix flask dependency issue on centos - fixup printing commands in sub-processes - fix kernel header lookups for rtpengine install - fixup centos rtpengine install code - change rtpengine to use kernel packet forwarding by default - improve efficiency by exporting sub-process required funcs - update rtpengine version to latest release - add nginx changes to centos8 - update centos to use kamailio 5.3 - fixed grep file checks outputting text during install - add some utility functions to `dsip_lib.sh` - revert centos8 to using mariadb instead of mysql - fix dependency conflicts between mysql-common and mariadb-common - fix centos8 python dependencies - various fixes for centos8 service install scripts --- [//]: # (END_SECTION b883b914b2ca6c0e6d9908c4611e9c4251c2eb0a) [//]: # (START_SECTION 348d856841f14014714bc55206cdbbfb4c849445) ### Fixes issue #291 and removes the default Nginx server > Commit: [348d856841f14014714bc55206cdbbfb4c849445](https://github.com/dOpensource/dsiprouter/commit/348d856841f14014714bc55206cdbbfb4c849445) > Date: Tue, 17 Nov 2020 00:56:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 348d856841f14014714bc55206cdbbfb4c849445) [//]: # (START_SECTION e17b362a94175aaab6ffc9e711e95fa80df08acc) ### Fixed Debian 9 dSIPRouter install > Commit: [e17b362a94175aaab6ffc9e711e95fa80df08acc](https://github.com/dOpensource/dsiprouter/commit/e17b362a94175aaab6ffc9e711e95fa80df08acc) > Date: Sun, 15 Nov 2020 04:17:36 +0000 > Author: root (root@testing-vm.5sbaumxjh52uxarymuyxislztd.ex.internal.cloudapp.net) > Committer: root (root@testing-vm.5sbaumxjh52uxarymuyxislztd.ex.internal.cloudapp.net) > Signed: --- [//]: # (END_SECTION e17b362a94175aaab6ffc9e711e95fa80df08acc) [//]: # (START_SECTION b04ed0097dd4ba5867062c16ec2d654e72ac6d6e) ### Dynamically obtain the username that nginx is running under > Commit: [b04ed0097dd4ba5867062c16ec2d654e72ac6d6e](https://github.com/dOpensource/dsiprouter/commit/b04ed0097dd4ba5867062c16ec2d654e72ac6d6e) > Date: Sun, 15 Nov 2020 01:17:27 +0000 > Author: root (root@testing-vm.4fyalwlohqaujmzfo3kwxhyerh.ex.internal.cloudapp.net) > Committer: root (root@testing-vm.4fyalwlohqaujmzfo3kwxhyerh.ex.internal.cloudapp.net) > Signed: --- [//]: # (END_SECTION b04ed0097dd4ba5867062c16ec2d654e72ac6d6e) [//]: # (START_SECTION 315cbdcde908c5e94fc7f283c2b9d0c76e63f4a6) ### Update 9.sh > Commit: [315cbdcde908c5e94fc7f283c2b9d0c76e63f4a6](https://github.com/dOpensource/dsiprouter/commit/315cbdcde908c5e94fc7f283c2b9d0c76e63f4a6) > Date: Sat, 14 Nov 2020 19:38:47 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 315cbdcde908c5e94fc7f283c2b9d0c76e63f4a6) [//]: # (START_SECTION 7f50034e56da5e6562eb3e03c813263a5c13fbb7) ### Update 10.sh > Commit: [7f50034e56da5e6562eb3e03c813263a5c13fbb7](https://github.com/dOpensource/dsiprouter/commit/7f50034e56da5e6562eb3e03c813263a5c13fbb7) > Date: Sat, 14 Nov 2020 19:37:48 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7f50034e56da5e6562eb3e03c813263a5c13fbb7) [//]: # (START_SECTION 52f64d507d7c5d867f3d8fbeb97aa52ad664ec36) ### Updates > Commit: [52f64d507d7c5d867f3d8fbeb97aa52ad664ec36](https://github.com/dOpensource/dsiprouter/commit/52f64d507d7c5d867f3d8fbeb97aa52ad664ec36) > Date: Sat, 14 Nov 2020 22:22:21 +0000 > Author: root (root@nightly-centos7.dsiprouter.org) > Committer: root (root@nightly-centos7.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 52f64d507d7c5d867f3d8fbeb97aa52ad664ec36) [//]: # (START_SECTION 302a6275995e2398f18aa59ad871554456d4b68c) ### Changed the order in which firewall rules are applied first and then firewalld is started > Commit: [302a6275995e2398f18aa59ad871554456d4b68c](https://github.com/dOpensource/dsiprouter/commit/302a6275995e2398f18aa59ad871554456d4b68c) > Date: Sat, 14 Nov 2020 21:29:37 +0000 > Author: root (root@nightly-centos7.dsiprouter.org) > Committer: root (root@nightly-centos7.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 302a6275995e2398f18aa59ad871554456d4b68c) [//]: # (START_SECTION 979e7f28b2c2efbf113f898b689d6603bfd274e0) ### dSIP Documentation - Re-enabled documentation to be generated > Commit: [979e7f28b2c2efbf113f898b689d6603bfd274e0](https://github.com/dOpensource/dsiprouter/commit/979e7f28b2c2efbf113f898b689d6603bfd274e0) > Date: Sat, 14 Nov 2020 19:33:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 979e7f28b2c2efbf113f898b689d6603bfd274e0) [//]: # (START_SECTION 7507cd91dffe48de1b52cd1060610332753dc890) ### Updates to support Kamailio on CentOS > Commit: [7507cd91dffe48de1b52cd1060610332753dc890](https://github.com/dOpensource/dsiprouter/commit/7507cd91dffe48de1b52cd1060610332753dc890) > Date: Sat, 14 Nov 2020 19:31:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7507cd91dffe48de1b52cd1060610332753dc890) [//]: # (START_SECTION bad92e9378ef8164cd37f1c190f92e08ec4c5836) ### CentOS Updated - Added kamailio-sipdump package to the installer > Commit: [bad92e9378ef8164cd37f1c190f92e08ec4c5836](https://github.com/dOpensource/dsiprouter/commit/bad92e9378ef8164cd37f1c190f92e08ec4c5836) > Date: Sat, 14 Nov 2020 19:24:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bad92e9378ef8164cd37f1c190f92e08ec4c5836) [//]: # (START_SECTION e8480a8c7036b9965c4a7424fc438aa35e4d423b) ### Updates to support Nginx on CentOS 7 > Commit: [e8480a8c7036b9965c4a7424fc438aa35e4d423b](https://github.com/dOpensource/dsiprouter/commit/e8480a8c7036b9965c4a7424fc438aa35e4d423b) > Date: Sat, 14 Nov 2020 19:00:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e8480a8c7036b9965c4a7424fc438aa35e4d423b) [//]: # (START_SECTION a2e9980ec557656d248823fc4366c7fa0f8edb24) ### Added missing library for Sphinx > Commit: [a2e9980ec557656d248823fc4366c7fa0f8edb24](https://github.com/dOpensource/dsiprouter/commit/a2e9980ec557656d248823fc4366c7fa0f8edb24) > Date: Sat, 14 Nov 2020 02:30:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a2e9980ec557656d248823fc4366c7fa0f8edb24) [//]: # (START_SECTION 7b65855389c4be45c90bfb866eddc9451a8e1d9b) ### Update requirements to add back in the uwsgi server > Commit: [7b65855389c4be45c90bfb866eddc9451a8e1d9b](https://github.com/dOpensource/dsiprouter/commit/7b65855389c4be45c90bfb866eddc9451a8e1d9b) > Date: Sat, 14 Nov 2020 01:17:49 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 7b65855389c4be45c90bfb866eddc9451a8e1d9b) [//]: # (START_SECTION 16d2a8ea03bd62fbce4078450c714bab9625175d) ### Update kamailio_dsiprouter.cfg > Commit: [16d2a8ea03bd62fbce4078450c714bab9625175d](https://github.com/dOpensource/dsiprouter/commit/16d2a8ea03bd62fbce4078450c714bab9625175d) > Date: Fri, 13 Nov 2020 18:08:30 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Adding support for servernat --- [//]: # (END_SECTION 16d2a8ea03bd62fbce4078450c714bab9625175d) [//]: # (START_SECTION 3fd6ffb00ebd98f4e93fe0e8db7a8c4121a6c670) ### Update dsiprouter.sh > Commit: [3fd6ffb00ebd98f4e93fe0e8db7a8c4121a6c670](https://github.com/dOpensource/dsiprouter/commit/3fd6ffb00ebd98f4e93fe0e8db7a8c4121a6c670) > Date: Fri, 13 Nov 2020 16:32:12 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3fd6ffb00ebd98f4e93fe0e8db7a8c4121a6c670) [//]: # (START_SECTION 46667fae98f6c8b5ac627d4e0ab717413c9abb08) ### Update kamailio_dsiprouter.cfg > Commit: [46667fae98f6c8b5ac627d4e0ab717413c9abb08](https://github.com/dOpensource/dsiprouter/commit/46667fae98f6c8b5ac627d4e0ab717413c9abb08) > Date: Fri, 13 Nov 2020 16:08:42 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 46667fae98f6c8b5ac627d4e0ab717413c9abb08) [//]: # (START_SECTION 31515bd149e609f6f676bb357d461d6e83a01608) ### Update kamailio_dsiprouter.cfg > Commit: [31515bd149e609f6f676bb357d461d6e83a01608](https://github.com/dOpensource/dsiprouter/commit/31515bd149e609f6f676bb357d461d6e83a01608) > Date: Fri, 13 Nov 2020 15:51:28 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 31515bd149e609f6f676bb357d461d6e83a01608) [//]: # (START_SECTION 373b3b573a04bc6dfa83cab99bd9d881e799f741) ### Removed Sphinx due to causing build to fail > Commit: [373b3b573a04bc6dfa83cab99bd9d881e799f741](https://github.com/dOpensource/dsiprouter/commit/373b3b573a04bc6dfa83cab99bd9d881e799f741) > Date: Fri, 13 Nov 2020 20:45:24 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 373b3b573a04bc6dfa83cab99bd9d881e799f741) [//]: # (START_SECTION a4dff281c4621b70e411a6013293396a1b673c70) ### Add placeholders for Docs Dirs > Commit: [a4dff281c4621b70e411a6013293396a1b673c70](https://github.com/dOpensource/dsiprouter/commit/a4dff281c4621b70e411a6013293396a1b673c70) > Date: Fri, 13 Nov 2020 13:28:29 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix sphinx missing dir by adding placeholders --- [//]: # (END_SECTION a4dff281c4621b70e411a6013293396a1b673c70) [//]: # (START_SECTION aeb39781690a8641f1164d35fe1651f1fbfe355c) ### Added Nginx support - CentOS 7 - Debian 10 > Commit: [aeb39781690a8641f1164d35fe1651f1fbfe355c](https://github.com/dOpensource/dsiprouter/commit/aeb39781690a8641f1164d35fe1651f1fbfe355c) > Date: Fri, 13 Nov 2020 16:55:13 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION aeb39781690a8641f1164d35fe1651f1fbfe355c) [//]: # (START_SECTION 07639d4e4af2b8232ad4835ca890f2c7e1ad7414) ### Add Rendered Docs to GUI > Commit: [07639d4e4af2b8232ad4835ca890f2c7e1ad7414](https://github.com/dOpensource/dsiprouter/commit/07639d4e4af2b8232ad4835ca890f2c7e1ad7414) > Date: Fri, 13 Nov 2020 11:21:22 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move old docs to user documentation - add developer docs - add routing docs - create local docs on install - add sphinx deps to git hooks --- [//]: # (END_SECTION 07639d4e4af2b8232ad4835ca890f2c7e1ad7414) [//]: # (START_SECTION 9c97ea3d520064dde2bd147946e9181b3145dcc2) ### Fixed permissions > Commit: [9c97ea3d520064dde2bd147946e9181b3145dcc2](https://github.com/dOpensource/dsiprouter/commit/9c97ea3d520064dde2bd147946e9181b3145dcc2) > Date: Fri, 13 Nov 2020 14:50:00 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 9c97ea3d520064dde2bd147946e9181b3145dcc2) [//]: # (START_SECTION dd45f9e65664bc3f4bf324e6bfbd5053f4d72e99) ### Fixed permissions - Changed kamailio_dsiprouter.cfg permissions to allow the dSIPRouter UI to access it > Commit: [dd45f9e65664bc3f4bf324e6bfbd5053f4d72e99](https://github.com/dOpensource/dsiprouter/commit/dd45f9e65664bc3f4bf324e6bfbd5053f4d72e99) > Date: Fri, 13 Nov 2020 13:48:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION dd45f9e65664bc3f4bf324e6bfbd5053f4d72e99) [//]: # (START_SECTION 5b6ff83b93434abffcc48d57b1b716d5194cb2a2) ### Changed the owner of settings.py to dsiprouter > Commit: [5b6ff83b93434abffcc48d57b1b716d5194cb2a2](https://github.com/dOpensource/dsiprouter/commit/5b6ff83b93434abffcc48d57b1b716d5194cb2a2) > Date: Fri, 13 Nov 2020 00:32:25 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 5b6ff83b93434abffcc48d57b1b716d5194cb2a2) [//]: # (START_SECTION bf8e7fc878dd464ca797d2eea74dad58332261ca) ### Updated dSIP version > Commit: [bf8e7fc878dd464ca797d2eea74dad58332261ca](https://github.com/dOpensource/dsiprouter/commit/bf8e7fc878dd464ca797d2eea74dad58332261ca) > Date: Fri, 13 Nov 2020 00:04:09 +0000 > Author: root (root@nightly.dsiprouter.org) > Committer: root (root@nightly.dsiprouter.org) > Signed: --- [//]: # (END_SECTION bf8e7fc878dd464ca797d2eea74dad58332261ca) [//]: # (START_SECTION 3f3bb7416475875a443b77aa98f7f4886458c207) ### Nginx fixes - Changed the permissions of /etc/dsiprouter/privkey to user dsiprouter > Commit: [3f3bb7416475875a443b77aa98f7f4886458c207](https://github.com/dOpensource/dsiprouter/commit/3f3bb7416475875a443b77aa98f7f4886458c207) > Date: Thu, 12 Nov 2020 19:40:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3f3bb7416475875a443b77aa98f7f4886458c207) [//]: # (START_SECTION 73ed951374c89b5aacc089e88710118f4fdfc203) ### Update Nginx configuration > Commit: [73ed951374c89b5aacc089e88710118f4fdfc203](https://github.com/dOpensource/dsiprouter/commit/73ed951374c89b5aacc089e88710118f4fdfc203) > Date: Thu, 12 Nov 2020 19:25:59 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 73ed951374c89b5aacc089e88710118f4fdfc203) [//]: # (START_SECTION e2c2b6d70dcad60da704a37e9800948dbbe469e2) ### Nginx update: Issues #60 > Commit: [e2c2b6d70dcad60da704a37e9800948dbbe469e2](https://github.com/dOpensource/dsiprouter/commit/e2c2b6d70dcad60da704a37e9800948dbbe469e2) > Date: Thu, 12 Nov 2020 17:59:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e2c2b6d70dcad60da704a37e9800948dbbe469e2) [//]: # (START_SECTION 9cf083098084c9e450154138455c57cc2843952f) ### Nginx Support - Initial commit for Issue #60 - Debian 9 support > Commit: [9cf083098084c9e450154138455c57cc2843952f](https://github.com/dOpensource/dsiprouter/commit/9cf083098084c9e450154138455c57cc2843952f) > Date: Thu, 12 Nov 2020 17:36:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9cf083098084c9e450154138455c57cc2843952f) [//]: # (START_SECTION 2687815c353e29a9251ff41bdf334757d5376daf) ### Update install.sh > Commit: [2687815c353e29a9251ff41bdf334757d5376daf](https://github.com/dOpensource/dsiprouter/commit/2687815c353e29a9251ff41bdf334757d5376daf) > Date: Thu, 12 Nov 2020 10:30:35 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2687815c353e29a9251ff41bdf334757d5376daf) [//]: # (START_SECTION d72d5a8a07999ad84d0a1197db00e979b588f054) ### Update dsiprouter.sh > Commit: [d72d5a8a07999ad84d0a1197db00e979b588f054](https://github.com/dOpensource/dsiprouter/commit/d72d5a8a07999ad84d0a1197db00e979b588f054) > Date: Wed, 11 Nov 2020 12:40:54 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d72d5a8a07999ad84d0a1197db00e979b588f054) [//]: # (START_SECTION 22cab0ab976068d4b49ecd7d4fa02e65bea4849a) ### Update 9.sh > Commit: [22cab0ab976068d4b49ecd7d4fa02e65bea4849a](https://github.com/dOpensource/dsiprouter/commit/22cab0ab976068d4b49ecd7d4fa02e65bea4849a) > Date: Wed, 11 Nov 2020 12:38:44 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 22cab0ab976068d4b49ecd7d4fa02e65bea4849a) [//]: # (START_SECTION 10c95ab999173de41a257a626d3cd89017b52b1f) ### Resolves #268 > Commit: [10c95ab999173de41a257a626d3cd89017b52b1f](https://github.com/dOpensource/dsiprouter/commit/10c95ab999173de41a257a626d3cd89017b52b1f) > Date: Wed, 11 Nov 2020 06:38:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 10c95ab999173de41a257a626d3cd89017b52b1f) [//]: # (START_SECTION a1d693a7da90ba8eaaca8134f36260485f2f051c) ### Fix ServerNAT Aliasing For External-Internal Address > Commit: [a1d693a7da90ba8eaaca8134f36260485f2f051c](https://github.com/dOpensource/dsiprouter/commit/a1d693a7da90ba8eaaca8134f36260485f2f051c) > Date: Mon, 9 Nov 2020 17:20:44 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add r2=on record route param to domain routing and outbound routing --- [//]: # (END_SECTION a1d693a7da90ba8eaaca8134f36260485f2f051c) [//]: # (START_SECTION 9243cade3cdb3c1cbbe77a3ca0a46ef8cc593d47) ### Update install.sh > Commit: [9243cade3cdb3c1cbbe77a3ca0a46ef8cc593d47](https://github.com/dOpensource/dsiprouter/commit/9243cade3cdb3c1cbbe77a3ca0a46ef8cc593d47) > Date: Thu, 5 Nov 2020 10:12:30 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9243cade3cdb3c1cbbe77a3ca0a46ef8cc593d47) [//]: # (START_SECTION dfcc241369d1b0f5abbbc163493bfbd3273bbc66) ### Update install.sh > Commit: [dfcc241369d1b0f5abbbc163493bfbd3273bbc66](https://github.com/dOpensource/dsiprouter/commit/dfcc241369d1b0f5abbbc163493bfbd3273bbc66) > Date: Thu, 5 Nov 2020 09:32:33 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dfcc241369d1b0f5abbbc163493bfbd3273bbc66) [//]: # (START_SECTION 6258abfd7c1817492550798d663e3e5a5c3a54c8) ### CentOS 8 RTPEngine Fix > Commit: [6258abfd7c1817492550798d663e3e5a5c3a54c8](https://github.com/dOpensource/dsiprouter/commit/6258abfd7c1817492550798d663e3e5a5c3a54c8) > Date: Wed, 4 Nov 2020 11:29:50 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6258abfd7c1817492550798d663e3e5a5c3a54c8) [//]: # (START_SECTION 247eb3fbb0690de397e3283df37eeee8dd53a6ea) ### Update install.sh > Commit: [247eb3fbb0690de397e3283df37eeee8dd53a6ea](https://github.com/dOpensource/dsiprouter/commit/247eb3fbb0690de397e3283df37eeee8dd53a6ea) > Date: Wed, 4 Nov 2020 10:35:49 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 247eb3fbb0690de397e3283df37eeee8dd53a6ea) [//]: # (START_SECTION 2b6ee47fc7611438ab7f2b0584cce3cf086ca6f4) ### CentOS 8 RTPEngine > Commit: [2b6ee47fc7611438ab7f2b0584cce3cf086ca6f4](https://github.com/dOpensource/dsiprouter/commit/2b6ee47fc7611438ab7f2b0584cce3cf086ca6f4) > Date: Tue, 3 Nov 2020 16:19:19 +0000 > Author: richard (richstorm781@gmail.com) > Committer: richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION 2b6ee47fc7611438ab7f2b0584cce3cf086ca6f4) [//]: # (START_SECTION 20c240e9c512c3d4966f0f7ca4142b80e8de896f) ### API Updated - Updated the inboundmapping API - Added a Postman collection of the API to make it easier for developers to test the API > Commit: [20c240e9c512c3d4966f0f7ca4142b80e8de896f](https://github.com/dOpensource/dsiprouter/commit/20c240e9c512c3d4966f0f7ca4142b80e8de896f) > Date: Mon, 2 Nov 2020 19:31:59 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 20c240e9c512c3d4966f0f7ca4142b80e8de896f) [//]: # (START_SECTION 9c57c6f953bed6e9348cd47ab06a132357ddf573) ### Update install.sh > Commit: [9c57c6f953bed6e9348cd47ab06a132357ddf573](https://github.com/dOpensource/dsiprouter/commit/9c57c6f953bed6e9348cd47ab06a132357ddf573) > Date: Mon, 2 Nov 2020 12:45:04 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9c57c6f953bed6e9348cd47ab06a132357ddf573) [//]: # (START_SECTION db071da5fc4d62ffcb9e97ab8b5ed4a040c01de7) ### Update install.sh > Commit: [db071da5fc4d62ffcb9e97ab8b5ed4a040c01de7](https://github.com/dOpensource/dsiprouter/commit/db071da5fc4d62ffcb9e97ab8b5ed4a040c01de7) > Date: Mon, 2 Nov 2020 11:50:11 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION db071da5fc4d62ffcb9e97ab8b5ed4a040c01de7) [//]: # (START_SECTION c43e91b217077be43e04f48a2d014d096f274d59) ### Centos 8 RtpEngine fix > Commit: [c43e91b217077be43e04f48a2d014d096f274d59](https://github.com/dOpensource/dsiprouter/commit/c43e91b217077be43e04f48a2d014d096f274d59) > Date: Mon, 2 Nov 2020 15:46:51 +0000 > Author: Richard (richstorm781@gmail.com) > Committer: Richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION c43e91b217077be43e04f48a2d014d096f274d59) [//]: # (START_SECTION 19c08a7c0a935506b65ae130bb5c1edb214cfbbf) ### Fixed issue with inbound calls coming from Carrier or PBX to MSTeams > Commit: [19c08a7c0a935506b65ae130bb5c1edb214cfbbf](https://github.com/dOpensource/dsiprouter/commit/19c08a7c0a935506b65ae130bb5c1edb214cfbbf) > Date: Mon, 2 Nov 2020 12:00:13 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 19c08a7c0a935506b65ae130bb5c1edb214cfbbf) [//]: # (START_SECTION 4c4110991857ec7ecbf5f1ce0280d3057b4ed9c7) ### Fixed issue with messages not routing back to MSTeams from a Carrier or PBX > Commit: [4c4110991857ec7ecbf5f1ce0280d3057b4ed9c7](https://github.com/dOpensource/dsiprouter/commit/4c4110991857ec7ecbf5f1ce0280d3057b4ed9c7) > Date: Mon, 2 Nov 2020 10:50:17 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4c4110991857ec7ecbf5f1ce0280d3057b4ed9c7) [//]: # (START_SECTION f595b16e67adcf5278e9bdcec065422cbe5c9b4b) ### Update install.sh > Commit: [f595b16e67adcf5278e9bdcec065422cbe5c9b4b](https://github.com/dOpensource/dsiprouter/commit/f595b16e67adcf5278e9bdcec065422cbe5c9b4b) > Date: Sun, 1 Nov 2020 22:39:26 -0500 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f595b16e67adcf5278e9bdcec065422cbe5c9b4b) [//]: # (START_SECTION 0f60305db7c4f3476af529ea7b778540e06a9958) ### Update install.sh > Commit: [0f60305db7c4f3476af529ea7b778540e06a9958](https://github.com/dOpensource/dsiprouter/commit/0f60305db7c4f3476af529ea7b778540e06a9958) > Date: Wed, 28 Oct 2020 11:56:49 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0f60305db7c4f3476af529ea7b778540e06a9958) [//]: # (START_SECTION 51efbde55b88dcd80688a6055608daa5fdb30a27) ### Debian 10 Fixes > Commit: [51efbde55b88dcd80688a6055608daa5fdb30a27](https://github.com/dOpensource/dsiprouter/commit/51efbde55b88dcd80688a6055608daa5fdb30a27) > Date: Wed, 28 Oct 2020 10:55:08 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add linux headers to rtpengine deps for deb10 - change deb10 install to use legacy iptables - remove BETA header for deb10 install - add deb11 symlink for BETA testers - increase size of multidomain mapping columns --- [//]: # (END_SECTION 51efbde55b88dcd80688a6055608daa5fdb30a27) [//]: # (START_SECTION e7e33f0ca3224bab3d75b40cdce93d3da36ccac6) ### Update install.sh > Commit: [e7e33f0ca3224bab3d75b40cdce93d3da36ccac6](https://github.com/dOpensource/dsiprouter/commit/e7e33f0ca3224bab3d75b40cdce93d3da36ccac6) > Date: Wed, 28 Oct 2020 10:52:38 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e7e33f0ca3224bab3d75b40cdce93d3da36ccac6) [//]: # (START_SECTION ed99fde349f38f1f1f5250ff4d9a02faecab9c3b) ### Update install.sh > Commit: [ed99fde349f38f1f1f5250ff4d9a02faecab9c3b](https://github.com/dOpensource/dsiprouter/commit/ed99fde349f38f1f1f5250ff4d9a02faecab9c3b) > Date: Tue, 27 Oct 2020 12:55:55 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ed99fde349f38f1f1f5250ff4d9a02faecab9c3b) [//]: # (START_SECTION b3f1701f70ea9a610e81a4bc08fa5e1ff3ac9a93) ### Update install.sh > Commit: [b3f1701f70ea9a610e81a4bc08fa5e1ff3ac9a93](https://github.com/dOpensource/dsiprouter/commit/b3f1701f70ea9a610e81a4bc08fa5e1ff3ac9a93) > Date: Tue, 27 Oct 2020 11:50:15 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b3f1701f70ea9a610e81a4bc08fa5e1ff3ac9a93) [//]: # (START_SECTION 220ec7f1827267091059abd467b88137e717ad94) ### Update install.sh > Commit: [220ec7f1827267091059abd467b88137e717ad94](https://github.com/dOpensource/dsiprouter/commit/220ec7f1827267091059abd467b88137e717ad94) > Date: Tue, 27 Oct 2020 10:24:15 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 220ec7f1827267091059abd467b88137e717ad94) [//]: # (START_SECTION 81a951367ee99d623c35d9ecad79e57cc24869c0) ### Added support for dispatcher to Kamailio > Commit: [81a951367ee99d623c35d9ecad79e57cc24869c0](https://github.com/dOpensource/dsiprouter/commit/81a951367ee99d623c35d9ecad79e57cc24869c0) > Date: Mon, 26 Oct 2020 21:45:13 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 81a951367ee99d623c35d9ecad79e57cc24869c0) [//]: # (START_SECTION 52a4b5eb30342d16b5272d769b033ad90cd4d196) ### Added logic to update/delete inbound routes and endpont groups with load balancing configured > Commit: [52a4b5eb30342d16b5272d769b033ad90cd4d196](https://github.com/dOpensource/dsiprouter/commit/52a4b5eb30342d16b5272d769b033ad90cd4d196) > Date: Mon, 26 Oct 2020 21:25:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 52a4b5eb30342d16b5272d769b033ad90cd4d196) [//]: # (START_SECTION 8976863ab2f8aee31a6222a0010e62d9b08a3be2) ### Patch Pre-commit Hook > Commit: [8976863ab2f8aee31a6222a0010e62d9b08a3be2](https://github.com/dOpensource/dsiprouter/commit/8976863ab2f8aee31a6222a0010e62d9b08a3be2) > Date: Mon, 26 Oct 2020 15:43:36 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix pre-commit check for failing pipreqs --- [//]: # (END_SECTION 8976863ab2f8aee31a6222a0010e62d9b08a3be2) [//]: # (START_SECTION 2ebaca3cb6894916c37e7746e7f9b4c95473cbe0) ### Update install.sh > Commit: [2ebaca3cb6894916c37e7746e7f9b4c95473cbe0](https://github.com/dOpensource/dsiprouter/commit/2ebaca3cb6894916c37e7746e7f9b4c95473cbe0) > Date: Mon, 26 Oct 2020 15:37:14 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2ebaca3cb6894916c37e7746e7f9b4c95473cbe0) [//]: # (START_SECTION e3673ab836946e34bc759df32499a89bad460878) ### Cleaning up commits > Commit: [e3673ab836946e34bc759df32499a89bad460878](https://github.com/dOpensource/dsiprouter/commit/e3673ab836946e34bc759df32499a89bad460878) > Date: Mon, 26 Oct 2020 12:58:06 -0400 > Author: Richard Bolaji (richard@goflyball.com) > Committer: Richard Bolaji (richard@goflyball.com) > Signed: - Changed buster debian install - Edited rtp engine install script - # Please enter the commit message for your changes. Lines starting - # with '#' will be ignored, and an empty message aborts the commit. - # - # On branch v0.64 - # Your branch is up to date with 'origin/v0.64'. - # - # Changes to be committed: - # deleted: .rtpengineinstalled - # modified: CONTRIBUTORS.md - # modified: dsiprouter.sh - # modified: gui/requirements.txt - # modified: rtpengine/debian/install.sh - # --- [//]: # (END_SECTION e3673ab836946e34bc759df32499a89bad460878) [//]: # (START_SECTION 5387bc1373db5438e5a06c0c81b80ab717398163) ### Added logic for updating weights in endpoint groups > Commit: [5387bc1373db5438e5a06c0c81b80ab717398163](https://github.com/dOpensource/dsiprouter/commit/5387bc1373db5438e5a06c0c81b80ab717398163) > Date: Sun, 25 Oct 2020 13:12:41 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5387bc1373db5438e5a06c0c81b80ab717398163) [//]: # (START_SECTION aa88b8fc61ed0bac537901196b0a7b7fedc79ad4) ### Update 8.sh > Commit: [aa88b8fc61ed0bac537901196b0a7b7fedc79ad4](https://github.com/dOpensource/dsiprouter/commit/aa88b8fc61ed0bac537901196b0a7b7fedc79ad4) > Date: Wed, 21 Oct 2020 09:27:37 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aa88b8fc61ed0bac537901196b0a7b7fedc79ad4) [//]: # (START_SECTION 1693e7fa706fe6c8bb414712ace18f90b86c3585) ### Inbound DID LB - Added logic to add the hostname to the address table - Added logic to build the dispatcher set; ' > Commit: [1693e7fa706fe6c8bb414712ace18f90b86c3585](https://github.com/dOpensource/dsiprouter/commit/1693e7fa706fe6c8bb414712ace18f90b86c3585) > Date: Tue, 20 Oct 2020 05:56:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1693e7fa706fe6c8bb414712ace18f90b86c3585) [//]: # (START_SECTION 662ef335d84401bb58c2cefcd88b41533e359a1d) ### Added support for Inbound Load Balancing from Carrier to Endpoint Groups > Commit: [662ef335d84401bb58c2cefcd88b41533e359a1d](https://github.com/dOpensource/dsiprouter/commit/662ef335d84401bb58c2cefcd88b41533e359a1d) > Date: Sun, 18 Oct 2020 23:30:51 +0000 > Author: root (root@v0621lb0.localdomain) > Committer: root (root@v0621lb0.localdomain) > Signed: --- [//]: # (END_SECTION 662ef335d84401bb58c2cefcd88b41533e359a1d) [//]: # (START_SECTION 2c71440a60bee65291c6a88981857eb86279d958) ### Update install.sh > Commit: [2c71440a60bee65291c6a88981857eb86279d958](https://github.com/dOpensource/dsiprouter/commit/2c71440a60bee65291c6a88981857eb86279d958) > Date: Fri, 16 Oct 2020 13:17:54 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2c71440a60bee65291c6a88981857eb86279d958) [//]: # (START_SECTION 3525c446bb0056b05fe6745085ead8ba106403af) ### Update install.sh > Commit: [3525c446bb0056b05fe6745085ead8ba106403af](https://github.com/dOpensource/dsiprouter/commit/3525c446bb0056b05fe6745085ead8ba106403af) > Date: Fri, 16 Oct 2020 11:48:20 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3525c446bb0056b05fe6745085ead8ba106403af) [//]: # (START_SECTION 02e128e74c941b83c3f8c1aea14c1387070c1393) ### Added the ability to handle INTERNAL MSTeams Call Transfers > Commit: [02e128e74c941b83c3f8c1aea14c1387070c1393](https://github.com/dOpensource/dsiprouter/commit/02e128e74c941b83c3f8c1aea14c1387070c1393) > Date: Thu, 15 Oct 2020 19:54:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 02e128e74c941b83c3f8c1aea14c1387070c1393) [//]: # (START_SECTION 0b27f18583f624ac932f8d5cbabbd3b5407efd9f) ### Update install.sh > Commit: [0b27f18583f624ac932f8d5cbabbd3b5407efd9f](https://github.com/dOpensource/dsiprouter/commit/0b27f18583f624ac932f8d5cbabbd3b5407efd9f) > Date: Wed, 14 Oct 2020 21:57:13 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0b27f18583f624ac932f8d5cbabbd3b5407efd9f) [//]: # (START_SECTION 595544e72ea35d3e85034389459821b27716520a) ### Update settings.py > Commit: [595544e72ea35d3e85034389459821b27716520a](https://github.com/dOpensource/dsiprouter/commit/595544e72ea35d3e85034389459821b27716520a) > Date: Wed, 14 Oct 2020 11:13:04 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 595544e72ea35d3e85034389459821b27716520a) [//]: # (START_SECTION 8697661b71b53e5dbf4e7e3755f3d6daea35248a) ### Update cert_combined.crt > Commit: [8697661b71b53e5dbf4e7e3755f3d6daea35248a](https://github.com/dOpensource/dsiprouter/commit/8697661b71b53e5dbf4e7e3755f3d6daea35248a) > Date: Wed, 14 Oct 2020 11:02:08 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8697661b71b53e5dbf4e7e3755f3d6daea35248a) [//]: # (START_SECTION 2d69d1b3639238a9082d9dd1befe7b3b97fa8ad5) ### Update cert.key > Commit: [2d69d1b3639238a9082d9dd1befe7b3b97fa8ad5](https://github.com/dOpensource/dsiprouter/commit/2d69d1b3639238a9082d9dd1befe7b3b97fa8ad5) > Date: Wed, 14 Oct 2020 11:01:17 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2d69d1b3639238a9082d9dd1befe7b3b97fa8ad5) [//]: # (START_SECTION fc41bcee84c9a43fc507116dd63d8b62dae7fdb2) ### Centos 8 update > Commit: [fc41bcee84c9a43fc507116dd63d8b62dae7fdb2](https://github.com/dOpensource/dsiprouter/commit/fc41bcee84c9a43fc507116dd63d8b62dae7fdb2) > Date: Wed, 14 Oct 2020 14:53:33 +0000 > Author: richard (richstorm781@gmail.com) > Committer: richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION fc41bcee84c9a43fc507116dd63d8b62dae7fdb2) [//]: # (START_SECTION ab07be7a8d96b82155c8447d5e73bb7df8c9e6a3) ### Update 8.sh > Commit: [ab07be7a8d96b82155c8447d5e73bb7df8c9e6a3](https://github.com/dOpensource/dsiprouter/commit/ab07be7a8d96b82155c8447d5e73bb7df8c9e6a3) > Date: Mon, 12 Oct 2020 14:53:50 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ab07be7a8d96b82155c8447d5e73bb7df8c9e6a3) [//]: # (START_SECTION 333d72ebbc833e2b2424cc63a0d19b23be9cc3c7) ### Update 8.sh > Commit: [333d72ebbc833e2b2424cc63a0d19b23be9cc3c7](https://github.com/dOpensource/dsiprouter/commit/333d72ebbc833e2b2424cc63a0d19b23be9cc3c7) > Date: Mon, 12 Oct 2020 13:36:18 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 333d72ebbc833e2b2424cc63a0d19b23be9cc3c7) [//]: # (START_SECTION 0d97d8d35d0cb44c8ff2444964967acfbab8aaf3) ### Update 8.sh > Commit: [0d97d8d35d0cb44c8ff2444964967acfbab8aaf3](https://github.com/dOpensource/dsiprouter/commit/0d97d8d35d0cb44c8ff2444964967acfbab8aaf3) > Date: Mon, 12 Oct 2020 12:01:16 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0d97d8d35d0cb44c8ff2444964967acfbab8aaf3) [//]: # (START_SECTION b6c1ba5ef380ab555007b98c54c5af03309c1042) ### Update 8.sh > Commit: [b6c1ba5ef380ab555007b98c54c5af03309c1042](https://github.com/dOpensource/dsiprouter/commit/b6c1ba5ef380ab555007b98c54c5af03309c1042) > Date: Mon, 12 Oct 2020 11:09:27 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b6c1ba5ef380ab555007b98c54c5af03309c1042) [//]: # (START_SECTION b89473a34ba4911150e72d636e7660763c6eb62d) ### Update 8.sh > Commit: [b89473a34ba4911150e72d636e7660763c6eb62d](https://github.com/dOpensource/dsiprouter/commit/b89473a34ba4911150e72d636e7660763c6eb62d) > Date: Mon, 12 Oct 2020 09:43:38 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b89473a34ba4911150e72d636e7660763c6eb62d) [//]: # (START_SECTION d9039e6c980daefce4e5e3e1acd81fc74864283c) ### Cent0S 8 update > Commit: [d9039e6c980daefce4e5e3e1acd81fc74864283c](https://github.com/dOpensource/dsiprouter/commit/d9039e6c980daefce4e5e3e1acd81fc74864283c) > Date: Fri, 9 Oct 2020 03:46:08 +0000 > Author: richard (richstorm781@gmail.com) > Committer: richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION d9039e6c980daefce4e5e3e1acd81fc74864283c) [//]: # (START_SECTION 963a62c8dd0f0dea807bd2ecd041cab385e54daa) ### Update 8.sh > Commit: [963a62c8dd0f0dea807bd2ecd041cab385e54daa](https://github.com/dOpensource/dsiprouter/commit/963a62c8dd0f0dea807bd2ecd041cab385e54daa) > Date: Thu, 8 Oct 2020 11:15:42 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 963a62c8dd0f0dea807bd2ecd041cab385e54daa) [//]: # (START_SECTION fd2a050cb03bc8a496075f26ae7a8659739b4647) ### Update 8.sh > Commit: [fd2a050cb03bc8a496075f26ae7a8659739b4647](https://github.com/dOpensource/dsiprouter/commit/fd2a050cb03bc8a496075f26ae7a8659739b4647) > Date: Thu, 8 Oct 2020 10:47:29 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fd2a050cb03bc8a496075f26ae7a8659739b4647) [//]: # (START_SECTION 69e29d707000c0414c64ee0b96530628364f0cc8) ### Update 8.sh > Commit: [69e29d707000c0414c64ee0b96530628364f0cc8](https://github.com/dOpensource/dsiprouter/commit/69e29d707000c0414c64ee0b96530628364f0cc8) > Date: Thu, 8 Oct 2020 09:39:27 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 69e29d707000c0414c64ee0b96530628364f0cc8) [//]: # (START_SECTION dc31e424924c5e02936096bb158b6ac210979f68) ### Update 8.sh > Commit: [dc31e424924c5e02936096bb158b6ac210979f68](https://github.com/dOpensource/dsiprouter/commit/dc31e424924c5e02936096bb158b6ac210979f68) > Date: Thu, 8 Oct 2020 09:37:13 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dc31e424924c5e02936096bb158b6ac210979f68) [//]: # (START_SECTION f73c6e8e4228f1009aa4820521ca638945e9bd20) ### Update dsiprouter.sh > Commit: [f73c6e8e4228f1009aa4820521ca638945e9bd20](https://github.com/dOpensource/dsiprouter/commit/f73c6e8e4228f1009aa4820521ca638945e9bd20) > Date: Wed, 7 Oct 2020 09:51:17 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f73c6e8e4228f1009aa4820521ca638945e9bd20) [//]: # (START_SECTION 62485dd6238c815d2714729fac136dc37a7770b9) ### Update settings.py > Commit: [62485dd6238c815d2714729fac136dc37a7770b9](https://github.com/dOpensource/dsiprouter/commit/62485dd6238c815d2714729fac136dc37a7770b9) > Date: Mon, 5 Oct 2020 16:46:09 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62485dd6238c815d2714729fac136dc37a7770b9) [//]: # (START_SECTION b5d6c624ae39a2fe49d4107dd05af163936b4d35) ### Update cert_combined.crt > Commit: [b5d6c624ae39a2fe49d4107dd05af163936b4d35](https://github.com/dOpensource/dsiprouter/commit/b5d6c624ae39a2fe49d4107dd05af163936b4d35) > Date: Mon, 5 Oct 2020 16:45:04 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b5d6c624ae39a2fe49d4107dd05af163936b4d35) [//]: # (START_SECTION 61ed44cd0573769b198807e232ebc005561d62a9) ### Update cert.key > Commit: [61ed44cd0573769b198807e232ebc005561d62a9](https://github.com/dOpensource/dsiprouter/commit/61ed44cd0573769b198807e232ebc005561d62a9) > Date: Mon, 5 Oct 2020 16:43:11 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 61ed44cd0573769b198807e232ebc005561d62a9) [//]: # (START_SECTION 2227edd47210f18a730d6a9abefa8256e09b996c) ### CentOS 8 support > Commit: [2227edd47210f18a730d6a9abefa8256e09b996c](https://github.com/dOpensource/dsiprouter/commit/2227edd47210f18a730d6a9abefa8256e09b996c) > Date: Mon, 5 Oct 2020 20:34:23 +0000 > Author: Richard (richstorm781@gmail.com) > Committer: Richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION 2227edd47210f18a730d6a9abefa8256e09b996c) [//]: # (START_SECTION 79c5b318678348c4e3b3fa7b19f0f784b334f0e1) ### Update settings.py > Commit: [79c5b318678348c4e3b3fa7b19f0f784b334f0e1](https://github.com/dOpensource/dsiprouter/commit/79c5b318678348c4e3b3fa7b19f0f784b334f0e1) > Date: Mon, 5 Oct 2020 15:37:15 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79c5b318678348c4e3b3fa7b19f0f784b334f0e1) [//]: # (START_SECTION 3f31a4e61a2aa5e1cca17471bff1073f5652a649) ### Update cert_combined.crt > Commit: [3f31a4e61a2aa5e1cca17471bff1073f5652a649](https://github.com/dOpensource/dsiprouter/commit/3f31a4e61a2aa5e1cca17471bff1073f5652a649) > Date: Mon, 5 Oct 2020 15:30:06 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f31a4e61a2aa5e1cca17471bff1073f5652a649) [//]: # (START_SECTION d4e7eaa96c7270a12b833c6d7430db5f1cd69405) ### Update cert.key > Commit: [d4e7eaa96c7270a12b833c6d7430db5f1cd69405](https://github.com/dOpensource/dsiprouter/commit/d4e7eaa96c7270a12b833c6d7430db5f1cd69405) > Date: Mon, 5 Oct 2020 11:42:19 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d4e7eaa96c7270a12b833c6d7430db5f1cd69405) [//]: # (START_SECTION 0aba1d2e1c1885f67df744297f296f8fa1d41202) ### Fixed CentOS installation error > Commit: [0aba1d2e1c1885f67df744297f296f8fa1d41202](https://github.com/dOpensource/dsiprouter/commit/0aba1d2e1c1885f67df744297f296f8fa1d41202) > Date: Mon, 5 Oct 2020 15:37:27 +0000 > Author: richard (richstorm781@gmail.com) > Committer: richard (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION 0aba1d2e1c1885f67df744297f296f8fa1d41202) [//]: # (START_SECTION ebea34b830e20160c547ee399b0a89a42528354d) ### Update settings.py > Commit: [ebea34b830e20160c547ee399b0a89a42528354d](https://github.com/dOpensource/dsiprouter/commit/ebea34b830e20160c547ee399b0a89a42528354d) > Date: Tue, 29 Sep 2020 10:10:30 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ebea34b830e20160c547ee399b0a89a42528354d) [//]: # (START_SECTION 97e1f9bdee0f277a10348208ced3c5ecc52fe030) ### Update cert_combined.crt > Commit: [97e1f9bdee0f277a10348208ced3c5ecc52fe030](https://github.com/dOpensource/dsiprouter/commit/97e1f9bdee0f277a10348208ced3c5ecc52fe030) > Date: Tue, 29 Sep 2020 10:01:47 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 97e1f9bdee0f277a10348208ced3c5ecc52fe030) [//]: # (START_SECTION 5292d8fdc00d79026d1ce6e3a08245308f8d60d5) ### Update cert.key > Commit: [5292d8fdc00d79026d1ce6e3a08245308f8d60d5](https://github.com/dOpensource/dsiprouter/commit/5292d8fdc00d79026d1ce6e3a08245308f8d60d5) > Date: Tue, 29 Sep 2020 10:00:14 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5292d8fdc00d79026d1ce6e3a08245308f8d60d5) [//]: # (START_SECTION d26de7235ab9db2370396b262cdb604814aafde9) ### Fixed #217 > Commit: [d26de7235ab9db2370396b262cdb604814aafde9](https://github.com/dOpensource/dsiprouter/commit/d26de7235ab9db2370396b262cdb604814aafde9) > Date: Mon, 28 Sep 2020 14:31:36 +0000 > Author: RichSosa28 (richstorm781@gmail.com) > Committer: RichSosa28 (richstorm781@gmail.com) > Signed: --- [//]: # (END_SECTION d26de7235ab9db2370396b262cdb604814aafde9) [//]: # (START_SECTION a2296b5a2bde683ee4d11558e583777fb3e24f23) ### Update install.sh > Commit: [a2296b5a2bde683ee4d11558e583777fb3e24f23](https://github.com/dOpensource/dsiprouter/commit/a2296b5a2bde683ee4d11558e583777fb3e24f23) > Date: Fri, 25 Sep 2020 10:33:17 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a2296b5a2bde683ee4d11558e583777fb3e24f23) [//]: # (START_SECTION 514a3ed75f3c8497c6dc15550d1f25ccf79c1734) ### Fixed Compile Issue > Commit: [514a3ed75f3c8497c6dc15550d1f25ccf79c1734](https://github.com/dOpensource/dsiprouter/commit/514a3ed75f3c8497c6dc15550d1f25ccf79c1734) > Date: Sat, 12 Sep 2020 12:51:58 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 514a3ed75f3c8497c6dc15550d1f25ccf79c1734) [//]: # (START_SECTION 1a9f29175a7be590bf6e9af55961cfc1450b3f62) ### Recomplied dSIPRouter Module for Kamailio 5.36 > Commit: [1a9f29175a7be590bf6e9af55961cfc1450b3f62](https://github.com/dOpensource/dsiprouter/commit/1a9f29175a7be590bf6e9af55961cfc1450b3f62) > Date: Sat, 12 Sep 2020 12:26:25 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 1a9f29175a7be590bf6e9af55961cfc1450b3f62) [//]: # (START_SECTION 8f9a792030bebbd18185c86bacafb1e134b34841) ### Update 10.sh > Commit: [8f9a792030bebbd18185c86bacafb1e134b34841](https://github.com/dOpensource/dsiprouter/commit/8f9a792030bebbd18185c86bacafb1e134b34841) > Date: Thu, 24 Sep 2020 21:12:54 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8f9a792030bebbd18185c86bacafb1e134b34841) [//]: # (START_SECTION 02fc3dc6e85b78e87f8262532ce8dde6c10cdcba) ### Create 10.sh > Commit: [02fc3dc6e85b78e87f8262532ce8dde6c10cdcba](https://github.com/dOpensource/dsiprouter/commit/02fc3dc6e85b78e87f8262532ce8dde6c10cdcba) > Date: Thu, 24 Sep 2020 14:17:32 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 02fc3dc6e85b78e87f8262532ce8dde6c10cdcba) [//]: # (START_SECTION 035535dd27f4661db5b130576828a4dae9246175) ### Delete 10.sh > Commit: [035535dd27f4661db5b130576828a4dae9246175](https://github.com/dOpensource/dsiprouter/commit/035535dd27f4661db5b130576828a4dae9246175) > Date: Thu, 24 Sep 2020 14:17:14 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 035535dd27f4661db5b130576828a4dae9246175) [//]: # (START_SECTION 863138e4529b0176a20a2927a33ee1bc9ddde4f0) ### Create 10.sh > Commit: [863138e4529b0176a20a2927a33ee1bc9ddde4f0](https://github.com/dOpensource/dsiprouter/commit/863138e4529b0176a20a2927a33ee1bc9ddde4f0) > Date: Thu, 24 Sep 2020 11:59:20 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 863138e4529b0176a20a2927a33ee1bc9ddde4f0) [//]: # (START_SECTION 3f30c92abead39a75bb0ffcc3b4adf424ceaa792) ### Update 10.sh > Commit: [3f30c92abead39a75bb0ffcc3b4adf424ceaa792](https://github.com/dOpensource/dsiprouter/commit/3f30c92abead39a75bb0ffcc3b4adf424ceaa792) > Date: Thu, 24 Sep 2020 11:55:47 -0400 > Author: Richard Bolaji (56362787+RichSosa28@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f30c92abead39a75bb0ffcc3b4adf424ceaa792) [//]: # (START_SECTION 5a18865fbce5cfd9dab6db8e86aa0fd75782af5d) ### Fixed MSTeams Test Connectivity Button so that the TLS Certificate would be tested properly > Commit: [5a18865fbce5cfd9dab6db8e86aa0fd75782af5d](https://github.com/dOpensource/dsiprouter/commit/5a18865fbce5cfd9dab6db8e86aa0fd75782af5d) > Date: Sun, 20 Sep 2020 22:07:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5a18865fbce5cfd9dab6db8e86aa0fd75782af5d) [//]: # (START_SECTION 50d4d13b68096b45d1b76b3ced4f18701da68a56) ### Added a link for dSIPRouter Core Subscription > Commit: [50d4d13b68096b45d1b76b3ced4f18701da68a56](https://github.com/dOpensource/dsiprouter/commit/50d4d13b68096b45d1b76b3ced4f18701da68a56) > Date: Sun, 20 Sep 2020 08:57:19 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 50d4d13b68096b45d1b76b3ced4f18701da68a56) [//]: # (START_SECTION 8618f4f292ecf3547c230744dbd0b5c73192a2c7) ### Made Skytel the default carrier again > Commit: [8618f4f292ecf3547c230744dbd0b5c73192a2c7](https://github.com/dOpensource/dsiprouter/commit/8618f4f292ecf3547c230744dbd0b5c73192a2c7) > Date: Sun, 20 Sep 2020 05:25:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8618f4f292ecf3547c230744dbd0b5c73192a2c7) [//]: # (START_SECTION a2579b15708e02c1acdf28bc929754b58d9bd41c) ### Added Twilio as a default Carrier > Commit: [a2579b15708e02c1acdf28bc929754b58d9bd41c](https://github.com/dOpensource/dsiprouter/commit/a2579b15708e02c1acdf28bc929754b58d9bd41c) > Date: Sun, 20 Sep 2020 04:25:53 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a2579b15708e02c1acdf28bc929754b58d9bd41c) [//]: # (START_SECTION 041c87f228eca59819fc9af856d9982f38c4e973) ### Fixes Issue #259 > Commit: [041c87f228eca59819fc9af856d9982f38c4e973](https://github.com/dOpensource/dsiprouter/commit/041c87f228eca59819fc9af856d9982f38c4e973) > Date: Sat, 19 Sep 2020 11:40:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 041c87f228eca59819fc9af856d9982f38c4e973) [//]: # (START_SECTION 78d23b58a76940d8f58e6af1550344598110572b) ### Merge of 0.63-fixes to 0.63 > Commit: [78d23b58a76940d8f58e6af1550344598110572b](https://github.com/dOpensource/dsiprouter/commit/78d23b58a76940d8f58e6af1550344598110572b) > Date: Sat, 19 Sep 2020 11:23:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 78d23b58a76940d8f58e6af1550344598110572b) [//]: # (START_SECTION f04ecbdf9d38ad1d9b796bb56a3cbf981f7da4fe) ### Merge or 0.63-fixes to 0.63 > Commit: [f04ecbdf9d38ad1d9b796bb56a3cbf981f7da4fe](https://github.com/dOpensource/dsiprouter/commit/f04ecbdf9d38ad1d9b796bb56a3cbf981f7da4fe) > Date: Sat, 19 Sep 2020 11:23:22 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f04ecbdf9d38ad1d9b796bb56a3cbf981f7da4fe) [//]: # (START_SECTION 0c7c05ff628c54f8b8fecb0c9b807f92905ded70) ### dSIPRouter Installed Fixes - Fixed an issue with conflicts between MySQL and MariaDB Dev libaries in the dSIPRouter UI installer > Commit: [0c7c05ff628c54f8b8fecb0c9b807f92905ded70](https://github.com/dOpensource/dsiprouter/commit/0c7c05ff628c54f8b8fecb0c9b807f92905ded70) > Date: Wed, 16 Sep 2020 10:57:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0c7c05ff628c54f8b8fecb0c9b807f92905ded70) [//]: # (START_SECTION cde7d17a518207871133990e4427e2ebe3afb2de) ### Fixed Compile Issue > Commit: [cde7d17a518207871133990e4427e2ebe3afb2de](https://github.com/dOpensource/dsiprouter/commit/cde7d17a518207871133990e4427e2ebe3afb2de) > Date: Sat, 12 Sep 2020 12:51:58 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cde7d17a518207871133990e4427e2ebe3afb2de) [//]: # (START_SECTION c16d04c234423f448de65f8a1825658e9d1544de) ### Recomplied dSIPRouter Module for Kamailio 5.36 > Commit: [c16d04c234423f448de65f8a1825658e9d1544de](https://github.com/dOpensource/dsiprouter/commit/c16d04c234423f448de65f8a1825658e9d1544de) > Date: Sat, 12 Sep 2020 12:26:25 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c16d04c234423f448de65f8a1825658e9d1544de) [//]: # (START_SECTION 19090e57612572aa6007c4ea1e1c752e38ac0fa8) ### Merge in updated MSTeams Logic > Commit: [19090e57612572aa6007c4ea1e1c752e38ac0fa8](https://github.com/dOpensource/dsiprouter/commit/19090e57612572aa6007c4ea1e1c752e38ac0fa8) > Date: Sun, 13 Sep 2020 12:56:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 19090e57612572aa6007c4ea1e1c752e38ac0fa8) [//]: # (START_SECTION a8055bd9aef058d810836fe24e73e8fe6d8527e2) ### Fixed issue #261 > Commit: [a8055bd9aef058d810836fe24e73e8fe6d8527e2](https://github.com/dOpensource/dsiprouter/commit/a8055bd9aef058d810836fe24e73e8fe6d8527e2) > Date: Sat, 12 Sep 2020 21:55:36 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a8055bd9aef058d810836fe24e73e8fe6d8527e2) [//]: # (START_SECTION d4ed38deb180211c1f6744691a7147a77e63adfd) ### Fixed issue #261 > Commit: [d4ed38deb180211c1f6744691a7147a77e63adfd](https://github.com/dOpensource/dsiprouter/commit/d4ed38deb180211c1f6744691a7147a77e63adfd) > Date: Sat, 12 Sep 2020 21:55:36 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION d4ed38deb180211c1f6744691a7147a77e63adfd) [//]: # (START_SECTION df155e9611f1b685c31e895a17117f098f8b64d8) ### Update dsiprouter.sh > Commit: [df155e9611f1b685c31e895a17117f098f8b64d8](https://github.com/dOpensource/dsiprouter/commit/df155e9611f1b685c31e895a17117f098f8b64d8) > Date: Sat, 12 Sep 2020 16:32:51 -0400 > Author: Dean Forester (58430470+DForester-FTC@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: - Fixed typo - Datebase to Database --- [//]: # (END_SECTION df155e9611f1b685c31e895a17117f098f8b64d8) [//]: # (START_SECTION 963356453cdded1169f9c4adbf5effa8a16caff4) ### Fixed Compile Issue > Commit: [963356453cdded1169f9c4adbf5effa8a16caff4](https://github.com/dOpensource/dsiprouter/commit/963356453cdded1169f9c4adbf5effa8a16caff4) > Date: Sat, 12 Sep 2020 12:51:58 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 963356453cdded1169f9c4adbf5effa8a16caff4) [//]: # (START_SECTION 9f5c7c72eaab5cf9ddb0cead7dca609e60423f3d) ### Recomplied dSIPRouter Module for Kamailio 5.36 > Commit: [9f5c7c72eaab5cf9ddb0cead7dca609e60423f3d](https://github.com/dOpensource/dsiprouter/commit/9f5c7c72eaab5cf9ddb0cead7dca609e60423f3d) > Date: Sat, 12 Sep 2020 12:26:25 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 9f5c7c72eaab5cf9ddb0cead7dca609e60423f3d) [//]: # (START_SECTION 46777afc96ba104bcfffddd7a1406a3c21a80f39) ### Fix SQL Statement For User/Pass Registration > Commit: [46777afc96ba104bcfffddd7a1406a3c21a80f39](https://github.com/dOpensource/dsiprouter/commit/46777afc96ba104bcfffddd7a1406a3c21a80f39) > Date: Fri, 11 Sep 2020 13:56:02 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves dOpensource/dsiprouter#258 --- [//]: # (END_SECTION 46777afc96ba104bcfffddd7a1406a3c21a80f39) [//]: # (START_SECTION 1d29383b9c8f4c77ed93584b183ba8c1a314c155) ### Fix SQL Statement For User/Pass Registration > Commit: [1d29383b9c8f4c77ed93584b183ba8c1a314c155](https://github.com/dOpensource/dsiprouter/commit/1d29383b9c8f4c77ed93584b183ba8c1a314c155) > Date: Fri, 11 Sep 2020 13:56:02 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves dOpensource/dsiprouter#258 --- [//]: # (END_SECTION 1d29383b9c8f4c77ed93584b183ba8c1a314c155) [//]: # (START_SECTION bda4091c8e359bfc3d2ca5f613eac84e37385f05) ### Update settings.py > Commit: [bda4091c8e359bfc3d2ca5f613eac84e37385f05](https://github.com/dOpensource/dsiprouter/commit/bda4091c8e359bfc3d2ca5f613eac84e37385f05) > Date: Fri, 11 Sep 2020 07:46:46 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bda4091c8e359bfc3d2ca5f613eac84e37385f05) [//]: # (START_SECTION 16824e42f60e3c2d21e0bc3f81a7244b42a75e06) ### Updated upgrade documentation > Commit: [16824e42f60e3c2d21e0bc3f81a7244b42a75e06](https://github.com/dOpensource/dsiprouter/commit/16824e42f60e3c2d21e0bc3f81a7244b42a75e06) > Date: Tue, 25 Aug 2020 02:25:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 16824e42f60e3c2d21e0bc3f81a7244b42a75e06) [//]: # (START_SECTION af8797fd57a4b341a53644df2f04785175a8096e) ### Added additional logic to the upgrade feature > Commit: [af8797fd57a4b341a53644df2f04785175a8096e](https://github.com/dOpensource/dsiprouter/commit/af8797fd57a4b341a53644df2f04785175a8096e) > Date: Mon, 24 Aug 2020 22:01:24 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION af8797fd57a4b341a53644df2f04785175a8096e) [//]: # (START_SECTION 70ab6bea7b72e633ebb43fb979da2ffef6640830) ### Added the ability to upgrade the Kamailio Configuration from a new release > Commit: [70ab6bea7b72e633ebb43fb979da2ffef6640830](https://github.com/dOpensource/dsiprouter/commit/70ab6bea7b72e633ebb43fb979da2ffef6640830) > Date: Sun, 23 Aug 2020 22:13:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 70ab6bea7b72e633ebb43fb979da2ffef6640830) [//]: # (START_SECTION 461d7f7677ad4aaa411cbc66d88b7729a05f8b5b) ### Typo Correction CVS to CSV > Commit: [461d7f7677ad4aaa411cbc66d88b7729a05f8b5b](https://github.com/dOpensource/dsiprouter/commit/461d7f7677ad4aaa411cbc66d88b7729a05f8b5b) > Date: Fri, 21 Aug 2020 15:09:26 -0500 > Author: demonspork (demonspork@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: - Typo Corrections! - Feel free to just fix this yourself instead of accepting this PR, it might honestly be faster :) --- [//]: # (END_SECTION 461d7f7677ad4aaa411cbc66d88b7729a05f8b5b) [//]: # (START_SECTION 52282aff92f1992955beef6fa70417b9b09410c3) ### Fixed - Implemented logic to configure the dispatcher socket based on if dSIP is in servernat mode Fixes: https://git.flyball.co/dsiprouter/enterprise/issues/80 > Commit: [52282aff92f1992955beef6fa70417b9b09410c3](https://github.com/dOpensource/dsiprouter/commit/52282aff92f1992955beef6fa70417b9b09410c3) > Date: Thu, 20 Aug 2020 11:14:34 +0000 > Author: root (root@ip-172-31-29-213.ec2.internal) > Committer: root (root@ip-172-31-29-213.ec2.internal) > Signed: --- [//]: # (END_SECTION 52282aff92f1992955beef6fa70417b9b09410c3) [//]: # (START_SECTION 0f337cd8c71673748cfc3272cf5f457053458b47) ### Fixed issue with Inbound to MSTeams Session Timers > Commit: [0f337cd8c71673748cfc3272cf5f457053458b47](https://github.com/dOpensource/dsiprouter/commit/0f337cd8c71673748cfc3272cf5f457053458b47) > Date: Wed, 19 Aug 2020 12:51:55 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0f337cd8c71673748cfc3272cf5f457053458b47) [//]: # (START_SECTION 0c7714a4f95ac3f8981d44e526e35200e8512425) ### Updated the documentation to contain a list of the supported configurations > Commit: [0c7714a4f95ac3f8981d44e526e35200e8512425](https://github.com/dOpensource/dsiprouter/commit/0c7714a4f95ac3f8981d44e526e35200e8512425) > Date: Tue, 18 Aug 2020 21:31:46 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 0c7714a4f95ac3f8981d44e526e35200e8512425) [//]: # (START_SECTION f76634964bd6d89dabac201afc71c392db27d5ae) ### Fixed issues: - Session Timers from MSTeams to Carriers - Add Support for MSTeams Carrier Use Cases, where a Carrier has multiple customers > Commit: [f76634964bd6d89dabac201afc71c392db27d5ae](https://github.com/dOpensource/dsiprouter/commit/f76634964bd6d89dabac201afc71c392db27d5ae) > Date: Mon, 17 Aug 2020 12:19:44 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f76634964bd6d89dabac201afc71c392db27d5ae) [//]: # (START_SECTION f68f44037b0ced445391958b21e1d862112aae80) ### Update Precommit Hook > Commit: [f68f44037b0ced445391958b21e1d862112aae80](https://github.com/dOpensource/dsiprouter/commit/f68f44037b0ced445391958b21e1d862112aae80) > Date: Wed, 5 Aug 2020 18:41:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix empty `requirements.txt` when no deps found --- [//]: # (END_SECTION f68f44037b0ced445391958b21e1d862112aae80) [//]: # (START_SECTION 6c89858eeaafb8a03f709613572ee28a48ff0032) ### Git CE Improvements > Commit: [6c89858eeaafb8a03f709613572ee28a48ff0032](https://github.com/dOpensource/dsiprouter/commit/6c89858eeaafb8a03f709613572ee28a48ff0032) > Date: Wed, 5 Aug 2020 17:05:39 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update syntax checker for vcs hooks --- [//]: # (END_SECTION 6c89858eeaafb8a03f709613572ee28a48ff0032) [//]: # (START_SECTION 73e05ee0f38d2f206cd60de6c84a8593371d3e0b) ### Fix Kamailio Versioning > Commit: [73e05ee0f38d2f206cd60de6c84a8593371d3e0b](https://github.com/dOpensource/dsiprouter/commit/73e05ee0f38d2f206cd60de6c84a8593371d3e0b) > Date: Wed, 5 Aug 2020 16:56:04 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add apt preferences for kamailio repo --- [//]: # (END_SECTION 73e05ee0f38d2f206cd60de6c84a8593371d3e0b) [//]: # (START_SECTION 2612c8303932ff5a8fafad624f39ad220e65e72e) ### Added support for Continuous Integration with Jenkins and Gitlab > Commit: [2612c8303932ff5a8fafad624f39ad220e65e72e](https://github.com/dOpensource/dsiprouter/commit/2612c8303932ff5a8fafad624f39ad220e65e72e) > Date: Tue, 4 Aug 2020 05:36:22 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 2612c8303932ff5a8fafad624f39ad220e65e72e) [//]: # (START_SECTION d0f03270c6429c25f275af3baf0680f4dc69aa10) ### Went back to RTPEngine mr6.1.1.1 > Commit: [d0f03270c6429c25f275af3baf0680f4dc69aa10](https://github.com/dOpensource/dsiprouter/commit/d0f03270c6429c25f275af3baf0680f4dc69aa10) > Date: Tue, 4 Aug 2020 03:32:20 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION d0f03270c6429c25f275af3baf0680f4dc69aa10) [//]: # (START_SECTION 8b643f37be453043787bc55b33f1c648a9274fcc) ### Update dsiprouter.sh > Commit: [8b643f37be453043787bc55b33f1c648a9274fcc](https://github.com/dOpensource/dsiprouter/commit/8b643f37be453043787bc55b33f1c648a9274fcc) > Date: Tue, 4 Aug 2020 02:37:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8b643f37be453043787bc55b33f1c648a9274fcc) [//]: # (START_SECTION 586cc5d9e7aebfef68491efcdd2d2621ae8b6b4f) ### RTPEngine Upgrade Patches for Debian > Commit: [586cc5d9e7aebfef68491efcdd2d2621ae8b6b4f](https://github.com/dOpensource/dsiprouter/commit/586cc5d9e7aebfef68491efcdd2d2621ae8b6b4f) > Date: Mon, 3 Aug 2020 17:49:25 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add apt repo configuration to install process - update rtpengine install to fix debian dependecy issues --- [//]: # (END_SECTION 586cc5d9e7aebfef68491efcdd2d2621ae8b6b4f) [//]: # (START_SECTION f4a320d63763ef5598ad98eb5f9a4da8648736a5) ### Updated Kamailio config to fix error > Commit: [f4a320d63763ef5598ad98eb5f9a4da8648736a5](https://github.com/dOpensource/dsiprouter/commit/f4a320d63763ef5598ad98eb5f9a4da8648736a5) > Date: Mon, 3 Aug 2020 13:04:26 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION f4a320d63763ef5598ad98eb5f9a4da8648736a5) [//]: # (START_SECTION e2a8fa552f782c6dfe955ffd66bc3576c55d72d8) ### Configure SSL from Command Line - Added a feature that allows a user to configure the system wide/default cert from the command line - The user can force the certificate to be re-generated by using a -f or --force > Commit: [e2a8fa552f782c6dfe955ffd66bc3576c55d72d8](https://github.com/dOpensource/dsiprouter/commit/e2a8fa552f782c6dfe955ffd66bc3576c55d72d8) > Date: Sun, 2 Aug 2020 16:45:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e2a8fa552f782c6dfe955ffd66bc3576c55d72d8) [//]: # (START_SECTION c95f0c44e4c765d890aec8214205e51820224161) ### Added the Let's Encrypt Root Certificate > Commit: [c95f0c44e4c765d890aec8214205e51820224161](https://github.com/dOpensource/dsiprouter/commit/c95f0c44e4c765d890aec8214205e51820224161) > Date: Sun, 2 Aug 2020 16:26:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c95f0c44e4c765d890aec8214205e51820224161) [//]: # (START_SECTION acd96690acb1d5298b429816a5ddf2aa5ad7b8ea) ### WIP: Current MSTeams Changes > Commit: [acd96690acb1d5298b429816a5ddf2aa5ad7b8ea](https://github.com/dOpensource/dsiprouter/commit/acd96690acb1d5298b429816a5ddf2aa5ad7b8ea) > Date: Thu, 30 Jul 2020 14:19:36 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add current work in progress for msteams bug fixes --- [//]: # (END_SECTION acd96690acb1d5298b429816a5ddf2aa5ad7b8ea) [//]: # (START_SECTION 96f140127d8092590d12f6a0f0474779b780ae44) ### Fixed the FusionPBX Sync function - Resolves #https://github.com/dOpensource/dsiprouter/issues/247 > Commit: [96f140127d8092590d12f6a0f0474779b780ae44](https://github.com/dOpensource/dsiprouter/commit/96f140127d8092590d12f6a0f0474779b780ae44) > Date: Sun, 19 Jul 2020 22:51:57 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 96f140127d8092590d12f6a0f0474779b780ae44) [//]: # (START_SECTION deef4bcbe3f9b1ffdb112370672b49a29a34f6af) ### Enhancements to work with FusionPBX Domain Support - Resolves #https://github.com/dOpensource/dsiprouter/issues/245 > Commit: [deef4bcbe3f9b1ffdb112370672b49a29a34f6af](https://github.com/dOpensource/dsiprouter/commit/deef4bcbe3f9b1ffdb112370672b49a29a34f6af) > Date: Sat, 18 Jul 2020 02:41:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION deef4bcbe3f9b1ffdb112370672b49a29a34f6af) [//]: # (START_SECTION 35ddd3d327dff18a151fcdb52767fddd27e9736f) ### Made is easier to execute the commands that are needed to enable FusionPBX pass-thru > Commit: [35ddd3d327dff18a151fcdb52767fddd27e9736f](https://github.com/dOpensource/dsiprouter/commit/35ddd3d327dff18a151fcdb52767fddd27e9736f) > Date: Sat, 18 Jul 2020 02:25:35 +0000 > Author: root (root@sbc4.dsiprouter.net) > Committer: root (root@sbc4.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 35ddd3d327dff18a151fcdb52767fddd27e9736f) [//]: # (START_SECTION 0121f1cb99c979c68027b16660ccca25a9acc43b) ### Fixes to handle SIP Updates - Resolves #https://github.com/dOpensource/dsiprouter/issues/245 > Commit: [0121f1cb99c979c68027b16660ccca25a9acc43b](https://github.com/dOpensource/dsiprouter/commit/0121f1cb99c979c68027b16660ccca25a9acc43b) > Date: Wed, 15 Jul 2020 16:59:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0121f1cb99c979c68027b16660ccca25a9acc43b) [//]: # (START_SECTION 37aaa51a4282e6843a16f560ee3cae2333564562) ### Kamailio fix for tls issue Resolves #https://git.flyball.co/dsiprouter/enterprise/issues/71 > Commit: [37aaa51a4282e6843a16f560ee3cae2333564562](https://github.com/dOpensource/dsiprouter/commit/37aaa51a4282e6843a16f560ee3cae2333564562) > Date: Tue, 14 Jul 2020 12:14:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 37aaa51a4282e6843a16f560ee3cae2333564562) [//]: # (START_SECTION 7ee0942d174d95cb9acf7aca092a883c43fc6cdd) ### Fixed the tls_config parameters > Commit: [7ee0942d174d95cb9acf7aca092a883c43fc6cdd](https://github.com/dOpensource/dsiprouter/commit/7ee0942d174d95cb9acf7aca092a883c43fc6cdd) > Date: Thu, 2 Jul 2020 18:58:14 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 7ee0942d174d95cb9acf7aca092a883c43fc6cdd) [//]: # (START_SECTION c5bb21e63eecd953a2585a0874f447a43d760aec) ### Fixes for CSRF and Unregister > Commit: [c5bb21e63eecd953a2585a0874f447a43d760aec](https://github.com/dOpensource/dsiprouter/commit/c5bb21e63eecd953a2585a0874f447a43d760aec) > Date: Thu, 2 Jul 2020 13:09:39 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix external ua -> api csrf error - fix autoregister entries not being removed on unregister - cleanup kam config --- [//]: # (END_SECTION c5bb21e63eecd953a2585a0874f447a43d760aec) [//]: # (START_SECTION 06f03f513ada2fad54be0a113646e44146bb982d) ### Inbound DID Import - Fixed the endpoint override. Resolves https://git.flyball.co/dsiprouter/enterprise/issues/72 - Placed the sample CSV template into the proper diretory so that it could be downloaded > Commit: [06f03f513ada2fad54be0a113646e44146bb982d](https://github.com/dOpensource/dsiprouter/commit/06f03f513ada2fad54be0a113646e44146bb982d) > Date: Thu, 2 Jul 2020 10:06:56 +0000 > Author: root (root@demo.dsiprouter.org) > Committer: root (root@demo.dsiprouter.org) > Signed: --- [//]: # (END_SECTION 06f03f513ada2fad54be0a113646e44146bb982d) [//]: # (START_SECTION 5cc24e46160f96401ed15304bb893a10fef3ceae) ### Added a sample csv file to gui/static/template Resolves https://git.flyball.co/dsiprouter/enterprise/issues/72 > Commit: [5cc24e46160f96401ed15304bb893a10fef3ceae](https://github.com/dOpensource/dsiprouter/commit/5cc24e46160f96401ed15304bb893a10fef3ceae) > Date: Sun, 28 Jun 2020 09:43:23 -0700 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 5cc24e46160f96401ed15304bb893a10fef3ceae) [//]: # (START_SECTION ee98cfaa069f12ca4469caeaea2301c10537df5b) ### Adding db.commit() Resolves https://github.com/dOpensource/dsiprouter/issues/238 > Commit: [ee98cfaa069f12ca4469caeaea2301c10537df5b](https://github.com/dOpensource/dsiprouter/commit/ee98cfaa069f12ca4469caeaea2301c10537df5b) > Date: Sun, 28 Jun 2020 13:39:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ee98cfaa069f12ca4469caeaea2301c10537df5b) [//]: # (START_SECTION cb0805ff8bb419be0862283bf395e10eadb32792) ### Fix for transport=tls parameter missing Resolves https://git.flyball.co/dsiprouter/enterprise/issues/71 > Commit: [cb0805ff8bb419be0862283bf395e10eadb32792](https://github.com/dOpensource/dsiprouter/commit/cb0805ff8bb419be0862283bf395e10eadb32792) > Date: Fri, 26 Jun 2020 11:59:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cb0805ff8bb419be0862283bf395e10eadb32792) [//]: # (START_SECTION accaa33ba8bde121822be1af560944207b1c1c39) ### Updated the version number > Commit: [accaa33ba8bde121822be1af560944207b1c1c39](https://github.com/dOpensource/dsiprouter/commit/accaa33ba8bde121822be1af560944207b1c1c39) > Date: Tue, 23 Jun 2020 10:37:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION accaa33ba8bde121822be1af560944207b1c1c39) [//]: # (START_SECTION 947ce453dd787b1d398e5935f768abb3034ba42b) ### Updating dSIPRouter 5.3.5 > Commit: [947ce453dd787b1d398e5935f768abb3034ba42b](https://github.com/dOpensource/dsiprouter/commit/947ce453dd787b1d398e5935f768abb3034ba42b) > Date: Mon, 22 Jun 2020 15:03:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 947ce453dd787b1d398e5935f768abb3034ba42b) [//]: # (START_SECTION fa64ad6c012d104c56280cfb9799297a7884fca4) ### Updated dSIPRouter license module for Kamailio 5.35 > Commit: [fa64ad6c012d104c56280cfb9799297a7884fca4](https://github.com/dOpensource/dsiprouter/commit/fa64ad6c012d104c56280cfb9799297a7884fca4) > Date: Mon, 22 Jun 2020 13:45:36 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION fa64ad6c012d104c56280cfb9799297a7884fca4) [//]: # (START_SECTION 7c7385b77478d340b548fe63613efb6e0dbafdc2) ### Fixed issue with certificate validation > Commit: [7c7385b77478d340b548fe63613efb6e0dbafdc2](https://github.com/dOpensource/dsiprouter/commit/7c7385b77478d340b548fe63613efb6e0dbafdc2) > Date: Mon, 22 Jun 2020 12:49:23 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7c7385b77478d340b548fe63613efb6e0dbafdc2) [//]: # (START_SECTION 3259ebdc73fe4256876a2d0652bdcd0ae186629c) ### Updated README > Commit: [3259ebdc73fe4256876a2d0652bdcd0ae186629c](https://github.com/dOpensource/dsiprouter/commit/3259ebdc73fe4256876a2d0652bdcd0ae186629c) > Date: Mon, 22 Jun 2020 04:48:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3259ebdc73fe4256876a2d0652bdcd0ae186629c) [//]: # (START_SECTION 5c76eeb103ca9ee1627db466461f7d428c5763d0) ### Updated README > Commit: [5c76eeb103ca9ee1627db466461f7d428c5763d0](https://github.com/dOpensource/dsiprouter/commit/5c76eeb103ca9ee1627db466461f7d428c5763d0) > Date: Mon, 22 Jun 2020 04:45:51 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5c76eeb103ca9ee1627db466461f7d428c5763d0) [//]: # (START_SECTION 1b1add09d1e189297a0198fd0189b0a505616106) ### Fixed MSTeam Test Connectivity Feature > Commit: [1b1add09d1e189297a0198fd0189b0a505616106](https://github.com/dOpensource/dsiprouter/commit/1b1add09d1e189297a0198fd0189b0a505616106) > Date: Mon, 22 Jun 2020 04:19:41 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Changed the logic so that the TLS check just validates the connection to the Kamailio Server using the SNI --- [//]: # (END_SECTION 1b1add09d1e189297a0198fd0189b0a505616106) [//]: # (START_SECTION 03976c746507769e95416b888af101e01d8a3361) ### Added the Carrier Outboind and Inbound Enrichments back in > Commit: [03976c746507769e95416b888af101e01d8a3361](https://github.com/dOpensource/dsiprouter/commit/03976c746507769e95416b888af101e01d8a3361) > Date: Mon, 22 Jun 2020 03:35:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 03976c746507769e95416b888af101e01d8a3361) [//]: # (START_SECTION c949e68309974eec224f3709667ab2e73d2edf80) ### v0.621 Bug Fixes > Commit: [c949e68309974eec224f3709667ab2e73d2edf80](https://github.com/dOpensource/dsiprouter/commit/c949e68309974eec224f3709667ab2e73d2edf80) > Date: Fri, 19 Jun 2020 14:28:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix missing parenthesis in kam cfg - add failure check to requirements creation in hook - rename async as identifier was shadowing builtin --- [//]: # (END_SECTION c949e68309974eec224f3709667ab2e73d2edf80) [//]: # (START_SECTION a129fb2eb5e4099d82d15e92ba1f83db88606ee7) ### Add merge conflict check to pre-commit > Commit: [a129fb2eb5e4099d82d15e92ba1f83db88606ee7](https://github.com/dOpensource/dsiprouter/commit/a129fb2eb5e4099d82d15e92ba1f83db88606ee7) > Date: Fri, 19 Jun 2020 17:07:41 +0000 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION a129fb2eb5e4099d82d15e92ba1f83db88606ee7) [//]: # (START_SECTION 07d2fd16487913f7174e0e7d2e626f50121c344c) ### Update requirements.txt > Commit: [07d2fd16487913f7174e0e7d2e626f50121c344c](https://github.com/dOpensource/dsiprouter/commit/07d2fd16487913f7174e0e7d2e626f50121c344c) > Date: Fri, 19 Jun 2020 17:06:24 +0000 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 07d2fd16487913f7174e0e7d2e626f50121c344c) [//]: # (START_SECTION f7f14a6b4c8f1bdba9975f6da08e88385185c229) ### v0.621 Bug Fixes > Commit: [f7f14a6b4c8f1bdba9975f6da08e88385185c229](https://github.com/dOpensource/dsiprouter/commit/f7f14a6b4c8f1bdba9975f6da08e88385185c229) > Date: Thu, 18 Jun 2020 23:25:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/61 - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/63 - Workaround for TLS reload bug [kam upstream issue] - Move prefix char definition to non-db backed settings - Improve reformat URI kam route - Add route priority handling to kam gwgroup lookup - Fix missing endif in kam nat route - Fix issues with precommit hook --- [//]: # (END_SECTION f7f14a6b4c8f1bdba9975f6da08e88385185c229) [//]: # (START_SECTION 885cb512a28f28cf7a9bfbaa294398bfab2044ed) ### Document FusionPBX UI Proxy - Resolves #62 > Commit: [885cb512a28f28cf7a9bfbaa294398bfab2044ed](https://github.com/dOpensource/dsiprouter/commit/885cb512a28f28cf7a9bfbaa294398bfab2044ed) > Date: Fri, 19 Jun 2020 16:27:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 885cb512a28f28cf7a9bfbaa294398bfab2044ed) [//]: # (START_SECTION c67c821d22016544fd0cb52111314ce64e9020a0) ### Certificates > Commit: [c67c821d22016544fd0cb52111314ce64e9020a0](https://github.com/dOpensource/dsiprouter/commit/c67c821d22016544fd0cb52111314ce64e9020a0) > Date: Fri, 19 Jun 2020 14:50:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Added support for updating the default Certificate #Resolves #65 - - Changed the renewsslcert command so that it doesn't renew the default cert if it was uploaded - - Added a cacert.pem that just contains the MSTeams Root Certs --- [//]: # (END_SECTION c67c821d22016544fd0cb52111314ce64e9020a0) [//]: # (START_SECTION d720a5ed18a74aa50c1e53eb486b8b62ac36c713) ### 0.62 Fixes - Pull tls.reload out of the Reload API for now - Fixed issues with uploading a certificate. Users must restart Kamailio for a cert to become active - Change the message bar slideup time from 3 secs to 10 seconds - Fixed an issue with the CDR page giving a Failed alert when clicking on the CDR page > Commit: [d720a5ed18a74aa50c1e53eb486b8b62ac36c713](https://github.com/dOpensource/dsiprouter/commit/d720a5ed18a74aa50c1e53eb486b8b62ac36c713) > Date: Fri, 12 Jun 2020 06:28:11 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION d720a5ed18a74aa50c1e53eb486b8b62ac36c713) [//]: # (START_SECTION 9069634da8b9b8f0aa037702d4ec0394458ded10) ### Fixes - Fixed Javascript errors in Domains and MSTeams Validation Pages - Removed logic to convert OS certs to PEM. They are already in PEM format > Commit: [9069634da8b9b8f0aa037702d4ec0394458ded10](https://github.com/dOpensource/dsiprouter/commit/9069634da8b9b8f0aa037702d4ec0394458ded10) > Date: Thu, 11 Jun 2020 23:16:36 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 9069634da8b9b8f0aa037702d4ec0394458ded10) [//]: # (START_SECTION 86645a3177b6281d66e1b4e35e1811b52338c11c) ### Fixed an issue that prevented the Domain authtype to disable the other fields > Commit: [86645a3177b6281d66e1b4e35e1811b52338c11c](https://github.com/dOpensource/dsiprouter/commit/86645a3177b6281d66e1b4e35e1811b52338c11c) > Date: Thu, 11 Jun 2020 18:42:01 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 86645a3177b6281d66e1b4e35e1811b52338c11c) [//]: # (START_SECTION 678dd84fab164bc2c7b31437ba1a98a982d50aee) ### Added support for manually creating wildcard certs > Commit: [678dd84fab164bc2c7b31437ba1a98a982d50aee](https://github.com/dOpensource/dsiprouter/commit/678dd84fab164bc2c7b31437ba1a98a982d50aee) > Date: Thu, 11 Jun 2020 02:57:06 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 678dd84fab164bc2c7b31437ba1a98a982d50aee) [//]: # (START_SECTION e05fdf5204a0b9955e4d8f1ed38fab19a2c8b1d9) ### Certificates - Fixed issues with Adding, Updating and Delete Certifcates > Commit: [e05fdf5204a0b9955e4d8f1ed38fab19a2c8b1d9](https://github.com/dOpensource/dsiprouter/commit/e05fdf5204a0b9955e4d8f1ed38fab19a2c8b1d9) > Date: Thu, 11 Jun 2020 02:07:07 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION e05fdf5204a0b9955e4d8f1ed38fab19a2c8b1d9) [//]: # (START_SECTION 5622efa5487a31dce38ca45e71ff7208bba67778) ### Continued V0.62 Bug Fixes > Commit: [5622efa5487a31dce38ca45e71ff7208bba67778](https://github.com/dOpensource/dsiprouter/commit/5622efa5487a31dce38ca45e71ff7208bba67778) > Date: Wed, 10 Jun 2020 17:29:47 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/59 - fixed restart CLI cmd missing start function - fixed kamcmd empty string processing when updating kamailio - fixed `pre-snapshot.sh` missing function definition - fixed CDR endpoint URL's malformed --- [//]: # (END_SECTION 5622efa5487a31dce38ca45e71ff7208bba67778) [//]: # (START_SECTION c250876b72f02c2288d02ae211d5c0b7920bf981) ### Update PreCommit Hook with Requirements Changes > Commit: [c250876b72f02c2288d02ae211d5c0b7920bf981](https://github.com/dOpensource/dsiprouter/commit/c250876b72f02c2288d02ae211d5c0b7920bf981) > Date: Wed, 10 Jun 2020 10:21:42 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add certbot to forced includes - add acme and josepy to forced excludes --- [//]: # (END_SECTION c250876b72f02c2288d02ae211d5c0b7920bf981) [//]: # (START_SECTION dd215cc504ee085a031aac6577c81ad8ae753a7f) ### Removed the acme and josepy from requirements.txt and just added in certbot > Commit: [dd215cc504ee085a031aac6577c81ad8ae753a7f](https://github.com/dOpensource/dsiprouter/commit/dd215cc504ee085a031aac6577c81ad8ae753a7f) > Date: Wed, 10 Jun 2020 11:38:45 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION dd215cc504ee085a031aac6577c81ad8ae753a7f) [//]: # (START_SECTION e391260463b04eeaed6769f316c95ff9088b3a43) ### Fixes for v0.62 Release > Commit: [e391260463b04eeaed6769f316c95ff9088b3a43](https://github.com/dOpensource/dsiprouter/commit/e391260463b04eeaed6769f316c95ff9088b3a43) > Date: Wed, 10 Jun 2020 06:34:06 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/53 - fix carrier group and endpoint group listener issues - fix improper record route handling - add example improvements for fusionpbx sync - fix carrier group delete exceptions on UAC DNE --- [//]: # (END_SECTION e391260463b04eeaed6769f316c95ff9088b3a43) [//]: # (START_SECTION a37c39557a4ba52f53f4e83454a363bd778f1a3f) ### Certificates - Completed Add, Update and Delete of Certificates > Commit: [a37c39557a4ba52f53f4e83454a363bd778f1a3f](https://github.com/dOpensource/dsiprouter/commit/a37c39557a4ba52f53f4e83454a363bd778f1a3f) > Date: Tue, 9 Jun 2020 08:49:40 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION a37c39557a4ba52f53f4e83454a363bd778f1a3f) [//]: # (START_SECTION 34de2c54dcb4b73865783cc24ca0b96969b1e87e) ### Add uuidgen Dependency to Install > Commit: [34de2c54dcb4b73865783cc24ca0b96969b1e87e](https://github.com/dOpensource/dsiprouter/commit/34de2c54dcb4b73865783cc24ca0b96969b1e87e) > Date: Mon, 8 Jun 2020 16:48:33 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix missing uuidgen binary on install --- [//]: # (END_SECTION 34de2c54dcb4b73865783cc24ca0b96969b1e87e) [//]: # (START_SECTION 25b99e1b211e78ecb9ed20cb803594b1959b3000) ### Fix Misordered Logic in Endpoint Groups URI Parsing > Commit: [25b99e1b211e78ecb9ed20cb803594b1959b3000](https://github.com/dOpensource/dsiprouter/commit/25b99e1b211e78ecb9ed20cb803594b1959b3000) > Date: Mon, 8 Jun 2020 16:30:01 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix URI parsing for endpoint groups --- [//]: # (END_SECTION 25b99e1b211e78ecb9ed20cb803594b1959b3000) [//]: # (START_SECTION 5c7816d4ae274af72fadf354bffe70319e4bacdc) ### Fix API Regressions > Commit: [5c7816d4ae274af72fadf354bffe70319e4bacdc](https://github.com/dOpensource/dsiprouter/commit/5c7816d4ae274af72fadf354bffe70319e4bacdc) > Date: Mon, 8 Jun 2020 16:20:29 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix regressions from bad merge --- [//]: # (END_SECTION 5c7816d4ae274af72fadf354bffe70319e4bacdc) [//]: # (START_SECTION f43cae7dfbb13d56ea9a8c3d1d816b2e4fe1cfc9) ### Fixed issue with certificates > Commit: [f43cae7dfbb13d56ea9a8c3d1d816b2e4fe1cfc9](https://github.com/dOpensource/dsiprouter/commit/f43cae7dfbb13d56ea9a8c3d1d816b2e4fe1cfc9) > Date: Mon, 8 Jun 2020 19:24:04 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION f43cae7dfbb13d56ea9a8c3d1d816b2e4fe1cfc9) [//]: # (START_SECTION 330d69481ece2323d04de2ac8f5b496e503f24cc) ### Fix Kamailio TLS Config Handling > Commit: [330d69481ece2323d04de2ac8f5b496e503f24cc](https://github.com/dOpensource/dsiprouter/commit/330d69481ece2323d04de2ac8f5b496e503f24cc) > Date: Mon, 8 Jun 2020 15:09:32 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - improve regex and config management in `kamtls.py` - fix small bug in CLI restart command defaults --- [//]: # (END_SECTION 330d69481ece2323d04de2ac8f5b496e503f24cc) [//]: # (START_SECTION b6e233d04a02cfbee6e25e62d8f033510b08e527) ### Removed acme for testing > Commit: [b6e233d04a02cfbee6e25e62d8f033510b08e527](https://github.com/dOpensource/dsiprouter/commit/b6e233d04a02cfbee6e25e62d8f033510b08e527) > Date: Mon, 8 Jun 2020 18:38:34 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION b6e233d04a02cfbee6e25e62d8f033510b08e527) [//]: # (START_SECTION cd5df131c230e5f47b03dd5a93bcfc9d0b17b019) ### Cron Services - Changed the permissions of the file to 744 so that it's executable > Commit: [cd5df131c230e5f47b03dd5a93bcfc9d0b17b019](https://github.com/dOpensource/dsiprouter/commit/cd5df131c230e5f47b03dd5a93bcfc9d0b17b019) > Date: Mon, 8 Jun 2020 18:22:50 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION cd5df131c230e5f47b03dd5a93bcfc9d0b17b019) [//]: # (START_SECTION cb2428d137a134aff9dbb3bc8a61b9bde38d8f36) ### Removed josepy > Commit: [cb2428d137a134aff9dbb3bc8a61b9bde38d8f36](https://github.com/dOpensource/dsiprouter/commit/cb2428d137a134aff9dbb3bc8a61b9bde38d8f36) > Date: Mon, 8 Jun 2020 18:08:34 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION cb2428d137a134aff9dbb3bc8a61b9bde38d8f36) [//]: # (START_SECTION 3b91672c6c9c1357190caf3369545b5150fad649) ### Certificates - Changed out version of josepy module - Changed the error handling of adding Kam certificates to the TLS config > Commit: [3b91672c6c9c1357190caf3369545b5150fad649](https://github.com/dOpensource/dsiprouter/commit/3b91672c6c9c1357190caf3369545b5150fad649) > Date: Mon, 8 Jun 2020 17:22:23 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 3b91672c6c9c1357190caf3369545b5150fad649) [//]: # (START_SECTION 3f5074c9b8147552ec8e1592d74df60c9e46a50e) ### Fix Command Parsing for Commands with Defaults > Commit: [3f5074c9b8147552ec8e1592d74df60c9e46a50e](https://github.com/dOpensource/dsiprouter/commit/3f5074c9b8147552ec8e1592d74df60c9e46a50e) > Date: Mon, 8 Jun 2020 09:44:26 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - parse debug option before checking for num of args --- [//]: # (END_SECTION 3f5074c9b8147552ec8e1592d74df60c9e46a50e) [//]: # (START_SECTION 2aed9209cbce02686c61aca1c580907f2b174df4) ### Changed the order of PyOpenSSL > Commit: [2aed9209cbce02686c61aca1c580907f2b174df4](https://github.com/dOpensource/dsiprouter/commit/2aed9209cbce02686c61aca1c580907f2b174df4) > Date: Mon, 8 Jun 2020 13:26:22 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 2aed9209cbce02686c61aca1c580907f2b174df4) [//]: # (START_SECTION b5de18522aa45387382973031ce9c4861cbc8422) ### Fix Carrier Group Issue > Commit: [b5de18522aa45387382973031ce9c4861cbc8422](https://github.com/dOpensource/dsiprouter/commit/b5de18522aa45387382973031ce9c4861cbc8422) > Date: Mon, 8 Jun 2020 08:29:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix issue causing server error on parsing auth type --- [//]: # (END_SECTION b5de18522aa45387382973031ce9c4861cbc8422) [//]: # (START_SECTION 06bbc86f9ac496d95efd98bca3d26a7089d56c94) ### Change Deafults for Start/Stop/Restart commands > Commit: [06bbc86f9ac496d95efd98bca3d26a7089d56c94](https://github.com/dOpensource/dsiprouter/commit/06bbc86f9ac496d95efd98bca3d26a7089d56c94) > Date: Mon, 8 Jun 2020 07:56:28 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - make start/stop/restart CLI commands default to only dsiprouter - add command line completion for new command options --- [//]: # (END_SECTION 06bbc86f9ac496d95efd98bca3d26a7089d56c94) [//]: # (START_SECTION c49afc5cca89af0112ce7bcc046a993e37363552) ### Certificates - Can now upload certificates - TODO: Fix table update after upload, finish update and fix kamtls output of certs > Commit: [c49afc5cca89af0112ce7bcc046a993e37363552](https://github.com/dOpensource/dsiprouter/commit/c49afc5cca89af0112ce7bcc046a993e37363552) > Date: Mon, 8 Jun 2020 03:48:57 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION c49afc5cca89af0112ce7bcc046a993e37363552) [//]: # (START_SECTION e73013afd487afb3ea4b564fc29f101d9412d629) ### Certificate Management - Completed the ability to Add a Certficate - Completed the ability to Delete a Certificate - TODO: Add Upload Cert Capability > Commit: [e73013afd487afb3ea4b564fc29f101d9412d629](https://github.com/dOpensource/dsiprouter/commit/e73013afd487afb3ea4b564fc29f101d9412d629) > Date: Sun, 7 Jun 2020 00:25:55 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION e73013afd487afb3ea4b564fc29f101d9412d629) [//]: # (START_SECTION 601657baf95fe3ce7884389bb00695bf7bb4330b) ### v0.62 Fixes > Commit: [601657baf95fe3ce7884389bb00695bf7bb4330b](https://github.com/dOpensource/dsiprouter/commit/601657baf95fe3ce7884389bb00695bf7bb4330b) > Date: Sat, 6 Jun 2020 03:04:17 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix endpoint group address table mapping - update default gateways to match address table mapping - add support for ipv6 in a majority of functions - add support for uri parsing - fix uacreg entry handling in GUI routes - fix endpoint group error on add - fix error handling when modal visible - update python docstring documentation --- [//]: # (END_SECTION 601657baf95fe3ce7884389bb00695bf7bb4330b) [//]: # (START_SECTION 3657bfd59850a0b74e2a94c53c72348513abcf50) ### Certificates - Added support for adding certificates > Commit: [3657bfd59850a0b74e2a94c53c72348513abcf50](https://github.com/dOpensource/dsiprouter/commit/3657bfd59850a0b74e2a94c53c72348513abcf50) > Date: Fri, 5 Jun 2020 23:24:55 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 3657bfd59850a0b74e2a94c53c72348513abcf50) [//]: # (START_SECTION 31359590de49b8f5098a8568432929555645689e) ### Initial commit of LetsEncrypt functions > Commit: [31359590de49b8f5098a8568432929555645689e](https://github.com/dOpensource/dsiprouter/commit/31359590de49b8f5098a8568432929555645689e) > Date: Fri, 5 Jun 2020 20:52:58 +0000 > Author: root (root@mack.dsiprouter.net) > Committer: root (root@mack.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 31359590de49b8f5098a8568432929555645689e) [//]: # (START_SECTION 9fab8dbf3eee065837301bec528982ffc32c6089) ### Fix Endpoint Group Table Update > Commit: [9fab8dbf3eee065837301bec528982ffc32c6089](https://github.com/dOpensource/dsiprouter/commit/9fab8dbf3eee065837301bec528982ffc32c6089) > Date: Wed, 3 Jun 2020 17:20:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix typo in `endpointgroups.js` causing updating in GUI to fail --- [//]: # (END_SECTION 9fab8dbf3eee065837301bec528982ffc32c6089) [//]: # (START_SECTION b3873d2aad6a8a8d58f65a369499771db8162013) ### Improved Error Handling > Commit: [b3873d2aad6a8a8d58f65a369499771db8162013](https://github.com/dOpensource/dsiprouter/commit/b3873d2aad6a8a8d58f65a369499771db8162013) > Date: Wed, 3 Jun 2020 16:49:10 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - allow error messages to propogate to error handler - handle error in same page on kamailio reload error - set kamailio uac reg reload limit to 30 sec --- [//]: # (END_SECTION b3873d2aad6a8a8d58f65a369499771db8162013) [//]: # (START_SECTION e4defd9cd3b98b19fd9b2a50c95559a37d26765f) ### Resolves https://git.flyball.co/dsiprouter/enterprise/issues/55 - implements some standardization as mentioned in https://git.flyball.co/dsiprouter/enterprise/issues/49 - fixes endpoint group update/add - fixes endpoint add/update/delete - fixes call limit default - fixes kam reload for API - extends `jquery.tableEdit` functionality - added comments in API for future updates - abstract request data handling into `shared.py` - use `util.security.py` funcs for password generation - fix API login timeout redirection url - fix dashboard stats update to match API kam reload - fix CA certificate format issue for debian OS - reset settings.py defaults > Commit: [e4defd9cd3b98b19fd9b2a50c95559a37d26765f](https://github.com/dOpensource/dsiprouter/commit/e4defd9cd3b98b19fd9b2a50c95559a37d26765f) > Date: Wed, 3 Jun 2020 14:40:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION e4defd9cd3b98b19fd9b2a50c95559a37d26765f) [//]: # (START_SECTION 65b76a75e8c8a28560f096d3ae61df52aae053ca) ### mend > Commit: [65b76a75e8c8a28560f096d3ae61df52aae053ca](https://github.com/dOpensource/dsiprouter/commit/65b76a75e8c8a28560f096d3ae61df52aae053ca) > Date: Tue, 2 Jun 2020 21:41:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 65b76a75e8c8a28560f096d3ae61df52aae053ca) [//]: # (START_SECTION 87b771ec8ea8ec5fcbf08c24fcbf2c34ae76db9f) ### Certificates Module - Added SQL to store the certs in the DB - Added the standard install script > Commit: [87b771ec8ea8ec5fcbf08c24fcbf2c34ae76db9f](https://github.com/dOpensource/dsiprouter/commit/87b771ec8ea8ec5fcbf08c24fcbf2c34ae76db9f) > Date: Tue, 2 Jun 2020 21:40:30 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 87b771ec8ea8ec5fcbf08c24fcbf2c34ae76db9f) [//]: # (START_SECTION 698ab1c7481fad6fedeaa698dcc4d7b00deea0bc) ### Certificates - Added UI components to allow a user to select Generate or Upload a certificate > Commit: [698ab1c7481fad6fedeaa698dcc4d7b00deea0bc](https://github.com/dOpensource/dsiprouter/commit/698ab1c7481fad6fedeaa698dcc4d7b00deea0bc) > Date: Mon, 1 Jun 2020 13:22:09 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 698ab1c7481fad6fedeaa698dcc4d7b00deea0bc) [//]: # (START_SECTION d57d36e0580f4dc5dc69536825fd7f26dfe9b77d) ### Fixup GUI and API Issues > Commit: [d57d36e0580f4dc5dc69536825fd7f26dfe9b77d](https://github.com/dOpensource/dsiprouter/commit/d57d36e0580f4dc5dc69536825fd7f26dfe9b77d) > Date: Sun, 31 May 2020 18:26:18 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/55 - fixed various endpoint group bugs - fixed reload button issue on specific pages - fixed edge case for request error handler - started refactoring endpoint groups for new architecture - made url globals read only in `fullwidth_layout.html` --- [//]: # (END_SECTION d57d36e0580f4dc5dc69536825fd7f26dfe9b77d) [//]: # (START_SECTION 8cf4a51e31112475d0e67fe0ff6faa555f59e71a) ### Added a default .gitignore file and added settings.py to it > Commit: [8cf4a51e31112475d0e67fe0ff6faa555f59e71a](https://github.com/dOpensource/dsiprouter/commit/8cf4a51e31112475d0e67fe0ff6faa555f59e71a) > Date: Sat, 30 May 2020 01:26:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8cf4a51e31112475d0e67fe0ff6faa555f59e71a) [//]: # (START_SECTION f33d6146ddd5dbb8fd4a5f5555aa430788c85c72) ### Put the default settings.py file back > Commit: [f33d6146ddd5dbb8fd4a5f5555aa430788c85c72](https://github.com/dOpensource/dsiprouter/commit/f33d6146ddd5dbb8fd4a5f5555aa430788c85c72) > Date: Sat, 30 May 2020 01:18:08 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION f33d6146ddd5dbb8fd4a5f5555aa430788c85c72) [//]: # (START_SECTION 806831ba03e3a5265bed7022d3d723751c2c6eac) ### mend > Commit: [806831ba03e3a5265bed7022d3d723751c2c6eac](https://github.com/dOpensource/dsiprouter/commit/806831ba03e3a5265bed7022d3d723751c2c6eac) > Date: Fri, 29 May 2020 14:23:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 806831ba03e3a5265bed7022d3d723751c2c6eac) [//]: # (START_SECTION 1ee4502f9b109ca2c06d030c1ec12c1b733348a4) ### Added API's for managing certs > Commit: [1ee4502f9b109ca2c06d030c1ec12c1b733348a4](https://github.com/dOpensource/dsiprouter/commit/1ee4502f9b109ca2c06d030c1ec12c1b733348a4) > Date: Fri, 29 May 2020 14:20:09 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 1ee4502f9b109ca2c06d030c1ec12c1b733348a4) [//]: # (START_SECTION e1523407df5d05d6019b36faac922ca53fc603e5) ### Frontend Updates > Commit: [e1523407df5d05d6019b36faac922ca53fc603e5](https://github.com/dOpensource/dsiprouter/commit/e1523407df5d05d6019b36faac922ca53fc603e5) > Date: Fri, 29 May 2020 09:54:33 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/2 - Updates https://git.flyball.co/dsiprouter/enterprise/issues/54 - refactor frontend to be more modular - add CSRF protection - add error handling to frontend for API calls - possible fixes for https://git.flyball.co/dsiprouter/enterprise/issues/53 --- [//]: # (END_SECTION e1523407df5d05d6019b36faac922ca53fc603e5) [//]: # (START_SECTION 682677ce50baefea11903b0c868a4f5efedbefb2) ### Bug Fixes for v0.62 > Commit: [682677ce50baefea11903b0c868a4f5efedbefb2](https://github.com/dOpensource/dsiprouter/commit/682677ce50baefea11903b0c868a4f5efedbefb2) > Date: Fri, 22 May 2020 16:56:50 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/52 - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/40 - removed `sslenable` CLI command as this is the default now - removed `mpath` CLI command as this is not needed by the end user - update CLI command options documentation - update CLI tab completion for changes - SECURITY FIX: change hash checks to store salt with hash - SECURITY FIX: fix size limits on credentials and salts - fix logrotate not working with some services - fix logging hangs on systemd journal rotate - cleanup `dsip_lib.sh` comments - fix layout for `dashboard.html` and `endpointgroups.html` - fix edge cases for https://git.flyball.co/dsiprouter/enterprise/issues/40 - fix API redirection for endpointgroups endpoint, ref: https://git.flyball.co/dsiprouter/enterprise/issues/2 - update `dsip_settings` table to accomdate security fixes - add `error` endpoint to GUI so API's can redirect there - add several ports to python update functions in `dsiprouter.sh` - add hot reloading to kam update functions in `dsiprouter.sh` - fix bug in `dr_gateways` and `subscribers` default args - consolidate/update credential setting/resetting functions in `dsiprouter.sh` - update diplayed credentials upon install finish - update `help` commands documentation to be more concise - expand `resetpassword` command functionality - update several install scripts; ubuntu, debian, centos, amazon - add utility functions in `main.js` - inject settings into template context processor in `dsiprouter.py` - update some tests that were not working; common, 7.sh --- [//]: # (END_SECTION 682677ce50baefea11903b0c868a4f5efedbefb2) [//]: # (START_SECTION 1d22d38baeffde8996987d5fe2a6fbf91ad3a4b9) ### Initial commit of the certificate module > Commit: [1d22d38baeffde8996987d5fe2a6fbf91ad3a4b9](https://github.com/dOpensource/dsiprouter/commit/1d22d38baeffde8996987d5fe2a6fbf91ad3a4b9) > Date: Fri, 22 May 2020 01:49:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1d22d38baeffde8996987d5fe2a6fbf91ad3a4b9) [//]: # (START_SECTION 33dc11da36ed72996df70997833e5510e7b45c87) ### Update README and CONTRIBUTING docs > Commit: [33dc11da36ed72996df70997833e5510e7b45c87](https://github.com/dOpensource/dsiprouter/commit/33dc11da36ed72996df70997833e5510e7b45c87) > Date: Tue, 19 May 2020 16:04:58 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/42 - Spruce up `README.md` and update links - Add Getting Started section to `CONTRIBUTING.md` --- [//]: # (END_SECTION 33dc11da36ed72996df70997833e5510e7b45c87) [//]: # (START_SECTION 2f1aca8ae2ee142d3bb185c204ee5c64b52033ea) ### Resolves https://git.flyball.co/dsiprouter/enterprise/issues/40 - move ami build script to more generic `build_image.sh` - add more system hardening in `pre-snapshot.sh` - update pw resetting to only apply to VM/VPS image on 1st boot > Commit: [2f1aca8ae2ee142d3bb185c204ee5c64b52033ea](https://github.com/dOpensource/dsiprouter/commit/2f1aca8ae2ee142d3bb185c204ee5c64b52033ea) > Date: Tue, 19 May 2020 13:47:55 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2f1aca8ae2ee142d3bb185c204ee5c64b52033ea) [//]: # (START_SECTION a958ecfae0c0277bd5b9755e6dacdc480a98d652) ### Map Gateway Groups to CDRs > Commit: [a958ecfae0c0277bd5b9755e6dacdc480a98d652](https://github.com/dOpensource/dsiprouter/commit/a958ecfae0c0277bd5b9755e6dacdc480a98d652) > Date: Mon, 18 May 2020 13:45:22 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/48 - add mapping between CDRs and gwgroups - update DB tables to handle new data - update api routes to handle new data - update CDR's GUI page - Add more sophisticated JSON serializer - Cleanup `kamailio.cfg` - Add support in dr_gateways trigger for adding new attrs - Cleanup `dsip_lcr.sql` and `dsip_mainmode.sql` - Update GUI to make tables slightly larger / fit better - Update dr_gateways defaults to be more reasonable --- [//]: # (END_SECTION a958ecfae0c0277bd5b9755e6dacdc480a98d652) [//]: # (START_SECTION 7e3ae78ed13627dc420cac7016921ec85d4aa947) ### Update README.md > Commit: [7e3ae78ed13627dc420cac7016921ec85d4aa947](https://github.com/dOpensource/dsiprouter/commit/7e3ae78ed13627dc420cac7016921ec85d4aa947) > Date: Sun, 17 May 2020 12:21:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7e3ae78ed13627dc420cac7016921ec85d4aa947) [//]: # (START_SECTION 3d9118d7f619649dd6cbfedf510fc94cc8365ac3) ### Update README.md > Commit: [3d9118d7f619649dd6cbfedf510fc94cc8365ac3](https://github.com/dOpensource/dsiprouter/commit/3d9118d7f619649dd6cbfedf510fc94cc8365ac3) > Date: Sun, 17 May 2020 12:20:43 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d9118d7f619649dd6cbfedf510fc94cc8365ac3) [//]: # (START_SECTION 51d6bafff4fc1e0cbbb8e2f55efcc959da4722a3) ### Removed dnsmasq from the -all install parameter > Commit: [51d6bafff4fc1e0cbbb8e2f55efcc959da4722a3](https://github.com/dOpensource/dsiprouter/commit/51d6bafff4fc1e0cbbb8e2f55efcc959da4722a3) > Date: Sun, 17 May 2020 14:32:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 51d6bafff4fc1e0cbbb8e2f55efcc959da4722a3) [//]: # (START_SECTION 53dbbfe3e7d9e264e3706f3a76bd668b6995f805) ### Enrichment Framework > Commit: [53dbbfe3e7d9e264e3706f3a76bd668b6995f805](https://github.com/dOpensource/dsiprouter/commit/53dbbfe3e7d9e264e3706f3a76bd668b6995f805) > Date: Sun, 17 May 2020 00:00:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - - Added support for Inbound Enrichment - - Added Enrichment procedure for SignalWire --- [//]: # (END_SECTION 53dbbfe3e7d9e264e3706f3a76bd668b6995f805) [//]: # (START_SECTION 606d7460776dbc264fa3822396f8ea47dd628e36) ### Add New Settings to Database > Commit: [606d7460776dbc264fa3822396f8ea47dd628e36](https://github.com/dOpensource/dsiprouter/commit/606d7460776dbc264fa3822396f8ea47dd628e36) > Date: Fri, 15 May 2020 18:11:15 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add new settings from https://git.flyball.co/dsiprouter/enterprise/commit/52ecf3ad8b0875477bca362c855c22776df30a8b to dsip_settings table - add new settings to database update functions --- [//]: # (END_SECTION 606d7460776dbc264fa3822396f8ea47dd628e36) [//]: # (START_SECTION 52ecf3ad8b0875477bca362c855c22776df30a8b) ### Add Backend Supoport for Managing Domain TLS Certs > Commit: [52ecf3ad8b0875477bca362c855c22776df30a8b](https://github.com/dOpensource/dsiprouter/commit/52ecf3ad8b0875477bca362c855c22776df30a8b) > Date: Fri, 15 May 2020 17:40:15 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Add backend functions to support https://git.flyball.co/dsiprouter/enterprise/issues/41 - Make tls module reload on kamreload --- [//]: # (END_SECTION 52ecf3ad8b0875477bca362c855c22776df30a8b) [//]: # (START_SECTION 47c2b4bf8e54fa56e329a878ad13e001eb83d6dc) ### Fix Domain Setting on User/Pass Auth > Commit: [47c2b4bf8e54fa56e329a878ad13e001eb83d6dc](https://github.com/dOpensource/dsiprouter/commit/47c2b4bf8e54fa56e329a878ad13e001eb83d6dc) > Date: Wed, 13 May 2020 14:29:07 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/33 - change `EXTERNAL_FQDN` to resolve from external DNS resolver - set `EXTERNAL_FQDN` to `EXTERNAL_IP` on failed resolution - set default domain when not specified per endpoint group --- [//]: # (END_SECTION 47c2b4bf8e54fa56e329a878ad13e001eb83d6dc) [//]: # (START_SECTION 0e4d9929cfcf243a29b0d0da63cffaaf250fe891) ### Add Support for Gitlab in Hooks > Commit: [0e4d9929cfcf243a29b0d0da63cffaaf250fe891](https://github.com/dOpensource/dsiprouter/commit/0e4d9929cfcf243a29b0d0da63cffaaf250fe891) > Date: Wed, 13 May 2020 13:51:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/43 --- [//]: # (END_SECTION 0e4d9929cfcf243a29b0d0da63cffaaf250fe891) [//]: # (START_SECTION 159f5320f9466298e66caccb5b483ef6e5055b77) ### Outbound Routes Carrier Group Selection > Commit: [159f5320f9466298e66caccb5b483ef6e5055b77](https://github.com/dOpensource/dsiprouter/commit/159f5320f9466298e66caccb5b483ef6e5055b77) > Date: Tue, 12 May 2020 17:00:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/27 - add a selection dropdown to outbound routes for carrier group selection --- [//]: # (END_SECTION 159f5320f9466298e66caccb5b483ef6e5055b77) [//]: # (START_SECTION 958e418f8a950b1ed20d690b8309262d05827a01) ### Fix Crosslinking Issues > Commit: [958e418f8a950b1ed20d690b8309262d05827a01](https://github.com/dOpensource/dsiprouter/commit/958e418f8a950b1ed20d690b8309262d05827a01) > Date: Tue, 12 May 2020 13:28:34 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/9 --- [//]: # (END_SECTION 958e418f8a950b1ed20d690b8309262d05827a01) [//]: # (START_SECTION c7deab623214aa39432e4ef483be4e1b5b578dbf) ### Update Domain on Install > Commit: [c7deab623214aa39432e4ef483be4e1b5b578dbf](https://github.com/dOpensource/dsiprouter/commit/c7deab623214aa39432e4ef483be4e1b5b578dbf) > Date: Tue, 12 May 2020 13:13:29 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/33 - update DOMAIN in `settings.py` when python settings updated --- [//]: # (END_SECTION c7deab623214aa39432e4ef483be4e1b5b578dbf) [//]: # (START_SECTION 40e8d85965c90166eafb4425f2159949a2e764ed) ### Update Prefix and Strip Descriptions > Commit: [40e8d85965c90166eafb4425f2159949a2e764ed](https://github.com/dOpensource/dsiprouter/commit/40e8d85965c90166eafb4425f2159949a2e764ed) > Date: Tue, 12 May 2020 12:58:57 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/20 - update descriptions to infer call direction supported --- [//]: # (END_SECTION 40e8d85965c90166eafb4425f2159949a2e764ed) [//]: # (START_SECTION 29d156221015801c663e86e15c3923c768d62caf) ### Update Documentation > Commit: [29d156221015801c663e86e15c3923c768d62caf](https://github.com/dOpensource/dsiprouter/commit/29d156221015801c663e86e15c3923c768d62caf) > Date: Tue, 12 May 2020 12:34:33 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves https://git.flyball.co/dsiprouter/enterprise/issues/21 - update install options - move `CONTRIBUTING.md` to project root (standard location) --- [//]: # (END_SECTION 29d156221015801c663e86e15c3923c768d62caf) [//]: # (START_SECTION cbae76acf8e8549ce8b510ee9e5bf10b9665dce8) ### Fixed regression with gui/settings.py > Commit: [cbae76acf8e8549ce8b510ee9e5bf10b9665dce8](https://github.com/dOpensource/dsiprouter/commit/cbae76acf8e8549ce8b510ee9e5bf10b9665dce8) > Date: Tue, 12 May 2020 06:03:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cbae76acf8e8549ce8b510ee9e5bf10b9665dce8) [//]: # (START_SECTION 3916bd328bf72d08f372edb7f79a0d6958cc4f6a) ### mend > Commit: [3916bd328bf72d08f372edb7f79a0d6958cc4f6a](https://github.com/dOpensource/dsiprouter/commit/3916bd328bf72d08f372edb7f79a0d6958cc4f6a) > Date: Tue, 12 May 2020 05:25:31 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3916bd328bf72d08f372edb7f79a0d6958cc4f6a) [//]: # (START_SECTION 211cb4b9731b40e36b51c4f1e29b0b5491e280ad) ### Azure Fixes - Fixed bug with INTERNAL_FQDN > Commit: [211cb4b9731b40e36b51c4f1e29b0b5491e280ad](https://github.com/dOpensource/dsiprouter/commit/211cb4b9731b40e36b51c4f1e29b0b5491e280ad) > Date: Tue, 12 May 2020 05:22:22 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 211cb4b9731b40e36b51c4f1e29b0b5491e280ad) [//]: # (START_SECTION adc817e32355e6f5339c35c0c83dd521cb0ad9c8) ### Azure Installation Fixes - Added logic to use the hostname command for the hostname if dig can't perform a reverse lookup based on the IP address - Changed the URL used to obtain the vmID from the Azure Metadata service > Commit: [adc817e32355e6f5339c35c0c83dd521cb0ad9c8](https://github.com/dOpensource/dsiprouter/commit/adc817e32355e6f5339c35c0c83dd521cb0ad9c8) > Date: Tue, 12 May 2020 04:44:19 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION adc817e32355e6f5339c35c0c83dd521cb0ad9c8) [//]: # (START_SECTION 4db9e48d53dc3b098158a30c418a65ce1d095c24) ### Azure Installation Fixes - Added logic to use the hostname command for the hostname if dig can't perform a reverse lookup based on the IP address - Changed the URL used to obtain the vmID from the Azure Metadata service > Commit: [4db9e48d53dc3b098158a30c418a65ce1d095c24](https://github.com/dOpensource/dsiprouter/commit/4db9e48d53dc3b098158a30c418a65ce1d095c24) > Date: Tue, 12 May 2020 04:42:16 +0000 > Author: root (root@sbc.dsiprouter.net) > Committer: root (root@sbc.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 4db9e48d53dc3b098158a30c418a65ce1d095c24) [//]: # (START_SECTION 0fcecf8474d694bbd425388b62c67925786c472d) ### Carrier Groups > Commit: [0fcecf8474d694bbd425388b62c67925786c472d](https://github.com/dOpensource/dsiprouter/commit/0fcecf8474d694bbd425388b62c67925786c472d) > Date: Mon, 11 May 2020 22:00:24 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: - resolves dsiprouter@enterprise#39 --- [//]: # (END_SECTION 0fcecf8474d694bbd425388b62c67925786c472d) [//]: # (START_SECTION cc096b110266d6a71ced8a3b27ba3ad9589147d8) ### Update CONTRIBUTING.md > Commit: [cc096b110266d6a71ced8a3b27ba3ad9589147d8](https://github.com/dOpensource/dsiprouter/commit/cc096b110266d6a71ced8a3b27ba3ad9589147d8) > Date: Mon, 11 May 2020 15:45:14 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION cc096b110266d6a71ced8a3b27ba3ad9589147d8) [//]: # (START_SECTION 0e8a5f3c97049b8e101820d33c521215e46c9d28) ### MSTeams Inbound Integration - Fixed the Kamailio logic so that the contact is not being re-written. Hence, allowing the ACk to be accepted by MSTeams > Commit: [0e8a5f3c97049b8e101820d33c521215e46c9d28](https://github.com/dOpensource/dsiprouter/commit/0e8a5f3c97049b8e101820d33c521215e46c9d28) > Date: Mon, 11 May 2020 15:42:33 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0e8a5f3c97049b8e101820d33c521215e46c9d28) [//]: # (START_SECTION e71a9384f9d3b9cb5dcb280655a1fe1c86badb8e) ### Update CONTRIBUTING.md > Commit: [e71a9384f9d3b9cb5dcb280655a1fe1c86badb8e](https://github.com/dOpensource/dsiprouter/commit/e71a9384f9d3b9cb5dcb280655a1fe1c86badb8e) > Date: Mon, 11 May 2020 15:17:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e71a9384f9d3b9cb5dcb280655a1fe1c86badb8e) [//]: # (START_SECTION 14dd6be9cd21681f23d8ef63bb77805b24df71e0) ### Update CONTRIBUTING.md > Commit: [14dd6be9cd21681f23d8ef63bb77805b24df71e0](https://github.com/dOpensource/dsiprouter/commit/14dd6be9cd21681f23d8ef63bb77805b24df71e0) > Date: Mon, 11 May 2020 15:15:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 14dd6be9cd21681f23d8ef63bb77805b24df71e0) [//]: # (START_SECTION e8668ea90a39405a3518b3a02071ad1f94ae6476) ### Update CONTRIBUTING.md > Commit: [e8668ea90a39405a3518b3a02071ad1f94ae6476](https://github.com/dOpensource/dsiprouter/commit/e8668ea90a39405a3518b3a02071ad1f94ae6476) > Date: Mon, 11 May 2020 14:46:10 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e8668ea90a39405a3518b3a02071ad1f94ae6476) [//]: # (START_SECTION b617dfc5df3a6d6a93ecafb79fbd1291295392b7) ### Crontab Edge Case Handling > Commit: [b617dfc5df3a6d6a93ecafb79fbd1291295392b7](https://github.com/dOpensource/dsiprouter/commit/b617dfc5df3a6d6a93ecafb79fbd1291295392b7) > Date: Fri, 8 May 2020 17:20:34 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - resolves dsiprouter@enterprise#38 --- [//]: # (END_SECTION b617dfc5df3a6d6a93ecafb79fbd1291295392b7) [//]: # (START_SECTION 2f52ca511ff7f9de4ae3aa2142fe55997c1c3b16) ### MSTeams Inbound Routing - Added logic that will create an endpoint group when a MSTEAMS domain is created - Updated the Kamailio.cfg will with logic to support inbound MSTEAM calls > Commit: [2f52ca511ff7f9de4ae3aa2142fe55997c1c3b16](https://github.com/dOpensource/dsiprouter/commit/2f52ca511ff7f9de4ae3aa2142fe55997c1c3b16) > Date: Fri, 8 May 2020 21:00:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2f52ca511ff7f9de4ae3aa2142fe55997c1c3b16) [//]: # (START_SECTION 9c184a81ef4d7110ed1f013765fc58306b646fcf) ### Bug Fixes > Commit: [9c184a81ef4d7110ed1f013765fc58306b646fcf](https://github.com/dOpensource/dsiprouter/commit/9c184a81ef4d7110ed1f013765fc58306b646fcf) > Date: Fri, 8 May 2020 16:34:00 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix endpoints not shown in endpoint groups - fix endpoint groups wrong tabs active on re-edit - fix endpoint groups update button color on re-edit - fix regression with FLT_DIALOG in `kamailio.cfg` - fix http/https variable for api --- [//]: # (END_SECTION 9c184a81ef4d7110ed1f013765fc58306b646fcf) [//]: # (START_SECTION 466caaad9e55c4d3887389203f37935fa9e2e74f) ### Fix Proxmox Install > Commit: [466caaad9e55c4d3887389203f37935fa9e2e74f](https://github.com/dOpensource/dsiprouter/commit/466caaad9e55c4d3887389203f37935fa9e2e74f) > Date: Thu, 7 May 2020 18:36:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - resolves dsiprouter@enterprise#36 --- [//]: # (END_SECTION 466caaad9e55c4d3887389203f37935fa9e2e74f) [//]: # (START_SECTION 2c57eaa22af0449cd931b6c2aaa28226f6eeb5dc) ### - resolves dsiprouter@enterprise#24 - resolves dsiprouter@enterprise#29 - fix custom routes issue in shared.py - add CDR feature enhancements - redo crontab dsiprouter integration - create crontab integratino script - add loading spinning to table layout - add field validation to endpoints endpoint - update / optimize dsip_settings table - update settings.py adding some fields to DB - fix typos in inboundmapping.js - update dsip_cdrinfo table - add cron usability functions - increase logout timeout while in debug mode - various changes to install scripts for cron updates - update CDR Report fields - add urandomChars to util.security (future use) - seperate cdrs static code from template code > Commit: [2c57eaa22af0449cd931b6c2aaa28226f6eeb5dc](https://github.com/dOpensource/dsiprouter/commit/2c57eaa22af0449cd931b6c2aaa28226f6eeb5dc) > Date: Thu, 7 May 2020 17:49:42 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2c57eaa22af0449cd931b6c2aaa28226f6eeb5dc) [//]: # (START_SECTION 63704b7f57b5225a072d58f2dc84b00cec2f9f6e) ### Fixed issue > Commit: [63704b7f57b5225a072d58f2dc84b00cec2f9f6e](https://github.com/dOpensource/dsiprouter/commit/63704b7f57b5225a072d58f2dc84b00cec2f9f6e) > Date: Wed, 6 May 2020 21:07:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 63704b7f57b5225a072d58f2dc84b00cec2f9f6e) [//]: # (START_SECTION af0745134f8e9d7efa14ff0cbff2bf05e76bb7c5) ### Added dSIPRouter Support for Kamailio 5.3.4 - Added logic to install the dSIPRouter module based on the verison of Kamailio installed - Added source code for dSIPRouter Module > Commit: [af0745134f8e9d7efa14ff0cbff2bf05e76bb7c5](https://github.com/dOpensource/dsiprouter/commit/af0745134f8e9d7efa14ff0cbff2bf05e76bb7c5) > Date: Wed, 6 May 2020 20:47:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION af0745134f8e9d7efa14ff0cbff2bf05e76bb7c5) [//]: # (START_SECTION 371bbf536cbd2982f2f25647e885b061a71b662a) ### CDR Monthly Email Process - Used a function that replace non-alphanumeric characters with underscores. Fixed: https://git.flyball.co/dsiprouter/enterprise/issues/34 > Commit: [371bbf536cbd2982f2f25647e885b061a71b662a](https://github.com/dOpensource/dsiprouter/commit/371bbf536cbd2982f2f25647e885b061a71b662a) > Date: Tue, 5 May 2020 19:31:21 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 371bbf536cbd2982f2f25647e885b061a71b662a) [//]: # (START_SECTION 877e46d801a3a6f47a1cb04231ec22a251c1d05d) ### Bug Fixes and CLI QOL Updates > Commit: [877e46d801a3a6f47a1cb04231ec22a251c1d05d](https://github.com/dOpensource/dsiprouter/commit/877e46d801a3a6f47a1cb04231ec22a251c1d05d) > Date: Fri, 1 May 2020 19:53:24 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add dsip uuid generation on install - fix syntax bug in `gui/modules/domain/domain_routes.py` - add generatekamconfig command to `dsiprouter.sh` - add version command to `dsiprouter.sh` - add command completion to `dsiprouter` cmd - reset default configs --- [//]: # (END_SECTION 877e46d801a3a6f47a1cb04231ec22a251c1d05d) [//]: # (START_SECTION 0208e6faccfd390c8948c344869d283bd0612369) ### Update README.md > Commit: [0208e6faccfd390c8948c344869d283bd0612369](https://github.com/dOpensource/dsiprouter/commit/0208e6faccfd390c8948c344869d283bd0612369) > Date: Fri, 1 May 2020 13:44:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0208e6faccfd390c8948c344869d283bd0612369) [//]: # (START_SECTION 43a3d3db002f3040cb7d4807d65cb4b157ed563e) ### Update README.md > Commit: [43a3d3db002f3040cb7d4807d65cb4b157ed563e](https://github.com/dOpensource/dsiprouter/commit/43a3d3db002f3040cb7d4807d65cb4b157ed563e) > Date: Fri, 1 May 2020 13:42:32 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 43a3d3db002f3040cb7d4807d65cb4b157ed563e) [//]: # (START_SECTION 6e2961f62120e0784b352802f032a7e023a41705) ### MSTeams Fixes - Fixed Kamailio option messages for MSTeams so that it works with any domain - Fixed the Option Message Status Check - Added styling to the MSTeams Test Connectivity tooltips > Commit: [6e2961f62120e0784b352802f032a7e023a41705](https://github.com/dOpensource/dsiprouter/commit/6e2961f62120e0784b352802f032a7e023a41705) > Date: Fri, 1 May 2020 12:46:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6e2961f62120e0784b352802f032a7e023a41705) [//]: # (START_SECTION ab88f69fa9fd45e21854fae16956f47b474595c2) ### Added Tooltips to MSTeams test connectivity > Commit: [ab88f69fa9fd45e21854fae16956f47b474595c2](https://github.com/dOpensource/dsiprouter/commit/ab88f69fa9fd45e21854fae16956f47b474595c2) > Date: Fri, 1 May 2020 11:49:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ab88f69fa9fd45e21854fae16956f47b474595c2) [//]: # (START_SECTION 6117350ddcd962e464318e8757399155507cb48f) ### Fix MSTeams hostname check > Commit: [6117350ddcd962e464318e8757399155507cb48f](https://github.com/dOpensource/dsiprouter/commit/6117350ddcd962e464318e8757399155507cb48f) > Date: Fri, 1 May 2020 10:50:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6117350ddcd962e464318e8757399155507cb48f) [//]: # (START_SECTION 291097a63daf3743365e8a53cdf56e2077802cd8) ### Fixes for MSTeams Connectivity Checks > Commit: [291097a63daf3743365e8a53cdf56e2077802cd8](https://github.com/dOpensource/dsiprouter/commit/291097a63daf3743365e8a53cdf56e2077802cd8) > Date: Fri, 1 May 2020 04:37:30 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 291097a63daf3743365e8a53cdf56e2077802cd8) [//]: # (START_SECTION 883ff5e6ba4d94da068e855ce1fc0f309ea7f382) ### v0.60 Release Bug Fixes > Commit: [883ff5e6ba4d94da068e855ce1fc0f309ea7f382](https://github.com/dOpensource/dsiprouter/commit/883ff5e6ba4d94da068e855ce1fc0f309ea7f382) > Date: Thu, 30 Apr 2020 18:58:26 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug in `dsiprouter/dsip_lib.sh` that caused bad kamcfg settings - fix uac auth bug causing improper uacreg lookup - add validated carrier IP's for centurylink carrier enrichment - add some TODO's to `dsiprouter.sh` - reset defaults in `gui/settings.py` - fix bug in `gui/shared.py` causing exceptions when debugging --- [//]: # (END_SECTION 883ff5e6ba4d94da068e855ce1fc0f309ea7f382) [//]: # (START_SECTION d1c0d7f6556cff41c5ef4dc99ce5502a5e48fe7e) ### Fixed RTP selection > Commit: [d1c0d7f6556cff41c5ef4dc99ce5502a5e48fe7e](https://github.com/dOpensource/dsiprouter/commit/d1c0d7f6556cff41c5ef4dc99ce5502a5e48fe7e) > Date: Thu, 30 Apr 2020 16:03:06 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION d1c0d7f6556cff41c5ef4dc99ce5502a5e48fe7e) [//]: # (START_SECTION 8114297a1d628d8f277dbc21f05e5861c1be9e72) ### MOTD Updates and set the protocol of the GUI to http > Commit: [8114297a1d628d8f277dbc21f05e5861c1be9e72](https://github.com/dOpensource/dsiprouter/commit/8114297a1d628d8f277dbc21f05e5861c1be9e72) > Date: Thu, 30 Apr 2020 02:14:06 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 8114297a1d628d8f277dbc21f05e5861c1be9e72) [//]: # (START_SECTION 03fb8aece5a640dc3db4b69e79b0d7413a0aa4db) ### Installer / GUI Fixes > Commit: [03fb8aece5a640dc3db4b69e79b0d7413a0aa4db](https://github.com/dOpensource/dsiprouter/commit/03fb8aece5a640dc3db4b69e79b0d7413a0aa4db) > Date: Wed, 29 Apr 2020 19:16:08 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - various GUI fixes - installer fixes --- [//]: # (END_SECTION 03fb8aece5a640dc3db4b69e79b0d7413a0aa4db) [//]: # (START_SECTION 246a128be0e5e83a193dc73b8be1400867ceeca4) ### Fixed issue with endpointgroup regressions > Commit: [246a128be0e5e83a193dc73b8be1400867ceeca4](https://github.com/dOpensource/dsiprouter/commit/246a128be0e5e83a193dc73b8be1400867ceeca4) > Date: Wed, 29 Apr 2020 22:26:33 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 246a128be0e5e83a193dc73b8be1400867ceeca4) [//]: # (START_SECTION 8e5192baf3c5772c63bb4ddabebd4a1231950409) ### Fixed regression with endpointgroup > Commit: [8e5192baf3c5772c63bb4ddabebd4a1231950409](https://github.com/dOpensource/dsiprouter/commit/8e5192baf3c5772c63bb4ddabebd4a1231950409) > Date: Wed, 29 Apr 2020 21:09:50 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 8e5192baf3c5772c63bb4ddabebd4a1231950409) [//]: # (START_SECTION e1d46db6f7bed641bdcbd127e18e5c4c796afb65) ### Fixed regression with endpointgroup > Commit: [e1d46db6f7bed641bdcbd127e18e5c4c796afb65](https://github.com/dOpensource/dsiprouter/commit/e1d46db6f7bed641bdcbd127e18e5c4c796afb65) > Date: Wed, 29 Apr 2020 20:45:23 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION e1d46db6f7bed641bdcbd127e18e5c4c796afb65) [//]: # (START_SECTION 20fcdf84403bab2f83ccca2b7b059b4c5f838764) ### Packaging fixes - Changed login splash pic - Updated version from 0.60+ent to just 0.60 > Commit: [20fcdf84403bab2f83ccca2b7b059b4c5f838764](https://github.com/dOpensource/dsiprouter/commit/20fcdf84403bab2f83ccca2b7b059b4c5f838764) > Date: Wed, 29 Apr 2020 15:05:37 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 20fcdf84403bab2f83ccca2b7b059b4c5f838764) [//]: # (START_SECTION 73be03970380f6b4ee6bed206672290f2f700f13) ### We applied commmit 4d67b83 becasue it was overriden > Commit: [73be03970380f6b4ee6bed206672290f2f700f13](https://github.com/dOpensource/dsiprouter/commit/73be03970380f6b4ee6bed206672290f2f700f13) > Date: Mon, 27 Apr 2020 19:08:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 73be03970380f6b4ee6bed206672290f2f700f13) [//]: # (START_SECTION 49f7abb47ecc6f5799be16aa057c7382bbbdd1cb) ### Fixed error in install script for configuring SSL > Commit: [49f7abb47ecc6f5799be16aa057c7382bbbdd1cb](https://github.com/dOpensource/dsiprouter/commit/49f7abb47ecc6f5799be16aa057c7382bbbdd1cb) > Date: Wed, 29 Apr 2020 11:10:57 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 49f7abb47ecc6f5799be16aa057c7382bbbdd1cb) [//]: # (START_SECTION 9f09554d741c879c37b50561ff4610a635328d17) ### SSL Fixes - Added a renewsslcert option to dsiprouter.sh - Added a cronjob that will call renewsslcert. It will revnew the Let's Encrypt Certificate if it needs to be renewed > Commit: [9f09554d741c879c37b50561ff4610a635328d17](https://github.com/dOpensource/dsiprouter/commit/9f09554d741c879c37b50561ff4610a635328d17) > Date: Wed, 29 Apr 2020 05:24:38 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 9f09554d741c879c37b50561ff4610a635328d17) [//]: # (START_SECTION 4d1ca93260487e72f2c9767892e100fcc780127f) ### Pre-release Fixes > Commit: [4d1ca93260487e72f2c9767892e100fcc780127f](https://github.com/dOpensource/dsiprouter/commit/4d1ca93260487e72f2c9767892e100fcc780127f) > Date: Tue, 28 Apr 2020 20:25:14 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update dispatcher settings in kam config - cleanup non-debug output on install - fix regressions in api routes - fix external fqdn resolution bug - various code reformatting - remove copy of file_handling.py --- [//]: # (END_SECTION 4d1ca93260487e72f2c9767892e100fcc780127f) [//]: # (START_SECTION 62b3f81321d59bc796f2203a349415e54395ec2a) ### Fixed an issue with importing addresses that contain MSTeams SBC's > Commit: [62b3f81321d59bc796f2203a349415e54395ec2a](https://github.com/dOpensource/dsiprouter/commit/62b3f81321d59bc796f2203a349415e54395ec2a) > Date: Tue, 28 Apr 2020 22:44:39 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 62b3f81321d59bc796f2203a349415e54395ec2a) [//]: # (START_SECTION 1165c6fcf796434b5924a747527a9d31e3e2513f) ### Updating the Support links > Commit: [1165c6fcf796434b5924a747527a9d31e3e2513f](https://github.com/dOpensource/dsiprouter/commit/1165c6fcf796434b5924a747527a9d31e3e2513f) > Date: Tue, 28 Apr 2020 18:06:55 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 1165c6fcf796434b5924a747527a9d31e3e2513f) [//]: # (START_SECTION 4f31943f01eec31d76c18352c3c8da5059d50184) ### Fixed a regression in dsiprouter.sh - An echo command was in mistakenly packed in the sources.list file > Commit: [4f31943f01eec31d76c18352c3c8da5059d50184](https://github.com/dOpensource/dsiprouter/commit/4f31943f01eec31d76c18352c3c8da5059d50184) > Date: Tue, 28 Apr 2020 14:35:10 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4f31943f01eec31d76c18352c3c8da5059d50184) [//]: # (START_SECTION 465acc71768d23ed3f544ede236cd8fa8d107cc7) ### MS Teams Support - Added dsiprouter module binary to the repo and logic to install it - Added logic to the Domains page that checks if the user has a valid dSIPRouter Subscription - Added a button to check MS Teams Connectivity > Commit: [465acc71768d23ed3f544ede236cd8fa8d107cc7](https://github.com/dOpensource/dsiprouter/commit/465acc71768d23ed3f544ede236cd8fa8d107cc7) > Date: Tue, 28 Apr 2020 13:31:47 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 465acc71768d23ed3f544ede236cd8fa8d107cc7) [//]: # (START_SECTION 4d67b831ad017435924be58ae4b91d34bc302ab1) ### v0.60+ent pre-release fixes > Commit: [4d67b831ad017435924be58ae4b91d34bc302ab1](https://github.com/dOpensource/dsiprouter/commit/4d67b831ad017435924be58ae4b91d34bc302ab1) > Date: Mon, 27 Apr 2020 19:08:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix subscriber table reversion - add animation to hidden css class - fix endpoint groups ip auth selection issue --- [//]: # (END_SECTION 4d67b831ad017435924be58ae4b91d34bc302ab1) [//]: # (START_SECTION 9c69fc7899a019d48f63185b1aa1a1aa3cfe36e6) ### Small Fixes > Commit: [9c69fc7899a019d48f63185b1aa1a1aa3cfe36e6](https://github.com/dOpensource/dsiprouter/commit/9c69fc7899a019d48f63185b1aa1a1aa3cfe36e6) > Date: Mon, 27 Apr 2020 16:34:04 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move ENRICH_CARRIER_CENTURYLINK route outside of custom routes - fix custom routes display in outbound routes - fix endpoint group icon misaligned - commit current work on endpoint group ip auth selection bug fix - fix carrier group auth proxy bug by using r_username - small formatting updates - update debian9 install to use /etc/apt/sources.list.d for kam repos --- [//]: # (END_SECTION 9c69fc7899a019d48f63185b1aa1a1aa3cfe36e6) [//]: # (START_SECTION ad576c81c8872ae8f3e2c913bf1a7aa89f2be46f) ### Adding Fixes > Commit: [ad576c81c8872ae8f3e2c913bf1a7aa89f2be46f](https://github.com/dOpensource/dsiprouter/commit/ad576c81c8872ae8f3e2c913bf1a7aa89f2be46f) > Date: Mon, 27 Apr 2020 13:34:21 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION ad576c81c8872ae8f3e2c913bf1a7aa89f2be46f) [//]: # (START_SECTION 792ee6d70184c078a4d0e7eb7a5759daffde9575) ### Added a firewall rule to allow Letsencrypt to validate the hostname > Commit: [792ee6d70184c078a4d0e7eb7a5759daffde9575](https://github.com/dOpensource/dsiprouter/commit/792ee6d70184c078a4d0e7eb7a5759daffde9575) > Date: Fri, 24 Apr 2020 11:26:27 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 792ee6d70184c078a4d0e7eb7a5759daffde9575) [//]: # (START_SECTION a726868f2a0352dd69509f29b228753f066bf0b4) ### Added certbot to the Kamailio installer > Commit: [a726868f2a0352dd69509f29b228753f066bf0b4](https://github.com/dOpensource/dsiprouter/commit/a726868f2a0352dd69509f29b228753f066bf0b4) > Date: Fri, 24 Apr 2020 11:00:46 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION a726868f2a0352dd69509f29b228753f066bf0b4) [//]: # (START_SECTION d9f9802ae6c4fe2f0e81b3bb033d53c948dc44e1) ### Fixed issues with installed > Commit: [d9f9802ae6c4fe2f0e81b3bb033d53c948dc44e1](https://github.com/dOpensource/dsiprouter/commit/d9f9802ae6c4fe2f0e81b3bb033d53c948dc44e1) > Date: Fri, 24 Apr 2020 10:49:35 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION d9f9802ae6c4fe2f0e81b3bb033d53c948dc44e1) [//]: # (START_SECTION bad988b5fc9f0656ef94d7f60547b15349f925d7) ### Kamailio password has to be in plain text during the initial install > Commit: [bad988b5fc9f0656ef94d7f60547b15349f925d7](https://github.com/dOpensource/dsiprouter/commit/bad988b5fc9f0656ef94d7f60547b15349f925d7) > Date: Fri, 24 Apr 2020 10:34:51 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION bad988b5fc9f0656ef94d7f60547b15349f925d7) [//]: # (START_SECTION 82c2cc261fc2899d674cae1faf4c3b3f9ee4482f) ### Installed Fixes - Added Letencrypt support for MSTeams - Fixed issue with MSTeams gateways being configured on install - Remove PySpark from the Python Requirements.txt file > Commit: [82c2cc261fc2899d674cae1faf4c3b3f9ee4482f](https://github.com/dOpensource/dsiprouter/commit/82c2cc261fc2899d674cae1faf4c3b3f9ee4482f) > Date: Thu, 23 Apr 2020 23:37:43 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 82c2cc261fc2899d674cae1faf4c3b3f9ee4482f) [//]: # (START_SECTION a6e0d1325b70c263ef61bac6480a5724cda69d63) ### Updated the version of Kamailio to 5.3 > Commit: [a6e0d1325b70c263ef61bac6480a5724cda69d63](https://github.com/dOpensource/dsiprouter/commit/a6e0d1325b70c263ef61bac6480a5724cda69d63) > Date: Thu, 23 Apr 2020 04:24:04 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION a6e0d1325b70c263ef61bac6480a5724cda69d63) [//]: # (START_SECTION e122a48ff42eb16f07d1e4c60fa5e798c137c133) ### Added MSTeams Support - Support from calling from MSTeams Client to a Carrier using SIP with SRTP - Added the MSTeams SBC's to the address table on install - Updated the Kamailio TLS file so that it installs with the proper configuration - Changed the default amount of Shared and Private Memory from 64MB and 8MB to 128MB and 16MB respectfully > Commit: [e122a48ff42eb16f07d1e4c60fa5e798c137c133](https://github.com/dOpensource/dsiprouter/commit/e122a48ff42eb16f07d1e4c60fa5e798c137c133) > Date: Thu, 23 Apr 2020 04:17:53 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION e122a48ff42eb16f07d1e4c60fa5e798c137c133) [//]: # (START_SECTION a57a7d284d6b29d26d661fdcb2fb5a568a3d6399) ### Updated Carrier Registration - Can support auth username and auth password authentication, where the SIP username can be different then auth username - Support manual input of auth_proxy URI from the GUI > Commit: [a57a7d284d6b29d26d661fdcb2fb5a568a3d6399](https://github.com/dOpensource/dsiprouter/commit/a57a7d284d6b29d26d661fdcb2fb5a568a3d6399) > Date: Tue, 21 Apr 2020 17:10:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a57a7d284d6b29d26d661fdcb2fb5a568a3d6399) [//]: # (START_SECTION bc080a6a5218483c333d8a8d62ad3c9f42effc8a) ### Added Auth Proxy authentication > Commit: [bc080a6a5218483c333d8a8d62ad3c9f42effc8a](https://github.com/dOpensource/dsiprouter/commit/bc080a6a5218483c333d8a8d62ad3c9f42effc8a) > Date: Mon, 20 Apr 2020 21:46:18 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bc080a6a5218483c333d8a8d62ad3c9f42effc8a) [//]: # (START_SECTION 8ffa9ff5428a7e90f5946ab178517578cd5dbe7f) ### Added Support for CenturyLink Carrier - Added an Enrichment Carrier Framework where custom SIP manipulation can be done per the carriers requirements - Updated the logic for doing User/Password auth to a carrier > Commit: [8ffa9ff5428a7e90f5946ab178517578cd5dbe7f](https://github.com/dOpensource/dsiprouter/commit/8ffa9ff5428a7e90f5946ab178517578cd5dbe7f) > Date: Mon, 20 Apr 2020 12:50:10 +0000 > Author: root (root@dSIP060entNightly-0.localdomain) > Committer: root (root@dSIP060entNightly-0.localdomain) > Signed: --- [//]: # (END_SECTION 8ffa9ff5428a7e90f5946ab178517578cd5dbe7f) [//]: # (START_SECTION 013ef8b186632b8e625a7f70a688291e4fd93204) ### FusionPBX Domain Routing Fixes - Modified the logic to work with Endpoint Groups - Fixed the Domains page so that it displays the Endpoint Group that contains the FusionPBX Server - Fixed the Fusion Sync script so that it works with Endpoint Groups - Fixed an issue with the Kamailio Script that was caused by the DMQ module, which prevent endponts from registering > Commit: [013ef8b186632b8e625a7f70a688291e4fd93204](https://github.com/dOpensource/dsiprouter/commit/013ef8b186632b8e625a7f70a688291e4fd93204) > Date: Sun, 19 Apr 2020 14:27:45 +0000 > Author: root (root@dSIP060entNightly-0.localdomain) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 013ef8b186632b8e625a7f70a688291e4fd93204) [//]: # (START_SECTION a297703456ffe8ec046d25411f087d303ee8527b) ### Turned DMQ off by default > Commit: [a297703456ffe8ec046d25411f087d303ee8527b](https://github.com/dOpensource/dsiprouter/commit/a297703456ffe8ec046d25411f087d303ee8527b) > Date: Sat, 18 Apr 2020 18:41:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a297703456ffe8ec046d25411f087d303ee8527b) [//]: # (START_SECTION ef54273ce5a79fc9fa4f268c67f647be21854a6a) ### SIP to WebRTC and WebRTC to SIP has been tested with 2-way audio > Commit: [ef54273ce5a79fc9fa4f268c67f647be21854a6a](https://github.com/dOpensource/dsiprouter/commit/ef54273ce5a79fc9fa4f268c67f647be21854a6a) > Date: Sat, 18 Apr 2020 16:14:09 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ef54273ce5a79fc9fa4f268c67f647be21854a6a) [//]: # (START_SECTION 3c19fec781e2176b5135ee5f082c43008f47bdef) ### Merged in changes from gogcit branch > Commit: [3c19fec781e2176b5135ee5f082c43008f47bdef](https://github.com/dOpensource/dsiprouter/commit/3c19fec781e2176b5135ee5f082c43008f47bdef) > Date: Fri, 17 Apr 2020 10:39:57 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3c19fec781e2176b5135ee5f082c43008f47bdef) [//]: # (START_SECTION efc63d151901fdcb6dcccca38c723091ed3670e5) ### SIP to Web Working with Contact being re-written properly for FusionPBX > Commit: [efc63d151901fdcb6dcccca38c723091ed3670e5](https://github.com/dOpensource/dsiprouter/commit/efc63d151901fdcb6dcccca38c723091ed3670e5) > Date: Tue, 14 Apr 2020 03:44:49 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION efc63d151901fdcb6dcccca38c723091ed3670e5) [//]: # (START_SECTION c03761dd6db163fdac549c68dc765f2b1b2de134) ### Testing from Web to SIP worked > Commit: [c03761dd6db163fdac549c68dc765f2b1b2de134](https://github.com/dOpensource/dsiprouter/commit/c03761dd6db163fdac549c68dc765f2b1b2de134) > Date: Sun, 12 Apr 2020 12:51:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c03761dd6db163fdac549c68dc765f2b1b2de134) [//]: # (START_SECTION 11c48611038c211b9272a39eb1a558beb257ae2a) ### Initial testing of Web to SIP > Commit: [11c48611038c211b9272a39eb1a558beb257ae2a](https://github.com/dOpensource/dsiprouter/commit/11c48611038c211b9272a39eb1a558beb257ae2a) > Date: Sat, 11 Apr 2020 23:07:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 11c48611038c211b9272a39eb1a558beb257ae2a) [//]: # (START_SECTION 15a7af2fdbe19138fc1d279301322941d72df051) ### Updated configuration file with SIP-to-SIP calls working with Audio > Commit: [15a7af2fdbe19138fc1d279301322941d72df051](https://github.com/dOpensource/dsiprouter/commit/15a7af2fdbe19138fc1d279301322941d72df051) > Date: Sat, 11 Apr 2020 18:04:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 15a7af2fdbe19138fc1d279301322941d72df051) [//]: # (START_SECTION 50e4a4832f60cb11953440d6ab5cc318ba6c292f) ### Added logic to decode the encrypted database password > Commit: [50e4a4832f60cb11953440d6ab5cc318ba6c292f](https://github.com/dOpensource/dsiprouter/commit/50e4a4832f60cb11953440d6ab5cc318ba6c292f) > Date: Sat, 11 Apr 2020 16:07:58 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 50e4a4832f60cb11953440d6ab5cc318ba6c292f) [//]: # (START_SECTION 71c67854aaf9634bb00d3f0cabf04faa45b1703d) ### Integrated the refactored functions into the script > Commit: [71c67854aaf9634bb00d3f0cabf04faa45b1703d](https://github.com/dOpensource/dsiprouter/commit/71c67854aaf9634bb00d3f0cabf04faa45b1703d) > Date: Sat, 11 Apr 2020 13:18:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 71c67854aaf9634bb00d3f0cabf04faa45b1703d) [//]: # (START_SECTION 726439a19dd7ea4ae87b185e68bcfd348cf8fdc5) ### Fixed dSIPCDRInfo Table - The definition was missing in the database mapping file > Commit: [726439a19dd7ea4ae87b185e68bcfd348cf8fdc5](https://github.com/dOpensource/dsiprouter/commit/726439a19dd7ea4ae87b185e68bcfd348cf8fdc5) > Date: Fri, 10 Apr 2020 11:32:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 726439a19dd7ea4ae87b185e68bcfd348cf8fdc5) [//]: # (START_SECTION a7db2e9b74cc69f9862d3216d2c857ebaf57dc14) ### Initial Refactoring of Kamailio.cfg > Commit: [a7db2e9b74cc69f9862d3216d2c857ebaf57dc14](https://github.com/dOpensource/dsiprouter/commit/a7db2e9b74cc69f9862d3216d2c857ebaf57dc14) > Date: Sat, 11 Apr 2020 12:18:33 +0000 > Author: root (root@dSIP060entNightly-0.localdomain) > Committer: root (root@dSIP060entNightly-0.localdomain) > Signed: --- [//]: # (END_SECTION a7db2e9b74cc69f9862d3216d2c857ebaf57dc14) [//]: # (START_SECTION f126871d033c243282abc4e572d68f52a6987d8a) ### Fixed dSIPCDRInfo Table - The definition was missing in the database mapping file > Commit: [f126871d033c243282abc4e572d68f52a6987d8a](https://github.com/dOpensource/dsiprouter/commit/f126871d033c243282abc4e572d68f52a6987d8a) > Date: Fri, 10 Apr 2020 11:32:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f126871d033c243282abc4e572d68f52a6987d8a) [//]: # (START_SECTION f2b2a9202d15e047129f223bfd1046c240f172d0) ### Added WebSocket Support - Added the kamailio websocket module to the install on Debian 9 - Added Support in Kamailio script > Commit: [f2b2a9202d15e047129f223bfd1046c240f172d0](https://github.com/dOpensource/dsiprouter/commit/f2b2a9202d15e047129f223bfd1046c240f172d0) > Date: Fri, 10 Apr 2020 01:03:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f2b2a9202d15e047129f223bfd1046c240f172d0) [//]: # (START_SECTION eeac604c28012dd95c83af6d9164fed333f5c587) ### Fixed Issue #22 - Bug in the Inbound DID Mapping Import > Commit: [eeac604c28012dd95c83af6d9164fed333f5c587](https://github.com/dOpensource/dsiprouter/commit/eeac604c28012dd95c83af6d9164fed333f5c587) > Date: Tue, 7 Apr 2020 05:17:56 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eeac604c28012dd95c83af6d9164fed333f5c587) [//]: # (START_SECTION 6deb85b4571275288851976ec031caac1101dc3d) ### Fixed typo > Commit: [6deb85b4571275288851976ec031caac1101dc3d](https://github.com/dOpensource/dsiprouter/commit/6deb85b4571275288851976ec031caac1101dc3d) > Date: Fri, 3 Apr 2020 11:41:24 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION 6deb85b4571275288851976ec031caac1101dc3d) [//]: # (START_SECTION c1ea635a6606e7800f03813ead94aa660f9d374f) ### MS Teams Support - Ability to handle one MS Teams Domain - Configure CA List for Kamaiio certs - Added MS Teams Support to Domains - Added a MS Teams Conifguration page > Commit: [c1ea635a6606e7800f03813ead94aa660f9d374f](https://github.com/dOpensource/dsiprouter/commit/c1ea635a6606e7800f03813ead94aa660f9d374f) > Date: Fri, 3 Apr 2020 10:20:26 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION c1ea635a6606e7800f03813ead94aa660f9d374f) [//]: # (START_SECTION e244a66db7c5f9bdad8697c4af31e9f33126bcac) ### Microsoft Teams Support - Added configuration to the Kamailio configuration to have a listen address with a hostname - Added External Hostname parameters - Configured TLS configuration to validate server certs - Add support for sending OPTION messages with a Contact Header > Commit: [e244a66db7c5f9bdad8697c4af31e9f33126bcac](https://github.com/dOpensource/dsiprouter/commit/e244a66db7c5f9bdad8697c4af31e9f33126bcac) > Date: Tue, 31 Mar 2020 22:48:01 +0000 > Author: Mack Hendricks (mack@dopensource.net) > Committer: Mack Hendricks (mack@dopensource.net) > Signed: --- [//]: # (END_SECTION e244a66db7c5f9bdad8697c4af31e9f33126bcac) [//]: # (START_SECTION 470fbe91ac813f08980a9603b4b3a6d77a903825) ### Microsoft Teams Support - Added configuration to the Kamailio configuration to have a listen address with a hostname - Added External Hostname parameters - Configured TLS configuration to validate server certs - Add support for sending OPTION messages with a Contact Header > Commit: [470fbe91ac813f08980a9603b4b3a6d77a903825](https://github.com/dOpensource/dsiprouter/commit/470fbe91ac813f08980a9603b4b3a6d77a903825) > Date: Tue, 31 Mar 2020 22:46:43 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 470fbe91ac813f08980a9603b4b3a6d77a903825) [//]: # (START_SECTION 901af97eb7a61588afcb32a7747c167c6736295f) ### System CA Certs - Configured Kamailio to use the CA's shipped with the OS > Commit: [901af97eb7a61588afcb32a7747c167c6736295f](https://github.com/dOpensource/dsiprouter/commit/901af97eb7a61588afcb32a7747c167c6736295f) > Date: Mon, 30 Mar 2020 00:39:50 +0000 > Author: root (root@sbc3.dsiprouter.net) > Committer: root (root@sbc3.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 901af97eb7a61588afcb32a7747c167c6736295f) [//]: # (START_SECTION b734fc096d4e3d6534a21f67b053b4491d67513b) ### SSL Enable - Fixed permission issues > Commit: [b734fc096d4e3d6534a21f67b053b4491d67513b](https://github.com/dOpensource/dsiprouter/commit/b734fc096d4e3d6534a21f67b053b4491d67513b) > Date: Mon, 30 Mar 2020 00:04:45 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION b734fc096d4e3d6534a21f67b053b4491d67513b) [//]: # (START_SECTION 67d5925d3dedfb92935a3983010dfba6286ea636) ### Added LetsEncrypt Support - Will try to create LetsEncrypt certs if TEAMS support is enabled - Will create Self-Signed Certs if LetsEncrypt fails - Started a CONTRIBUTING page to capture our coding standards > Commit: [67d5925d3dedfb92935a3983010dfba6286ea636](https://github.com/dOpensource/dsiprouter/commit/67d5925d3dedfb92935a3983010dfba6286ea636) > Date: Sun, 29 Mar 2020 17:40:47 +0000 > Author: root (root@sbc2.dsiprouter.net) > Committer: root (root@sbc2.dsiprouter.net) > Signed: --- [//]: # (END_SECTION 67d5925d3dedfb92935a3983010dfba6286ea636) [//]: # (START_SECTION 7143eb17c6c1cc9c78c7b6600f30452f921f7f8c) ### Fix Carrier Update Changing Reload Button > Commit: [7143eb17c6c1cc9c78c7b6600f30452f921f7f8c](https://github.com/dOpensource/dsiprouter/commit/7143eb17c6c1cc9c78c7b6600f30452f921f7f8c) > Date: Thu, 26 Mar 2020 16:25:53 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update carriers.html to propagate reload state - Resolves #15 --- [//]: # (END_SECTION 7143eb17c6c1cc9c78c7b6600f30452f921f7f8c) [//]: # (START_SECTION eefe02ba547f850e9da5c56892a9ac45cfbb29c4) ### Fix Carrier Address Mismatch Issue > Commit: [eefe02ba547f850e9da5c56892a9ac45cfbb29c4](https://github.com/dOpensource/dsiprouter/commit/eefe02ba547f850e9da5c56892a9ac45cfbb29c4) > Date: Thu, 26 Mar 2020 15:31:27 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update dr_gateways to track address entry id - Resolves #18 --- [//]: # (END_SECTION eefe02ba547f850e9da5c56892a9ac45cfbb29c4) [//]: # (START_SECTION a6d7a717a154ae35ae26baaa396978d3d7ba4a68) ### Set KAM_DB_PASS to the default value of kamailiorw > Commit: [a6d7a717a154ae35ae26baaa396978d3d7ba4a68](https://github.com/dOpensource/dsiprouter/commit/a6d7a717a154ae35ae26baaa396978d3d7ba4a68) > Date: Wed, 25 Mar 2020 11:11:14 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a6d7a717a154ae35ae26baaa396978d3d7ba4a68) [//]: # (START_SECTION b59ada6025e875ea781d2b0f07cd2cab114543d1) ### Fixed a regression > Commit: [b59ada6025e875ea781d2b0f07cd2cab114543d1](https://github.com/dOpensource/dsiprouter/commit/b59ada6025e875ea781d2b0f07cd2cab114543d1) > Date: Wed, 25 Mar 2020 10:25:07 +0000 > Author: root (root@dSIP060entNightly-0.localdomain) > Committer: root (root@dSIP060entNightly-0.localdomain) > Signed: --- [//]: # (END_SECTION b59ada6025e875ea781d2b0f07cd2cab114543d1) [//]: # (START_SECTION 9948823743a42effcd85c3ae147daa1d9d15d31d) ### TLS Support - Added support that will enable TLS on initial install - Added support to install the proper Kamailio modules for Debian - Added logic to generate a self-signed certitifcate during install > Commit: [9948823743a42effcd85c3ae147daa1d9d15d31d](https://github.com/dOpensource/dsiprouter/commit/9948823743a42effcd85c3ae147daa1d9d15d31d) > Date: Wed, 25 Mar 2020 00:18:39 +0000 > Author: root (root@dSIP060entNightly-0.localdomain) > Committer: root (root@dSIP060entNightly-0.localdomain) > Signed: --- [//]: # (END_SECTION 9948823743a42effcd85c3ae147daa1d9d15d31d) [//]: # (START_SECTION 4bcdc842c6a4645265230b921ced574dbe15588f) ### Update mysqldump Commands to Handle More Use Cases > Commit: [4bcdc842c6a4645265230b921ced574dbe15588f](https://github.com/dOpensource/dsiprouter/commit/4bcdc842c6a4645265230b921ced574dbe15588f) > Date: Mon, 23 Mar 2020 14:04:50 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update mysql dump and commands to remove definer and MyISAM - update mysql dump and restore commands in api to be more secure - resolves #11 --- [//]: # (END_SECTION 4bcdc842c6a4645265230b921ced574dbe15588f) [//]: # (START_SECTION cfc1cf6ebf8f8bf9f534b2d640af4d4564213467) ### Increase Kamailio Max Loop Count > Commit: [cfc1cf6ebf8f8bf9f534b2d640af4d4564213467](https://github.com/dOpensource/dsiprouter/commit/cfc1cf6ebf8f8bf9f534b2d640af4d4564213467) > Date: Mon, 23 Mar 2020 10:25:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #8 --- [//]: # (END_SECTION cfc1cf6ebf8f8bf9f534b2d640af4d4564213467) [//]: # (START_SECTION 5a493fc61cb370b5c2c0c2487aa18f8ca81ef067) ### Fix inbound mapping import issues > Commit: [5a493fc61cb370b5c2c0c2487aa18f8ca81ef067](https://github.com/dOpensource/dsiprouter/commit/5a493fc61cb370b5c2c0c2487aa18f8ca81ef067) > Date: Fri, 20 Mar 2020 14:10:00 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 5a493fc61cb370b5c2c0c2487aa18f8ca81ef067) [//]: # (START_SECTION 14fa0a028a6568cc9c660893502917fb960f93d7) ### Stability Fixes and Cluster Sync/Install Updates > Commit: [14fa0a028a6568cc9c660893502917fb960f93d7](https://github.com/dOpensource/dsiprouter/commit/14fa0a028a6568cc9c660893502917fb960f93d7) > Date: Fri, 20 Mar 2020 11:53:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update pbx invite timeout on 180,181,183 response - update cdr email to send only last month of cdr's - disable mysql service on remote db configuration - fix redirection for isinstance checks - fix mysql service linking - update kamctlrc to include DB settings - fix issues installing with remote db - add setkamdbconfig command to allow changing db configs - add current work on clusterinstall command --- [//]: # (END_SECTION 14fa0a028a6568cc9c660893502917fb960f93d7) [//]: # (START_SECTION cd8dbc26b3cd8def1aa00e8391bbaf18c01530b1) ### Fix errors in dsiprouter script > Commit: [cd8dbc26b3cd8def1aa00e8391bbaf18c01530b1](https://github.com/dOpensource/dsiprouter/commit/cd8dbc26b3cd8def1aa00e8391bbaf18c01530b1) > Date: Fri, 13 Mar 2020 13:19:55 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix missing semi-colon - re-order OS info function --- [//]: # (END_SECTION cd8dbc26b3cd8def1aa00e8391bbaf18c01530b1) [//]: # (START_SECTION 3aa8587aae55ce50f5389923521388a467d2a7ea) ### CDR / Backup Feature Fixes > Commit: [3aa8587aae55ce50f5389923521388a467d2a7ea](https://github.com/dOpensource/dsiprouter/commit/3aa8587aae55ce50f5389923521388a467d2a7ea) > Date: Fri, 13 Mar 2020 11:51:22 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change sorting to be done on backend for CDR's - fix shell quoting issue w/ backup/restore cmds - handle bad file upload on restore cmd - auto format api_routes code --- [//]: # (END_SECTION 3aa8587aae55ce50f5389923521388a467d2a7ea) [//]: # (START_SECTION 91d038e332e1b38799eb7b5e594d4fb7e93675f7) ### Merge v0.55+ent changes into v0.60+ent > Commit: [91d038e332e1b38799eb7b5e594d4fb7e93675f7](https://github.com/dOpensource/dsiprouter/commit/91d038e332e1b38799eb7b5e594d4fb7e93675f7) > Date: Thu, 12 Mar 2020 16:24:26 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - merge in cdr and backup/restore changes - various styling / syntax fixes - make bootstrap-datepicker.css local - update docker compose file for v0.60 - fix some remaining db session bugs missed b4 - fix dsiprouter syslog not logging pid issue --- [//]: # (END_SECTION 91d038e332e1b38799eb7b5e594d4fb7e93675f7) [//]: # (START_SECTION 62dc6be4fc038b2526ccfdd2e9a31e424652571e) ### Merge dmq-feature branch into v0.60+ent > Commit: [62dc6be4fc038b2526ccfdd2e9a31e424652571e](https://github.com/dOpensource/dsiprouter/commit/62dc6be4fc038b2526ccfdd2e9a31e424652571e) > Date: Thu, 12 Mar 2020 10:37:41 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 62dc6be4fc038b2526ccfdd2e9a31e424652571e) [//]: # (START_SECTION 817a8062a283827aa22d90a8d5aaa201310effac) ### Fix Testing Makefile > Commit: [817a8062a283827aa22d90a8d5aaa201310effac](https://github.com/dOpensource/dsiprouter/commit/817a8062a283827aa22d90a8d5aaa201310effac) > Date: Tue, 25 Feb 2020 15:33:22 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Makefile requires tabs, re-tabbed spaces --- [//]: # (END_SECTION 817a8062a283827aa22d90a8d5aaa201310effac) [//]: # (START_SECTION c2027be6c0363d6a0011bb6df9948ba734021921) ### Fix RTPEngine Kernel Header Install Issue > Commit: [c2027be6c0363d6a0011bb6df9948ba734021921](https://github.com/dOpensource/dsiprouter/commit/c2027be6c0363d6a0011bb6df9948ba734021921) > Date: Tue, 25 Feb 2020 14:54:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - find current kernels headers instead of installing new kernel --- [//]: # (END_SECTION c2027be6c0363d6a0011bb6df9948ba734021921) [//]: # (START_SECTION fcd9ef8c7d66d8b2af645be760aa0f99a3c3cbac) ### V0.60 Bug Fixes > Commit: [fcd9ef8c7d66d8b2af645be760aa0f99a3c3cbac](https://github.com/dOpensource/dsiprouter/commit/fcd9ef8c7d66d8b2af645be760aa0f99a3c3cbac) > Date: Mon, 24 Feb 2020 10:55:29 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - dynamic kam db url updating - AES implementation update / fix - allow dsip dummy session to handle scoped session calls - update api auth to use Accept header instead of User-Agent - fix cdr start time field - fix inbound mapping failover fwd description - allow same endpoint on failover for inbound mappings - fix mail user / pass ENV variables - fix git pre/post commit hook - fix parsing of special chars in kam config db url update --- [//]: # (END_SECTION fcd9ef8c7d66d8b2af645be760aa0f99a3c3cbac) [//]: # (START_SECTION fe4e90763318d06f134b4093e7fec2f9dfd74dcf) ### Update settings.py > Commit: [fe4e90763318d06f134b4093e7fec2f9dfd74dcf](https://github.com/dOpensource/dsiprouter/commit/fe4e90763318d06f134b4093e7fec2f9dfd74dcf) > Date: Mon, 4 Nov 2019 03:48:25 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fe4e90763318d06f134b4093e7fec2f9dfd74dcf) [//]: # (START_SECTION efa8d3fb5a9c3c28325df28cfe4244711020be28) ### Update domains.rst > Commit: [efa8d3fb5a9c3c28325df28cfe4244711020be28](https://github.com/dOpensource/dsiprouter/commit/efa8d3fb5a9c3c28325df28cfe4244711020be28) > Date: Mon, 4 Nov 2019 03:45:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION efa8d3fb5a9c3c28325df28cfe4244711020be28) [//]: # (START_SECTION a4875920e015c40c97023e103cdfa28b21282e37) ### Update supported_configurations.rst > Commit: [a4875920e015c40c97023e103cdfa28b21282e37](https://github.com/dOpensource/dsiprouter/commit/a4875920e015c40c97023e103cdfa28b21282e37) > Date: Mon, 4 Nov 2019 03:21:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a4875920e015c40c97023e103cdfa28b21282e37) [//]: # (START_SECTION 0a1b04837ef6f9793989fc65d48bad9c7df94bea) ### Update supported_configurations.rst > Commit: [0a1b04837ef6f9793989fc65d48bad9c7df94bea](https://github.com/dOpensource/dsiprouter/commit/0a1b04837ef6f9793989fc65d48bad9c7df94bea) > Date: Mon, 4 Nov 2019 03:19:17 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0a1b04837ef6f9793989fc65d48bad9c7df94bea) [//]: # (START_SECTION f55a391d94e4da8c5eaf3ffeeab3a2e27ad6adc9) ### Update domains.rst > Commit: [f55a391d94e4da8c5eaf3ffeeab3a2e27ad6adc9](https://github.com/dOpensource/dsiprouter/commit/f55a391d94e4da8c5eaf3ffeeab3a2e27ad6adc9) > Date: Mon, 4 Nov 2019 01:54:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f55a391d94e4da8c5eaf3ffeeab3a2e27ad6adc9) [//]: # (START_SECTION fe9218330241deaf90871bbdef5a671634bce81e) ### Update supported_configurations.rst > Commit: [fe9218330241deaf90871bbdef5a671634bce81e](https://github.com/dOpensource/dsiprouter/commit/fe9218330241deaf90871bbdef5a671634bce81e) > Date: Mon, 4 Nov 2019 01:37:43 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fe9218330241deaf90871bbdef5a671634bce81e) [//]: # (START_SECTION 2c2a83c592a934a35346e5dd9467fa8c4820dc60) ### Update supported_configurations.rst > Commit: [2c2a83c592a934a35346e5dd9467fa8c4820dc60](https://github.com/dOpensource/dsiprouter/commit/2c2a83c592a934a35346e5dd9467fa8c4820dc60) > Date: Mon, 4 Nov 2019 01:20:21 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2c2a83c592a934a35346e5dd9467fa8c4820dc60) [//]: # (START_SECTION df92443f24256ca322a777b2e39623a4373fb681) ### Update supported_configurations.rst > Commit: [df92443f24256ca322a777b2e39623a4373fb681](https://github.com/dOpensource/dsiprouter/commit/df92443f24256ca322a777b2e39623a4373fb681) > Date: Sun, 3 Nov 2019 17:55:27 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION df92443f24256ca322a777b2e39623a4373fb681) [//]: # (START_SECTION c4cd5ad10f765a8afc4f33e31fbf919c765507ec) ### Update supported_configurations.rst > Commit: [c4cd5ad10f765a8afc4f33e31fbf919c765507ec](https://github.com/dOpensource/dsiprouter/commit/c4cd5ad10f765a8afc4f33e31fbf919c765507ec) > Date: Sun, 3 Nov 2019 17:45:00 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c4cd5ad10f765a8afc4f33e31fbf919c765507ec) [//]: # (START_SECTION 5c507275ea83d7164daedc690b3f8c8879b21e83) ### Update supported_configurations.rst > Commit: [5c507275ea83d7164daedc690b3f8c8879b21e83](https://github.com/dOpensource/dsiprouter/commit/5c507275ea83d7164daedc690b3f8c8879b21e83) > Date: Sun, 3 Nov 2019 17:42:28 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5c507275ea83d7164daedc690b3f8c8879b21e83) [//]: # (START_SECTION 0f44d04cf565cc434ad0d76e7a48e41e882acd5a) ### Update supported_configurations.rst > Commit: [0f44d04cf565cc434ad0d76e7a48e41e882acd5a](https://github.com/dOpensource/dsiprouter/commit/0f44d04cf565cc434ad0d76e7a48e41e882acd5a) > Date: Sun, 3 Nov 2019 17:40:34 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0f44d04cf565cc434ad0d76e7a48e41e882acd5a) [//]: # (START_SECTION 50ffb4ad4a36804c57a6897a101a9d4a937104d4) ### Update supported_configurations.rst > Commit: [50ffb4ad4a36804c57a6897a101a9d4a937104d4](https://github.com/dOpensource/dsiprouter/commit/50ffb4ad4a36804c57a6897a101a9d4a937104d4) > Date: Sun, 3 Nov 2019 17:30:19 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 50ffb4ad4a36804c57a6897a101a9d4a937104d4) [//]: # (START_SECTION 91ed94f3e7ba639bcbbd97ee5319416bd64890d4) ### Update supported_configurations.rst > Commit: [91ed94f3e7ba639bcbbd97ee5319416bd64890d4](https://github.com/dOpensource/dsiprouter/commit/91ed94f3e7ba639bcbbd97ee5319416bd64890d4) > Date: Sun, 3 Nov 2019 17:28:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 91ed94f3e7ba639bcbbd97ee5319416bd64890d4) [//]: # (START_SECTION 750a611301607ec8613ffa1e07ce863362a27570) ### Update supported_configurations.rst > Commit: [750a611301607ec8613ffa1e07ce863362a27570](https://github.com/dOpensource/dsiprouter/commit/750a611301607ec8613ffa1e07ce863362a27570) > Date: Sun, 3 Nov 2019 17:27:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 750a611301607ec8613ffa1e07ce863362a27570) [//]: # (START_SECTION b183b1e717e37c7243b99ec18b8d697aa8b0a1fc) ### Made Pass Thru authentication the default method > Commit: [b183b1e717e37c7243b99ec18b8d697aa8b0a1fc](https://github.com/dOpensource/dsiprouter/commit/b183b1e717e37c7243b99ec18b8d697aa8b0a1fc) > Date: Sun, 3 Nov 2019 22:23:06 +0000 > Author: root (root@dSIPRouterMackv0523Hotfix-0.localdomain) > Committer: root (root@dSIPRouterMackv0523Hotfix-0.localdomain) > Signed: --- [//]: # (END_SECTION b183b1e717e37c7243b99ec18b8d697aa8b0a1fc) [//]: # (START_SECTION c13ff8da9d0e6d04c5f530a992501fd1ff3c3992) ### Fixed #102 for both FreePBX with chan_sip and FusionPBX with Sofia. > Commit: [c13ff8da9d0e6d04c5f530a992501fd1ff3c3992](https://github.com/dOpensource/dsiprouter/commit/c13ff8da9d0e6d04c5f530a992501fd1ff3c3992) > Date: Sun, 3 Nov 2019 21:26:59 +0000 > Author: root (root@dSIPMack0523hotfix-0.localdomain) > Committer: root (root@dSIPMack0523hotfix-0.localdomain) > Signed: --- [//]: # (END_SECTION c13ff8da9d0e6d04c5f530a992501fd1ff3c3992) [//]: # (START_SECTION a5eac062f964e5485f309248e295d8f0110b2dd7) ### Fixed logic that checks if dSIPRouter is running on a cloud provider. The Google Cloud Check was not evaluating correctly and resulted in setting an empty string password for the admin user. > Commit: [a5eac062f964e5485f309248e295d8f0110b2dd7](https://github.com/dOpensource/dsiprouter/commit/a5eac062f964e5485f309248e295d8f0110b2dd7) > Date: Sat, 2 Nov 2019 21:52:56 +1100 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a5eac062f964e5485f309248e295d8f0110b2dd7) [//]: # (START_SECTION 224322a9885f4b5b452d96bfc68a226ca0a02bd3) ### FusionPBX Extension to Extension Dialing - This fix should resovle issues with users being able to do Extension to Extension calls on FusionPBX > Commit: [224322a9885f4b5b452d96bfc68a226ca0a02bd3](https://github.com/dOpensource/dsiprouter/commit/224322a9885f4b5b452d96bfc68a226ca0a02bd3) > Date: Mon, 14 Oct 2019 04:04:18 +0000 > Author: root (root@dSIP0523SSLEnable-0.localdomain) > Committer: root (root@dSIP0523SSLEnable-0.localdomain) > Signed: --- [//]: # (END_SECTION 224322a9885f4b5b452d96bfc68a226ca0a02bd3) [//]: # (START_SECTION a0824bac4caeee87b7f21e22d362f29c9dc8843a) ### Fixed issue with ServerNAT being enabled by default > Commit: [a0824bac4caeee87b7f21e22d362f29c9dc8843a](https://github.com/dOpensource/dsiprouter/commit/a0824bac4caeee87b7f21e22d362f29c9dc8843a) > Date: Sun, 13 Oct 2019 07:15:48 +0000 > Author: root (root@dSIP0523SSLEnable-0.localdomain) > Committer: root (root@dSIP0523SSLEnable-0.localdomain) > Signed: --- [//]: # (END_SECTION a0824bac4caeee87b7f21e22d362f29c9dc8843a) [//]: # (START_SECTION 6fcc6255f4eb63135e4c714d65638c524f8278bc) ### Location information is now stored under extension@ > Commit: [6fcc6255f4eb63135e4c714d65638c524f8278bc](https://github.com/dOpensource/dsiprouter/commit/6fcc6255f4eb63135e4c714d65638c524f8278bc) > Date: Sun, 13 Oct 2019 02:43:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6fcc6255f4eb63135e4c714d65638c524f8278bc) [//]: # (START_SECTION dda982bfbd1c8a2f3879197c08b04775eb95be82) ### Fix pre-commit hook > Commit: [dda982bfbd1c8a2f3879197c08b04775eb95be82](https://github.com/dOpensource/dsiprouter/commit/dda982bfbd1c8a2f3879197c08b04775eb95be82) > Date: Tue, 8 Oct 2019 06:20:48 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix bug where git stash apply would result in conflicts --- [//]: # (END_SECTION dda982bfbd1c8a2f3879197c08b04775eb95be82) [//]: # (START_SECTION 3db8d9052c1a7e486657de0b16945b9e2f6cec2b) ### Increase Efficiency of CHANGELOG creation > Commit: [3db8d9052c1a7e486657de0b16945b9e2f6cec2b](https://github.com/dOpensource/dsiprouter/commit/3db8d9052c1a7e486657de0b16945b9e2f6cec2b) > Date: Tue, 8 Oct 2019 03:26:36 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - updates to post-commit hook made 25% execution time decrease --- [//]: # (END_SECTION 3db8d9052c1a7e486657de0b16945b9e2f6cec2b) [//]: # (START_SECTION 2d57dbb72c691d6b333499718de50c954ff8acca) ### Add dsiprouter to list of tracked services for consul > Commit: [2d57dbb72c691d6b333499718de50c954ff8acca](https://github.com/dOpensource/dsiprouter/commit/2d57dbb72c691d6b333499718de50c954ff8acca) > Date: Sat, 5 Oct 2019 19:07:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2d57dbb72c691d6b333499718de50c954ff8acca) [//]: # (START_SECTION b1e0868ef0edb473718fdfc8494ea0e3fe54722c) ### DB Session Management Updates and Feature Additions > Commit: [b1e0868ef0edb473718fdfc8494ea0e3fe54722c](https://github.com/dOpensource/dsiprouter/commit/b1e0868ef0edb473718fdfc8494ea0e3fe54722c) > Date: Fri, 4 Oct 2019 20:37:33 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - overhaul DB session management architecture to support multithreading - update app to close all db sessions and connections when stopping - update galera, group replication, and kamcluster scripts to fix some small install bugs - update cluster install scripts to support firewalld - add support for consul cluster installation and configuration - update exception and endpoint debug functions to have more sane defaults - move filehandling.py to util module - fix syslogging bugs in dsiprouter - update dsiprouter syslog format to be more useful - fix various bugs in api_routes code - fix various bugs related to incorrect session handling - add utility functions ti shared_lib for HA scripts - fix regression with kamailio config updates on install - add DummySession class to fix exception handling logic flow - fix enterprise_enabled global to really be global --- [//]: # (END_SECTION b1e0868ef0edb473718fdfc8494ea0e3fe54722c) [//]: # (START_SECTION 41529c00be9ed9586eec0e9407c83cff258c0c8e) ### Merge Commit [c88b214251333b7350b10b27fa2dae683ef3b602](https://github.com/dOpensource/dsiprouter/commit/c88b214251333b7350b10b27fa2dae683ef3b602) From OSS Repo > Commit: [41529c00be9ed9586eec0e9407c83cff258c0c8e](https://github.com/dOpensource/dsiprouter/commit/41529c00be9ed9586eec0e9407c83cff258c0c8e) > Date: Wed, 2 Oct 2019 16:59:11 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 41529c00be9ed9586eec0e9407c83cff258c0c8e) [//]: # (START_SECTION c88b214251333b7350b10b27fa2dae683ef3b602) ### Hotfix for 401/407 reply not sent on Pass Through Auth > Commit: [c88b214251333b7350b10b27fa2dae683ef3b602](https://github.com/dOpensource/dsiprouter/commit/c88b214251333b7350b10b27fa2dae683ef3b602) > Date: Wed, 2 Oct 2019 16:53:56 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION c88b214251333b7350b10b27fa2dae683ef3b602) [//]: # (START_SECTION c7bb17fc14cbbc27ac46b7ee34dd8287301a76db) ### Update RTPEngine Default Config > Commit: [c7bb17fc14cbbc27ac46b7ee34dd8287301a76db](https://github.com/dOpensource/dsiprouter/commit/c7bb17fc14cbbc27ac46b7ee34dd8287301a76db) > Date: Tue, 1 Oct 2019 17:58:13 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change default config to use explicit ip definition to avoid confusion --- [//]: # (END_SECTION c7bb17fc14cbbc27ac46b7ee34dd8287301a76db) [//]: # (START_SECTION 5defb56428352d12eb1a9ff27755c37cf9c7cac2) ### Update Enterprise and OSS Features > Commit: [5defb56428352d12eb1a9ff27755c37cf9c7cac2](https://github.com/dOpensource/dsiprouter/commit/5defb56428352d12eb1a9ff27755c37cf9c7cac2) > Date: Tue, 1 Oct 2019 16:40:26 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - merge HA branch into v0.60+ent - improve portability and reliability of HA install scripts - fix iptables save bug in HA install - fix merging functions in HA install - add new standardized git workflow for contributors and option to install in repo - add / update git hooks: pre-commit, post-commit, prepare-commit-msg, commit-msg - add / upfate git configs: .gitignore, .gitattributes, .gitconfig - add merge conflict drivers for git hook generated files, such as CHANGELOG - add full support for storing settings in DB with security - add asymmetric, symmetric, and hashing functions - add setcredentials command for convenience - add support for settings updates through domain sockets - add support for settings updates through prcoess signaling (reload) - add functions to support ipc with dsiprouter process - add support for hot reload of updated settings - add option to enable lcr on install - update in progress work on AA Group Replication install - update asterisk prefix conversions to be reusable and moved to conversions.py - update all credentials and settings to be stored securely - update support for settings updates through env variables to only debug mode - update READEME to reflect enterprise version and display enterprise features - update a couple shared.py functions to be more robust - update kam config update functions to support all the dynamic settings - remove deprecated lcr module (it is already part of core dsiprouter) --- [//]: # (END_SECTION 5defb56428352d12eb1a9ff27755c37cf9c7cac2) [//]: # (START_SECTION a92d2e3aa03be1a7bc634f5ba71e085a8a48fe89) ### Slight Tweak to dr_gateways trigger > Commit: [a92d2e3aa03be1a7bc634f5ba71e085a8a48fe89](https://github.com/dOpensource/dsiprouter/commit/a92d2e3aa03be1a7bc634f5ba71e085a8a48fe89) > Date: Tue, 1 Oct 2019 14:06:23 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update trigger to support multi-row inserts --- [//]: # (END_SECTION a92d2e3aa03be1a7bc634f5ba71e085a8a48fe89) [//]: # (START_SECTION 3988878bb233c3354cb4f3b3aef7d30c73afc465) ### Merge branch v0.523+ent into v0.60+ent > Commit: [3988878bb233c3354cb4f3b3aef7d30c73afc465](https://github.com/dOpensource/dsiprouter/commit/3988878bb233c3354cb4f3b3aef7d30c73afc465) > Date: Mon, 30 Sep 2019 20:51:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 3988878bb233c3354cb4f3b3aef7d30c73afc465) [//]: # (START_SECTION 586cfb543c7062b0df0d0c5bec468af01cf7fa53) ### Bug Fixes and Forwarding Feature Update > Commit: [586cfb543c7062b0df0d0c5bec468af01cf7fa53](https://github.com/dOpensource/dsiprouter/commit/586cfb543c7062b0df0d0c5bec468af01cf7fa53) > Date: Thu, 26 Sep 2019 23:37:28 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update dsip_lib.sh kam config funcs to use a better separator - update dr_gateways db mapping class to fit new updates - update sql files to match utf-8 encoding from previous commit - update dsip_forwarding.sql to use DSIP_ID set at install - update kam to track src and dest gwgroup for call limiting - update kam SEND_NOTIFICATION route to support dynamic gwid and gwgroupid - create trigger for dr_gateways to support feature updates - fix rtpengine install detection (typo) - fix and improve various sql queries in resistrar --- [//]: # (END_SECTION 586cfb543c7062b0df0d0c5bec468af01cf7fa53) [//]: # (START_SECTION 643c07c7d544ad83df7bb855cd90aab8651adbb1) ### Update to DID Forwarding > Commit: [643c07c7d544ad83df7bb855cd90aab8651adbb1](https://github.com/dOpensource/dsiprouter/commit/643c07c7d544ad83df7bb855cd90aab8651adbb1) > Date: Mon, 23 Sep 2019 19:25:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add feature to allow DID routing without specifying gwgroup - update inbound mapping route to accomodate new feature - update gwgroup selection to allow default option - update toggleElemDisabled() to be more robust - change hardfwd toggle button to set gwgroup selection to default option - move inbound mapping js logic from main to its own file - fix bug where failover forwarding would overwrite fwd info --- [//]: # (END_SECTION 643c07c7d544ad83df7bb855cd90aab8651adbb1) [//]: # (START_SECTION 17f31180eeac77b4377c5a854bd40ad613e01210) ### Add Security Features and Database Settings Update > Commit: [17f31180eeac77b4377c5a854bd40ad613e01210](https://github.com/dOpensource/dsiprouter/commit/17f31180eeac77b4377c5a854bd40ad613e01210) > Date: Fri, 20 Sep 2019 16:01:15 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add automatic db/file settings update (on startup) based on mode - add database dump for v0.60+ent - add security functions for hashing (pbkdf2_hmac) and encrpyting (AES256) credentials - add secure credential usage to db connection functions - add setcredentials command to allow user to set credentials securely using script - add root priviledge check to dsiprouter script - add securing permissions on kam cfg to dsiprouter script - add private AES key generation to dsiprouter script - add option to configure kam db hosts from install command - add option to configure dsip id from install command - update getConfigAttrib() and setConfigAttrib() to support byte string literals - update kam cfg update functions to use a better delimiter when parsing - update install script to secure credentials by default - update updateConfig() function to support byte string literals - update MakeFile to run dsiprouter in debug mode and allow credential hot swapping - update testing scripts to use credential hot swapping - update install script to be more efficient by isolating a few functions - update displayLoginIfno() to be more reliable and show kam credentials - update dsip_forwarding.sql to support multiple dsiprouter instances - change secure credential env loading only to debug mode - change dsip/api/mail credentials auth to use encrypted credentials - change dsip credentials auth to compare hashes - change default db characterset to utf-8 (should always be used moving forward) - change default admin password generation to use /dev/urandom - fix kam cfg DBLUSTER settings update logic - fix kam cfg DBURL settings update logic - fix mail default sender bug - fix byte string storage bug by adding encoding/decoding to security functions - fix table detection bug when running installSQL() in a few modules - fix schema load order for dsip_forwarding.sql - fix kam cfg symlink not created issue - fix is rtpengine installed check --- [//]: # (END_SECTION 17f31180eeac77b4377c5a854bd40ad613e01210) [//]: # (START_SECTION 4dee94abce5cf59e8a14f54a57dd64ee480aab01) ### Forwarding fixes and Misc Updates > Commit: [4dee94abce5cf59e8a14f54a57dd64ee480aab01](https://github.com/dOpensource/dsiprouter/commit/4dee94abce5cf59e8a14f54a57dd64ee480aab01) > Date: Fri, 13 Sep 2019 11:35:41 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix forwarding dr_groupid update - allow fwding rule create/delete on update - fix outbound route dr_groupid filtering - update kamreload cmd with reload for new htables - update harfwd and failfwd dr_rule matching on dr_groupid - clean up kam config - reset defaults to prod values --- [//]: # (END_SECTION 4dee94abce5cf59e8a14f54a57dd64ee480aab01) [//]: # (START_SECTION 9298bd4d47c9210c8ed8c353d14fc4d06e9b9744) ### Fixes - Fixed the Endpoint Group Modal - Fixed issues with the Call Limit not working with User/Pass Registration - Fixed issues with the API > Commit: [9298bd4d47c9210c8ed8c353d14fc4d06e9b9744](https://github.com/dOpensource/dsiprouter/commit/9298bd4d47c9210c8ed8c353d14fc4d06e9b9744) > Date: Fri, 13 Sep 2019 02:17:08 +0000 > Author: root (root@dev-siprouter01.ynyybpir3miebggok1eqcyxpaf.gx.internal.cloudapp.net) > Committer: root (root@dev-siprouter01.ynyybpir3miebggok1eqcyxpaf.gx.internal.cloudapp.net) > Signed: --- [//]: # (END_SECTION 9298bd4d47c9210c8ed8c353d14fc4d06e9b9744) [//]: # (START_SECTION ea4bc9f5aa3517aea230ab662dde65592ec5a0a2) ### v0523 Fixes > Commit: [ea4bc9f5aa3517aea230ab662dde65592ec5a0a2](https://github.com/dOpensource/dsiprouter/commit/ea4bc9f5aa3517aea230ab662dde65592ec5a0a2) > Date: Wed, 11 Sep 2019 20:03:40 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed invalid GCE instance detection - fix hardfwd and failfwd always insert issue - fix typo in inbound mapping route - fix inbound mapping modal update for fwd's - fix hardfwd and failfwd matching logic - fix inbound mapping edit modal fwd buttons toggle update - added prefix mapping htable and db view - added dsip_settings db table with a subset of the settings - refactor username/password to follow naming convention - added new features to install process - added sql dump of v0.523+ent to testing sql files --- [//]: # (END_SECTION ea4bc9f5aa3517aea230ab662dde65592ec5a0a2) [//]: # (START_SECTION 50b50a73de61b9c6d6482b1baa15c6b8b8483baf) ### v0523 Fixes > Commit: [50b50a73de61b9c6d6482b1baa15c6b8b8483baf](https://github.com/dOpensource/dsiprouter/commit/50b50a73de61b9c6d6482b1baa15c6b8b8483baf) > Date: Wed, 11 Sep 2019 20:03:40 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fixed invalid GCE instance detection - fix hardfwd and failfwd always insert issue - fix typo in inbound mapping route - fix inbound mapping modal update for fwd's - fix hardfwd and failfwd matching logic - fix inbound mapping edit modal fwd buttons toggle update - added prefix mapping htable and db view - added dsip_settings db table with a subset of the settings - refactor username/password to follow naming convention - added new features to install process - added sql dump of v0.523+ent to testing sql files --- [//]: # (END_SECTION 50b50a73de61b9c6d6482b1baa15c6b8b8483baf) [//]: # (START_SECTION d2c8da09ff1faa9d405d24c9798c41711cecf996) ### Added support for dynamically setting up DR gateways and gateway list when an endpont registers > Commit: [d2c8da09ff1faa9d405d24c9798c41711cecf996](https://github.com/dOpensource/dsiprouter/commit/d2c8da09ff1faa9d405d24c9798c41711cecf996) > Date: Wed, 11 Sep 2019 13:31:06 +0000 > Author: root (root@dev-siprouter01.ynyybpir3miebggok1eqcyxpaf.gx.internal.cloudapp.net) > Committer: root (root@dev-siprouter01.ynyybpir3miebggok1eqcyxpaf.gx.internal.cloudapp.net) > Signed: --- [//]: # (END_SECTION d2c8da09ff1faa9d405d24c9798c41711cecf996) [//]: # (START_SECTION 3f6b43155a0af4c5dacf7e493eae2c49caeac858) ### Fixed a regression in the Carrier Groups Add Carrier Modal. > Commit: [3f6b43155a0af4c5dacf7e493eae2c49caeac858](https://github.com/dOpensource/dsiprouter/commit/3f6b43155a0af4c5dacf7e493eae2c49caeac858) > Date: Thu, 5 Sep 2019 07:23:48 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 3f6b43155a0af4c5dacf7e493eae2c49caeac858) [//]: # (START_SECTION d9f60de3ad0efb4241b060fb174227ec8700f90f) ### Refactored the Endpoint Groups > Commit: [d9f60de3ad0efb4241b060fb174227ec8700f90f](https://github.com/dOpensource/dsiprouter/commit/d9f60de3ad0efb4241b060fb174227ec8700f90f) > Date: Fri, 30 Aug 2019 11:49:12 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d9f60de3ad0efb4241b060fb174227ec8700f90f) [//]: # (START_SECTION df0dcf4dcdd6bcf3f573c3d6dcc9eae9db6b9bb5) ### The DSIP_API_TOKEN value will be admin before install > Commit: [df0dcf4dcdd6bcf3f573c3d6dcc9eae9db6b9bb5](https://github.com/dOpensource/dsiprouter/commit/df0dcf4dcdd6bcf3f573c3d6dcc9eae9db6b9bb5) > Date: Fri, 30 Aug 2019 07:46:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION df0dcf4dcdd6bcf3f573c3d6dcc9eae9db6b9bb5) [//]: # (START_SECTION 4eb68a4fc61ea3b88fa0c3361393e240b12a1e71) ### Fixed issues that resulted from the merge > Commit: [4eb68a4fc61ea3b88fa0c3361393e240b12a1e71](https://github.com/dOpensource/dsiprouter/commit/4eb68a4fc61ea3b88fa0c3361393e240b12a1e71) > Date: Fri, 30 Aug 2019 07:42:15 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4eb68a4fc61ea3b88fa0c3361393e240b12a1e71) [//]: # (START_SECTION 74a3b0670179ce95695a9969fd1fb549de988f0f) ### Fixed merge issue > Commit: [74a3b0670179ce95695a9969fd1fb549de988f0f](https://github.com/dOpensource/dsiprouter/commit/74a3b0670179ce95695a9969fd1fb549de988f0f) > Date: Fri, 30 Aug 2019 03:30:54 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 74a3b0670179ce95695a9969fd1fb549de988f0f) [//]: # (START_SECTION 9e74d60fb0df2a1b90fd16109e14c1309a2743b7) ### Enhancements - Added CDR's to the GUI and via a RESTFul endpoint - Added the ability to update an endpont group record > Commit: [9e74d60fb0df2a1b90fd16109e14c1309a2743b7](https://github.com/dOpensource/dsiprouter/commit/9e74d60fb0df2a1b90fd16109e14c1309a2743b7) > Date: Fri, 30 Aug 2019 03:15:15 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9e74d60fb0df2a1b90fd16109e14c1309a2743b7) [//]: # (START_SECTION 2ce8f4bea06100605980b80b424e62e988ae1088) ### - update call limit to use gwgroup - fix hardfwd and failfwd routing logic - update notification feature to use bearer token - fix looping bug with failover fwd - move enpoint groups js to fix conflict - add insert,update,delete triggers for gw2gwroup table - update dsip fwding to match on prefix instead of gwgroup - make gui templates more standardized (description field) - update inbound mapping to use gwgroups - add hardfwd and failfwd to inbound mapping - change templates to show hostname support in drouting - NOTE: inbound mapping updated but still needs work - update kam reload in api to match gui - fix misc issues in api_routes - add new icons for forwarding - clear add modals when opening again > Commit: [2ce8f4bea06100605980b80b424e62e988ae1088](https://github.com/dOpensource/dsiprouter/commit/2ce8f4bea06100605980b80b424e62e988ae1088) > Date: Thu, 29 Aug 2019 01:49:27 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 2ce8f4bea06100605980b80b424e62e988ae1088) [//]: # (START_SECTION a72dd21df0278f8c70539083ae28dfa1b1fb93e2) ### EndpointGroup Bug Fixes and CDR API - Fixed issues with saving and deleting EndpointGroups - Implemented a CDR RestFul API for requesting Call Detail Record (CDR) Information > Commit: [a72dd21df0278f8c70539083ae28dfa1b1fb93e2](https://github.com/dOpensource/dsiprouter/commit/a72dd21df0278f8c70539083ae28dfa1b1fb93e2) > Date: Wed, 28 Aug 2019 16:45:07 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a72dd21df0278f8c70539083ae28dfa1b1fb93e2) [//]: # (START_SECTION 9ce9add4478a6fcb0f4c328ef1424c02fcf082ad) ### Fixed API Token Security function > Commit: [9ce9add4478a6fcb0f4c328ef1424c02fcf082ad](https://github.com/dOpensource/dsiprouter/commit/9ce9add4478a6fcb0f4c328ef1424c02fcf082ad) > Date: Mon, 26 Aug 2019 13:42:34 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9ce9add4478a6fcb0f4c328ef1424c02fcf082ad) [//]: # (START_SECTION 6d3f5e2e1b6fc5ac57c411d4adb91691b9e96ef6) ### Merging in Notificaition changes > Commit: [6d3f5e2e1b6fc5ac57c411d4adb91691b9e96ef6](https://github.com/dOpensource/dsiprouter/commit/6d3f5e2e1b6fc5ac57c411d4adb91691b9e96ef6) > Date: Mon, 26 Aug 2019 08:50:04 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6d3f5e2e1b6fc5ac57c411d4adb91691b9e96ef6) [//]: # (START_SECTION a60d94f60fd7a9fa869ef1aa4101338e2e982938) ### EndpointGroups - Added API's for updating and deleting EndpointGroups - Added the supporting UI components > Commit: [a60d94f60fd7a9fa869ef1aa4101338e2e982938](https://github.com/dOpensource/dsiprouter/commit/a60d94f60fd7a9fa869ef1aa4101338e2e982938) > Date: Mon, 26 Aug 2019 08:34:21 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a60d94f60fd7a9fa869ef1aa4101338e2e982938) [//]: # (START_SECTION 41adfd261f7f4544d0a982a5c09cddf455afa97b) ### Add Enterprise Features Backend Support > Commit: [41adfd261f7f4544d0a982a5c09cddf455afa97b](https://github.com/dOpensource/dsiprouter/commit/41adfd261f7f4544d0a982a5c09cddf455afa97b) > Date: Sun, 25 Aug 2019 18:45:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - make notifications a separate callable route - add support for endpoint failure notifications - update dsip_hardfwd table - update dsip_failfwd table - improve email notification messages - add improvements to flag handling in kamailio routes - add support for hard forwarding to DID - add support for failover forwarding to DID - update inbound mapping lcr flag handling - update some defaults for TESTING in settings.py (remove for production) - fix missing servernat option for configurekam command in dsiprouter.sh - update command options in dsiprouter.sh - fix #!ifdef mismatch in kamailio.cfg --- [//]: # (END_SECTION 41adfd261f7f4544d0a982a5c09cddf455afa97b) [//]: # (START_SECTION 9a8c7cb2648822d9247d083f80a352f337ff70e0) ### Notification API Feature Backend Support > Commit: [9a8c7cb2648822d9247d083f80a352f337ff70e0](https://github.com/dOpensource/dsiprouter/commit/9a8c7cb2648822d9247d083f80a352f337ff70e0) > Date: Sat, 24 Aug 2019 21:44:47 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add kamailio route for notifications / http requests - add gw2gwroup table - add tables for hard forwarding and failover forwarding - add email notification support - add file upload support - add async func wrapper - add endpoint for notifications to API - add email settings in settings.py - move API security functions to API routes - add http_async_client module to install scripts --- [//]: # (END_SECTION 9a8c7cb2648822d9247d083f80a352f337ff70e0) [//]: # (START_SECTION d8c506a48ab502f34b44bd173e6e9b836764d6b3) ### Added support for adding endpoints within an Endpoint Group > Commit: [d8c506a48ab502f34b44bd173e6e9b836764d6b3](https://github.com/dOpensource/dsiprouter/commit/d8c506a48ab502f34b44bd173e6e9b836764d6b3) > Date: Fri, 23 Aug 2019 11:00:09 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d8c506a48ab502f34b44bd173e6e9b836764d6b3) [//]: # (START_SECTION bc4884df2a3ad22b2ed32eb044b66ab298ad4ffc) ### Added database table for gateway to gateway group lookup - gwip2gwgroup > Commit: [bc4884df2a3ad22b2ed32eb044b66ab298ad4ffc](https://github.com/dOpensource/dsiprouter/commit/bc4884df2a3ad22b2ed32eb044b66ab298ad4ffc) > Date: Wed, 21 Aug 2019 15:28:24 +0000 > Author: root (root@dsip0523entMack.localdomain) > Committer: root (root@dsip0523entMack.localdomain) > Signed: --- [//]: # (END_SECTION bc4884df2a3ad22b2ed32eb044b66ab298ad4ffc) [//]: # (START_SECTION 1e1f55b0670ea5b1d03a4d9ac56610bf2845bf4c) ### Fixed issue with merge - forgot to fix conflict > Commit: [1e1f55b0670ea5b1d03a4d9ac56610bf2845bf4c](https://github.com/dOpensource/dsiprouter/commit/1e1f55b0670ea5b1d03a4d9ac56610bf2845bf4c) > Date: Wed, 21 Aug 2019 07:58:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1e1f55b0670ea5b1d03a4d9ac56610bf2845bf4c) [//]: # (START_SECTION ed8786a06b8623938d82c9a1c38982b36df11139) ### Fixed issue with merge - forgot to fix conflict > Commit: [ed8786a06b8623938d82c9a1c38982b36df11139](https://github.com/dOpensource/dsiprouter/commit/ed8786a06b8623938d82c9a1c38982b36df11139) > Date: Wed, 21 Aug 2019 07:55:41 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ed8786a06b8623938d82c9a1c38982b36df11139) [//]: # (START_SECTION 096c9254dc09665a7f9645281306c25fe9ba257b) ### Fixed issues with the Endpoint API and dSIPNotification SQL > Commit: [096c9254dc09665a7f9645281306c25fe9ba257b](https://github.com/dOpensource/dsiprouter/commit/096c9254dc09665a7f9645281306c25fe9ba257b) > Date: Wed, 21 Aug 2019 07:45:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 096c9254dc09665a7f9645281306c25fe9ba257b) [//]: # (START_SECTION d4747c96ab72b8f2766b5797080d9917363a753f) ### Fixed the mail settins > Commit: [d4747c96ab72b8f2766b5797080d9917363a753f](https://github.com/dOpensource/dsiprouter/commit/d4747c96ab72b8f2766b5797080d9917363a753f) > Date: Wed, 21 Aug 2019 02:56:42 +0000 > Author: root (root@dsip0523entMerge.localdomain) > Committer: root (root@dsip0523entMerge.localdomain) > Signed: --- [//]: # (END_SECTION d4747c96ab72b8f2766b5797080d9917363a753f) [//]: # (START_SECTION 97f71a42a0a32e1c93904327ac8c195feabde1ec) ### Fixed the version number > Commit: [97f71a42a0a32e1c93904327ac8c195feabde1ec](https://github.com/dOpensource/dsiprouter/commit/97f71a42a0a32e1c93904327ac8c195feabde1ec) > Date: Wed, 21 Aug 2019 02:47:05 +0000 > Author: root (root@dsip0523entMerge.localdomain) > Committer: root (root@dsip0523entMerge.localdomain) > Signed: --- [//]: # (END_SECTION 97f71a42a0a32e1c93904327ac8c195feabde1ec) [//]: # (START_SECTION f2eded53fd2b0e9e03af1deb658f0d1315c90f99) ### Fixed minor issues: - dsip_calllimit table was not being installed at install time - The email settings for the notifiation service was not in the settings.py file > Commit: [f2eded53fd2b0e9e03af1deb658f0d1315c90f99](https://github.com/dOpensource/dsiprouter/commit/f2eded53fd2b0e9e03af1deb658f0d1315c90f99) > Date: Wed, 21 Aug 2019 02:45:09 +0000 > Author: root (root@dsip0523entMerge.localdomain) > Committer: root (root@dsip0523entMerge.localdomain) > Signed: --- [//]: # (END_SECTION f2eded53fd2b0e9e03af1deb658f0d1315c90f99) [//]: # (START_SECTION eee1276a68eb23b3df81a8f8f0ec0f5ba293ee2e) ### Docker Compose: - Added the dsip_notification schema to the SQL file that is used for priming the database of the MySQL container > Commit: [eee1276a68eb23b3df81a8f8f0ec0f5ba293ee2e](https://github.com/dOpensource/dsiprouter/commit/eee1276a68eb23b3df81a8f8f0ec0f5ba293ee2e) > Date: Wed, 21 Aug 2019 02:07:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION eee1276a68eb23b3df81a8f8f0ec0f5ba293ee2e) [//]: # (START_SECTION 807b1a2ef9913086bd2eb27aee47b17d872be14b) ### Docker Compose: - Added the dsip_notification schema to the SQL file that is used for priming the database of the MySQL container > Commit: [807b1a2ef9913086bd2eb27aee47b17d872be14b](https://github.com/dOpensource/dsiprouter/commit/807b1a2ef9913086bd2eb27aee47b17d872be14b) > Date: Tue, 20 Aug 2019 21:50:20 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 807b1a2ef9913086bd2eb27aee47b17d872be14b) [//]: # (START_SECTION 2f960dfd6dda36d6154656d1ee439abfa44db357) ### Added support for adding endpoints > Commit: [2f960dfd6dda36d6154656d1ee439abfa44db357](https://github.com/dOpensource/dsiprouter/commit/2f960dfd6dda36d6154656d1ee439abfa44db357) > Date: Tue, 20 Aug 2019 08:10:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2f960dfd6dda36d6154656d1ee439abfa44db357) [//]: # (START_SECTION 05983fa408408f5f0d5ec0541db5c61c28a55b8c) ### Added logic to store an endpoint group > Commit: [05983fa408408f5f0d5ec0541db5c61c28a55b8c](https://github.com/dOpensource/dsiprouter/commit/05983fa408408f5f0d5ec0541db5c61c28a55b8c) > Date: Mon, 19 Aug 2019 07:01:53 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 05983fa408408f5f0d5ec0541db5c61c28a55b8c) [//]: # (START_SECTION 6ed938930ea697c7ed89332b610fd4b057da66d4) ### Create RESTFul API to add an endpoint group > Commit: [6ed938930ea697c7ed89332b610fd4b057da66d4](https://github.com/dOpensource/dsiprouter/commit/6ed938930ea697c7ed89332b610fd4b057da66d4) > Date: Fri, 16 Aug 2019 14:47:11 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6ed938930ea697c7ed89332b610fd4b057da66d4) [//]: # (START_SECTION 74f3517bc57aade400a68b128da983acf4b0c11a) ### Fixed issues with the docker compose changes > Commit: [74f3517bc57aade400a68b128da983acf4b0c11a](https://github.com/dOpensource/dsiprouter/commit/74f3517bc57aade400a68b128da983acf4b0c11a) > Date: Fri, 16 Aug 2019 14:39:05 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 74f3517bc57aade400a68b128da983acf4b0c11a) [//]: # (START_SECTION 6a0ca8f77b62006bbd58a5d6ff0897e3c24ee99c) ### Added logic to create the Call Limit Schema > Commit: [6a0ca8f77b62006bbd58a5d6ff0897e3c24ee99c](https://github.com/dOpensource/dsiprouter/commit/6a0ca8f77b62006bbd58a5d6ff0897e3c24ee99c) > Date: Tue, 13 Aug 2019 02:45:45 +0000 > Author: root (root@dsip0523entMack-0.localdomain) > Committer: root (root@dsip0523entMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 6a0ca8f77b62006bbd58a5d6ff0897e3c24ee99c) [//]: # (START_SECTION 9e487891682a7f7b00211139e64e069e146e8736) ### Updated to handle different version of Python being already installed on the system > Commit: [9e487891682a7f7b00211139e64e069e146e8736](https://github.com/dOpensource/dsiprouter/commit/9e487891682a7f7b00211139e64e069e146e8736) > Date: Mon, 12 Aug 2019 12:49:25 +0000 > Author: root (root@ip-172-31-23-165.us-east-2.compute.internal) > Committer: root (root@ip-172-31-23-165.us-east-2.compute.internal) > Signed: --- [//]: # (END_SECTION 9e487891682a7f7b00211139e64e069e146e8736) [//]: # (START_SECTION de5c873f83ac38efb8b9ba0f5b9a0c6109d3178b) ### FusionPBX Provisioning Services: - The docker container now starts up with a self signed cert > Commit: [de5c873f83ac38efb8b9ba0f5b9a0c6109d3178b](https://github.com/dOpensource/dsiprouter/commit/de5c873f83ac38efb8b9ba0f5b9a0c6109d3178b) > Date: Wed, 7 Aug 2019 12:00:45 +0000 > Author: root (root@dsip0523-qa-0.localdomain) > Committer: root (root@dsip0523-qa-0.localdomain) > Signed: --- [//]: # (END_SECTION de5c873f83ac38efb8b9ba0f5b9a0c6109d3178b) [//]: # (START_SECTION 6939b1cdbfb0c98d611fefbaa935462dddeabea7) ### Fixed an self-signed cert configurtion. The Country portion was set to USA, versus US > Commit: [6939b1cdbfb0c98d611fefbaa935462dddeabea7](https://github.com/dOpensource/dsiprouter/commit/6939b1cdbfb0c98d611fefbaa935462dddeabea7) > Date: Wed, 7 Aug 2019 05:21:11 +0000 > Author: Mack Hendricks (mack@goflyball.com) > Committer: Mack Hendricks (mack@goflyball.com) > Signed: --- [//]: # (END_SECTION 6939b1cdbfb0c98d611fefbaa935462dddeabea7) [//]: # (START_SECTION ab513ab3a8c9e9e74e0aee9e404b33c6cbad8cd9) ### Carrier Groups: - Fixed an error that occured when creating a carrier that used Username/Password auth > Commit: [ab513ab3a8c9e9e74e0aee9e404b33c6cbad8cd9](https://github.com/dOpensource/dsiprouter/commit/ab513ab3a8c9e9e74e0aee9e404b33c6cbad8cd9) > Date: Wed, 7 Aug 2019 04:47:40 +0000 > Author: root (root@dsip0523qa-0.localdomain) > Committer: root (root@dsip0523qa-0.localdomain) > Signed: --- [//]: # (END_SECTION ab513ab3a8c9e9e74e0aee9e404b33c6cbad8cd9) [//]: # (START_SECTION 3c6a47ce1599c071b84814e64cc22bfe70f4e74a) ### Domains - Local Subscriber: - Fixed authentication logic. It will now properly authenticate against the subscriber table > Commit: [3c6a47ce1599c071b84814e64cc22bfe70f4e74a](https://github.com/dOpensource/dsiprouter/commit/3c6a47ce1599c071b84814e64cc22bfe70f4e74a) > Date: Mon, 5 Aug 2019 22:09:48 +0000 > Author: root (root@dsip0523qa-0.localdomain) > Committer: root (root@dsip0523qa-0.localdomain) > Signed: --- [//]: # (END_SECTION 3c6a47ce1599c071b84814e64cc22bfe70f4e74a) [//]: # (START_SECTION a5498c61858525d9781dfbe7c3dea27194ef0b04) ### Update settings.py > Commit: [a5498c61858525d9781dfbe7c3dea27194ef0b04](https://github.com/dOpensource/dsiprouter/commit/a5498c61858525d9781dfbe7c3dea27194ef0b04) > Date: Mon, 5 Aug 2019 06:36:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a5498c61858525d9781dfbe7c3dea27194ef0b04) [//]: # (START_SECTION 8531cbfd4662a25a66a14e73c4f914adeb71145c) ### Update settings.py > Commit: [8531cbfd4662a25a66a14e73c4f914adeb71145c](https://github.com/dOpensource/dsiprouter/commit/8531cbfd4662a25a66a14e73c4f914adeb71145c) > Date: Mon, 5 Aug 2019 06:33:53 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8531cbfd4662a25a66a14e73c4f914adeb71145c) [//]: # (START_SECTION 3111739f6130a87ac9c379617732a3ae040b590f) ### Update settings.py > Commit: [3111739f6130a87ac9c379617732a3ae040b590f](https://github.com/dOpensource/dsiprouter/commit/3111739f6130a87ac9c379617732a3ae040b590f) > Date: Mon, 5 Aug 2019 06:33:26 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3111739f6130a87ac9c379617732a3ae040b590f) [//]: # (START_SECTION 0da71dd8bf4cd3919d035f1039baedbd228f684e) ### Update index.rst > Commit: [0da71dd8bf4cd3919d035f1039baedbd228f684e](https://github.com/dOpensource/dsiprouter/commit/0da71dd8bf4cd3919d035f1039baedbd228f684e) > Date: Mon, 5 Aug 2019 05:44:29 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0da71dd8bf4cd3919d035f1039baedbd228f684e) [//]: # (START_SECTION 56a77e02cec99cd19a00e54e9eddea62040af0cf) ### Domain Mapping: - Added the domain_list_hash field to support syncing with FusioinPBX > Commit: [56a77e02cec99cd19a00e54e9eddea62040af0cf](https://github.com/dOpensource/dsiprouter/commit/56a77e02cec99cd19a00e54e9eddea62040af0cf) > Date: Mon, 5 Aug 2019 02:34:49 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 56a77e02cec99cd19a00e54e9eddea62040af0cf) [//]: # (START_SECTION b47ec8a86d55548b785676fd1ba431386b4e4dae) ### Update upgrade_0.522_to_0.523.rst > Commit: [b47ec8a86d55548b785676fd1ba431386b4e4dae](https://github.com/dOpensource/dsiprouter/commit/b47ec8a86d55548b785676fd1ba431386b4e4dae) > Date: Sun, 4 Aug 2019 22:33:23 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b47ec8a86d55548b785676fd1ba431386b4e4dae) [//]: # (START_SECTION fcd85f373341fd8421801364e362acd30ef2ebe4) ### Update upgrade_0.522_to_0.523.rst > Commit: [fcd85f373341fd8421801364e362acd30ef2ebe4](https://github.com/dOpensource/dsiprouter/commit/fcd85f373341fd8421801364e362acd30ef2ebe4) > Date: Sun, 4 Aug 2019 22:29:26 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fcd85f373341fd8421801364e362acd30ef2ebe4) [//]: # (START_SECTION a0dd65902d3535e222b9a259744401f54df2c95e) ### Update upgrade_0.522_to_0.523.rst > Commit: [a0dd65902d3535e222b9a259744401f54df2c95e](https://github.com/dOpensource/dsiprouter/commit/a0dd65902d3535e222b9a259744401f54df2c95e) > Date: Sun, 4 Aug 2019 22:27:32 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a0dd65902d3535e222b9a259744401f54df2c95e) [//]: # (START_SECTION 550c842e6a2e45e1eb3493c65aeec3fc3977bb0b) ### Update upgrade_0.522_to_0.523.rst > Commit: [550c842e6a2e45e1eb3493c65aeec3fc3977bb0b](https://github.com/dOpensource/dsiprouter/commit/550c842e6a2e45e1eb3493c65aeec3fc3977bb0b) > Date: Sun, 4 Aug 2019 22:25:54 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 550c842e6a2e45e1eb3493c65aeec3fc3977bb0b) [//]: # (START_SECTION 6585ed0b8a888855de07660cbd45e5ddbffde34e) ### Rename upgrade_0522_to_0523.rst to upgrade_0.522_to_0.523.rst > Commit: [6585ed0b8a888855de07660cbd45e5ddbffde34e](https://github.com/dOpensource/dsiprouter/commit/6585ed0b8a888855de07660cbd45e5ddbffde34e) > Date: Sun, 4 Aug 2019 22:23:35 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6585ed0b8a888855de07660cbd45e5ddbffde34e) [//]: # (START_SECTION a2355994144a5319aaba781c68ccec9cc1341b61) ### Update upgrade.rst > Commit: [a2355994144a5319aaba781c68ccec9cc1341b61](https://github.com/dOpensource/dsiprouter/commit/a2355994144a5319aaba781c68ccec9cc1341b61) > Date: Sun, 4 Aug 2019 22:18:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a2355994144a5319aaba781c68ccec9cc1341b61) [//]: # (START_SECTION 35bd7a2206f730e73e49b74b623d115e5be330bc) ### Update upgrade.rst > Commit: [35bd7a2206f730e73e49b74b623d115e5be330bc](https://github.com/dOpensource/dsiprouter/commit/35bd7a2206f730e73e49b74b623d115e5be330bc) > Date: Sun, 4 Aug 2019 22:17:42 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 35bd7a2206f730e73e49b74b623d115e5be330bc) [//]: # (START_SECTION fa4ae601d9472a0d7fe9e0d45529ec6f56c28147) ### Update upgrade.rst > Commit: [fa4ae601d9472a0d7fe9e0d45529ec6f56c28147](https://github.com/dOpensource/dsiprouter/commit/fa4ae601d9472a0d7fe9e0d45529ec6f56c28147) > Date: Sun, 4 Aug 2019 22:17:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fa4ae601d9472a0d7fe9e0d45529ec6f56c28147) [//]: # (START_SECTION 40a3da0adf309c1f7831f10abcd512275771a9ca) ### Update upgrade.rst > Commit: [40a3da0adf309c1f7831f10abcd512275771a9ca](https://github.com/dOpensource/dsiprouter/commit/40a3da0adf309c1f7831f10abcd512275771a9ca) > Date: Sun, 4 Aug 2019 22:14:53 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 40a3da0adf309c1f7831f10abcd512275771a9ca) [//]: # (START_SECTION 0469c1961ed0b19ba46027db7d537446a9ba2fca) ### Update upgrade.rst > Commit: [0469c1961ed0b19ba46027db7d537446a9ba2fca](https://github.com/dOpensource/dsiprouter/commit/0469c1961ed0b19ba46027db7d537446a9ba2fca) > Date: Sun, 4 Aug 2019 22:13:08 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0469c1961ed0b19ba46027db7d537446a9ba2fca) [//]: # (START_SECTION 8d6defe515f156e06444884592426c62871ac95f) ### Update upgrade.rst > Commit: [8d6defe515f156e06444884592426c62871ac95f](https://github.com/dOpensource/dsiprouter/commit/8d6defe515f156e06444884592426c62871ac95f) > Date: Sun, 4 Aug 2019 22:08:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8d6defe515f156e06444884592426c62871ac95f) [//]: # (START_SECTION e0d1cb3accc65ccf3f0279e576eabb2ab4712e84) ### Create upgrade.rst > Commit: [e0d1cb3accc65ccf3f0279e576eabb2ab4712e84](https://github.com/dOpensource/dsiprouter/commit/e0d1cb3accc65ccf3f0279e576eabb2ab4712e84) > Date: Sun, 4 Aug 2019 22:07:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e0d1cb3accc65ccf3f0279e576eabb2ab4712e84) [//]: # (START_SECTION 76f93a93638bbb8fb8bc0e5c0a714b0f01e480bd) ### Create upgrade_0522_to_0523.rst > Commit: [76f93a93638bbb8fb8bc0e5c0a714b0f01e480bd](https://github.com/dOpensource/dsiprouter/commit/76f93a93638bbb8fb8bc0e5c0a714b0f01e480bd) > Date: Sun, 4 Aug 2019 22:06:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 76f93a93638bbb8fb8bc0e5c0a714b0f01e480bd) [//]: # (START_SECTION c1007d77f7c4539a299657fbefc14b5a87d527a1) ### Rename upgrade.rst to upgrade_0.50_to_0.51.rst > Commit: [c1007d77f7c4539a299657fbefc14b5a87d527a1](https://github.com/dOpensource/dsiprouter/commit/c1007d77f7c4539a299657fbefc14b5a87d527a1) > Date: Sun, 4 Aug 2019 22:04:44 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c1007d77f7c4539a299657fbefc14b5a87d527a1) [//]: # (START_SECTION b0d6fac4b564797f4f356f9dc81372df9c61e92f) ### Changed the table of contents > Commit: [b0d6fac4b564797f4f356f9dc81372df9c61e92f](https://github.com/dOpensource/dsiprouter/commit/b0d6fac4b564797f4f356f9dc81372df9c61e92f) > Date: Sun, 4 Aug 2019 21:57:21 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Moved the API section from the bottom. --- [//]: # (END_SECTION b0d6fac4b564797f4f356f9dc81372df9c61e92f) [//]: # (START_SECTION 4d74524706b5b3ee9765518a616079174e10f017) ### FusionPBX Domain Routing Sync: - Added logic that will generate a hash of domain names during the sync. The sync will only run if the hash changes - Added logic to create a self-signed certificate for nginx. This will allow the service to start up using SSL Fixes #193 > Commit: [4d74524706b5b3ee9765518a616079174e10f017](https://github.com/dOpensource/dsiprouter/commit/4d74524706b5b3ee9765518a616079174e10f017) > Date: Mon, 5 Aug 2019 01:47:29 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4d74524706b5b3ee9765518a616079174e10f017) [//]: # (START_SECTION edd4cbca59a1c748928ab7ee81b8caeccc628e8d) ### FusionPBX Domain Routing Sync: - Added logic that will generate a hash of domain names during the sync. The sync will only run if the hash changes - Added logic to create a self-signed certificate for nginx. This will allow the service to start up using SSL > Commit: [edd4cbca59a1c748928ab7ee81b8caeccc628e8d](https://github.com/dOpensource/dsiprouter/commit/edd4cbca59a1c748928ab7ee81b8caeccc628e8d) > Date: Mon, 5 Aug 2019 01:42:28 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION edd4cbca59a1c748928ab7ee81b8caeccc628e8d) [//]: # (START_SECTION 7e905c1c721018cd7a0336d2b630ed273fe9b6b3) ### Domain Support for Local Subscriber Table - Added logic to reload the dispatcher table - Added logic to probe each server defined within a Domain > Commit: [7e905c1c721018cd7a0336d2b630ed273fe9b6b3](https://github.com/dOpensource/dsiprouter/commit/7e905c1c721018cd7a0336d2b630ed273fe9b6b3) > Date: Sat, 3 Aug 2019 00:30:10 +0000 > Author: root (root@demo-dsiprouter-0.localdomain) > Committer: root (root@demo-dsiprouter-0.localdomain) > Signed: --- [//]: # (END_SECTION 7e905c1c721018cd7a0336d2b630ed273fe9b6b3) [//]: # (START_SECTION 91b08445116f6b25b51ee1fcd1d27d262e0a5309) ### PBX INVITE TIMER - Changed the logic so that INVITE messages from PBX's that receive a SIP 100 message will be assigned a different INVITE timer timeout - Fixes #195 > Commit: [91b08445116f6b25b51ee1fcd1d27d262e0a5309](https://github.com/dOpensource/dsiprouter/commit/91b08445116f6b25b51ee1fcd1d27d262e0a5309) > Date: Thu, 1 Aug 2019 11:14:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 91b08445116f6b25b51ee1fcd1d27d262e0a5309) [//]: # (START_SECTION 7c99508394adb92cb499e62312db871e21a09fc6) ### PBX INVITE TIMER - Increased the INVITE TIMER by two once we see a SIP 100 Message. This will give the endpoint more time to respond to the invite and it will trigger the secondary server if it doesn't answer - Fixes #195 > Commit: [7c99508394adb92cb499e62312db871e21a09fc6](https://github.com/dOpensource/dsiprouter/commit/7c99508394adb92cb499e62312db871e21a09fc6) > Date: Wed, 31 Jul 2019 06:35:11 +0000 > Author: root (root@dsip0523-0.localdomain) > Committer: root (root@dsip0523-0.localdomain) > Signed: --- [//]: # (END_SECTION 7c99508394adb92cb499e62312db871e21a09fc6) [//]: # (START_SECTION bde414d405a8051901fddf90415cb91f82a444da) ### Redesign of PBX page: - The term PBX is switched to Endpoint to represent a more generic use - Added tabs to the Add modal for each configuration area > Commit: [bde414d405a8051901fddf90415cb91f82a444da](https://github.com/dOpensource/dsiprouter/commit/bde414d405a8051901fddf90415cb91f82a444da) > Date: Tue, 30 Jul 2019 19:17:07 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bde414d405a8051901fddf90415cb91f82a444da) [//]: # (START_SECTION 73fb41db8345debc967eb50dcf1f1c461a9ac499) ### Docker support - Added dockerfiles for dSIPRouter and MySQL - Added a docker-compose configuration to allow the dSIPRouter GUI to spin up with a docker-compose up - Added environment variables to allow the dSIP usernamae, password and kamailio database settings can be set on runtime > Commit: [73fb41db8345debc967eb50dcf1f1c461a9ac499](https://github.com/dOpensource/dsiprouter/commit/73fb41db8345debc967eb50dcf1f1c461a9ac499) > Date: Mon, 29 Jul 2019 14:29:50 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 73fb41db8345debc967eb50dcf1f1c461a9ac499) [//]: # (START_SECTION ecc3f8ca6dc41d000495851de0631b1cc64ce344) ### Refactoring the PBX/Endpoint page > Commit: [ecc3f8ca6dc41d000495851de0631b1cc64ce344](https://github.com/dOpensource/dsiprouter/commit/ecc3f8ca6dc41d000495851de0631b1cc64ce344) > Date: Fri, 26 Jul 2019 01:42:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ecc3f8ca6dc41d000495851de0631b1cc64ce344) [//]: # (START_SECTION 04094157b805103337eaca75891ce1121c63cb44) ### Disabled verbose output for a test > Commit: [04094157b805103337eaca75891ce1121c63cb44](https://github.com/dOpensource/dsiprouter/commit/04094157b805103337eaca75891ce1121c63cb44) > Date: Wed, 24 Jul 2019 13:30:30 +0000 > Author: Mack Hendricks (mack@goflyball.com) > Committer: Mack Hendricks (mack@goflyball.com) > Signed: --- [//]: # (END_SECTION 04094157b805103337eaca75891ce1121c63cb44) [//]: # (START_SECTION 05e699c9f4bc2a91241038e16b80b426320d005a) ### Removed the ExecStart command that was reseting the dSIPRouter password to the instanceid of the instance. Except for Amazon images > Commit: [05e699c9f4bc2a91241038e16b80b426320d005a](https://github.com/dOpensource/dsiprouter/commit/05e699c9f4bc2a91241038e16b80b426320d005a) > Date: Wed, 24 Jul 2019 13:25:47 +0000 > Author: Mack Hendricks (mack@goflyball.com) > Committer: Mack Hendricks (mack@goflyball.com) > Signed: --- [//]: # (END_SECTION 05e699c9f4bc2a91241038e16b80b426320d005a) [//]: # (START_SECTION 43300c6e1639679564a12f7aafdb577edfcc20da) ### Python repo issue - > The yum package manager couldn't install python36u-pip because of a conflict with the python36 packages > which are in the epel-release repo. We now remove the python36 libraries and install Python from the ius repo > Commit: [43300c6e1639679564a12f7aafdb577edfcc20da](https://github.com/dOpensource/dsiprouter/commit/43300c6e1639679564a12f7aafdb577edfcc20da) > Date: Tue, 23 Jul 2019 16:11:44 +0000 > Author: root (root@dsip-centOS7.6) > Committer: root (root@dsip-centOS7.6) > Signed: --- [//]: # (END_SECTION 43300c6e1639679564a12f7aafdb577edfcc20da) [//]: # (START_SECTION 2490692a8f8b5913d529a81a70f5f05e11871085) ### Added Call Limit Support to the Kamailio configuration and fixed the dSIPRouter logic to handle it > Commit: [2490692a8f8b5913d529a81a70f5f05e11871085](https://github.com/dOpensource/dsiprouter/commit/2490692a8f8b5913d529a81a70f5f05e11871085) > Date: Mon, 22 Jul 2019 11:17:17 +0000 > Author: root (root@dsip0522ent-0.localdomain) > Committer: root (root@dsip0522ent-0.localdomain) > Signed: --- [//]: # (END_SECTION 2490692a8f8b5913d529a81a70f5f05e11871085) [//]: # (START_SECTION 4d31a7f92e1ad98f3309708dc23194c9c001d3b3) ### Added logic to manage call limits > Commit: [4d31a7f92e1ad98f3309708dc23194c9c001d3b3](https://github.com/dOpensource/dsiprouter/commit/4d31a7f92e1ad98f3309708dc23194c9c001d3b3) > Date: Fri, 19 Jul 2019 14:19:19 +0000 > Author: root (root@dsip0522ent-0.localdomain) > Committer: root (root@dsip0522ent-0.localdomain) > Signed: --- [//]: # (END_SECTION 4d31a7f92e1ad98f3309708dc23194c9c001d3b3) [//]: # (START_SECTION d270313ca80adfae3b66015a59a40eb65c3bf498) ### First commit of enterprise > Commit: [d270313ca80adfae3b66015a59a40eb65c3bf498](https://github.com/dOpensource/dsiprouter/commit/d270313ca80adfae3b66015a59a40eb65c3bf498) > Date: Thu, 18 Jul 2019 19:10:43 +0000 > Author: root (root@dsip0522ent-0.localdomain) > Committer: root (root@dsip0522ent-0.localdomain) > Signed: --- [//]: # (END_SECTION d270313ca80adfae3b66015a59a40eb65c3bf498) [//]: # (START_SECTION 5eebed34cd7055bece9fb0bd46c7689c1927dcf0) ### Update api.rst > Commit: [5eebed34cd7055bece9fb0bd46c7689c1927dcf0](https://github.com/dOpensource/dsiprouter/commit/5eebed34cd7055bece9fb0bd46c7689c1927dcf0) > Date: Wed, 3 Jul 2019 18:49:23 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5eebed34cd7055bece9fb0bd46c7689c1927dcf0) [//]: # (START_SECTION f8d363e6e24e942988aaa48a6fcdbf092a65f936) ### Update Docs > Commit: [f8d363e6e24e942988aaa48a6fcdbf092a65f936](https://github.com/dOpensource/dsiprouter/commit/f8d363e6e24e942988aaa48a6fcdbf092a65f936) > Date: Wed, 3 Jul 2019 17:21:15 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix couple broken links - fix `api.rst` --- [//]: # (END_SECTION f8d363e6e24e942988aaa48a6fcdbf092a65f936) [//]: # (START_SECTION 1a93a8d468627bddba6244aa27a210ded5fe232a) ### Update API Docs > Commit: [1a93a8d468627bddba6244aa27a210ded5fe232a](https://github.com/dOpensource/dsiprouter/commit/1a93a8d468627bddba6244aa27a210ded5fe232a) > Date: Wed, 3 Jul 2019 16:36:46 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 1a93a8d468627bddba6244aa27a210ded5fe232a) [//]: # (START_SECTION 2b647fd576e2c58129686b480b5b32f76f9c0a2d) ### Update api.rst > Commit: [2b647fd576e2c58129686b480b5b32f76f9c0a2d](https://github.com/dOpensource/dsiprouter/commit/2b647fd576e2c58129686b480b5b32f76f9c0a2d) > Date: Wed, 3 Jul 2019 16:10:17 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2b647fd576e2c58129686b480b5b32f76f9c0a2d) [//]: # (START_SECTION dec8a4c87057cc31951b297e0796bfea37751e17) ### Merge Documentation Fixes > Commit: [dec8a4c87057cc31951b297e0796bfea37751e17](https://github.com/dOpensource/dsiprouter/commit/dec8a4c87057cc31951b297e0796bfea37751e17) > Date: Wed, 3 Jul 2019 15:30:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION dec8a4c87057cc31951b297e0796bfea37751e17) [//]: # (START_SECTION 7ec9412c035ffda8374d701956805021f8ba1bd0) ### Update api.rst > Commit: [7ec9412c035ffda8374d701956805021f8ba1bd0](https://github.com/dOpensource/dsiprouter/commit/7ec9412c035ffda8374d701956805021f8ba1bd0) > Date: Wed, 3 Jul 2019 15:08:56 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7ec9412c035ffda8374d701956805021f8ba1bd0) [//]: # (START_SECTION 22d40857924ef8d7a4e0e61c9e527891500d3d7c) ### Fix inconsistencies in documentation > Commit: [22d40857924ef8d7a4e0e61c9e527891500d3d7c](https://github.com/dOpensource/dsiprouter/commit/22d40857924ef8d7a4e0e61c9e527891500d3d7c) > Date: Wed, 3 Jul 2019 14:54:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 22d40857924ef8d7a4e0e61c9e527891500d3d7c) [//]: # (START_SECTION 1dfaa1f43594c6139c2ffcfe76c33af85e596e5d) ### Merge Documentation Changes > Commit: [1dfaa1f43594c6139c2ffcfe76c33af85e596e5d](https://github.com/dOpensource/dsiprouter/commit/1dfaa1f43594c6139c2ffcfe76c33af85e596e5d) > Date: Wed, 3 Jul 2019 13:17:11 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Update api.rst --- [//]: # (END_SECTION 1dfaa1f43594c6139c2ffcfe76c33af85e596e5d) [//]: # (START_SECTION 91cb29f477cb92b35ee77870445f225c798a1cde) ### Merge documentation Updates > Commit: [91cb29f477cb92b35ee77870445f225c798a1cde](https://github.com/dOpensource/dsiprouter/commit/91cb29f477cb92b35ee77870445f225c798a1cde) > Date: Wed, 3 Jul 2019 13:13:52 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Update domains.rst --- [//]: # (END_SECTION 91cb29f477cb92b35ee77870445f225c798a1cde) [//]: # (START_SECTION fd2fd068e5b3d537ccc027d1c14330660cd4a030) ### Update api.rst > Commit: [fd2fd068e5b3d537ccc027d1c14330660cd4a030](https://github.com/dOpensource/dsiprouter/commit/fd2fd068e5b3d537ccc027d1c14330660cd4a030) > Date: Wed, 3 Jul 2019 11:40:51 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fd2fd068e5b3d537ccc027d1c14330660cd4a030) [//]: # (START_SECTION b7d2c765ff46acd6c9df645f97b9b181715257d2) ### Update api.rst > Commit: [b7d2c765ff46acd6c9df645f97b9b181715257d2](https://github.com/dOpensource/dsiprouter/commit/b7d2c765ff46acd6c9df645f97b9b181715257d2) > Date: Wed, 3 Jul 2019 11:30:49 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b7d2c765ff46acd6c9df645f97b9b181715257d2) [//]: # (START_SECTION 18e679561335e0fdf259c8c21b664724ebc03e84) ### Update api.rst > Commit: [18e679561335e0fdf259c8c21b664724ebc03e84](https://github.com/dOpensource/dsiprouter/commit/18e679561335e0fdf259c8c21b664724ebc03e84) > Date: Wed, 3 Jul 2019 11:29:43 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 18e679561335e0fdf259c8c21b664724ebc03e84) [//]: # (START_SECTION d2b0eb4eafdef691e71691cf6c1da85ac07b9fd9) ### Update api.rst > Commit: [d2b0eb4eafdef691e71691cf6c1da85ac07b9fd9](https://github.com/dOpensource/dsiprouter/commit/d2b0eb4eafdef691e71691cf6c1da85ac07b9fd9) > Date: Wed, 3 Jul 2019 11:20:48 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d2b0eb4eafdef691e71691cf6c1da85ac07b9fd9) [//]: # (START_SECTION f6137cc2fc34a4ae334c710c0c237c8f089d7e20) ### Update api.rst > Commit: [f6137cc2fc34a4ae334c710c0c237c8f089d7e20](https://github.com/dOpensource/dsiprouter/commit/f6137cc2fc34a4ae334c710c0c237c8f089d7e20) > Date: Wed, 3 Jul 2019 11:10:39 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f6137cc2fc34a4ae334c710c0c237c8f089d7e20) [//]: # (START_SECTION aea0913b894ef74d3935df3ff3198ccb86dc89e6) ### Update index.rst > Commit: [aea0913b894ef74d3935df3ff3198ccb86dc89e6](https://github.com/dOpensource/dsiprouter/commit/aea0913b894ef74d3935df3ff3198ccb86dc89e6) > Date: Wed, 3 Jul 2019 11:01:13 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aea0913b894ef74d3935df3ff3198ccb86dc89e6) [//]: # (START_SECTION d3d4628ac44663d42115e68c3195cc57cf48d4c7) ### Update and rename API.rst to api.rst > Commit: [d3d4628ac44663d42115e68c3195cc57cf48d4c7](https://github.com/dOpensource/dsiprouter/commit/d3d4628ac44663d42115e68c3195cc57cf48d4c7) > Date: Wed, 3 Jul 2019 10:58:19 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d3d4628ac44663d42115e68c3195cc57cf48d4c7) [//]: # (START_SECTION dcb2af1cdd32868cc3c900defddea926bb85be0f) ### Update Inbound Mapping Endpoint > Commit: [dcb2af1cdd32868cc3c900defddea926bb85be0f](https://github.com/dOpensource/dsiprouter/commit/dcb2af1cdd32868cc3c900defddea926bb85be0f) > Date: Wed, 3 Jul 2019 10:55:01 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - change endpoint path to /api/v1/inboundmapping --- [//]: # (END_SECTION dcb2af1cdd32868cc3c900defddea926bb85be0f) [//]: # (START_SECTION 786ff9b649a0a9d6f836c4eda390165034573d44) ### Update index.rst > Commit: [786ff9b649a0a9d6f836c4eda390165034573d44](https://github.com/dOpensource/dsiprouter/commit/786ff9b649a0a9d6f836c4eda390165034573d44) > Date: Wed, 3 Jul 2019 10:39:17 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 786ff9b649a0a9d6f836c4eda390165034573d44) [//]: # (START_SECTION 403543b8e778f048dbef3238e38ddca4707b38a2) ### Update index.rst > Commit: [403543b8e778f048dbef3238e38ddca4707b38a2](https://github.com/dOpensource/dsiprouter/commit/403543b8e778f048dbef3238e38ddca4707b38a2) > Date: Wed, 3 Jul 2019 10:38:37 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 403543b8e778f048dbef3238e38ddca4707b38a2) [//]: # (START_SECTION 82f0cbe7e117718aaf97860090bc01edfd939a93) ### Create API.rst > Commit: [82f0cbe7e117718aaf97860090bc01edfd939a93](https://github.com/dOpensource/dsiprouter/commit/82f0cbe7e117718aaf97860090bc01edfd939a93) > Date: Wed, 3 Jul 2019 10:26:08 -0400 > Author: Omari S. King (46901954+OmariKing@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 82f0cbe7e117718aaf97860090bc01edfd939a93) [//]: # (START_SECTION cce099319717c5fa6335cedab8512035b0d2e6b1) ### Make Primary PBX Required in GUI > Commit: [cce099319717c5fa6335cedab8512035b0d2e6b1](https://github.com/dOpensource/dsiprouter/commit/cce099319717c5fa6335cedab8512035b0d2e6b1) > Date: Tue, 2 Jul 2019 21:50:58 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #181 - make select field for primary pbx required in front end --- [//]: # (END_SECTION cce099319717c5fa6335cedab8512035b0d2e6b1) [//]: # (START_SECTION f0b772396303c122ef9ebaad2b4c17d34f15f2b2) ### Inbound DID Mapping Through API > Commit: [f0b772396303c122ef9ebaad2b4c17d34f15f2b2](https://github.com/dOpensource/dsiprouter/commit/f0b772396303c122ef9ebaad2b4c17d34f15f2b2) > Date: Tue, 2 Jul 2019 21:02:21 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #77 - create new API DID Mapping endpoint for GET,POST,PUT,DELETE - add tests to API test `19.sh` - fix creation of duplicate inbound DID's - update exception handling to be more useful - update globals - add TODO comments - update rowToDict function - make `error.html` prettier / more palatable --- [//]: # (END_SECTION f0b772396303c122ef9ebaad2b4c17d34f15f2b2) [//]: # (START_SECTION 428b1d2c2324fda6afd6ad43f3d0ff697350ae9f) ### Default to IPv4 > Commit: [428b1d2c2324fda6afd6ad43f3d0ff697350ae9f](https://github.com/dOpensource/dsiprouter/commit/428b1d2c2324fda6afd6ad43f3d0ff697350ae9f) > Date: Mon, 1 Jul 2019 12:32:07 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #183 - set ip resolution to ipv4 by default - note: ipv6 support is not fully supported yet --- [//]: # (END_SECTION 428b1d2c2324fda6afd6ad43f3d0ff697350ae9f) [//]: # (START_SECTION 47cc12a4f06fb26c611be63bf340624cc80e955b) ### Fix Debian v09 mysqlclient Dependency Regression > Commit: [47cc12a4f06fb26c611be63bf340624cc80e955b](https://github.com/dOpensource/dsiprouter/commit/47cc12a4f06fb26c611be63bf340624cc80e955b) > Date: Sun, 30 Jun 2019 19:53:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add case for new package name default-libmysqlclient-dev --- [//]: # (END_SECTION 47cc12a4f06fb26c611be63bf340624cc80e955b) [//]: # (START_SECTION c59f76096c9ade54041cdfc8d4319683ddf1f133) ### Fix Debian v09 mysqlclient Dependency Regression > Commit: [c59f76096c9ade54041cdfc8d4319683ddf1f133](https://github.com/dOpensource/dsiprouter/commit/c59f76096c9ade54041cdfc8d4319683ddf1f133) > Date: Sun, 30 Jun 2019 19:53:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add case for new package name default-libmysqlclient-dev --- [//]: # (END_SECTION c59f76096c9ade54041cdfc8d4319683ddf1f133) [//]: # (START_SECTION 5e4b7f22a106a7562cdfc6328ac6349b6eaa317f) ### testing: Fixed Domain Pass-Thru using FreePBX test - The test will run the test on the externalip that it finds and then will try to run it on the internalip > Commit: [5e4b7f22a106a7562cdfc6328ac6349b6eaa317f](https://github.com/dOpensource/dsiprouter/commit/5e4b7f22a106a7562cdfc6328ac6349b6eaa317f) > Date: Fri, 28 Jun 2019 16:06:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5e4b7f22a106a7562cdfc6328ac6349b6eaa317f) [//]: # (START_SECTION 77d625cf97b5d206a64809153a85ae6052c1797a) ### testing: Fixed Domain Pass-Thru using FreePBX test - The test will run the test on the externalip that it finds and then will try to run it on the internalip > Commit: [77d625cf97b5d206a64809153a85ae6052c1797a](https://github.com/dOpensource/dsiprouter/commit/77d625cf97b5d206a64809153a85ae6052c1797a) > Date: Fri, 28 Jun 2019 16:03:47 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 77d625cf97b5d206a64809153a85ae6052c1797a) [//]: # (START_SECTION d131a82d6ade3c8f05713f7445b9452826d25d72) ### Merge v0.522 commits onto v0.523 > Commit: [d131a82d6ade3c8f05713f7445b9452826d25d72](https://github.com/dOpensource/dsiprouter/commit/d131a82d6ade3c8f05713f7445b9452826d25d72) > Date: Thu, 27 Jun 2019 14:15:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION d131a82d6ade3c8f05713f7445b9452826d25d72) [//]: # (START_SECTION 3971a4df0b2e68e7826c8850edfa74d0678c2120) ### Remove Unused Billing Calls in Kamailio Config > Commit: [3971a4df0b2e68e7826c8850edfa74d0678c2120](https://github.com/dOpensource/dsiprouter/commit/3971a4df0b2e68e7826c8850edfa74d0678c2120) > Date: Thu, 27 Jun 2019 12:35:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #182 - Resolves #88 - commented out call to kamailio_rating in kam cfg --- [//]: # (END_SECTION 3971a4df0b2e68e7826c8850edfa74d0678c2120) [//]: # (START_SECTION 83e79d97322144cadd49e99c4d568455c00e3fd7) ### Allow Excluding Libraries in Requirements git Hook > Commit: [83e79d97322144cadd49e99c4d568455c00e3fd7](https://github.com/dOpensource/dsiprouter/commit/83e79d97322144cadd49e99c4d568455c00e3fd7) > Date: Thu, 27 Jun 2019 11:42:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update pre-commit hook to allow exluding libraries - add docker_py to excluded libraries --- [//]: # (END_SECTION 83e79d97322144cadd49e99c4d568455c00e3fd7) [//]: # (START_SECTION 4b037caa02962d80745655b5e1ef6858ad04ea2b) ### Kam Cluster Stability Improvements > Commit: [4b037caa02962d80745655b5e1ef6858ad04ea2b](https://github.com/dOpensource/dsiprouter/commit/4b037caa02962d80745655b5e1ef6858ad04ea2b) > Date: Thu, 14 Mar 2019 10:48:56 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update resource wait times for pcs --- [//]: # (END_SECTION 4b037caa02962d80745655b5e1ef6858ad04ea2b) [//]: # (START_SECTION 0bd4654bb6b58218dff7e1a437c38f23860c17f1) ### Fix Cloud Stability Issues > Commit: [0bd4654bb6b58218dff7e1a437c38f23860c17f1](https://github.com/dOpensource/dsiprouter/commit/0bd4654bb6b58218dff7e1a437c38f23860c17f1) > Date: Wed, 26 Jun 2019 17:37:31 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix sipsak install fail due to memory allocation errors - fix apt and yum locking errors on install - fix dependency mismatching for mysldb - upadate requirements hook to allow non stdlib deps - add initial support getting GCE, AZURE, and DO images - fix centos false positive build errors - cleanup dsiprouter install debug statements - fix motd banner for centos and debian < v9 - fix debian user password reset on cloud images - added a couple TODO's for install scripts --- [//]: # (END_SECTION 0bd4654bb6b58218dff7e1a437c38f23860c17f1) [//]: # (START_SECTION e0d64dcc93e0fad10bd2f47c62c4b2a39fdee7ad) ### Update requirements.txt > Commit: [e0d64dcc93e0fad10bd2f47c62c4b2a39fdee7ad](https://github.com/dOpensource/dsiprouter/commit/e0d64dcc93e0fad10bd2f47c62c4b2a39fdee7ad) > Date: Thu, 27 Jun 2019 07:57:40 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Putting back the required libraries --- [//]: # (END_SECTION e0d64dcc93e0fad10bd2f47c62c4b2a39fdee7ad) [//]: # (START_SECTION eea03615c43deaad5bdf2989e4dbf4dc6209575c) ### Security Bug Modification > Commit: [eea03615c43deaad5bdf2989e4dbf4dc6209575c](https://github.com/dOpensource/dsiprouter/commit/eea03615c43deaad5bdf2989e4dbf4dc6209575c) > Date: Tue, 25 Jun 2019 18:00:12 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - ammending commit c8756c929ccce9793a1e1717439a257c97f22203 - Resolves #185 - add host check - allow cloud-init to re-add keys --- [//]: # (END_SECTION eea03615c43deaad5bdf2989e4dbf4dc6209575c) [//]: # (START_SECTION f0adeca772448524d7e14aecece343a4a22fb36a) ### kamailio.cfg: The SERVERNAT mode will now cause Kamailio to listen on TCP at 127.0.0.1:5060 > Commit: [f0adeca772448524d7e14aecece343a4a22fb36a](https://github.com/dOpensource/dsiprouter/commit/f0adeca772448524d7e14aecece343a4a22fb36a) > Date: Wed, 26 Jun 2019 18:09:28 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f0adeca772448524d7e14aecece343a4a22fb36a) [//]: # (START_SECTION a15d049d16a32896d73267da550d8f2826518526) ### Update kamailio51_dsiprouter.cfg > Commit: [a15d049d16a32896d73267da550d8f2826518526](https://github.com/dOpensource/dsiprouter/commit/a15d049d16a32896d73267da550d8f2826518526) > Date: Mon, 24 Jun 2019 13:02:29 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Changing the INVITE timer to a more standard timeout of 32 secs --- [//]: # (END_SECTION a15d049d16a32896d73267da550d8f2826518526) [//]: # (START_SECTION 42dbd37b3d0ebdf11991af51142ce4938d4bce99) ### Update kamailio51_dsiprouter.cfg > Commit: [42dbd37b3d0ebdf11991af51142ce4938d4bce99](https://github.com/dOpensource/dsiprouter/commit/42dbd37b3d0ebdf11991af51142ce4938d4bce99) > Date: Mon, 24 Jun 2019 13:02:29 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Changing the INVITE timer to a more standard timeout of 32 secs --- [//]: # (END_SECTION 42dbd37b3d0ebdf11991af51142ce4938d4bce99) [//]: # (START_SECTION c8756c929ccce9793a1e1717439a257c97f22203) ### Cloud Config Security Updates > Commit: [c8756c929ccce9793a1e1717439a257c97f22203](https://github.com/dOpensource/dsiprouter/commit/c8756c929ccce9793a1e1717439a257c97f22203) > Date: Mon, 24 Jun 2019 12:25:40 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #185 - fix cloud-init overwriting sshd_config - add ASLR check - fix grub prompt issue in snapshot configs for ubuntu - switch cloud build script to v0.522 --- [//]: # (END_SECTION c8756c929ccce9793a1e1717439a257c97f22203) [//]: # (START_SECTION edfe59b7a42884380acc1344d17663000f75eff2) ### Rewrote the reload API > Commit: [edfe59b7a42884380acc1344d17663000f75eff2](https://github.com/dOpensource/dsiprouter/commit/edfe59b7a42884380acc1344d17663000f75eff2) > Date: Fri, 31 May 2019 05:30:36 +0000 > Author: root (root@ip-172-31-13-3.us-east-2.compute.internal) > Committer: root (root@ip-172-31-13-3.us-east-2.compute.internal) > Signed: --- [//]: # (END_SECTION edfe59b7a42884380acc1344d17663000f75eff2) [//]: # (START_SECTION 5d739ba1d7f82408c41d858329ff9234f1ccd439) ### Set it back so that it binds to all interfaces. This will cause the dashboard not to work on some OS builds > Commit: [5d739ba1d7f82408c41d858329ff9234f1ccd439](https://github.com/dOpensource/dsiprouter/commit/5d739ba1d7f82408c41d858329ff9234f1ccd439) > Date: Tue, 28 May 2019 08:50:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5d739ba1d7f82408c41d858329ff9234f1ccd439) [//]: # (START_SECTION 6513f4410cb8923286d35492d48458efd5eb1d22) ### -Fixed the provisioning server template to default to 443 -Fixed the fusionpbx sync script to handle 443 properly > Commit: [6513f4410cb8923286d35492d48458efd5eb1d22](https://github.com/dOpensource/dsiprouter/commit/6513f4410cb8923286d35492d48458efd5eb1d22) > Date: Tue, 28 May 2019 08:36:16 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6513f4410cb8923286d35492d48458efd5eb1d22) [//]: # (START_SECTION 7ffc2a2a3d8727cfec8d298e85a81a5c3fb57bcb) ### Use python docker module versus docker_py > Commit: [7ffc2a2a3d8727cfec8d298e85a81a5c3fb57bcb](https://github.com/dOpensource/dsiprouter/commit/7ffc2a2a3d8727cfec8d298e85a81a5c3fb57bcb) > Date: Tue, 28 May 2019 07:07:40 +0000 > Author: root (root@p0.detroitpbx.com) > Committer: root (root@p0.detroitpbx.com) > Signed: --- [//]: # (END_SECTION 7ffc2a2a3d8727cfec8d298e85a81a5c3fb57bcb) [//]: # (START_SECTION 2bdd089bffb74d6c25e5f171748d16ebaea7c200) ### Fixed an error that occurs when there are no FusionPBX sources to sync domain info > Commit: [2bdd089bffb74d6c25e5f171748d16ebaea7c200](https://github.com/dOpensource/dsiprouter/commit/2bdd089bffb74d6c25e5f171748d16ebaea7c200) > Date: Sun, 26 May 2019 17:14:31 +0000 > Author: root (root@demo-dsiprouter-0.localdomain) > Committer: root (root@demo-dsiprouter-0.localdomain) > Signed: --- [//]: # (END_SECTION 2bdd089bffb74d6c25e5f171748d16ebaea7c200) [//]: # (START_SECTION af9dbcda856289029402349ed003efbe0f10e95e) ### Fixed a typo > Commit: [af9dbcda856289029402349ed003efbe0f10e95e](https://github.com/dOpensource/dsiprouter/commit/af9dbcda856289029402349ed003efbe0f10e95e) > Date: Sat, 25 May 2019 10:01:48 +0000 > Author: root (root@demo-dsiprouter-0.localdomain) > Committer: root (root@demo-dsiprouter-0.localdomain) > Signed: --- [//]: # (END_SECTION af9dbcda856289029402349ed003efbe0f10e95e) [//]: # (START_SECTION 6a82177b68c97bf88752b5dfb6ecf85d9abbd100) ### Fixed an issue with listening on udp and tcp ports > Commit: [6a82177b68c97bf88752b5dfb6ecf85d9abbd100](https://github.com/dOpensource/dsiprouter/commit/6a82177b68c97bf88752b5dfb6ecf85d9abbd100) > Date: Sat, 25 May 2019 09:29:37 +0000 > Author: root (root@demo-dsiprouter-0.localdomain) > Committer: root (root@demo-dsiprouter-0.localdomain) > Signed: --- [//]: # (END_SECTION 6a82177b68c97bf88752b5dfb6ecf85d9abbd100) [//]: # (START_SECTION b1dd12b4f0a9d8727d561a1a6b3395ae9c684211) ### Explicitly added a listen attribute for tcp. Fixed issue #170 > Commit: [b1dd12b4f0a9d8727d561a1a6b3395ae9c684211](https://github.com/dOpensource/dsiprouter/commit/b1dd12b4f0a9d8727d561a1a6b3395ae9c684211) > Date: Wed, 22 May 2019 15:54:05 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b1dd12b4f0a9d8727d561a1a6b3395ae9c684211) [//]: # (START_SECTION e04b046b75d3a0a31e7eebbcf30e36f10edb55e2) ### Fixed #164 > Commit: [e04b046b75d3a0a31e7eebbcf30e36f10edb55e2](https://github.com/dOpensource/dsiprouter/commit/e04b046b75d3a0a31e7eebbcf30e36f10edb55e2) > Date: Mon, 20 May 2019 19:58:01 +0000 > Author: root (root@OmariDev-0.localdomain) > Committer: root (root@OmariDev-0.localdomain) > Signed: --- [//]: # (END_SECTION e04b046b75d3a0a31e7eebbcf30e36f10edb55e2) [//]: # (START_SECTION e665ba2b11285ecac9ee987fd1bc4a1ab6b68978) ### Fixed a regression from Primary/Secondary Failover Enhancement > Commit: [e665ba2b11285ecac9ee987fd1bc4a1ab6b68978](https://github.com/dOpensource/dsiprouter/commit/e665ba2b11285ecac9ee987fd1bc4a1ab6b68978) > Date: Fri, 17 May 2019 17:59:45 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e665ba2b11285ecac9ee987fd1bc4a1ab6b68978) [//]: # (START_SECTION b2da46c1581c79c6d176dacf7d17f622d36e7b23) ### Update README.md > Commit: [b2da46c1581c79c6d176dacf7d17f622d36e7b23](https://github.com/dOpensource/dsiprouter/commit/b2da46c1581c79c6d176dacf7d17f622d36e7b23) > Date: Thu, 16 May 2019 13:39:24 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b2da46c1581c79c6d176dacf7d17f622d36e7b23) [//]: # (START_SECTION 4c8d56dffb72abe22830054d8936f750578f91b2) ### #163 fixed > Commit: [4c8d56dffb72abe22830054d8936f750578f91b2](https://github.com/dOpensource/dsiprouter/commit/4c8d56dffb72abe22830054d8936f750578f91b2) > Date: Wed, 15 May 2019 20:16:27 +0000 > Author: root (root@OmariDev-0.localdomain) > Committer: root (root@OmariDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 4c8d56dffb72abe22830054d8936f750578f91b2) [//]: # (START_SECTION 702cf8a46262a9eb2e4848faaa34697cfa83bd52) ### - The admin password is now being set properly on Debian - non AWS - Fixed the spacing when displaying the password info > Commit: [702cf8a46262a9eb2e4848faaa34697cfa83bd52](https://github.com/dOpensource/dsiprouter/commit/702cf8a46262a9eb2e4848faaa34697cfa83bd52) > Date: Wed, 15 May 2019 07:57:23 +0000 > Author: root (root@dSIPRouterJenkins-0.localdomain) > Committer: root (root@dSIPRouterJenkins-0.localdomain) > Signed: --- [//]: # (END_SECTION 702cf8a46262a9eb2e4848faaa34697cfa83bd52) [//]: # (START_SECTION 8ce7b4dab224dd31a175f1284cae93b91204eab0) ### fixed #160 > Commit: [8ce7b4dab224dd31a175f1284cae93b91204eab0](https://github.com/dOpensource/dsiprouter/commit/8ce7b4dab224dd31a175f1284cae93b91204eab0) > Date: Tue, 14 May 2019 21:24:08 +0000 > Author: root (root@OmariDev-0.localdomain) > Committer: root (root@OmariDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 8ce7b4dab224dd31a175f1284cae93b91204eab0) [//]: # (START_SECTION 743d40d88ffc853de5daef780fcb8297222a7d54) ### Added the ability to clean up expired leases to our system wide cron script > Commit: [743d40d88ffc853de5daef780fcb8297222a7d54](https://github.com/dOpensource/dsiprouter/commit/743d40d88ffc853de5daef780fcb8297222a7d54) > Date: Tue, 14 May 2019 11:23:07 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 743d40d88ffc853de5daef780fcb8297222a7d54) [//]: # (START_SECTION ae30e7972b8a261a7ce0d4f1a9d908e5547bf70d) ### Startup and General Fixes > Commit: [ae30e7972b8a261a7ce0d4f1a9d908e5547bf70d](https://github.com/dOpensource/dsiprouter/commit/ae30e7972b8a261a7ce0d4f1a9d908e5547bf70d) > Date: Tue, 14 May 2019 07:16:21 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix dsiprouter startup issue on centos and amazon linux - fix ssh server security holes - add git contributing pre-commit hook - update cloud instance checks - fix ubuntu unattended upgrade dialog issue - add custom MOTD banner - revert debug settings to production as default - improve ipv4 and ipv6 checks --- [//]: # (END_SECTION ae30e7972b8a261a7ce0d4f1a9d908e5547bf70d) [//]: # (START_SECTION 2cfc720178153e19c8d537a5aa8ea2cdab0975f9) ### Fixed LCR bug, Added a configuration parameter that allows the PBX INVITE timer to be changed globally during runtime > Commit: [2cfc720178153e19c8d537a5aa8ea2cdab0975f9](https://github.com/dOpensource/dsiprouter/commit/2cfc720178153e19c8d537a5aa8ea2cdab0975f9) > Date: Fri, 10 May 2019 21:57:14 +0000 > Author: root (root@dSIPRouterMackTest-0.localdomain) > Committer: root (root@dSIPRouterMackTest-0.localdomain) > Signed: --- [//]: # (END_SECTION 2cfc720178153e19c8d537a5aa8ea2cdab0975f9) [//]: # (START_SECTION 1d43d2ec96bfefb7132cb6b236dcf8ca06f6a09c) ### Updated the .gitignore file > Commit: [1d43d2ec96bfefb7132cb6b236dcf8ca06f6a09c](https://github.com/dOpensource/dsiprouter/commit/1d43d2ec96bfefb7132cb6b236dcf8ca06f6a09c) > Date: Fri, 10 May 2019 21:54:48 +0000 > Author: root (root@dSIPRouterMackTest-0.localdomain) > Committer: root (root@dSIPRouterMackTest-0.localdomain) > Signed: --- [//]: # (END_SECTION 1d43d2ec96bfefb7132cb6b236dcf8ca06f6a09c) [//]: # (START_SECTION 07d8e0f85b48c54ca8da920ec3153fcf02ff7a17) ### Removed old files > Commit: [07d8e0f85b48c54ca8da920ec3153fcf02ff7a17](https://github.com/dOpensource/dsiprouter/commit/07d8e0f85b48c54ca8da920ec3153fcf02ff7a17) > Date: Fri, 10 May 2019 21:50:09 +0000 > Author: root (root@dSIPRouterMackTest-0.localdomain) > Committer: root (root@dSIPRouterMackTest-0.localdomain) > Signed: --- [//]: # (END_SECTION 07d8e0f85b48c54ca8da920ec3153fcf02ff7a17) [//]: # (START_SECTION 740e37bc645b340a2093c85114b1dcf00c7d5057) ### Edits to carriergroups.js > Commit: [740e37bc645b340a2093c85114b1dcf00c7d5057](https://github.com/dOpensource/dsiprouter/commit/740e37bc645b340a2093c85114b1dcf00c7d5057) > Date: Fri, 10 May 2019 17:08:55 +0000 > Author: root (root@OmariDev-0.localdomain) > Committer: root (root@OmariDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 740e37bc645b340a2093c85114b1dcf00c7d5057) [//]: # (START_SECTION 7afa13f6302b3793a7ad04bb492f031c84d80f55) ### Added initial support for a Kamailio Reload API > Commit: [7afa13f6302b3793a7ad04bb492f031c84d80f55](https://github.com/dOpensource/dsiprouter/commit/7afa13f6302b3793a7ad04bb492f031c84d80f55) > Date: Thu, 9 May 2019 02:17:14 +0000 > Author: root (root@dSIPRouterMackTest-0.localdomain) > Committer: root (root@dSIPRouterMackTest-0.localdomain) > Signed: --- [//]: # (END_SECTION 7afa13f6302b3793a7ad04bb492f031c84d80f55) [//]: # (START_SECTION ad06c1cfaa2de146df9dab483f00fb43a2295f6b) ### Enabled the reload button when enabling and disabling an endpoint for maintenance > Commit: [ad06c1cfaa2de146df9dab483f00fb43a2295f6b](https://github.com/dOpensource/dsiprouter/commit/ad06c1cfaa2de146df9dab483f00fb43a2295f6b) > Date: Wed, 8 May 2019 10:02:25 +0000 > Author: root (root@dSIPRouterMackTest-0.localdomain) > Committer: root (root@dSIPRouterMackTest-0.localdomain) > Signed: --- [//]: # (END_SECTION ad06c1cfaa2de146df9dab483f00fb43a2295f6b) [//]: # (START_SECTION e7a573fa5ae649a379a6385557a42a7112ef095e) ### Added dsiprouter.sh to /usr/local/bin via a symbolic link > Commit: [e7a573fa5ae649a379a6385557a42a7112ef095e](https://github.com/dOpensource/dsiprouter/commit/e7a573fa5ae649a379a6385557a42a7112ef095e) > Date: Wed, 8 May 2019 08:37:26 +0000 > Author: root (root@dSIPRouterJenkins-0.localdomain) > Committer: root (root@dSIPRouterJenkins-0.localdomain) > Signed: --- [//]: # (END_SECTION e7a573fa5ae649a379a6385557a42a7112ef095e) [//]: # (START_SECTION c8f1c13f26238b9ce8ff5fe9b161210ba70ed48e) ### Fixed an issue with creating new carriergroups > Commit: [c8f1c13f26238b9ce8ff5fe9b161210ba70ed48e](https://github.com/dOpensource/dsiprouter/commit/c8f1c13f26238b9ce8ff5fe9b161210ba70ed48e) > Date: Wed, 8 May 2019 07:50:00 +0000 > Author: root (root@dSIPRouterJenkins-0.localdomain) > Committer: root (root@dSIPRouterJenkins-0.localdomain) > Signed: --- [//]: # (END_SECTION c8f1c13f26238b9ce8ff5fe9b161210ba70ed48e) [//]: # (START_SECTION 7e9de543000ce6b6633b33eec1d82d6854f3dee7) ### Added final logic to support gui and backend support for PBX failover and Endpoint Maintence > Commit: [7e9de543000ce6b6633b33eec1d82d6854f3dee7](https://github.com/dOpensource/dsiprouter/commit/7e9de543000ce6b6633b33eec1d82d6854f3dee7) > Date: Tue, 7 May 2019 02:17:34 +0000 > Author: root (root@dSIPRouterMack0522-0.localdomain) > Committer: root (root@dSIPRouterMack0522-0.localdomain) > Signed: --- [//]: # (END_SECTION 7e9de543000ce6b6633b33eec1d82d6854f3dee7) [//]: # (START_SECTION c3b788bdc8b2f59208a0c1f9f74cedd3513a75b0) ### Update domains.rst > Commit: [c3b788bdc8b2f59208a0c1f9f74cedd3513a75b0](https://github.com/dOpensource/dsiprouter/commit/c3b788bdc8b2f59208a0c1f9f74cedd3513a75b0) > Date: Mon, 6 May 2019 13:30:04 -0400 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c3b788bdc8b2f59208a0c1f9f74cedd3513a75b0) [//]: # (START_SECTION b666e3fa075ea5245a6aa43d9cbfe1007097c340) ### Add files via upload > Commit: [b666e3fa075ea5245a6aa43d9cbfe1007097c340](https://github.com/dOpensource/dsiprouter/commit/b666e3fa075ea5245a6aa43d9cbfe1007097c340) > Date: Mon, 6 May 2019 13:16:21 -0400 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b666e3fa075ea5245a6aa43d9cbfe1007097c340) [//]: # (START_SECTION 6afb800cba67485ac08beb712307b45ea95b64c7) ### Added logic to update the endpoint api, added logic to display an indicator when a pbx is in maintenance mode > Commit: [6afb800cba67485ac08beb712307b45ea95b64c7](https://github.com/dOpensource/dsiprouter/commit/6afb800cba67485ac08beb712307b45ea95b64c7) > Date: Mon, 6 May 2019 16:22:22 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6afb800cba67485ac08beb712307b45ea95b64c7) [//]: # (START_SECTION 2c8dd08c247cb459e0803564e21a8dc5841ed9d9) ### Added API for updating an endpoint and ability to put an endpoint in maintenance mode > Commit: [2c8dd08c247cb459e0803564e21a8dc5841ed9d9](https://github.com/dOpensource/dsiprouter/commit/2c8dd08c247cb459e0803564e21a8dc5841ed9d9) > Date: Fri, 3 May 2019 18:54:00 +0000 > Author: root (root@dSIPRouterMack0522-0.localdomain) > Committer: root (root@dSIPRouterMack0522-0.localdomain) > Signed: --- [//]: # (END_SECTION 2c8dd08c247cb459e0803564e21a8dc5841ed9d9) [//]: # (START_SECTION 257d9be36a877688a002ee68473ba0fdcf47ea12) ### Added the ability to REVOKE a lease > Commit: [257d9be36a877688a002ee68473ba0fdcf47ea12](https://github.com/dOpensource/dsiprouter/commit/257d9be36a877688a002ee68473ba0fdcf47ea12) > Date: Tue, 30 Apr 2019 08:24:14 +0000 > Author: root (root@dSIPRouterMack0522-0.localdomain) > Committer: root (root@dSIPRouterMack0522-0.localdomain) > Signed: --- [//]: # (END_SECTION 257d9be36a877688a002ee68473ba0fdcf47ea12) [//]: # (START_SECTION 1a6cddb03a08c61e26b2dd32b9773e020b8f589a) ### Turned off debugging > Commit: [1a6cddb03a08c61e26b2dd32b9773e020b8f589a](https://github.com/dOpensource/dsiprouter/commit/1a6cddb03a08c61e26b2dd32b9773e020b8f589a) > Date: Tue, 30 Apr 2019 06:29:05 +0000 > Author: root (root@dSIPRouterMack0522-0.localdomain) > Committer: root (root@dSIPRouterMack0522-0.localdomain) > Signed: --- [//]: # (END_SECTION 1a6cddb03a08c61e26b2dd32b9773e020b8f589a) [//]: # (START_SECTION 09313aa08dc8cccc7f27e03d5f6e6661d5e2b73d) ### Initial commit of the install script for the API module > Commit: [09313aa08dc8cccc7f27e03d5f6e6661d5e2b73d](https://github.com/dOpensource/dsiprouter/commit/09313aa08dc8cccc7f27e03d5f6e6661d5e2b73d) > Date: Mon, 29 Apr 2019 21:24:59 +0000 > Author: root (root@dSIPRouterMack0.522-0) > Committer: root (root@dSIPRouterMack0.522-0) > Signed: --- [//]: # (END_SECTION 09313aa08dc8cccc7f27e03d5f6e6661d5e2b73d) [//]: # (START_SECTION 5697532b99955d48dd8bdc38db85b86616bcd781) ### Removed dashboard.js from root directory > Commit: [5697532b99955d48dd8bdc38db85b86616bcd781](https://github.com/dOpensource/dsiprouter/commit/5697532b99955d48dd8bdc38db85b86616bcd781) > Date: Mon, 29 Apr 2019 21:07:31 +0000 > Author: root (root@dSIPRouterMack0.522-0) > Committer: root (root@dSIPRouterMack0.522-0) > Signed: --- [//]: # (END_SECTION 5697532b99955d48dd8bdc38db85b86616bcd781) [//]: # (START_SECTION c2d3945ca539d59fb3533774c99718ed8529c121) ### Inbound DID's will try the Primary and then the Secondary PBX. The user will receive a 502 Service not available if both fail > Commit: [c2d3945ca539d59fb3533774c99718ed8529c121](https://github.com/dOpensource/dsiprouter/commit/c2d3945ca539d59fb3533774c99718ed8529c121) > Date: Mon, 29 Apr 2019 21:04:03 +0000 > Author: root (root@dSIPRouterMack0.522-0) > Committer: root (root@dSIPRouterMack0.522-0) > Signed: --- [//]: # (END_SECTION c2d3945ca539d59fb3533774c99718ed8529c121) [//]: # (START_SECTION a3a3fc5a7e4b658806e0bba562e6b703cf0f25f7) ### Initial commit of the Endpoint API > Commit: [a3a3fc5a7e4b658806e0bba562e6b703cf0f25f7](https://github.com/dOpensource/dsiprouter/commit/a3a3fc5a7e4b658806e0bba562e6b703cf0f25f7) > Date: Mon, 29 Apr 2019 20:42:45 +0000 > Author: root (root@dSIPRouterMack0.522-0) > Committer: root (root@dSIPRouterMack0.522-0) > Signed: --- [//]: # (END_SECTION a3a3fc5a7e4b658806e0bba562e6b703cf0f25f7) [//]: # (START_SECTION 6880c7142972aabcdb864fc48a786478ffb8be49) ### Added a security model for our API framework > Commit: [6880c7142972aabcdb864fc48a786478ffb8be49](https://github.com/dOpensource/dsiprouter/commit/6880c7142972aabcdb864fc48a786478ffb8be49) > Date: Sun, 28 Apr 2019 03:53:21 +0000 > Author: root (root@dSIPRouterMack0522-0.localdomain) > Committer: root (root@dSIPRouterMack0522-0.localdomain) > Signed: --- [//]: # (END_SECTION 6880c7142972aabcdb864fc48a786478ffb8be49) [//]: # (START_SECTION 1fb03173971da31cdc9a60cf8f8823cfe0eb6a4d) ### Removed AMI changes that were made - going back to the orig > Commit: [1fb03173971da31cdc9a60cf8f8823cfe0eb6a4d](https://github.com/dOpensource/dsiprouter/commit/1fb03173971da31cdc9a60cf8f8823cfe0eb6a4d) > Date: Mon, 22 Apr 2019 00:04:46 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1fb03173971da31cdc9a60cf8f8823cfe0eb6a4d) [//]: # (START_SECTION 46d70a337e287f93b05db5d9bf57c08eaa41e535) ### Temporarily Removing AMI Checks to get Jenkins working > Commit: [46d70a337e287f93b05db5d9bf57c08eaa41e535](https://github.com/dOpensource/dsiprouter/commit/46d70a337e287f93b05db5d9bf57c08eaa41e535) > Date: Sun, 21 Apr 2019 23:59:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 46d70a337e287f93b05db5d9bf57c08eaa41e535) [//]: # (START_SECTION b9ce7647ba8f7e4d49f8824ca24b7914c1ef4404) ### Adding parameter to curl command for AMI check > Commit: [b9ce7647ba8f7e4d49f8824ca24b7914c1ef4404](https://github.com/dOpensource/dsiprouter/commit/b9ce7647ba8f7e4d49f8824ca24b7914c1ef4404) > Date: Sun, 21 Apr 2019 23:49:26 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b9ce7647ba8f7e4d49f8824ca24b7914c1ef4404) [//]: # (START_SECTION 3e78e39922364d8022190c8e188e07744e35b8ff) ### Update README.md > Commit: [3e78e39922364d8022190c8e188e07744e35b8ff](https://github.com/dOpensource/dsiprouter/commit/3e78e39922364d8022190c8e188e07744e35b8ff) > Date: Sun, 21 Apr 2019 23:00:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3e78e39922364d8022190c8e188e07744e35b8ff) [//]: # (START_SECTION 00900d28e831b38c5dc78aa3d66b2a4938d0d3a8) ### Update settings.py > Commit: [00900d28e831b38c5dc78aa3d66b2a4938d0d3a8](https://github.com/dOpensource/dsiprouter/commit/00900d28e831b38c5dc78aa3d66b2a4938d0d3a8) > Date: Wed, 17 Apr 2019 07:19:41 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 00900d28e831b38c5dc78aa3d66b2a4938d0d3a8) [//]: # (START_SECTION 32d04837b45a8d15a6d0ef3fba608383f4a67197) ### Fixed #130 - l_username field of the uac_reg table will be populated with the gateway group id. This will get rid of the error messages in the Kamailio log > Commit: [32d04837b45a8d15a6d0ef3fba608383f4a67197](https://github.com/dOpensource/dsiprouter/commit/32d04837b45a8d15a6d0ef3fba608383f4a67197) > Date: Tue, 16 Apr 2019 11:18:50 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 32d04837b45a8d15a6d0ef3fba608383f4a67197) [//]: # (START_SECTION e562ab294d1654152a4b8bad1e64590212d6e26d) ### Added the default value for l_username so that the system has it during bootup > Commit: [e562ab294d1654152a4b8bad1e64590212d6e26d](https://github.com/dOpensource/dsiprouter/commit/e562ab294d1654152a4b8bad1e64590212d6e26d) > Date: Tue, 16 Apr 2019 11:08:55 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION e562ab294d1654152a4b8bad1e64590212d6e26d) [//]: # (START_SECTION 9997109f3b7c7d397b5af4203765da6d582f9e83) ### Added unit test to test if known carrier ip's are being blocked by the PIKE module Fixed #148 > Commit: [9997109f3b7c7d397b5af4203765da6d582f9e83](https://github.com/dOpensource/dsiprouter/commit/9997109f3b7c7d397b5af4203765da6d582f9e83) > Date: Mon, 15 Apr 2019 19:19:47 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 9997109f3b7c7d397b5af4203765da6d582f9e83) [//]: # (START_SECTION 2eebe2e8e8fa3dd9924d95bcf2725f4f58146337) ### Working unit test for Domain Pass-Thru using FreePBX > Commit: [2eebe2e8e8fa3dd9924d95bcf2725f4f58146337](https://github.com/dOpensource/dsiprouter/commit/2eebe2e8e8fa3dd9924d95bcf2725f4f58146337) > Date: Mon, 15 Apr 2019 10:49:32 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 2eebe2e8e8fa3dd9924d95bcf2725f4f58146337) [//]: # (START_SECTION 156950f559f1c3cfc05cf1b69396fd39fc4343bc) ### Update README.md > Commit: [156950f559f1c3cfc05cf1b69396fd39fc4343bc](https://github.com/dOpensource/dsiprouter/commit/156950f559f1c3cfc05cf1b69396fd39fc4343bc) > Date: Sun, 14 Apr 2019 18:57:49 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 156950f559f1c3cfc05cf1b69396fd39fc4343bc) [//]: # (START_SECTION b1bd959c9dd83b36c94f1fe2ae7ddf038e58f8ca) ### Added basic exception handling > Commit: [b1bd959c9dd83b36c94f1fe2ae7ddf038e58f8ca](https://github.com/dOpensource/dsiprouter/commit/b1bd959c9dd83b36c94f1fe2ae7ddf038e58f8ca) > Date: Sun, 14 Apr 2019 19:07:11 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION b1bd959c9dd83b36c94f1fe2ae7ddf038e58f8ca) [//]: # (START_SECTION fae5015141acbff318d770f2dfa73f031c5fcbf8) ### Removed the old docker-py python library > Commit: [fae5015141acbff318d770f2dfa73f031c5fcbf8](https://github.com/dOpensource/dsiprouter/commit/fae5015141acbff318d770f2dfa73f031c5fcbf8) > Date: Sun, 14 Apr 2019 18:46:04 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION fae5015141acbff318d770f2dfa73f031c5fcbf8) [//]: # (START_SECTION 2d32a49fe117828144eb2f0503429b7c411816eb) ### Fixed #144 and fixed a regression with the FusionPBX Enable/Disable button in the PBX section > Commit: [2d32a49fe117828144eb2f0503429b7c411816eb](https://github.com/dOpensource/dsiprouter/commit/2d32a49fe117828144eb2f0503429b7c411816eb) > Date: Sun, 14 Apr 2019 18:16:36 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 2d32a49fe117828144eb2f0503429b7c411816eb) [//]: # (START_SECTION 2b9d82e43ce6c4d1bc50d1a7f90b6657c2549633) ### Removed the old docker-py library and added the docker > Commit: [2b9d82e43ce6c4d1bc50d1a7f90b6657c2549633](https://github.com/dOpensource/dsiprouter/commit/2b9d82e43ce6c4d1bc50d1a7f90b6657c2549633) > Date: Sun, 14 Apr 2019 03:41:11 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 2b9d82e43ce6c4d1bc50d1a7f90b6657c2549633) [//]: # (START_SECTION 61a20614269616a9a3f212eee62c988a6e98d531) ### Initial commit of unit test 17, which will be used for testing Domain Pass-Thru > Commit: [61a20614269616a9a3f212eee62c988a6e98d531](https://github.com/dOpensource/dsiprouter/commit/61a20614269616a9a3f212eee62c988a6e98d531) > Date: Sat, 13 Apr 2019 23:49:13 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 61a20614269616a9a3f212eee62c988a6e98d531) [//]: # (START_SECTION 75a875d51968f5c1e13ad65ab41e67da6e979c72) ### Fixed the Reload Kamailio button so that it reload the Domain module when pressed > Commit: [75a875d51968f5c1e13ad65ab41e67da6e979c72](https://github.com/dOpensource/dsiprouter/commit/75a875d51968f5c1e13ad65ab41e67da6e979c72) > Date: Sat, 13 Apr 2019 21:58:02 +0000 > Author: root (root@dSIPRouterMack-0.localdomain) > Committer: root (root@dSIPRouterMack-0.localdomain) > Signed: --- [//]: # (END_SECTION 75a875d51968f5c1e13ad65ab41e67da6e979c72) [//]: # (START_SECTION 8bb17331350a9111b5aca33e0869177d47562ed2) ### Fixed #145, Fixed #139, Fixed #142 - The Domain functionality has been fixed. Adding, Removing and Deleting DDomains has been fixed. Also, the parameter needed to route traffic when using pass-thru authentication has been fixed > Commit: [8bb17331350a9111b5aca33e0869177d47562ed2](https://github.com/dOpensource/dsiprouter/commit/8bb17331350a9111b5aca33e0869177d47562ed2) > Date: Sat, 13 Apr 2019 17:24:37 +0000 > Author: root (root@dSIPRouterNicole2.localdomain) > Committer: root (root@dSIPRouterNicole2.localdomain) > Signed: --- [//]: # (END_SECTION 8bb17331350a9111b5aca33e0869177d47562ed2) [//]: # (START_SECTION 87811202be34c5202ee3525cb5c2fda1c06f9d28) ### Added sngrep back to Debian 8 and 9 installs. Fixed #147 > Commit: [87811202be34c5202ee3525cb5c2fda1c06f9d28](https://github.com/dOpensource/dsiprouter/commit/87811202be34c5202ee3525cb5c2fda1c06f9d28) > Date: Thu, 11 Apr 2019 14:44:47 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 87811202be34c5202ee3525cb5c2fda1c06f9d28) [//]: # (START_SECTION daa6bbd9c0543d2687d9d5166f1a8b0b1c29ecc8) ### Fixed the AWS test > Commit: [daa6bbd9c0543d2687d9d5166f1a8b0b1c29ecc8](https://github.com/dOpensource/dsiprouter/commit/daa6bbd9c0543d2687d9d5166f1a8b0b1c29ecc8) > Date: Thu, 11 Apr 2019 09:59:33 +0000 > Author: root (root@ip-172-31-18-84.ec2.internal) > Committer: root (root@ip-172-31-18-84.ec2.internal) > Signed: --- [//]: # (END_SECTION daa6bbd9c0543d2687d9d5166f1a8b0b1c29ecc8) [//]: # (START_SECTION 4456d9b0414e7458bfa977830d14b32a312de50d) ### Removed the test exit command from the install script > Commit: [4456d9b0414e7458bfa977830d14b32a312de50d](https://github.com/dOpensource/dsiprouter/commit/4456d9b0414e7458bfa977830d14b32a312de50d) > Date: Thu, 11 Apr 2019 09:50:05 +0000 > Author: root (root@ip-172-31-18-84.ec2.internal) > Committer: root (root@ip-172-31-18-84.ec2.internal) > Signed: --- [//]: # (END_SECTION 4456d9b0414e7458bfa977830d14b32a312de50d) [//]: # (START_SECTION c2cab69eadc89cacaef04837c068830abf219cd1) ### Fixed the function in the testing harness that's responsible for validating if an instance is an EC2 instance on Amazon > Commit: [c2cab69eadc89cacaef04837c068830abf219cd1](https://github.com/dOpensource/dsiprouter/commit/c2cab69eadc89cacaef04837c068830abf219cd1) > Date: Thu, 11 Apr 2019 03:54:00 +0000 > Author: Mack Hendricks (mack@dopensource.comm) > Committer: Mack Hendricks (mack@dopensource.comm) > Signed: --- [//]: # (END_SECTION c2cab69eadc89cacaef04837c068830abf219cd1) [//]: # (START_SECTION 7ac6f555eb29477b3cb60d39f576ecceaf35859d) ### Fixed the URL endpont used to validate if the instance is an EC2 instancing running on Amazon > Commit: [7ac6f555eb29477b3cb60d39f576ecceaf35859d](https://github.com/dOpensource/dsiprouter/commit/7ac6f555eb29477b3cb60d39f576ecceaf35859d) > Date: Thu, 11 Apr 2019 03:52:47 +0000 > Author: Mack Hendricks (mack@dopensource.comm) > Committer: Mack Hendricks (mack@dopensource.comm) > Signed: --- [//]: # (END_SECTION 7ac6f555eb29477b3cb60d39f576ecceaf35859d) [//]: # (START_SECTION 42a866d4c75503843b0a7471d1f3ec6c4065b1d8) ### Update README.md > Commit: [42a866d4c75503843b0a7471d1f3ec6c4065b1d8](https://github.com/dOpensource/dsiprouter/commit/42a866d4c75503843b0a7471d1f3ec6c4065b1d8) > Date: Mon, 1 Apr 2019 07:11:11 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 42a866d4c75503843b0a7471d1f3ec6c4065b1d8) [//]: # (START_SECTION ff11825a7e043d30879b81b6e9436ad248e068ef) ### Update README.md > Commit: [ff11825a7e043d30879b81b6e9436ad248e068ef](https://github.com/dOpensource/dsiprouter/commit/ff11825a7e043d30879b81b6e9436ad248e068ef) > Date: Mon, 1 Apr 2019 07:10:32 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ff11825a7e043d30879b81b6e9436ad248e068ef) [//]: # (START_SECTION 36db3ecf92117d20a83581c018e83c09b89ef3a7) ### Update README.md > Commit: [36db3ecf92117d20a83581c018e83c09b89ef3a7](https://github.com/dOpensource/dsiprouter/commit/36db3ecf92117d20a83581c018e83c09b89ef3a7) > Date: Mon, 1 Apr 2019 07:10:04 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 36db3ecf92117d20a83581c018e83c09b89ef3a7) [//]: # (START_SECTION 3b15194e64db505f314e1e6f980eca588fb1aafd) ### Update installing.rst > Commit: [3b15194e64db505f314e1e6f980eca588fb1aafd](https://github.com/dOpensource/dsiprouter/commit/3b15194e64db505f314e1e6f980eca588fb1aafd) > Date: Mon, 1 Apr 2019 06:47:40 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 3b15194e64db505f314e1e6f980eca588fb1aafd) [//]: # (START_SECTION 6f6d1ea3a0b3bb1089e59b710357e83abd331287) ### Update command_line_options.rst > Commit: [6f6d1ea3a0b3bb1089e59b710357e83abd331287](https://github.com/dOpensource/dsiprouter/commit/6f6d1ea3a0b3bb1089e59b710357e83abd331287) > Date: Mon, 1 Apr 2019 06:45:55 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 6f6d1ea3a0b3bb1089e59b710357e83abd331287) [//]: # (START_SECTION b534497026ee08ca01f7a3d15c4fde24376cdb4a) ### Update installing.rst > Commit: [b534497026ee08ca01f7a3d15c4fde24376cdb4a](https://github.com/dOpensource/dsiprouter/commit/b534497026ee08ca01f7a3d15c4fde24376cdb4a) > Date: Mon, 1 Apr 2019 06:45:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION b534497026ee08ca01f7a3d15c4fde24376cdb4a) [//]: # (START_SECTION 2e8a0fd7cdea999ae4e90f9f1db1bf36aad95e00) ### Update command_line_options.rst > Commit: [2e8a0fd7cdea999ae4e90f9f1db1bf36aad95e00](https://github.com/dOpensource/dsiprouter/commit/2e8a0fd7cdea999ae4e90f9f1db1bf36aad95e00) > Date: Mon, 1 Apr 2019 06:42:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 2e8a0fd7cdea999ae4e90f9f1db1bf36aad95e00) [//]: # (START_SECTION 00fc1334ea6dcb933d56e9231a98f97b6976afa4) ### Update command_line_options.rst > Commit: [00fc1334ea6dcb933d56e9231a98f97b6976afa4](https://github.com/dOpensource/dsiprouter/commit/00fc1334ea6dcb933d56e9231a98f97b6976afa4) > Date: Mon, 1 Apr 2019 06:40:42 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 00fc1334ea6dcb933d56e9231a98f97b6976afa4) [//]: # (START_SECTION f8d704d99cce7f00696f0aa9ca6272fb7f04c005) ### Update command_line_options.rst > Commit: [f8d704d99cce7f00696f0aa9ca6272fb7f04c005](https://github.com/dOpensource/dsiprouter/commit/f8d704d99cce7f00696f0aa9ca6272fb7f04c005) > Date: Mon, 1 Apr 2019 06:38:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION f8d704d99cce7f00696f0aa9ca6272fb7f04c005) [//]: # (START_SECTION c469f0ba9886092a80dcb7b317a0fceecd9ce82d) ### Update command_line_options.rst > Commit: [c469f0ba9886092a80dcb7b317a0fceecd9ce82d](https://github.com/dOpensource/dsiprouter/commit/c469f0ba9886092a80dcb7b317a0fceecd9ce82d) > Date: Mon, 1 Apr 2019 06:35:20 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION c469f0ba9886092a80dcb7b317a0fceecd9ce82d) [//]: # (START_SECTION 0c20533d57f9a7b501c48a7e7d91b046a4cf1454) ### Update command_line_options.rst > Commit: [0c20533d57f9a7b501c48a7e7d91b046a4cf1454](https://github.com/dOpensource/dsiprouter/commit/0c20533d57f9a7b501c48a7e7d91b046a4cf1454) > Date: Mon, 1 Apr 2019 06:25:09 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 0c20533d57f9a7b501c48a7e7d91b046a4cf1454) [//]: # (START_SECTION 107f39e5db77df3f782b1a45d233e4f2b028dc2c) ### Update installing.rst > Commit: [107f39e5db77df3f782b1a45d233e4f2b028dc2c](https://github.com/dOpensource/dsiprouter/commit/107f39e5db77df3f782b1a45d233e4f2b028dc2c) > Date: Mon, 1 Apr 2019 05:55:36 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 107f39e5db77df3f782b1a45d233e4f2b028dc2c) [//]: # (START_SECTION 3bc03c149ff90926257ff722f82ef9bdcdd16d20) ### Update installing.rst > Commit: [3bc03c149ff90926257ff722f82ef9bdcdd16d20](https://github.com/dOpensource/dsiprouter/commit/3bc03c149ff90926257ff722f82ef9bdcdd16d20) > Date: Mon, 1 Apr 2019 05:52:35 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION 3bc03c149ff90926257ff722f82ef9bdcdd16d20) [//]: # (START_SECTION a1dd9044d6dc834354326a1cb23d30e018f7921e) ### Update installing.rst > Commit: [a1dd9044d6dc834354326a1cb23d30e018f7921e](https://github.com/dOpensource/dsiprouter/commit/a1dd9044d6dc834354326a1cb23d30e018f7921e) > Date: Mon, 1 Apr 2019 05:49:43 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION a1dd9044d6dc834354326a1cb23d30e018f7921e) [//]: # (START_SECTION a1f1377d8f50f44d6635b3eec779da2aa53ee279) ### Update centos-install.rst > Commit: [a1f1377d8f50f44d6635b3eec779da2aa53ee279](https://github.com/dOpensource/dsiprouter/commit/a1f1377d8f50f44d6635b3eec779da2aa53ee279) > Date: Mon, 1 Apr 2019 05:31:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION a1f1377d8f50f44d6635b3eec779da2aa53ee279) [//]: # (START_SECTION b8916a7cb5330c098b9d207305bd75656c98bc95) ### Update debian_install.rst > Commit: [b8916a7cb5330c098b9d207305bd75656c98bc95](https://github.com/dOpensource/dsiprouter/commit/b8916a7cb5330c098b9d207305bd75656c98bc95) > Date: Sun, 31 Mar 2019 10:55:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: --- [//]: # (END_SECTION b8916a7cb5330c098b9d207305bd75656c98bc95) [//]: # (START_SECTION c190908d01f3d219cc55add4d95d5416eaa7b332) ### Updated Debian Install Docs > Commit: [c190908d01f3d219cc55add4d95d5416eaa7b332](https://github.com/dOpensource/dsiprouter/commit/c190908d01f3d219cc55add4d95d5416eaa7b332) > Date: Sun, 31 Mar 2019 10:54:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@dSIPRouterMackMaster-0.localdomain) > Signed: - Modified the docs to reflect the new install options --- [//]: # (END_SECTION c190908d01f3d219cc55add4d95d5416eaa7b332) [//]: # (START_SECTION c394864619651a0fd186f0d12d520a25a669e3bb) ### Update installing.rst > Commit: [c394864619651a0fd186f0d12d520a25a669e3bb](https://github.com/dOpensource/dsiprouter/commit/c394864619651a0fd186f0d12d520a25a669e3bb) > Date: Mon, 1 Apr 2019 06:47:40 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c394864619651a0fd186f0d12d520a25a669e3bb) [//]: # (START_SECTION a35858ff9118b1e851b49132eea9581d532537da) ### Update command_line_options.rst > Commit: [a35858ff9118b1e851b49132eea9581d532537da](https://github.com/dOpensource/dsiprouter/commit/a35858ff9118b1e851b49132eea9581d532537da) > Date: Mon, 1 Apr 2019 06:45:55 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a35858ff9118b1e851b49132eea9581d532537da) [//]: # (START_SECTION 8ebf5fba21f99bf904f38569e3bda3306ae8fa9e) ### Update installing.rst > Commit: [8ebf5fba21f99bf904f38569e3bda3306ae8fa9e](https://github.com/dOpensource/dsiprouter/commit/8ebf5fba21f99bf904f38569e3bda3306ae8fa9e) > Date: Mon, 1 Apr 2019 06:45:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8ebf5fba21f99bf904f38569e3bda3306ae8fa9e) [//]: # (START_SECTION 75d76e36326e649ff354d6848dd07b34f8456dd9) ### Update command_line_options.rst > Commit: [75d76e36326e649ff354d6848dd07b34f8456dd9](https://github.com/dOpensource/dsiprouter/commit/75d76e36326e649ff354d6848dd07b34f8456dd9) > Date: Mon, 1 Apr 2019 06:42:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 75d76e36326e649ff354d6848dd07b34f8456dd9) [//]: # (START_SECTION fa1964b57de7a486694a979d393625c4224ae34c) ### Update command_line_options.rst > Commit: [fa1964b57de7a486694a979d393625c4224ae34c](https://github.com/dOpensource/dsiprouter/commit/fa1964b57de7a486694a979d393625c4224ae34c) > Date: Mon, 1 Apr 2019 06:40:42 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fa1964b57de7a486694a979d393625c4224ae34c) [//]: # (START_SECTION f79dc044b38d7cb50bedc6c8ab5860d0c6c361eb) ### Update command_line_options.rst > Commit: [f79dc044b38d7cb50bedc6c8ab5860d0c6c361eb](https://github.com/dOpensource/dsiprouter/commit/f79dc044b38d7cb50bedc6c8ab5860d0c6c361eb) > Date: Mon, 1 Apr 2019 06:38:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f79dc044b38d7cb50bedc6c8ab5860d0c6c361eb) [//]: # (START_SECTION 6204fdb79d99d8a6ec90185d14fffb2ec624693b) ### Update command_line_options.rst > Commit: [6204fdb79d99d8a6ec90185d14fffb2ec624693b](https://github.com/dOpensource/dsiprouter/commit/6204fdb79d99d8a6ec90185d14fffb2ec624693b) > Date: Mon, 1 Apr 2019 06:35:20 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6204fdb79d99d8a6ec90185d14fffb2ec624693b) [//]: # (START_SECTION c6640ff2ecf78225565be2778b9447bad0415571) ### Update command_line_options.rst > Commit: [c6640ff2ecf78225565be2778b9447bad0415571](https://github.com/dOpensource/dsiprouter/commit/c6640ff2ecf78225565be2778b9447bad0415571) > Date: Mon, 1 Apr 2019 06:25:09 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c6640ff2ecf78225565be2778b9447bad0415571) [//]: # (START_SECTION c855821012ed71159c14759768f173a0fa754495) ### Update installing.rst > Commit: [c855821012ed71159c14759768f173a0fa754495](https://github.com/dOpensource/dsiprouter/commit/c855821012ed71159c14759768f173a0fa754495) > Date: Mon, 1 Apr 2019 05:55:36 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c855821012ed71159c14759768f173a0fa754495) [//]: # (START_SECTION 9da93ccdb5043c45a03ea9efab26d69dda6df426) ### Update installing.rst > Commit: [9da93ccdb5043c45a03ea9efab26d69dda6df426](https://github.com/dOpensource/dsiprouter/commit/9da93ccdb5043c45a03ea9efab26d69dda6df426) > Date: Mon, 1 Apr 2019 05:52:35 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9da93ccdb5043c45a03ea9efab26d69dda6df426) [//]: # (START_SECTION e3cf3de293dbf4361f384539ef9248b495464953) ### Update installing.rst > Commit: [e3cf3de293dbf4361f384539ef9248b495464953](https://github.com/dOpensource/dsiprouter/commit/e3cf3de293dbf4361f384539ef9248b495464953) > Date: Mon, 1 Apr 2019 05:49:43 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e3cf3de293dbf4361f384539ef9248b495464953) [//]: # (START_SECTION 5929359151b00d7eda040110ab236d2a529137b8) ### Update centos-install.rst > Commit: [5929359151b00d7eda040110ab236d2a529137b8](https://github.com/dOpensource/dsiprouter/commit/5929359151b00d7eda040110ab236d2a529137b8) > Date: Mon, 1 Apr 2019 05:31:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5929359151b00d7eda040110ab236d2a529137b8) [//]: # (START_SECTION 8667c490c5f269cc10e1574e24ba940c7016630e) ### Update debian_install.rst > Commit: [8667c490c5f269cc10e1574e24ba940c7016630e](https://github.com/dOpensource/dsiprouter/commit/8667c490c5f269cc10e1574e24ba940c7016630e) > Date: Sun, 31 Mar 2019 10:55:37 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8667c490c5f269cc10e1574e24ba940c7016630e) [//]: # (START_SECTION ed9162b21b4851a8443c2fc9e4f86f24da57f536) ### Updated Debian Install Docs > Commit: [ed9162b21b4851a8443c2fc9e4f86f24da57f536](https://github.com/dOpensource/dsiprouter/commit/ed9162b21b4851a8443c2fc9e4f86f24da57f536) > Date: Sun, 31 Mar 2019 10:54:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Modified the docs to reflect the new install options --- [//]: # (END_SECTION ed9162b21b4851a8443c2fc9e4f86f24da57f536) [//]: # (START_SECTION 309020346d1d02532ba6479eb38c5b46fcbe1422) ### Initial commit of an active Dashboard > Commit: [309020346d1d02532ba6479eb38c5b46fcbe1422](https://github.com/dOpensource/dsiprouter/commit/309020346d1d02532ba6479eb38c5b46fcbe1422) > Date: Sat, 30 Mar 2019 23:48:14 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 309020346d1d02532ba6479eb38c5b46fcbe1422) [//]: # (START_SECTION 13996bebd47fe648fb374aae3517c6e8ae828478) ### Final AMI Updates for Release v0.52 > Commit: [13996bebd47fe648fb374aae3517c6e8ae828478](https://github.com/dOpensource/dsiprouter/commit/13996bebd47fe648fb374aae3517c6e8ae828478) > Date: Wed, 27 Mar 2019 21:29:11 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix double password reset issue - cleanup unneeded extra files - update dsiprouter service - allow color printing function use inline - added and updated comments / TODO's - allow DB driver selection - fix uninstall dsiprouter to not fail on pip cmd - fix configurePythonSettings issue - add support for debian8 (jessie) - add support for ubuntu 16.04 (xenial) - add support for amazon linux 2 - fix false negatives on install starting services - add color to usage cmd output --- [//]: # (END_SECTION 13996bebd47fe648fb374aae3517c6e8ae828478) [//]: # (START_SECTION a711d7e9597a015d2fed25a7a0bd10df04bdc9ab) ### Update dsiprouter.sh > Commit: [a711d7e9597a015d2fed25a7a0bd10df04bdc9ab](https://github.com/dOpensource/dsiprouter/commit/a711d7e9597a015d2fed25a7a0bd10df04bdc9ab) > Date: Wed, 27 Mar 2019 03:27:16 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Removed generatePassword from the displayLoginInfo function --- [//]: # (END_SECTION a711d7e9597a015d2fed25a7a0bd10df04bdc9ab) [//]: # (START_SECTION 20cbff25f55c0a305e3ef1528d785c581988e7d5) ### Update Version Number for Release v0.52 > Commit: [20cbff25f55c0a305e3ef1528d785c581988e7d5](https://github.com/dOpensource/dsiprouter/commit/20cbff25f55c0a305e3ef1528d785c581988e7d5) > Date: Tue, 26 Mar 2019 09:59:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 20cbff25f55c0a305e3ef1528d785c581988e7d5) [//]: # (START_SECTION 886179285c09f239edbd573b620257fcad3f3c01) ### - Fixes #103 - deprecate Debian v7 - deprecate Debian v8 - change CentOS RTPEngine install to RPM build - fix startup issues with dsip-init service on AWS - added dpkg defaults during script execution > Commit: [886179285c09f239edbd573b620257fcad3f3c01](https://github.com/dOpensource/dsiprouter/commit/886179285c09f239edbd573b620257fcad3f3c01) > Date: Tue, 26 Mar 2019 08:12:01 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 886179285c09f239edbd573b620257fcad3f3c01) [//]: # (START_SECTION 8ea6bb8ccc48d688e73d0ef614f33ee28d0c8a77) ### Merge feature-ami Branch Into dev Branch > Commit: [8ea6bb8ccc48d688e73d0ef614f33ee28d0c8a77](https://github.com/dOpensource/dsiprouter/commit/8ea6bb8ccc48d688e73d0ef614f33ee28d0c8a77) > Date: Mon, 25 Mar 2019 15:41:44 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - rename JSONRPC test to `16.sh` - merge feature-ami commits onto dev branch --- [//]: # (END_SECTION 8ea6bb8ccc48d688e73d0ef614f33ee28d0c8a77) [//]: # (START_SECTION b168f8ede2ed17483fa46c52baec81acdcbdbca5) ### Fixup Firewalld Commands > Commit: [b168f8ede2ed17483fa46c52baec81acdcbdbca5](https://github.com/dOpensource/dsiprouter/commit/b168f8ede2ed17483fa46c52baec81acdcbdbca5) > Date: Mon, 25 Mar 2019 15:01:28 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add tcp port for jsonrpc access - cleanup centos commands --- [//]: # (END_SECTION b168f8ede2ed17483fa46c52baec81acdcbdbca5) [//]: # (START_SECTION 63647ef1e00134c14307c65d29b2539d482607a3) ### - fix mariadb centos startup regression - fix module sql install username conflict - set default for ssl variables to avoid errors - move displaying login info back to after logo - update a few sed cmds to be more reliable > Commit: [63647ef1e00134c14307c65d29b2539d482607a3](https://github.com/dOpensource/dsiprouter/commit/63647ef1e00134c14307c65d29b2539d482607a3) > Date: Mon, 25 Mar 2019 14:49:38 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) --- [//]: # (END_SECTION 63647ef1e00134c14307c65d29b2539d482607a3) [//]: # (START_SECTION f653ce88a5fe3c38de000f0287eb44023067ab6c) ### AMI Feature Fixes > Commit: [f653ce88a5fe3c38de000f0287eb44023067ab6c](https://github.com/dOpensource/dsiprouter/commit/f653ce88a5fe3c38de000f0287eb44023067ab6c) > Date: Mon, 25 Mar 2019 10:53:34 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - update AMI funtions to run with systemd - add seperate log file for dsip cloud installs - fix broken paths - make kam cfg actual readable (spaces not tabs) - add test for syslog service - add test for AMI requirements - add test for dsip GUI login - add dev files for next tests to make - fix test sorting to work past 10 - add work on custom redirection function - fix login logic in routes and HTTP return codes --- [//]: # (END_SECTION f653ce88a5fe3c38de000f0287eb44023067ab6c) [//]: # (START_SECTION cd6fb18aa0d9c911a71e02a430555f4fb27a9bfd) ### Added a unit test to validate that JSON over HTTP access to Kamailio RPC Commands is working correctly > Commit: [cd6fb18aa0d9c911a71e02a430555f4fb27a9bfd](https://github.com/dOpensource/dsiprouter/commit/cd6fb18aa0d9c911a71e02a430555f4fb27a9bfd) > Date: Sat, 23 Mar 2019 12:01:01 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION cd6fb18aa0d9c911a71e02a430555f4fb27a9bfd) [//]: # (START_SECTION 756b12c6da7d180b2b09d9be3f8873bc93774366) ### Added supported jsonrpc over http on tcp port 5060 > Commit: [756b12c6da7d180b2b09d9be3f8873bc93774366](https://github.com/dOpensource/dsiprouter/commit/756b12c6da7d180b2b09d9be3f8873bc93774366) > Date: Sat, 23 Mar 2019 03:48:17 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 756b12c6da7d180b2b09d9be3f8873bc93774366) [//]: # (START_SECTION 1c534efabbce6c81be33b28a2fce4bab307fee70) ### Moved the creation of the LCR schema to the main install script and deprecated the LCR module > Commit: [1c534efabbce6c81be33b28a2fce4bab307fee70](https://github.com/dOpensource/dsiprouter/commit/1c534efabbce6c81be33b28a2fce4bab307fee70) > Date: Sat, 23 Mar 2019 00:10:40 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 1c534efabbce6c81be33b28a2fce4bab307fee70) [//]: # (START_SECTION fb3226afbb6a91e96b04670d362cc18192fa70d4) ### Fixed a regression with the gateway list import > Commit: [fb3226afbb6a91e96b04670d362cc18192fa70d4](https://github.com/dOpensource/dsiprouter/commit/fb3226afbb6a91e96b04670d362cc18192fa70d4) > Date: Fri, 22 Mar 2019 23:36:42 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION fb3226afbb6a91e96b04670d362cc18192fa70d4) [//]: # (START_SECTION 93a1ed46a904cf3f15fb12cc75fe4cf8c3d8ecf9) ### Fixed a regression with dr_gw_lists not being copied over to the /tmp/defaults directory > Commit: [93a1ed46a904cf3f15fb12cc75fe4cf8c3d8ecf9](https://github.com/dOpensource/dsiprouter/commit/93a1ed46a904cf3f15fb12cc75fe4cf8c3d8ecf9) > Date: Fri, 22 Mar 2019 22:47:42 +0000 > Author: root (root@dSIPRouterMackDev-0.localdomain) > Committer: root (root@dSIPRouterMackDev-0.localdomain) > Signed: --- [//]: # (END_SECTION 93a1ed46a904cf3f15fb12cc75fe4cf8c3d8ecf9) [//]: # (START_SECTION 259f64c4abff05757940c5cef61e152f7296d451) ### dSIPRouter Installation Overhaul > Commit: [259f64c4abff05757940c5cef61e152f7296d451](https://github.com/dOpensource/dsiprouter/commit/259f64c4abff05757940c5cef61e152f7296d451) > Date: Thu, 21 Mar 2019 12:31:35 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #42 - Resolves #103 - add localhost to bind addresses for testing - wrap LCR routing in #!ifdef WITH_LCR - fix RTPEngine service startup issue - update rtpengine service file - update dsiprouter service file - add debian support for rtpengine systemd service - add debian support for kernel packet forwarding - fix non-root user kernel packet forwarding support - make rtpengine service namespace cross platform compat - make centos mariadb service namespace alias to mysql.service - fix tests for reg, auth, and DOS - create service check tests - update test formatting to be cleaner - update tests documentation - update test Makefile to sort test execution - fix debian AMI instable repo lists - make getExternalIP function match logic from shared.py - create structure for systemd startup dependencies - add dsip-init systemd resource - fix AMI image creation service startup issues - add detailed debugging options in dsiprouter.sh - add colored output and cleanup script output - fix python dependency removal order in uninstall funcs - add dependency installation for sipsak - finish separating service install logic to independent functions - update install/uninstall options to allow for independent installs - improve path check logic to avoid duplicates - fix dr_gw_lists import regression (path issue) - change logo color (no orange in 8-bit so we use cyan now) --- [//]: # (END_SECTION 259f64c4abff05757940c5cef61e152f7296d451) [//]: # (START_SECTION a3df4ce4e01a93797428ecaa5ffd14e8ccfe6d4f) ### Allow Domain Editing > Commit: [a3df4ce4e01a93797428ecaa5ffd14e8ccfe6d4f](https://github.com/dOpensource/dsiprouter/commit/a3df4ce4e01a93797428ecaa5ffd14e8ccfe6d4f) > Date: Mon, 18 Mar 2019 18:21:32 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #87 - simplify domain routing - allow editing in domain route - update pre commit script --- [//]: # (END_SECTION a3df4ce4e01a93797428ecaa5ffd14e8ccfe6d4f) [//]: # (START_SECTION a87041465ba90930947920b9075673ec7884e0cf) ### Update kamailio51_dsiprouter.tpl > Commit: [a87041465ba90930947920b9075673ec7884e0cf](https://github.com/dOpensource/dsiprouter/commit/a87041465ba90930947920b9075673ec7884e0cf) > Date: Mon, 18 Mar 2019 11:57:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a87041465ba90930947920b9075673ec7884e0cf) [//]: # (START_SECTION a2af37311be5ad693a7d76f859f157095eaa6e4b) ### Fix for Google Cloud Mysql > Commit: [a2af37311be5ad693a7d76f859f157095eaa6e4b](https://github.com/dOpensource/dsiprouter/commit/a2af37311be5ad693a7d76f859f157095eaa6e4b) > Date: Fri, 15 Mar 2019 16:15:22 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #47 - add defaults for carrier form - remove uneeded DB drivers --- [//]: # (END_SECTION a2af37311be5ad693a7d76f859f157095eaa6e4b) [//]: # (START_SECTION eb706a5a3ac8bf004d015a9de54ada8f7063a3fb) ### Fix Regressions > Commit: [eb706a5a3ac8bf004d015a9de54ada8f7063a3fb](https://github.com/dOpensource/dsiprouter/commit/eb706a5a3ac8bf004d015a9de54ada8f7063a3fb) > Date: Thu, 14 Mar 2019 21:55:41 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - re-add configureKamailio command to install - fix DSIP_KAMAILIO_CONFIG_FILE path - remove uneeded kam code from hotfix --- [//]: # (END_SECTION eb706a5a3ac8bf004d015a9de54ada8f7063a3fb) [//]: # (START_SECTION f42eb0e6477757d050c07be2adec952672eaa083) ### Fix DID Notes DB Update > Commit: [f42eb0e6477757d050c07be2adec952672eaa083](https://github.com/dOpensource/dsiprouter/commit/f42eb0e6477757d050c07be2adec952672eaa083) > Date: Thu, 14 Mar 2019 21:27:03 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #123 - set form defaults on db update for inbound did route --- [//]: # (END_SECTION f42eb0e6477757d050c07be2adec952672eaa083) [//]: # (START_SECTION f22b141b1ad277f14c0e3dcad9506275a024d985) ### General Updates Cleanup Repo > Commit: [f22b141b1ad277f14c0e3dcad9506275a024d985](https://github.com/dOpensource/dsiprouter/commit/f22b141b1ad277f14c0e3dcad9506275a024d985) > Date: Thu, 14 Mar 2019 10:42:40 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - move rtpengine install to seperate dir - update git resources - fix merging issues with modules - seperate kamailio install function and logic - add printing functions / colors to install - update requirements.txt install for stability - add mysql imports for pipreqs pre-commit updates - update module sql merging --- [//]: # (END_SECTION f22b141b1ad277f14c0e3dcad9506275a024d985) [//]: # (START_SECTION 58cd4786f055db39c15980bf0574f3415e49bd4f) ### Tighten Install for Release > Commit: [58cd4786f055db39c15980bf0574f3415e49bd4f](https://github.com/dOpensource/dsiprouter/commit/58cd4786f055db39c15980bf0574f3415e49bd4f) > Date: Wed, 13 Mar 2019 10:24:40 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - fix cluster resource waiting stability - fix intermittent route editing issue - add centos support for galera replication - fix centos plugin auth issues --- [//]: # (END_SECTION 58cd4786f055db39c15980bf0574f3415e49bd4f) [//]: # (START_SECTION 880278affeb5729cb5f860903337bca3e53eb5f7) ### Added support for emergency numbers 911-999 Fixes: #121 > Commit: [880278affeb5729cb5f860903337bca3e53eb5f7](https://github.com/dOpensource/dsiprouter/commit/880278affeb5729cb5f860903337bca3e53eb5f7) > Date: Sun, 10 Mar 2019 23:06:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 880278affeb5729cb5f860903337bca3e53eb5f7) [//]: # (START_SECTION 17b35aea596261032c6183327689184e52515363) ### LCR Dynamic Prefix Routing > Commit: [17b35aea596261032c6183327689184e52515363](https://github.com/dOpensource/dsiprouter/commit/17b35aea596261032c6183327689184e52515363) > Date: Fri, 8 Mar 2019 18:07:06 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - Resolves #122 - add dynamic routing for LCR module (similar to dRouting matches) - make LCR prefix length configurable in kam config - update both kamailio template and config files - general cleanup on kam configs - update internal IP resolution - update PATH resolution (fix logic bug) - fix dsiprouter logrotate path --- [//]: # (END_SECTION 17b35aea596261032c6183327689184e52515363) [//]: # (START_SECTION eb82e5ab73697559cdaa5359ce2eb664d2fabe14) ### Add 2 new HA Features > Commit: [eb82e5ab73697559cdaa5359ce2eb664d2fabe14](https://github.com/dOpensource/dsiprouter/commit/eb82e5ab73697559cdaa5359ce2eb664d2fabe14) > Date: Fri, 8 Mar 2019 10:14:31 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - mysql galera replication - pacemaker / corosync cluster with floating ip --- [//]: # (END_SECTION eb82e5ab73697559cdaa5359ce2eb664d2fabe14) [//]: # (START_SECTION e55ab3e4eb8ea9a798c761bdf31e3428759cce2e) ### Make Project root more reliable > Commit: [e55ab3e4eb8ea9a798c761bdf31e3428759cce2e](https://github.com/dOpensource/dsiprouter/commit/e55ab3e4eb8ea9a798c761bdf31e3428759cce2e) > Date: Wed, 6 Mar 2019 16:05:08 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add redundancy checking for project root dir --- [//]: # (END_SECTION e55ab3e4eb8ea9a798c761bdf31e3428759cce2e) [//]: # (START_SECTION d887aa56a5c40c44f67735af585696fdc624a6f5) ### Update Internal IP Resolution > Commit: [d887aa56a5c40c44f67735af585696fdc624a6f5](https://github.com/dOpensource/dsiprouter/commit/d887aa56a5c40c44f67735af585696fdc624a6f5) > Date: Tue, 5 Mar 2019 23:19:55 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - changed internal ip resolution based on default route - fix rtpengine config update function - add rtpcfg variable for later use --- [//]: # (END_SECTION d887aa56a5c40c44f67735af585696fdc624a6f5) [//]: # (START_SECTION d87203edbf363e7e64eb4af1adfc1403d7e93bd9) ### Fix kamailio configure Bugs > Commit: [d87203edbf363e7e64eb4af1adfc1403d7e93bd9](https://github.com/dOpensource/dsiprouter/commit/d87203edbf363e7e64eb4af1adfc1403d7e93bd9) > Date: Tue, 5 Mar 2019 19:25:15 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - drop dr_custom_rules on fresh kam configure - remove hung locks when adding user --- [//]: # (END_SECTION d87203edbf363e7e64eb4af1adfc1403d7e93bd9) [//]: # (START_SECTION f90689cc8579169b4705ba603dad1cda5b427ced) ### Bug Fixes > Commit: [f90689cc8579169b4705ba603dad1cda5b427ced](https://github.com/dOpensource/dsiprouter/commit/f90689cc8579169b4705ba603dad1cda5b427ced) > Date: Tue, 5 Mar 2019 16:02:37 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: Tyler Moore (devopsec) - add curl timeout on AWS check - make PBX local digit length check globalls configurable - fix typos - fix line breaks - automate merging table data during install --- [//]: # (END_SECTION f90689cc8579169b4705ba603dad1cda5b427ced) [//]: # (START_SECTION 2014b89f5c3911f4b37bdde167e713217fe9ebd9) ### Add Mysql Replication Scripts > Commit: [2014b89f5c3911f4b37bdde167e713217fe9ebd9](https://github.com/dOpensource/dsiprouter/commit/2014b89f5c3911f4b37bdde167e713217fe9ebd9) > Date: Tue, 26 Feb 2019 11:58:06 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - support for group replication - support for galera replication - mysql size check script added --- [//]: # (END_SECTION 2014b89f5c3911f4b37bdde167e713217fe9ebd9) [//]: # (START_SECTION de65083142747932c726e75b451791b50eae56c8) ### Update kamailio51_dsiprouter.tpl > Commit: [de65083142747932c726e75b451791b50eae56c8](https://github.com/dOpensource/dsiprouter/commit/de65083142747932c726e75b451791b50eae56c8) > Date: Thu, 21 Feb 2019 16:43:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Set the domain flag: register_myself to 0. This flag was causing Kamailio to get stuck in a continuous loop when receiving an ACK from an endpoint. This is due to the fact that Kamailio sees the domains in the domains table reside on the Kamailio server with the register_myself flag being set to 1 --- [//]: # (END_SECTION de65083142747932c726e75b451791b50eae56c8) [//]: # (START_SECTION b22bf3ddef855c9ccd95430cbfaae3099d44540a) ### Add Useful Scripts To Resources > Commit: [b22bf3ddef855c9ccd95430cbfaae3099d44540a](https://github.com/dOpensource/dsiprouter/commit/b22bf3ddef855c9ccd95430cbfaae3099d44540a) > Date: Wed, 20 Feb 2019 15:12:14 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add changelog merging script - add some python testing scripts --- [//]: # (END_SECTION b22bf3ddef855c9ccd95430cbfaae3099d44540a) [//]: # (START_SECTION 1648791889665b29540d093f2cc38f4ef6a87ff9) ### Update RTPengine On Reload and Install Fixes > Commit: [1648791889665b29540d093f2cc38f4ef6a87ff9](https://github.com/dOpensource/dsiprouter/commit/1648791889665b29540d093f2cc38f4ef6a87ff9) > Date: Tue, 19 Feb 2019 10:50:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #115 - update rtpengine config on reboot - fix misc issues with install script - fix adding user issue - update exception function --- [//]: # (END_SECTION 1648791889665b29540d093f2cc38f4ef6a87ff9) [//]: # (START_SECTION fc0dc02dde3ff3dcdd62d95edcdfeb44e2348f4a) ### Initial commit > Commit: [fc0dc02dde3ff3dcdd62d95edcdfeb44e2348f4a](https://github.com/dOpensource/dsiprouter/commit/fc0dc02dde3ff3dcdd62d95edcdfeb44e2348f4a) > Date: Fri, 15 Feb 2019 16:31:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fc0dc02dde3ff3dcdd62d95edcdfeb44e2348f4a) [//]: # (START_SECTION ee97002db899a95857307e328775d2db7064e399) ### Update use-cases.rst > Commit: [ee97002db899a95857307e328775d2db7064e399](https://github.com/dOpensource/dsiprouter/commit/ee97002db899a95857307e328775d2db7064e399) > Date: Thu, 14 Feb 2019 09:55:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ee97002db899a95857307e328775d2db7064e399) [//]: # (START_SECTION 414109b4d53ef973361d5ceaefade981d1b43eed) ### Update use-cases.rst > Commit: [414109b4d53ef973361d5ceaefade981d1b43eed](https://github.com/dOpensource/dsiprouter/commit/414109b4d53ef973361d5ceaefade981d1b43eed) > Date: Wed, 13 Feb 2019 17:48:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 414109b4d53ef973361d5ceaefade981d1b43eed) [//]: # (START_SECTION 763485c1819485fd5455cd53814a7a5293870a2f) ### Add files via upload > Commit: [763485c1819485fd5455cd53814a7a5293870a2f](https://github.com/dOpensource/dsiprouter/commit/763485c1819485fd5455cd53814a7a5293870a2f) > Date: Wed, 13 Feb 2019 17:42:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 763485c1819485fd5455cd53814a7a5293870a2f) [//]: # (START_SECTION b0380ce7373a8053058e1dcc3bc436e241bc3717) ### Update use-cases.rst > Commit: [b0380ce7373a8053058e1dcc3bc436e241bc3717](https://github.com/dOpensource/dsiprouter/commit/b0380ce7373a8053058e1dcc3bc436e241bc3717) > Date: Wed, 13 Feb 2019 17:37:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b0380ce7373a8053058e1dcc3bc436e241bc3717) [//]: # (START_SECTION 6bcb68bb0c651266c30f0e9962f2eec80c771b5c) ### Update use-cases.rst > Commit: [6bcb68bb0c651266c30f0e9962f2eec80c771b5c](https://github.com/dOpensource/dsiprouter/commit/6bcb68bb0c651266c30f0e9962f2eec80c771b5c) > Date: Wed, 13 Feb 2019 15:25:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6bcb68bb0c651266c30f0e9962f2eec80c771b5c) [//]: # (START_SECTION b40709212a5dc2820ae9b737a584f291bdd29669) ### Update use-cases.rst > Commit: [b40709212a5dc2820ae9b737a584f291bdd29669](https://github.com/dOpensource/dsiprouter/commit/b40709212a5dc2820ae9b737a584f291bdd29669) > Date: Wed, 13 Feb 2019 15:23:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b40709212a5dc2820ae9b737a584f291bdd29669) [//]: # (START_SECTION 2dc2a5bd7646b376e9cad23f4f5db917d4068adc) ### Update ngcp-rtpengine-daemon.init > Commit: [2dc2a5bd7646b376e9cad23f4f5db917d4068adc](https://github.com/dOpensource/dsiprouter/commit/2dc2a5bd7646b376e9cad23f4f5db917d4068adc) > Date: Wed, 13 Feb 2019 13:41:59 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - Fixed an issue with a redirect --- [//]: # (END_SECTION 2dc2a5bd7646b376e9cad23f4f5db917d4068adc) [//]: # (START_SECTION 08e71702a2601e75310cebcb95faa5194ba549d3) ### Fix Bugs in GUI > Commit: [08e71702a2601e75310cebcb95faa5194ba549d3](https://github.com/dOpensource/dsiprouter/commit/08e71702a2601e75310cebcb95faa5194ba549d3) > Date: Mon, 11 Feb 2019 17:28:28 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix datatable auto width resolution issue - fix db connection issue - add dsiprouter flag definitions --- [//]: # (END_SECTION 08e71702a2601e75310cebcb95faa5194ba549d3) [//]: # (START_SECTION 171513f9e25c57b52492b481999681db7e0997ba) ### Update use-cases.rst > Commit: [171513f9e25c57b52492b481999681db7e0997ba](https://github.com/dOpensource/dsiprouter/commit/171513f9e25c57b52492b481999681db7e0997ba) > Date: Fri, 8 Feb 2019 23:17:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 171513f9e25c57b52492b481999681db7e0997ba) [//]: # (START_SECTION a14098100a655d494e48b4e847bb70f4d13d6476) ### Update use-cases.rst > Commit: [a14098100a655d494e48b4e847bb70f4d13d6476](https://github.com/dOpensource/dsiprouter/commit/a14098100a655d494e48b4e847bb70f4d13d6476) > Date: Fri, 8 Feb 2019 23:09:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a14098100a655d494e48b4e847bb70f4d13d6476) [//]: # (START_SECTION efd9e88fcf3e19f1ef43ff5879d9c742572b1a47) ### Add files via upload > Commit: [efd9e88fcf3e19f1ef43ff5879d9c742572b1a47](https://github.com/dOpensource/dsiprouter/commit/efd9e88fcf3e19f1ef43ff5879d9c742572b1a47) > Date: Fri, 8 Feb 2019 23:03:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION efd9e88fcf3e19f1ef43ff5879d9c742572b1a47) [//]: # (START_SECTION b60953ebe93c0577e2ee2ea292301ecf0a090aee) ### Update use-cases.rst > Commit: [b60953ebe93c0577e2ee2ea292301ecf0a090aee](https://github.com/dOpensource/dsiprouter/commit/b60953ebe93c0577e2ee2ea292301ecf0a090aee) > Date: Fri, 8 Feb 2019 23:01:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b60953ebe93c0577e2ee2ea292301ecf0a090aee) [//]: # (START_SECTION c3faeda237dc394ece62a71cf701930ff5ff6e9f) ### Update use-cases.rst > Commit: [c3faeda237dc394ece62a71cf701930ff5ff6e9f](https://github.com/dOpensource/dsiprouter/commit/c3faeda237dc394ece62a71cf701930ff5ff6e9f) > Date: Fri, 8 Feb 2019 22:31:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c3faeda237dc394ece62a71cf701930ff5ff6e9f) [//]: # (START_SECTION 8be2229d5c810a3152573c1a5c5d0c6f93021eb2) ### Update use-cases.rst > Commit: [8be2229d5c810a3152573c1a5c5d0c6f93021eb2](https://github.com/dOpensource/dsiprouter/commit/8be2229d5c810a3152573c1a5c5d0c6f93021eb2) > Date: Fri, 8 Feb 2019 22:22:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8be2229d5c810a3152573c1a5c5d0c6f93021eb2) [//]: # (START_SECTION 7f5bd28b10d793afb822d01fa2ed8bca6f78b697) ### Update use-cases.rst > Commit: [7f5bd28b10d793afb822d01fa2ed8bca6f78b697](https://github.com/dOpensource/dsiprouter/commit/7f5bd28b10d793afb822d01fa2ed8bca6f78b697) > Date: Fri, 8 Feb 2019 22:20:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7f5bd28b10d793afb822d01fa2ed8bca6f78b697) [//]: # (START_SECTION c92d4688407e0173c8e6ad66369a8f15e8400587) ### Update use-cases.rst > Commit: [c92d4688407e0173c8e6ad66369a8f15e8400587](https://github.com/dOpensource/dsiprouter/commit/c92d4688407e0173c8e6ad66369a8f15e8400587) > Date: Fri, 8 Feb 2019 22:18:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c92d4688407e0173c8e6ad66369a8f15e8400587) [//]: # (START_SECTION 870d44939d3046d8b79f0bd59f5e6d1cda31e97f) ### Update use-cases.rst > Commit: [870d44939d3046d8b79f0bd59f5e6d1cda31e97f](https://github.com/dOpensource/dsiprouter/commit/870d44939d3046d8b79f0bd59f5e6d1cda31e97f) > Date: Fri, 8 Feb 2019 14:47:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 870d44939d3046d8b79f0bd59f5e6d1cda31e97f) [//]: # (START_SECTION 9b3e2cbbd9010213a9e2205408594031807d3eb5) ### Inbound DID and Fail2Ban Update > Commit: [9b3e2cbbd9010213a9e2205408594031807d3eb5](https://github.com/dOpensource/dsiprouter/commit/9b3e2cbbd9010213a9e2205408594031807d3eb5) > Date: Thu, 7 Feb 2019 22:31:55 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #100 - Resolves #54 - add support for secondary pbx inbound route - DID failover is supported by adding another rule - add fail2ban instructions to domain and pbx pages - small syntax fixes - update combobox and fix issues - update inboundroutes routing and DB model --- [//]: # (END_SECTION 9b3e2cbbd9010213a9e2205408594031807d3eb5) [//]: # (START_SECTION 8111636d42d431e521ffbcc8511f61cacef3be00) ### Update use-cases.rst > Commit: [8111636d42d431e521ffbcc8511f61cacef3be00](https://github.com/dOpensource/dsiprouter/commit/8111636d42d431e521ffbcc8511f61cacef3be00) > Date: Thu, 7 Feb 2019 16:02:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8111636d42d431e521ffbcc8511f61cacef3be00) [//]: # (START_SECTION 9247eaefdfee193b8766945c6cd2377d48637c2a) ### Update use-cases.rst > Commit: [9247eaefdfee193b8766945c6cd2377d48637c2a](https://github.com/dOpensource/dsiprouter/commit/9247eaefdfee193b8766945c6cd2377d48637c2a) > Date: Thu, 7 Feb 2019 15:24:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9247eaefdfee193b8766945c6cd2377d48637c2a) [//]: # (START_SECTION cdbf7233f95ebf0034c664574e5b4afe45209567) ### AMI Provisioning Fixes > Commit: [cdbf7233f95ebf0034c664574e5b4afe45209567](https://github.com/dOpensource/dsiprouter/commit/cdbf7233f95ebf0034c664574e5b4afe45209567) > Date: Thu, 7 Feb 2019 14:30:28 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix PATH on install - dynamic kam config update - add updatekamconfig cli option - fix for debian 8 debhelper issue - fix kamailio and rtpengine user creation - fix rtpengine default conf file location - fix firewalld centos ami issue - fix merge issues (install,installSipsak) - minor improvements to syslog handler --- [//]: # (END_SECTION cdbf7233f95ebf0034c664574e5b4afe45209567) [//]: # (START_SECTION 7c7bd555e3dc8e056f90de9b2175959b3443f482) ### Update command_line_options.rst > Commit: [7c7bd555e3dc8e056f90de9b2175959b3443f482](https://github.com/dOpensource/dsiprouter/commit/7c7bd555e3dc8e056f90de9b2175959b3443f482) > Date: Thu, 7 Feb 2019 10:01:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7c7bd555e3dc8e056f90de9b2175959b3443f482) [//]: # (START_SECTION 993a7605687b42aa7c36987e121b70b3e6c6afd3) ### Adds the ability to change the name of the server presented to clients > Commit: [993a7605687b42aa7c36987e121b70b3e6c6afd3](https://github.com/dOpensource/dsiprouter/commit/993a7605687b42aa7c36987e121b70b3e6c6afd3) > Date: Wed, 6 Feb 2019 21:28:15 -0700 > Author: matmurdock (mat.murdock@gmail.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 993a7605687b42aa7c36987e121b70b3e6c6afd3) [//]: # (START_SECTION 83d90093a9f5e63379de1bcca145c51bf8618483) ### Fixed firewall issues > Commit: [83d90093a9f5e63379de1bcca145c51bf8618483](https://github.com/dOpensource/dsiprouter/commit/83d90093a9f5e63379de1bcca145c51bf8618483) > Date: Thu, 7 Feb 2019 01:18:41 +0000 > Author: root (root@ip-172-31-11-14.us-east-2.compute.internal) > Committer: root (root@ip-172-31-11-14.us-east-2.compute.internal) > Signed: --- [//]: # (END_SECTION 83d90093a9f5e63379de1bcca145c51bf8618483) [//]: # (START_SECTION 3534c6ea4bdd91ece5dbc71b85155bf07f9e4cdd) ### Changed order that firewalld rules are being added. This is workaround for cloud-init > Commit: [3534c6ea4bdd91ece5dbc71b85155bf07f9e4cdd](https://github.com/dOpensource/dsiprouter/commit/3534c6ea4bdd91ece5dbc71b85155bf07f9e4cdd) > Date: Thu, 7 Feb 2019 00:31:15 +0000 > Author: root (root@ip-172-31-31-55.us-east-2.compute.internal) > Committer: root (root@ip-172-31-31-55.us-east-2.compute.internal) > Signed: --- [//]: # (END_SECTION 3534c6ea4bdd91ece5dbc71b85155bf07f9e4cdd) [//]: # (START_SECTION 396c062ec0629ed16a30c714463422979ad83202) ### Added fix to the centos 7 kamailio install so that firewall rules can be added > Commit: [396c062ec0629ed16a30c714463422979ad83202](https://github.com/dOpensource/dsiprouter/commit/396c062ec0629ed16a30c714463422979ad83202) > Date: Wed, 6 Feb 2019 23:33:58 +0000 > Author: root (root@ip-172-31-38-36.us-east-2.compute.internal) > Committer: root (root@ip-172-31-38-36.us-east-2.compute.internal) > Signed: --- [//]: # (END_SECTION 396c062ec0629ed16a30c714463422979ad83202) [//]: # (START_SECTION f4008680a09bb4faeb340deaa9b81cdd09ec7216) ### Inbound DID Mapping Sort By Name > Commit: [f4008680a09bb4faeb340deaa9b81cdd09ec7216](https://github.com/dOpensource/dsiprouter/commit/f4008680a09bb4faeb340deaa9b81cdd09ec7216) > Date: Wed, 6 Feb 2019 17:36:14 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #76 - sort pbx select list on pbx name - enable combobox for imported did's - fix autoselect on add / import did modal --- [//]: # (END_SECTION f4008680a09bb4faeb340deaa9b81cdd09ec7216) [//]: # (START_SECTION 56b3c8974e36565744ffdb592fed64811bbae82d) ### Remove Carrier From gwlist On Delete > Commit: [56b3c8974e36565744ffdb592fed64811bbae82d](https://github.com/dOpensource/dsiprouter/commit/56b3c8974e36565744ffdb592fed64811bbae82d) > Date: Wed, 6 Feb 2019 15:17:34 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #7 - carriers removed from all related dr_rules gwlists on delete - create alert and warn user that related rules will be updated --- [//]: # (END_SECTION 56b3c8974e36565744ffdb592fed64811bbae82d) [//]: # (START_SECTION c0c3444708304739612bab676095883c823ef96b) ### Fix Carrier Modal Actions > Commit: [c0c3444708304739612bab676095883c823ef96b](https://github.com/dOpensource/dsiprouter/commit/c0c3444708304739612bab676095883c823ef96b) > Date: Tue, 5 Feb 2019 12:28:13 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #96 - replace data-tables ver w/ standalone library - rename imports --- [//]: # (END_SECTION c0c3444708304739612bab676095883c823ef96b) [//]: # (START_SECTION 755e1e7bd6bd44f358588a248e038826672944a9) ### Update use-cases.rst > Commit: [755e1e7bd6bd44f358588a248e038826672944a9](https://github.com/dOpensource/dsiprouter/commit/755e1e7bd6bd44f358588a248e038826672944a9) > Date: Wed, 6 Feb 2019 10:41:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 755e1e7bd6bd44f358588a248e038826672944a9) [//]: # (START_SECTION 5ac8de10cb3f1fef840d9d2acbe0ecce293712ee) ### Fixed a regression that caused the password not to be set correct when installed on a non-AMI > Commit: [5ac8de10cb3f1fef840d9d2acbe0ecce293712ee](https://github.com/dOpensource/dsiprouter/commit/5ac8de10cb3f1fef840d9d2acbe0ecce293712ee) > Date: Tue, 5 Feb 2019 19:30:53 +0000 > Author: root (root@dSIPRouterMackAMI.localdomain) > Committer: root (root@dSIPRouterMackAMI.localdomain) > Signed: --- [//]: # (END_SECTION 5ac8de10cb3f1fef840d9d2acbe0ecce293712ee) [//]: # (START_SECTION 0c974de72660d7e8ebbcc3f7ce495199837bcd26) ### Update use-cases.rst > Commit: [0c974de72660d7e8ebbcc3f7ce495199837bcd26](https://github.com/dOpensource/dsiprouter/commit/0c974de72660d7e8ebbcc3f7ce495199837bcd26) > Date: Tue, 5 Feb 2019 10:23:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0c974de72660d7e8ebbcc3f7ce495199837bcd26) [//]: # (START_SECTION 87c55ef12898069dec92fe13ee704dfac649f33d) ### Fixed testing scripts > Commit: [87c55ef12898069dec92fe13ee704dfac649f33d](https://github.com/dOpensource/dsiprouter/commit/87c55ef12898069dec92fe13ee704dfac649f33d) > Date: Tue, 5 Feb 2019 06:49:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 87c55ef12898069dec92fe13ee704dfac649f33d) [//]: # (START_SECTION 6fe68a149f51a757953ca35bcf08591f8a349f67) ### Added support for NOTIFY messages from PBX - which is used to update MWI > Commit: [6fe68a149f51a757953ca35bcf08591f8a349f67](https://github.com/dOpensource/dsiprouter/commit/6fe68a149f51a757953ca35bcf08591f8a349f67) > Date: Mon, 4 Feb 2019 21:30:19 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6fe68a149f51a757953ca35bcf08591f8a349f67) [//]: # (START_SECTION cad9957616e80eb216a8269ad552c105da553861) ### Update use-cases.rst > Commit: [cad9957616e80eb216a8269ad552c105da553861](https://github.com/dOpensource/dsiprouter/commit/cad9957616e80eb216a8269ad552c105da553861) > Date: Mon, 4 Feb 2019 12:34:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cad9957616e80eb216a8269ad552c105da553861) [//]: # (START_SECTION 51ebf27bd815180fe84a7813b4f7160d63e09abd) ### Update use-cases.rst > Commit: [51ebf27bd815180fe84a7813b4f7160d63e09abd](https://github.com/dOpensource/dsiprouter/commit/51ebf27bd815180fe84a7813b4f7160d63e09abd) > Date: Mon, 4 Feb 2019 12:13:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 51ebf27bd815180fe84a7813b4f7160d63e09abd) [//]: # (START_SECTION 9ff4531b0227acd84946a6ebd4f40928036442b2) ### Update use-cases.rst > Commit: [9ff4531b0227acd84946a6ebd4f40928036442b2](https://github.com/dOpensource/dsiprouter/commit/9ff4531b0227acd84946a6ebd4f40928036442b2) > Date: Mon, 4 Feb 2019 12:09:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9ff4531b0227acd84946a6ebd4f40928036442b2) [//]: # (START_SECTION 045c47dc9118fb22a20579c908d071342d3be8ca) ### Update use-cases.rst > Commit: [045c47dc9118fb22a20579c908d071342d3be8ca](https://github.com/dOpensource/dsiprouter/commit/045c47dc9118fb22a20579c908d071342d3be8ca) > Date: Mon, 4 Feb 2019 12:01:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 045c47dc9118fb22a20579c908d071342d3be8ca) [//]: # (START_SECTION 1b24a410cd70e3c1a09130db33b66ecdc123524c) ### Update use-cases.rst > Commit: [1b24a410cd70e3c1a09130db33b66ecdc123524c](https://github.com/dOpensource/dsiprouter/commit/1b24a410cd70e3c1a09130db33b66ecdc123524c) > Date: Mon, 4 Feb 2019 11:44:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1b24a410cd70e3c1a09130db33b66ecdc123524c) [//]: # (START_SECTION 6312d09c44a26d0cd989ea283200bdf11bf985bd) ### Update use-cases.rst > Commit: [6312d09c44a26d0cd989ea283200bdf11bf985bd](https://github.com/dOpensource/dsiprouter/commit/6312d09c44a26d0cd989ea283200bdf11bf985bd) > Date: Mon, 4 Feb 2019 11:34:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6312d09c44a26d0cd989ea283200bdf11bf985bd) [//]: # (START_SECTION 936c72146ac85c3a490de4c3441084c4d403ae29) ### Update use-cases.rst > Commit: [936c72146ac85c3a490de4c3441084c4d403ae29](https://github.com/dOpensource/dsiprouter/commit/936c72146ac85c3a490de4c3441084c4d403ae29) > Date: Mon, 4 Feb 2019 11:31:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 936c72146ac85c3a490de4c3441084c4d403ae29) [//]: # (START_SECTION 3e8f145578cf290075c710ce46dcdcafabb88898) ### Update use-cases.rst > Commit: [3e8f145578cf290075c710ce46dcdcafabb88898](https://github.com/dOpensource/dsiprouter/commit/3e8f145578cf290075c710ce46dcdcafabb88898) > Date: Mon, 4 Feb 2019 11:29:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3e8f145578cf290075c710ce46dcdcafabb88898) [//]: # (START_SECTION e4618758b9f80847d459dd053b6c5b60a26bb580) ### Update use-cases.rst > Commit: [e4618758b9f80847d459dd053b6c5b60a26bb580](https://github.com/dOpensource/dsiprouter/commit/e4618758b9f80847d459dd053b6c5b60a26bb580) > Date: Mon, 4 Feb 2019 11:11:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e4618758b9f80847d459dd053b6c5b60a26bb580) [//]: # (START_SECTION 0842d2135ade287427a82842735f404d09b807dd) ### Rename troubleshooting.rst.txt to troubleshooting.rst > Commit: [0842d2135ade287427a82842735f404d09b807dd](https://github.com/dOpensource/dsiprouter/commit/0842d2135ade287427a82842735f404d09b807dd) > Date: Mon, 4 Feb 2019 10:27:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0842d2135ade287427a82842735f404d09b807dd) [//]: # (START_SECTION 73c0c257d966bae0c310dcd841fb37d997df9483) ### Update troubleshooting.rst.txt > Commit: [73c0c257d966bae0c310dcd841fb37d997df9483](https://github.com/dOpensource/dsiprouter/commit/73c0c257d966bae0c310dcd841fb37d997df9483) > Date: Mon, 4 Feb 2019 10:25:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 73c0c257d966bae0c310dcd841fb37d997df9483) [//]: # (START_SECTION dce6af8d9c49166c1619fc762874ef274fc36913) ### Update troubleshooting.rst > Commit: [dce6af8d9c49166c1619fc762874ef274fc36913](https://github.com/dOpensource/dsiprouter/commit/dce6af8d9c49166c1619fc762874ef274fc36913) > Date: Mon, 4 Feb 2019 09:45:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dce6af8d9c49166c1619fc762874ef274fc36913) [//]: # (START_SECTION 2442d0d467e47af0e62e1a55059a242b2789ff3c) ### Rename troubleshooting.rst.txt to troubleshooting.rst > Commit: [2442d0d467e47af0e62e1a55059a242b2789ff3c](https://github.com/dOpensource/dsiprouter/commit/2442d0d467e47af0e62e1a55059a242b2789ff3c) > Date: Mon, 4 Feb 2019 09:40:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2442d0d467e47af0e62e1a55059a242b2789ff3c) [//]: # (START_SECTION c45597efaa5fa9ad6a98deca8c1b9f4ba0c7b388) ### Fixed the directory path that points to the rsyslog and logrotate settings > Commit: [c45597efaa5fa9ad6a98deca8c1b9f4ba0c7b388](https://github.com/dOpensource/dsiprouter/commit/c45597efaa5fa9ad6a98deca8c1b9f4ba0c7b388) > Date: Mon, 4 Feb 2019 10:59:09 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION c45597efaa5fa9ad6a98deca8c1b9f4ba0c7b388) [//]: # (START_SECTION f5e2d7155ff1f7d69f9d32fb02fc00c728ca0534) ### Moved the logrotate and syslog to the resouces directory > Commit: [f5e2d7155ff1f7d69f9d32fb02fc00c728ca0534](https://github.com/dOpensource/dsiprouter/commit/f5e2d7155ff1f7d69f9d32fb02fc00c728ca0534) > Date: Mon, 4 Feb 2019 10:05:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f5e2d7155ff1f7d69f9d32fb02fc00c728ca0534) [//]: # (START_SECTION 565bf63ac62792464caaeaf7fef7e12e6ee55424) ### Unit test for testing Denial of Service (DoS) Attacks > Commit: [565bf63ac62792464caaeaf7fef7e12e6ee55424](https://github.com/dOpensource/dsiprouter/commit/565bf63ac62792464caaeaf7fef7e12e6ee55424) > Date: Fri, 1 Feb 2019 11:37:20 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION 565bf63ac62792464caaeaf7fef7e12e6ee55424) [//]: # (START_SECTION e7a5f4f11da1b0219626e642c4183311b745ee3b) ### Fixed the SQL script so that it works with the newer versions of MariaDB > Commit: [e7a5f4f11da1b0219626e642c4183311b745ee3b](https://github.com/dOpensource/dsiprouter/commit/e7a5f4f11da1b0219626e642c4183311b745ee3b) > Date: Fri, 1 Feb 2019 11:31:56 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION e7a5f4f11da1b0219626e642c4183311b745ee3b) [//]: # (START_SECTION 8d5d0bb12e5cd91db0edf18448c5704b5639d4f8) ### Fixed issue with enabling PIKE > Commit: [8d5d0bb12e5cd91db0edf18448c5704b5639d4f8](https://github.com/dOpensource/dsiprouter/commit/8d5d0bb12e5cd91db0edf18448c5704b5639d4f8) > Date: Thu, 31 Jan 2019 17:39:16 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION 8d5d0bb12e5cd91db0edf18448c5704b5639d4f8) [//]: # (START_SECTION 55f8ec2471c4df074ee237fe1ec5adbba32db24a) ### Update README.md > Commit: [55f8ec2471c4df074ee237fe1ec5adbba32db24a](https://github.com/dOpensource/dsiprouter/commit/55f8ec2471c4df074ee237fe1ec5adbba32db24a) > Date: Thu, 31 Jan 2019 12:29:28 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 55f8ec2471c4df074ee237fe1ec5adbba32db24a) [//]: # (START_SECTION 843b6e0db426bacbf21994b137d27fffdc13222d) ### Update README.md > Commit: [843b6e0db426bacbf21994b137d27fffdc13222d](https://github.com/dOpensource/dsiprouter/commit/843b6e0db426bacbf21994b137d27fffdc13222d) > Date: Thu, 31 Jan 2019 12:28:47 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 843b6e0db426bacbf21994b137d27fffdc13222d) [//]: # (START_SECTION f979796b0c2a29a5e77e6c72dd933cc922a3a610) ### Moved the server_signature parameter > Commit: [f979796b0c2a29a5e77e6c72dd933cc922a3a610](https://github.com/dOpensource/dsiprouter/commit/f979796b0c2a29a5e77e6c72dd933cc922a3a610) > Date: Thu, 31 Jan 2019 17:01:01 +0000 > Author: root (root@dsiprouterMackKamsec.localdomain) > Committer: root (root@dsiprouterMackKamsec.localdomain) > Signed: --- [//]: # (END_SECTION f979796b0c2a29a5e77e6c72dd933cc922a3a610) [//]: # (START_SECTION 0b0a6abd67a44d8f3a219e9794ca9fb5dd9c3679) ### Added a record route before relaying to endpoints to ensure they route all traffic thru the proxy > Commit: [0b0a6abd67a44d8f3a219e9794ca9fb5dd9c3679](https://github.com/dOpensource/dsiprouter/commit/0b0a6abd67a44d8f3a219e9794ca9fb5dd9c3679) > Date: Thu, 31 Jan 2019 10:36:18 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION 0b0a6abd67a44d8f3a219e9794ca9fb5dd9c3679) [//]: # (START_SECTION 34e4ff3b7f27b17a0e7b6c0cdbb53bd68e012ea1) ### Added commit [776f17bd9ba1cb7a623803a4bc3f54e6d5954565](https://github.com/dOpensource/dsiprouter/commit/776f17bd9ba1cb7a623803a4bc3f54e6d5954565) by MatMurdock into the template file > Commit: [34e4ff3b7f27b17a0e7b6c0cdbb53bd68e012ea1](https://github.com/dOpensource/dsiprouter/commit/34e4ff3b7f27b17a0e7b6c0cdbb53bd68e012ea1) > Date: Thu, 31 Jan 2019 10:15:53 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION 34e4ff3b7f27b17a0e7b6c0cdbb53bd68e012ea1) [//]: # (START_SECTION eddbd60d7351d594c8f56e46a09f7e878424d9eb) ### Fixed an issue with the initial startup of RTPEngine > Commit: [eddbd60d7351d594c8f56e46a09f7e878424d9eb](https://github.com/dOpensource/dsiprouter/commit/eddbd60d7351d594c8f56e46a09f7e878424d9eb) > Date: Thu, 31 Jan 2019 09:54:58 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION eddbd60d7351d594c8f56e46a09f7e878424d9eb) [//]: # (START_SECTION 8336f450225b1088a02a40c62c6de74818681040) ### Fixed an issue with dsiprouter.sh running commands in the wrong directory. > Commit: [8336f450225b1088a02a40c62c6de74818681040](https://github.com/dOpensource/dsiprouter/commit/8336f450225b1088a02a40c62c6de74818681040) > Date: Thu, 31 Jan 2019 08:55:10 +0000 > Author: root (root@dsiprouterMackMaster.localdomain) > Committer: root (root@dsiprouterMackMaster.localdomain) > Signed: --- [//]: # (END_SECTION 8336f450225b1088a02a40c62c6de74818681040) [//]: # (START_SECTION ed0782b28b6c89069bd919709e1cf57222bc734e) ### Removed set -x > Commit: [ed0782b28b6c89069bd919709e1cf57222bc734e](https://github.com/dOpensource/dsiprouter/commit/ed0782b28b6c89069bd919709e1cf57222bc734e) > Date: Thu, 31 Jan 2019 02:58:09 +0000 > Author: root (root@dsiprouterMackDocs.localdomain) > Committer: root (root@dsiprouterMackDocs.localdomain) > Signed: --- [//]: # (END_SECTION ed0782b28b6c89069bd919709e1cf57222bc734e) [//]: # (START_SECTION 2d510c904fe14aa029ee9ea96e98f3aadb9a27a9) ### Remove the yaml file used for to host our website originally > Commit: [2d510c904fe14aa029ee9ea96e98f3aadb9a27a9](https://github.com/dOpensource/dsiprouter/commit/2d510c904fe14aa029ee9ea96e98f3aadb9a27a9) > Date: Thu, 31 Jan 2019 02:56:26 +0000 > Author: root (root@dsiprouterMackDocs.localdomain) > Committer: root (root@dsiprouterMackDocs.localdomain) > Signed: --- [//]: # (END_SECTION 2d510c904fe14aa029ee9ea96e98f3aadb9a27a9) [//]: # (START_SECTION 309d520d70d01ca5cb2911bb2b51889ee265f1dd) ### Fixed a regression that caused sipsak to be installed each time dSIPRouter started > Commit: [309d520d70d01ca5cb2911bb2b51889ee265f1dd](https://github.com/dOpensource/dsiprouter/commit/309d520d70d01ca5cb2911bb2b51889ee265f1dd) > Date: Thu, 31 Jan 2019 02:52:24 +0000 > Author: root (root@dsiprouterMackDocs.localdomain) > Committer: root (root@dsiprouterMackDocs.localdomain) > Signed: --- [//]: # (END_SECTION 309d520d70d01ca5cb2911bb2b51889ee265f1dd) [//]: # (START_SECTION 1ef917eb3b5c3674142c40c8d9c83a2542a097ee) ### Started the development of a test plan for Carrier Registration > Commit: [1ef917eb3b5c3674142c40c8d9c83a2542a097ee](https://github.com/dOpensource/dsiprouter/commit/1ef917eb3b5c3674142c40c8d9c83a2542a097ee) > Date: Wed, 30 Jan 2019 19:59:01 +0000 > Author: root (root@dsiprouterDroplet.localdomain) > Committer: root (root@dsiprouterDroplet.localdomain) > Signed: --- [//]: # (END_SECTION 1ef917eb3b5c3674142c40c8d9c83a2542a097ee) [//]: # (START_SECTION 3d4490f29327d1eb634369b349edaece42972c6f) ### AMI Startup Fixes and General Maintenance > Commit: [3d4490f29327d1eb634369b349edaece42972c6f](https://github.com/dOpensource/dsiprouter/commit/3d4490f29327d1eb634369b349edaece42972c6f) > Date: Wed, 30 Jan 2019 05:07:37 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #103 - change rtpengine install to be last - update usage options - update command line options - misc formatting improvements - fix centos ami kam repo issue - fix centos kamilio startup issue - fix rtpengine startup issue - fix debian ami sources issue - separate rtpengine source repo from project dir - fix rtpengine kernel packet forwarding issue - add location dependent redundancy checks in dsiprouter.sh - improve reliability of dynamic ip resolution - general cleanup in dsiprouter.sh - overhaul of arg / option parsing - improve usage readability - update usage options --- [//]: # (END_SECTION 3d4490f29327d1eb634369b349edaece42972c6f) [//]: # (START_SECTION 625d0d43defef73588a349fe4c9ae4c4b5b513ef) ### Delete unneeded files > Commit: [625d0d43defef73588a349fe4c9ae4c4b5b513ef](https://github.com/dOpensource/dsiprouter/commit/625d0d43defef73588a349fe4c9ae4c4b5b513ef) > Date: Tue, 29 Jan 2019 23:19:04 +0000 > Author: root (root@dsiprouterDroplet.localdomain) > Committer: root (root@dsiprouterDroplet.localdomain) > Signed: --- [//]: # (END_SECTION 625d0d43defef73588a349fe4c9ae4c4b5b513ef) [//]: # (START_SECTION 371c4ea6c98df00752e0e43ad40c2017c94596b1) ### - Added a basic Unit Testing Framework to allow us to test core dSIPRouter functionality - Fixed an issue with CDR's that will allow the SQL needed for CDR's to be ran during install - Added logic to install Sipsak for running Unit Testing and for users that want to troubleshoot SIP message without having a SIP client > Commit: [371c4ea6c98df00752e0e43ad40c2017c94596b1](https://github.com/dOpensource/dsiprouter/commit/371c4ea6c98df00752e0e43ad40c2017c94596b1) > Date: Tue, 29 Jan 2019 22:31:59 +0000 > Author: root (root@dsiprouterDroplet.localdomain) > Committer: root (root@dsiprouterDroplet.localdomain) > Signed: --- [//]: # (END_SECTION 371c4ea6c98df00752e0e43ad40c2017c94596b1) [//]: # (START_SECTION de96347a51d1259827a7feb556d702629363225a) ### Syslog Logging Fixes > Commit: [de96347a51d1259827a7feb556d702629363225a](https://github.com/dOpensource/dsiprouter/commit/de96347a51d1259827a7feb556d702629363225a) > Date: Tue, 29 Jan 2019 10:44:44 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fixed syslog config files - seperate syslog configs in install process - redirect rtpengine daemon output to syslog - move syslog log handler to top of imports - support redirecting stdout / sterr to syslog - fix function naming to match - add signal handler func - add nohup signal handling to python app --- [//]: # (END_SECTION de96347a51d1259827a7feb556d702629363225a) [//]: # (START_SECTION 3559b7e68f7f1ed0c0977ca4dde00c5ba84295a6) ### Update Logging > Commit: [3559b7e68f7f1ed0c0977ca4dde00c5ba84295a6](https://github.com/dOpensource/dsiprouter/commit/3559b7e68f7f1ed0c0977ca4dde00c5ba84295a6) > Date: Fri, 25 Jan 2019 17:13:50 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - move all logging to syslog - move all log rotation to logrotate - add syslog and logrotate as dependencies - update and create syslog configs for each service - add werkzurg and sqlalchemy log handlers from pull #36 - add syslog support for dsiprouter app - add script header in comments - update app DEBUG variable dynamically --- [//]: # (END_SECTION 3559b7e68f7f1ed0c0977ca4dde00c5ba84295a6) [//]: # (START_SECTION eb6d3af6e64c3bcb5e358f3f690aa1e2e68bce4a) ### Added ability for 7 Digit numbers > Commit: [eb6d3af6e64c3bcb5e358f3f690aa1e2e68bce4a](https://github.com/dOpensource/dsiprouter/commit/eb6d3af6e64c3bcb5e358f3f690aa1e2e68bce4a) > Date: Fri, 25 Jan 2019 14:58:25 -0700 > Author: Mat Murdock (mat.murdock@gmail.com) > Committer: Mat Murdock (mat.murdock@gmail.com) > Signed: --- [//]: # (END_SECTION eb6d3af6e64c3bcb5e358f3f690aa1e2e68bce4a) [//]: # (START_SECTION 7943f39c2d5d2fe4b4b0e693ab16e1efcc15df14) ### Create troubleshooting.rst.txt > Commit: [7943f39c2d5d2fe4b4b0e693ab16e1efcc15df14](https://github.com/dOpensource/dsiprouter/commit/7943f39c2d5d2fe4b4b0e693ab16e1efcc15df14) > Date: Fri, 25 Jan 2019 16:12:07 -0500 > Author: Nicole (ncannon@goflyball.com) > Committer: Nicole (ncannon@goflyball.com) > Signed: - -Created documentation for troubeshooting dSIPRouter, Kamailio and rtpengine when turning logging on and off. - - Includes information: - 1 how to turn it on - 2. how do to turn it off - 3. location of the log files - 4. how do i configure it - 5. References --- [//]: # (END_SECTION 7943f39c2d5d2fe4b4b0e693ab16e1efcc15df14) [//]: # (START_SECTION e408ca867b6a5af7cdd9804b1adc42a7fc0b428e) ### Added logic to lookup the uac registration info based on the source ip coming from the carrier since I couldn't grab the realm - Fixed issue #98 > Commit: [e408ca867b6a5af7cdd9804b1adc42a7fc0b428e](https://github.com/dOpensource/dsiprouter/commit/e408ca867b6a5af7cdd9804b1adc42a7fc0b428e) > Date: Fri, 25 Jan 2019 00:46:48 +0000 > Author: root (root@dsiprouter.localdomain) > Committer: root (root@dsiprouter.localdomain) > Signed: --- [//]: # (END_SECTION e408ca867b6a5af7cdd9804b1adc42a7fc0b428e) [//]: # (START_SECTION c7d4923d98be9c4a4b9fed8d8e22d72c99a8a66d) ### Update global_outbound_routes.rst > Commit: [c7d4923d98be9c4a4b9fed8d8e22d72c99a8a66d](https://github.com/dOpensource/dsiprouter/commit/c7d4923d98be9c4a4b9fed8d8e22d72c99a8a66d) > Date: Tue, 22 Jan 2019 11:42:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c7d4923d98be9c4a4b9fed8d8e22d72c99a8a66d) [//]: # (START_SECTION 8a2eb618c88996ce96f2b9651945086a9911987b) ### Added Pike and disbabled User Agent String > Commit: [8a2eb618c88996ce96f2b9651945086a9911987b](https://github.com/dOpensource/dsiprouter/commit/8a2eb618c88996ce96f2b9651945086a9911987b) > Date: Fri, 18 Jan 2019 22:40:54 +0000 > Author: root (root@debian-s-1vcpu-1gb-tor1-01.localdomain) > Committer: root (root@debian-s-1vcpu-1gb-tor1-01.localdomain) > Signed: --- [//]: # (END_SECTION 8a2eb618c88996ce96f2b9651945086a9911987b) [//]: # (START_SECTION 15d9cb64ccb64e28c7a8a31e8c70d7b11bb8ca45) ### Added Pike and disbabled User Agent String > Commit: [15d9cb64ccb64e28c7a8a31e8c70d7b11bb8ca45](https://github.com/dOpensource/dsiprouter/commit/15d9cb64ccb64e28c7a8a31e8c70d7b11bb8ca45) > Date: Fri, 18 Jan 2019 22:18:54 +0000 > Author: root (root@debian-s-1vcpu-1gb-tor1-01.localdomain) > Committer: root (root@debian-s-1vcpu-1gb-tor1-01.localdomain) > Signed: --- [//]: # (END_SECTION 15d9cb64ccb64e28c7a8a31e8c70d7b11bb8ca45) [//]: # (START_SECTION 06a2374ea824885faeb8423146f78977e585c921) ### ChanSIP Documentation > Commit: [06a2374ea824885faeb8423146f78977e585c921](https://github.com/dOpensource/dsiprouter/commit/06a2374ea824885faeb8423146f78977e585c921) > Date: Thu, 17 Jan 2019 13:33:50 -0500 > Author: Nicole (ncannon@goflyball.com) > Committer: Nicole (ncannon@goflyball.com) > Signed: - added images for chan sip - added work flow for chan sip --- [//]: # (END_SECTION 06a2374ea824885faeb8423146f78977e585c921) [//]: # (START_SECTION d752b7dadc3e93935c4473643ac459501855f69f) ### Install Script Fixes > Commit: [d752b7dadc3e93935c4473643ac459501855f69f](https://github.com/dOpensource/dsiprouter/commit/d752b7dadc3e93935c4473643ac459501855f69f) > Date: Mon, 14 Jan 2019 17:21:32 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Fix project path for absolute path resolution - Fix cron jobs for empty crontab use case --- [//]: # (END_SECTION d752b7dadc3e93935c4473643ac459501855f69f) [//]: # (START_SECTION 32f6c5185b7017a86944ab21ed861f3cda67ead2) ### Install Script Improvement > Commit: [32f6c5185b7017a86944ab21ed861f3cda67ead2](https://github.com/dOpensource/dsiprouter/commit/32f6c5185b7017a86944ab21ed861f3cda67ead2) > Date: Mon, 14 Jan 2019 15:19:01 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - create cronAppend and cronRemove library functions - replace overwriting cron commands - change permissions on git hook --- [//]: # (END_SECTION 32f6c5185b7017a86944ab21ed861f3cda67ead2) [//]: # (START_SECTION 763f35552506f642325d124def537b738dade694) ### Merge with Master > Commit: [763f35552506f642325d124def537b738dade694](https://github.com/dOpensource/dsiprouter/commit/763f35552506f642325d124def537b738dade694) > Date: Mon, 14 Jan 2019 14:29:25 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Merge with Master branch - update ami build to clone from feature branch --- [//]: # (END_SECTION 763f35552506f642325d124def537b738dade694) [//]: # (START_SECTION e17357b54f0392ae5559328fd404a1c02ad3373f) ### AMI updates > Commit: [e17357b54f0392ae5559328fd404a1c02ad3373f](https://github.com/dOpensource/dsiprouter/commit/e17357b54f0392ae5559328fd404a1c02ad3373f) > Date: Thu, 10 Jan 2019 13:12:12 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add getInstanceID to library script - allow independent execution of changelog hook - update ami bootstrap commands to be more robust - fix debian ami sys-maint user bug - fix PID check for startup process - fix python version check bug - added comments --- [//]: # (END_SECTION e17357b54f0392ae5559328fd404a1c02ad3373f) [//]: # (START_SECTION 494fc460f3533bca4e81fbb3cac52f381f0169ce) ### Update use-cases.rst > Commit: [494fc460f3533bca4e81fbb3cac52f381f0169ce](https://github.com/dOpensource/dsiprouter/commit/494fc460f3533bca4e81fbb3cac52f381f0169ce) > Date: Wed, 9 Jan 2019 15:46:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 494fc460f3533bca4e81fbb3cac52f381f0169ce) [//]: # (START_SECTION 353887e5360d94ce4ff5a0a891814aa2f03c1be0) ### Add Changelog > Commit: [353887e5360d94ce4ff5a0a891814aa2f03c1be0](https://github.com/dOpensource/dsiprouter/commit/353887e5360d94ce4ff5a0a891814aa2f03c1be0) > Date: Wed, 9 Jan 2019 09:27:47 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #81 - Add changelog markdown file - Add git hook for generating changelog --- [//]: # (END_SECTION 353887e5360d94ce4ff5a0a891814aa2f03c1be0) [//]: # (START_SECTION f673d614f9955d79b613eb248288d317349c5777) ### Update to Commit 2e7acf4 > Commit: [f673d614f9955d79b613eb248288d317349c5777](https://github.com/dOpensource/dsiprouter/commit/f673d614f9955d79b613eb248288d317349c5777) > Date: Mon, 7 Jan 2019 16:42:13 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - move AMI checks for debian before end of function - to ensure we do not return false positive to calling script --- [//]: # (END_SECTION f673d614f9955d79b613eb248288d317349c5777) [//]: # (START_SECTION 2e7acf4fe904c02ec796c6e8aebe73aa4364c073) ### AWS Image Debian Support > Commit: [2e7acf4fe904c02ec796c6e8aebe73aa4364c073](https://github.com/dOpensource/dsiprouter/commit/2e7acf4fe904c02ec796c6e8aebe73aa4364c073) > Date: Mon, 7 Jan 2019 16:34:28 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add support for debian AMI build - apply AWS AMI policies for debian build --- [//]: # (END_SECTION 2e7acf4fe904c02ec796c6e8aebe73aa4364c073) [//]: # (START_SECTION b6f4f1481ed3a36c3d6ae02cbca9a2877ddf6702) ### External IP BUG fix > Commit: [b6f4f1481ed3a36c3d6ae02cbca9a2877ddf6702](https://github.com/dOpensource/dsiprouter/commit/b6f4f1481ed3a36c3d6ae02cbca9a2877ddf6702) > Date: Fri, 4 Jan 2019 15:35:12 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - catch errors on external IP resolution failure - add commandline option for setting external ip - change permisions on ami build script --- [//]: # (END_SECTION b6f4f1481ed3a36c3d6ae02cbca9a2877ddf6702) [//]: # (START_SECTION e892735488702df24d6bc4d9b6847e5c13c57caa) ### Update use-cases.rst > Commit: [e892735488702df24d6bc4d9b6847e5c13c57caa](https://github.com/dOpensource/dsiprouter/commit/e892735488702df24d6bc4d9b6847e5c13c57caa) > Date: Thu, 3 Jan 2019 23:29:41 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e892735488702df24d6bc4d9b6847e5c13c57caa) [//]: # (START_SECTION c23f6f166ea3ee2e3a9c659b5e1763edc823420c) ### Updates for AMI install > Commit: [c23f6f166ea3ee2e3a9c659b5e1763edc823420c](https://github.com/dOpensource/dsiprouter/commit/c23f6f166ea3ee2e3a9c659b5e1763edc823420c) > Date: Wed, 2 Jan 2019 09:21:48 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - update debian-based install for unattended install --- [//]: # (END_SECTION c23f6f166ea3ee2e3a9c659b5e1763edc823420c) [//]: # (START_SECTION 5c2c32cf69b65865957b495122ea3252a1b74715) ### Update upgrade.rst > Commit: [5c2c32cf69b65865957b495122ea3252a1b74715](https://github.com/dOpensource/dsiprouter/commit/5c2c32cf69b65865957b495122ea3252a1b74715) > Date: Sat, 29 Dec 2018 14:47:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5c2c32cf69b65865957b495122ea3252a1b74715) [//]: # (START_SECTION 8319ce82acb079246cb64d185d99835bf195a11c) ### Fixed the install function so that dSIPRouter starts up after the install > Commit: [8319ce82acb079246cb64d185d99835bf195a11c](https://github.com/dOpensource/dsiprouter/commit/8319ce82acb079246cb64d185d99835bf195a11c) > Date: Sat, 29 Dec 2018 19:13:47 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8319ce82acb079246cb64d185d99835bf195a11c) [//]: # (START_SECTION a7b5433d897630789f687d9504b1d58cfaadb6ba) ### Update centos-install.rst > Commit: [a7b5433d897630789f687d9504b1d58cfaadb6ba](https://github.com/dOpensource/dsiprouter/commit/a7b5433d897630789f687d9504b1d58cfaadb6ba) > Date: Fri, 28 Dec 2018 18:17:17 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a7b5433d897630789f687d9504b1d58cfaadb6ba) [//]: # (START_SECTION 68f9b1f66a6579f058026aac150edf7d260c1e1b) ### Update centos-install.rst > Commit: [68f9b1f66a6579f058026aac150edf7d260c1e1b](https://github.com/dOpensource/dsiprouter/commit/68f9b1f66a6579f058026aac150edf7d260c1e1b) > Date: Fri, 28 Dec 2018 18:16:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 68f9b1f66a6579f058026aac150edf7d260c1e1b) [//]: # (START_SECTION fa306ebbebfe90ed57cecbf5d170a5670efc2151) ### Fixed an issue that stoped dSIPRouter from starting up after the install. Also, started to decouple the dSIPRouter UI from the rest of the install - Docker here we come > Commit: [fa306ebbebfe90ed57cecbf5d170a5670efc2151](https://github.com/dOpensource/dsiprouter/commit/fa306ebbebfe90ed57cecbf5d170a5670efc2151) > Date: Fri, 28 Dec 2018 23:14:33 +0000 > Author: root (mack@dsiprouter.org) > Committer: root (mack@dsiprouter.org) > Signed: --- [//]: # (END_SECTION fa306ebbebfe90ed57cecbf5d170a5670efc2151) [//]: # (START_SECTION a77115c64ec17be4382284d7e7da7ceffd81b796) ### Update centos-install.rst > Commit: [a77115c64ec17be4382284d7e7da7ceffd81b796](https://github.com/dOpensource/dsiprouter/commit/a77115c64ec17be4382284d7e7da7ceffd81b796) > Date: Fri, 28 Dec 2018 16:44:29 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a77115c64ec17be4382284d7e7da7ceffd81b796) [//]: # (START_SECTION 7c7e1cf85df43725857ab626818bb4f464a5fbed) ### Update centos-install.rst > Commit: [7c7e1cf85df43725857ab626818bb4f464a5fbed](https://github.com/dOpensource/dsiprouter/commit/7c7e1cf85df43725857ab626818bb4f464a5fbed) > Date: Fri, 28 Dec 2018 16:26:47 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7c7e1cf85df43725857ab626818bb4f464a5fbed) [//]: # (START_SECTION ba0abf4fe1b23978a2479d2e47d38777e88fe8da) ### Update centos-install.rst > Commit: [ba0abf4fe1b23978a2479d2e47d38777e88fe8da](https://github.com/dOpensource/dsiprouter/commit/ba0abf4fe1b23978a2479d2e47d38777e88fe8da) > Date: Fri, 28 Dec 2018 09:29:15 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ba0abf4fe1b23978a2479d2e47d38777e88fe8da) [//]: # (START_SECTION 5c9045fce266d3460c68c8a91db8c0bae73928d4) ### Update centos-install.rst > Commit: [5c9045fce266d3460c68c8a91db8c0bae73928d4](https://github.com/dOpensource/dsiprouter/commit/5c9045fce266d3460c68c8a91db8c0bae73928d4) > Date: Fri, 28 Dec 2018 09:27:50 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5c9045fce266d3460c68c8a91db8c0bae73928d4) [//]: # (START_SECTION 213dadb37638d3907b081787817dd0bd29c7801d) ### Update installing.rst > Commit: [213dadb37638d3907b081787817dd0bd29c7801d](https://github.com/dOpensource/dsiprouter/commit/213dadb37638d3907b081787817dd0bd29c7801d) > Date: Fri, 28 Dec 2018 08:55:49 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 213dadb37638d3907b081787817dd0bd29c7801d) [//]: # (START_SECTION 5dac9a380d67d645ef6ee855bc8db7bb2fa240f4) ### Update installing.rst > Commit: [5dac9a380d67d645ef6ee855bc8db7bb2fa240f4](https://github.com/dOpensource/dsiprouter/commit/5dac9a380d67d645ef6ee855bc8db7bb2fa240f4) > Date: Fri, 28 Dec 2018 08:49:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5dac9a380d67d645ef6ee855bc8db7bb2fa240f4) [//]: # (START_SECTION 56aadb779fa63e3751a4fe096fbf0f6fe0b8c6ff) ### Update centos-install.rst > Commit: [56aadb779fa63e3751a4fe096fbf0f6fe0b8c6ff](https://github.com/dOpensource/dsiprouter/commit/56aadb779fa63e3751a4fe096fbf0f6fe0b8c6ff) > Date: Fri, 28 Dec 2018 08:48:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 56aadb779fa63e3751a4fe096fbf0f6fe0b8c6ff) [//]: # (START_SECTION 380777056cadabb8b2088bc504764f6f5a988e3a) ### Update centos-install.rst > Commit: [380777056cadabb8b2088bc504764f6f5a988e3a](https://github.com/dOpensource/dsiprouter/commit/380777056cadabb8b2088bc504764f6f5a988e3a) > Date: Fri, 28 Dec 2018 08:48:15 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 380777056cadabb8b2088bc504764f6f5a988e3a) [//]: # (START_SECTION f8e12884d2b5b749d7f004a76bff484938ae9949) ### Create centos-install.rst > Commit: [f8e12884d2b5b749d7f004a76bff484938ae9949](https://github.com/dOpensource/dsiprouter/commit/f8e12884d2b5b749d7f004a76bff484938ae9949) > Date: Fri, 28 Dec 2018 08:45:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f8e12884d2b5b749d7f004a76bff484938ae9949) [//]: # (START_SECTION ff9cfc9e20b5804ed54ffac937af1c1d923c4b52) ### Update installing.rst > Commit: [ff9cfc9e20b5804ed54ffac937af1c1d923c4b52](https://github.com/dOpensource/dsiprouter/commit/ff9cfc9e20b5804ed54ffac937af1c1d923c4b52) > Date: Fri, 28 Dec 2018 08:44:38 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ff9cfc9e20b5804ed54ffac937af1c1d923c4b52) [//]: # (START_SECTION 11f42d1ca38c9a24d8c86d1e6f3b4f5697b62a50) ### Update debian_install.rst > Commit: [11f42d1ca38c9a24d8c86d1e6f3b4f5697b62a50](https://github.com/dOpensource/dsiprouter/commit/11f42d1ca38c9a24d8c86d1e6f3b4f5697b62a50) > Date: Fri, 28 Dec 2018 08:43:17 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 11f42d1ca38c9a24d8c86d1e6f3b4f5697b62a50) [//]: # (START_SECTION 8cd24935a6a68bc5da3f95ed7b8098c639b286a8) ### Update installing.rst > Commit: [8cd24935a6a68bc5da3f95ed7b8098c639b286a8](https://github.com/dOpensource/dsiprouter/commit/8cd24935a6a68bc5da3f95ed7b8098c639b286a8) > Date: Fri, 28 Dec 2018 08:41:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8cd24935a6a68bc5da3f95ed7b8098c639b286a8) [//]: # (START_SECTION 44db64adf744fca1005aaad64cb679c8e46480b9) ### Create debian_install.rst > Commit: [44db64adf744fca1005aaad64cb679c8e46480b9](https://github.com/dOpensource/dsiprouter/commit/44db64adf744fca1005aaad64cb679c8e46480b9) > Date: Fri, 28 Dec 2018 08:35:38 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 44db64adf744fca1005aaad64cb679c8e46480b9) [//]: # (START_SECTION 07a29500e828892fd6f9f3bd32791daa93523c33) ### Update installing.rst > Commit: [07a29500e828892fd6f9f3bd32791daa93523c33](https://github.com/dOpensource/dsiprouter/commit/07a29500e828892fd6f9f3bd32791daa93523c33) > Date: Fri, 28 Dec 2018 08:34:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 07a29500e828892fd6f9f3bd32791daa93523c33) [//]: # (START_SECTION 00d60f84e5beb610ad2d414caa16071ee8318c20) ### Fixed the CentOS 7 install so that MariaDB starts before Kamailio > Commit: [00d60f84e5beb610ad2d414caa16071ee8318c20](https://github.com/dOpensource/dsiprouter/commit/00d60f84e5beb610ad2d414caa16071ee8318c20) > Date: Fri, 28 Dec 2018 13:31:54 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 00d60f84e5beb610ad2d414caa16071ee8318c20) [//]: # (START_SECTION 9e6165884c69419cc706edbe695ee44bf594201c) ### Fixed RTPEngine > Commit: [9e6165884c69419cc706edbe695ee44bf594201c](https://github.com/dOpensource/dsiprouter/commit/9e6165884c69419cc706edbe695ee44bf594201c) > Date: Fri, 28 Dec 2018 10:04:49 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 9e6165884c69419cc706edbe695ee44bf594201c) [//]: # (START_SECTION db85442c55b983a53acf28b630c26156f5c78a7a) ### Removed the yum update from the RTPEngine install section for CentOS - it was causing us to reboot before completing the install of RTPEngine > Commit: [db85442c55b983a53acf28b630c26156f5c78a7a](https://github.com/dOpensource/dsiprouter/commit/db85442c55b983a53acf28b630c26156f5c78a7a) > Date: Fri, 28 Dec 2018 09:03:40 +0000 > Author: root (mack.hendricks@gmail.com) > Committer: root (mack.hendricks@gmail.com) > Signed: --- [//]: # (END_SECTION db85442c55b983a53acf28b630c26156f5c78a7a) [//]: # (START_SECTION a39c1523d84b91a9fb2153966c01f40545d263a9) ### Fixed issues with installing on CentOS 7 > Commit: [a39c1523d84b91a9fb2153966c01f40545d263a9](https://github.com/dOpensource/dsiprouter/commit/a39c1523d84b91a9fb2153966c01f40545d263a9) > Date: Fri, 28 Dec 2018 08:30:51 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION a39c1523d84b91a9fb2153966c01f40545d263a9) [//]: # (START_SECTION e4c32ffe5e1ae60996f08362524f0a31e156e580) ### Fixed the hostname of the service that provides the external ip of the server > Commit: [e4c32ffe5e1ae60996f08362524f0a31e156e580](https://github.com/dOpensource/dsiprouter/commit/e4c32ffe5e1ae60996f08362524f0a31e156e580) > Date: Thu, 27 Dec 2018 20:23:43 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e4c32ffe5e1ae60996f08362524f0a31e156e580) [//]: # (START_SECTION 53f6940f23637c92e9b3c7f2590489f1a04fee8b) ### Fixed the hostname of the service that provides the external ip of the server > Commit: [53f6940f23637c92e9b3c7f2590489f1a04fee8b](https://github.com/dOpensource/dsiprouter/commit/53f6940f23637c92e9b3c7f2590489f1a04fee8b) > Date: Thu, 27 Dec 2018 20:23:43 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 53f6940f23637c92e9b3c7f2590489f1a04fee8b) [//]: # (START_SECTION a31a5aab83265cae124030e31afb2d8161517a49) ### AMI build updates > Commit: [a31a5aab83265cae124030e31afb2d8161517a49](https://github.com/dOpensource/dsiprouter/commit/a31a5aab83265cae124030e31afb2d8161517a49) > Date: Fri, 21 Dec 2018 16:35:14 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add ami build script for v0.52 - fix firewalld not started bug - change rtpengine install to bootstrap on restart of AMI image - fix rc.local format bug from last commit - add descriptive comments - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION a31a5aab83265cae124030e31afb2d8161517a49) [//]: # (START_SECTION 4e66d02f7be46804f4291e52a2913dca11398bb4) ### AMI image pw reset fix > Commit: [4e66d02f7be46804f4291e52a2913dca11398bb4](https://github.com/dOpensource/dsiprouter/commit/4e66d02f7be46804f4291e52a2913dca11398bb4) > Date: Fri, 21 Dec 2018 13:50:05 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix AMI pw reset bug --- [//]: # (END_SECTION 4e66d02f7be46804f4291e52a2913dca11398bb4) [//]: # (START_SECTION d9c9a4e85c250fdfc52889e313e88bab93361a67) ### Fix AMI bootstrap file > Commit: [d9c9a4e85c250fdfc52889e313e88bab93361a67](https://github.com/dOpensource/dsiprouter/commit/d9c9a4e85c250fdfc52889e313e88bab93361a67) > Date: Fri, 21 Dec 2018 13:32:35 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fixed bootstrap file test --- [//]: # (END_SECTION d9c9a4e85c250fdfc52889e313e88bab93361a67) [//]: # (START_SECTION 3d427e156f02e5ae80971c5adedab736e4570218) ### Updates for AMI image install > Commit: [3d427e156f02e5ae80971c5adedab736e4570218](https://github.com/dOpensource/dsiprouter/commit/3d427e156f02e5ae80971c5adedab736e4570218) > Date: Fri, 21 Dec 2018 12:34:30 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix bootstrap file not to interfere with other cmds - fix centos rtpengine install - make centos rtpengine failure stop install --- [//]: # (END_SECTION 3d427e156f02e5ae80971c5adedab736e4570218) [//]: # (START_SECTION 2817457d03d14b9b8c919d14a4398ad11a9724f2) ### Fixes to AMI image support > Commit: [2817457d03d14b9b8c919d14a4398ad11a9724f2](https://github.com/dOpensource/dsiprouter/commit/2817457d03d14b9b8c919d14a4398ad11a9724f2) > Date: Fri, 21 Dec 2018 11:50:49 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - fix logical check for bootstrap file - add cmdExists function to dsip_lib --- [//]: # (END_SECTION 2817457d03d14b9b8c919d14a4398ad11a9724f2) [//]: # (START_SECTION cf8d21c1423bee2d235b7d2997fb08b13cb2576c) ### Updated restart message for AMI instances. > Commit: [cf8d21c1423bee2d235b7d2997fb08b13cb2576c](https://github.com/dOpensource/dsiprouter/commit/cf8d21c1423bee2d235b7d2997fb08b13cb2576c) > Date: Fri, 21 Dec 2018 11:34:04 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION cf8d21c1423bee2d235b7d2997fb08b13cb2576c) [//]: # (START_SECTION 461216e738dfa7790d86d0bf5fa32a94f22f6b30) ### Add support for AMI images > Commit: [461216e738dfa7790d86d0bf5fa32a94f22f6b30](https://github.com/dOpensource/dsiprouter/commit/461216e738dfa7790d86d0bf5fa32a94f22f6b30) > Date: Fri, 21 Dec 2018 11:21:36 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - add support for installing on AMI images - fixed small typos in install script - fixed error message when restarting process - fixed centos kernel headers install issue --- [//]: # (END_SECTION 461216e738dfa7790d86d0bf5fa32a94f22f6b30) [//]: # (START_SECTION 6fb37dafdc35a91ac066c8b82598db9a565c5c6a) ### Add files via upload > Commit: [6fb37dafdc35a91ac066c8b82598db9a565c5c6a](https://github.com/dOpensource/dsiprouter/commit/6fb37dafdc35a91ac066c8b82598db9a565c5c6a) > Date: Wed, 19 Dec 2018 15:03:28 -0600 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6fb37dafdc35a91ac066c8b82598db9a565c5c6a) [//]: # (START_SECTION dcda7bacf833e3388f96f369bc8e1a713cfff943) ### Update use-cases.rst > Commit: [dcda7bacf833e3388f96f369bc8e1a713cfff943](https://github.com/dOpensource/dsiprouter/commit/dcda7bacf833e3388f96f369bc8e1a713cfff943) > Date: Wed, 19 Dec 2018 10:18:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION dcda7bacf833e3388f96f369bc8e1a713cfff943) [//]: # (START_SECTION 6b7441318d53089d8178c5fd92d47b625836c38c) ### Update use-cases.rst > Commit: [6b7441318d53089d8178c5fd92d47b625836c38c](https://github.com/dOpensource/dsiprouter/commit/6b7441318d53089d8178c5fd92d47b625836c38c) > Date: Wed, 19 Dec 2018 10:12:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 6b7441318d53089d8178c5fd92d47b625836c38c) [//]: # (START_SECTION ed1d68e393d336f3860968921d17a19c080f82ef) ### Update use-cases.rst > Commit: [ed1d68e393d336f3860968921d17a19c080f82ef](https://github.com/dOpensource/dsiprouter/commit/ed1d68e393d336f3860968921d17a19c080f82ef) > Date: Wed, 19 Dec 2018 10:11:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION ed1d68e393d336f3860968921d17a19c080f82ef) [//]: # (START_SECTION 5165fdbdf164623a319c97df2d3a1dda239bc350) ### Update use-cases.rst > Commit: [5165fdbdf164623a319c97df2d3a1dda239bc350](https://github.com/dOpensource/dsiprouter/commit/5165fdbdf164623a319c97df2d3a1dda239bc350) > Date: Wed, 19 Dec 2018 10:10:04 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 5165fdbdf164623a319c97df2d3a1dda239bc350) [//]: # (START_SECTION edcd5b42498f1658c88938b38d66ef23b3e997b0) ### Update use-cases.rst > Commit: [edcd5b42498f1658c88938b38d66ef23b3e997b0](https://github.com/dOpensource/dsiprouter/commit/edcd5b42498f1658c88938b38d66ef23b3e997b0) > Date: Wed, 19 Dec 2018 10:07:45 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION edcd5b42498f1658c88938b38d66ef23b3e997b0) [//]: # (START_SECTION a8f51d584f6de535a545173d2e714c34c9f057d5) ### Update use-cases.rst > Commit: [a8f51d584f6de535a545173d2e714c34c9f057d5](https://github.com/dOpensource/dsiprouter/commit/a8f51d584f6de535a545173d2e714c34c9f057d5) > Date: Wed, 19 Dec 2018 10:05:32 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION a8f51d584f6de535a545173d2e714c34c9f057d5) [//]: # (START_SECTION 921ad20d8c0baebf6ad19398fca666347e17305b) ### Update use-cases.rst > Commit: [921ad20d8c0baebf6ad19398fca666347e17305b](https://github.com/dOpensource/dsiprouter/commit/921ad20d8c0baebf6ad19398fca666347e17305b) > Date: Wed, 19 Dec 2018 09:59:21 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 921ad20d8c0baebf6ad19398fca666347e17305b) [//]: # (START_SECTION 44fe70810398f30422f01c23f0b4d5119e5ac269) ### Update use-cases.rst > Commit: [44fe70810398f30422f01c23f0b4d5119e5ac269](https://github.com/dOpensource/dsiprouter/commit/44fe70810398f30422f01c23f0b4d5119e5ac269) > Date: Wed, 19 Dec 2018 09:57:55 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 44fe70810398f30422f01c23f0b4d5119e5ac269) [//]: # (START_SECTION 04d3d7dbe9aa5b49e4e6edcabaa73de93a99007b) ### Update use-cases.rst > Commit: [04d3d7dbe9aa5b49e4e6edcabaa73de93a99007b](https://github.com/dOpensource/dsiprouter/commit/04d3d7dbe9aa5b49e4e6edcabaa73de93a99007b) > Date: Wed, 19 Dec 2018 09:57:05 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 04d3d7dbe9aa5b49e4e6edcabaa73de93a99007b) [//]: # (START_SECTION eed48aadbcb3a0099d6339b16d0ca256efd5b2eb) ### Update use-cases.rst > Commit: [eed48aadbcb3a0099d6339b16d0ca256efd5b2eb](https://github.com/dOpensource/dsiprouter/commit/eed48aadbcb3a0099d6339b16d0ca256efd5b2eb) > Date: Wed, 19 Dec 2018 06:59:11 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION eed48aadbcb3a0099d6339b16d0ca256efd5b2eb) [//]: # (START_SECTION 77f487b70306be50873167a363b7cbc455ac7a1e) ### Update use-cases.rst > Commit: [77f487b70306be50873167a363b7cbc455ac7a1e](https://github.com/dOpensource/dsiprouter/commit/77f487b70306be50873167a363b7cbc455ac7a1e) > Date: Wed, 19 Dec 2018 06:57:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION 77f487b70306be50873167a363b7cbc455ac7a1e) [//]: # (START_SECTION bdc2997ed709155a166218a613f78877bc8adf99) ### Fixed the BYE issue #56 for FusionPBX as well > Commit: [bdc2997ed709155a166218a613f78877bc8adf99](https://github.com/dOpensource/dsiprouter/commit/bdc2997ed709155a166218a613f78877bc8adf99) > Date: Wed, 19 Dec 2018 19:05:34 +0000 > Author: root (root@debian-post51.localdomain) > Committer: root (root@debian-post51.localdomain) > Signed: --- [//]: # (END_SECTION bdc2997ed709155a166218a613f78877bc8adf99) [//]: # (START_SECTION e5f2cd20edad11523f89f70fb5a9a0bbe976e220) ### Fixed issue #56 > Commit: [e5f2cd20edad11523f89f70fb5a9a0bbe976e220](https://github.com/dOpensource/dsiprouter/commit/e5f2cd20edad11523f89f70fb5a9a0bbe976e220) > Date: Wed, 19 Dec 2018 17:40:05 +0000 > Author: root (root@demo-dsiprouter.localdomain) > Committer: root (root@demo-dsiprouter.localdomain) > Signed: --- [//]: # (END_SECTION e5f2cd20edad11523f89f70fb5a9a0bbe976e220) [//]: # (START_SECTION c8f7d9b4118bae93328cd9937a42cf078e12c989) ### Update use-cases.rst > Commit: [c8f7d9b4118bae93328cd9937a42cf078e12c989](https://github.com/dOpensource/dsiprouter/commit/c8f7d9b4118bae93328cd9937a42cf078e12c989) > Date: Wed, 19 Dec 2018 10:29:40 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c8f7d9b4118bae93328cd9937a42cf078e12c989) [//]: # (START_SECTION 3255032cd4fa5aaa8339ae3307fcc40e2c1e5526) ### Update use-cases.rst > Commit: [3255032cd4fa5aaa8339ae3307fcc40e2c1e5526](https://github.com/dOpensource/dsiprouter/commit/3255032cd4fa5aaa8339ae3307fcc40e2c1e5526) > Date: Wed, 19 Dec 2018 10:20:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3255032cd4fa5aaa8339ae3307fcc40e2c1e5526) [//]: # (START_SECTION fcb9686346d364b4d16eb7bebaebef1c9bbee05b) ### Update use-cases.rst > Commit: [fcb9686346d364b4d16eb7bebaebef1c9bbee05b](https://github.com/dOpensource/dsiprouter/commit/fcb9686346d364b4d16eb7bebaebef1c9bbee05b) > Date: Wed, 19 Dec 2018 10:18:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fcb9686346d364b4d16eb7bebaebef1c9bbee05b) [//]: # (START_SECTION 453da82b53c59342919230bb97ac103e7d19f5ca) ### Update use-cases.rst > Commit: [453da82b53c59342919230bb97ac103e7d19f5ca](https://github.com/dOpensource/dsiprouter/commit/453da82b53c59342919230bb97ac103e7d19f5ca) > Date: Wed, 19 Dec 2018 10:12:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 453da82b53c59342919230bb97ac103e7d19f5ca) [//]: # (START_SECTION 5df0b5ada91bead8b0c1ce43133ce31bb988721d) ### Update use-cases.rst > Commit: [5df0b5ada91bead8b0c1ce43133ce31bb988721d](https://github.com/dOpensource/dsiprouter/commit/5df0b5ada91bead8b0c1ce43133ce31bb988721d) > Date: Wed, 19 Dec 2018 10:11:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5df0b5ada91bead8b0c1ce43133ce31bb988721d) [//]: # (START_SECTION 0c92eda77ba173040b0a35da9232869f02e23b31) ### Update use-cases.rst > Commit: [0c92eda77ba173040b0a35da9232869f02e23b31](https://github.com/dOpensource/dsiprouter/commit/0c92eda77ba173040b0a35da9232869f02e23b31) > Date: Wed, 19 Dec 2018 10:10:04 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0c92eda77ba173040b0a35da9232869f02e23b31) [//]: # (START_SECTION c7f3890d56ca343518535df5254586262c26ca14) ### Update use-cases.rst > Commit: [c7f3890d56ca343518535df5254586262c26ca14](https://github.com/dOpensource/dsiprouter/commit/c7f3890d56ca343518535df5254586262c26ca14) > Date: Wed, 19 Dec 2018 10:07:45 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c7f3890d56ca343518535df5254586262c26ca14) [//]: # (START_SECTION f3622aab4b6f207b1ce84f7997dc3650f3f50a4e) ### Update use-cases.rst > Commit: [f3622aab4b6f207b1ce84f7997dc3650f3f50a4e](https://github.com/dOpensource/dsiprouter/commit/f3622aab4b6f207b1ce84f7997dc3650f3f50a4e) > Date: Wed, 19 Dec 2018 10:05:32 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f3622aab4b6f207b1ce84f7997dc3650f3f50a4e) [//]: # (START_SECTION ee463965c59f9fd8f31c162345be3784dd08ae92) ### Update use-cases.rst > Commit: [ee463965c59f9fd8f31c162345be3784dd08ae92](https://github.com/dOpensource/dsiprouter/commit/ee463965c59f9fd8f31c162345be3784dd08ae92) > Date: Wed, 19 Dec 2018 09:59:21 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ee463965c59f9fd8f31c162345be3784dd08ae92) [//]: # (START_SECTION 0616d64521ad05f27ec02c43467f4246d20439d3) ### Update use-cases.rst > Commit: [0616d64521ad05f27ec02c43467f4246d20439d3](https://github.com/dOpensource/dsiprouter/commit/0616d64521ad05f27ec02c43467f4246d20439d3) > Date: Wed, 19 Dec 2018 09:57:55 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0616d64521ad05f27ec02c43467f4246d20439d3) [//]: # (START_SECTION 97fe92ebc2bba8e18fdf11834703cd25a9889e55) ### Update use-cases.rst > Commit: [97fe92ebc2bba8e18fdf11834703cd25a9889e55](https://github.com/dOpensource/dsiprouter/commit/97fe92ebc2bba8e18fdf11834703cd25a9889e55) > Date: Wed, 19 Dec 2018 09:57:05 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 97fe92ebc2bba8e18fdf11834703cd25a9889e55) [//]: # (START_SECTION 8cc732379d61f12545214351227b96d33e81d6c8) ### Add files via upload > Commit: [8cc732379d61f12545214351227b96d33e81d6c8](https://github.com/dOpensource/dsiprouter/commit/8cc732379d61f12545214351227b96d33e81d6c8) > Date: Wed, 19 Dec 2018 08:56:39 -0600 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8cc732379d61f12545214351227b96d33e81d6c8) [//]: # (START_SECTION 0817c9ad7c76c8dc9f6c04a5b64230ceac788eb8) ### Update use-cases.rst > Commit: [0817c9ad7c76c8dc9f6c04a5b64230ceac788eb8](https://github.com/dOpensource/dsiprouter/commit/0817c9ad7c76c8dc9f6c04a5b64230ceac788eb8) > Date: Wed, 19 Dec 2018 06:59:11 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0817c9ad7c76c8dc9f6c04a5b64230ceac788eb8) [//]: # (START_SECTION daab25528053c5430942d3734593046b09c0735a) ### Update use-cases.rst > Commit: [daab25528053c5430942d3734593046b09c0735a](https://github.com/dOpensource/dsiprouter/commit/daab25528053c5430942d3734593046b09c0735a) > Date: Wed, 19 Dec 2018 06:57:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION daab25528053c5430942d3734593046b09c0735a) [//]: # (START_SECTION 6a30501a6e9356518a6d47951b7d201ae7462c00) ### Added files for documenting FreePBX - Pass Thru > Commit: [6a30501a6e9356518a6d47951b7d201ae7462c00](https://github.com/dOpensource/dsiprouter/commit/6a30501a6e9356518a6d47951b7d201ae7462c00) > Date: Wed, 19 Dec 2018 05:53:52 -0600 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a30501a6e9356518a6d47951b7d201ae7462c00) [//]: # (START_SECTION fa8222a169bfa56c56addb8cfb8e2782a0b291c1) ### Update index.rst > Commit: [fa8222a169bfa56c56addb8cfb8e2782a0b291c1](https://github.com/dOpensource/dsiprouter/commit/fa8222a169bfa56c56addb8cfb8e2782a0b291c1) > Date: Tue, 18 Dec 2018 05:43:29 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fa8222a169bfa56c56addb8cfb8e2782a0b291c1) [//]: # (START_SECTION 609550397bb6bde78991a8eea0701a9380113af6) ### Update upgrade.rst > Commit: [609550397bb6bde78991a8eea0701a9380113af6](https://github.com/dOpensource/dsiprouter/commit/609550397bb6bde78991a8eea0701a9380113af6) > Date: Tue, 18 Dec 2018 05:28:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 609550397bb6bde78991a8eea0701a9380113af6) [//]: # (START_SECTION 3d47aeae457dcaf92ed21b63d134359ed50e1af3) ### Update index.rst > Commit: [3d47aeae457dcaf92ed21b63d134359ed50e1af3](https://github.com/dOpensource/dsiprouter/commit/3d47aeae457dcaf92ed21b63d134359ed50e1af3) > Date: Mon, 17 Dec 2018 10:22:00 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d47aeae457dcaf92ed21b63d134359ed50e1af3) [//]: # (START_SECTION f3543191335117632af7ee612dc7b1f5e65f92ba) ### Update index.rst > Commit: [f3543191335117632af7ee612dc7b1f5e65f92ba](https://github.com/dOpensource/dsiprouter/commit/f3543191335117632af7ee612dc7b1f5e65f92ba) > Date: Mon, 17 Dec 2018 10:21:30 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f3543191335117632af7ee612dc7b1f5e65f92ba) [//]: # (START_SECTION 9dfa4e37ad094fb0c2076e0d03f547700c9a2250) ### Fixed domain support > Commit: [9dfa4e37ad094fb0c2076e0d03f547700c9a2250](https://github.com/dOpensource/dsiprouter/commit/9dfa4e37ad094fb0c2076e0d03f547700c9a2250) > Date: Mon, 17 Dec 2018 12:02:33 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 9dfa4e37ad094fb0c2076e0d03f547700c9a2250) [//]: # (START_SECTION c6ca8da2268aeb7471a4a7e7ecb0a91b6c1403f7) ### Update index.rst > Commit: [c6ca8da2268aeb7471a4a7e7ecb0a91b6c1403f7](https://github.com/dOpensource/dsiprouter/commit/c6ca8da2268aeb7471a4a7e7ecb0a91b6c1403f7) > Date: Sat, 15 Dec 2018 04:37:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c6ca8da2268aeb7471a4a7e7ecb0a91b6c1403f7) [//]: # (START_SECTION a2ea8cd20379fda7bde5886f59498235de34ad2d) ### Update upgrade.rst > Commit: [a2ea8cd20379fda7bde5886f59498235de34ad2d](https://github.com/dOpensource/dsiprouter/commit/a2ea8cd20379fda7bde5886f59498235de34ad2d) > Date: Fri, 14 Dec 2018 15:46:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a2ea8cd20379fda7bde5886f59498235de34ad2d) [//]: # (START_SECTION cbd26d3ca965a86c1ca286e0291ea0b7cd6faf31) ### Update upgrade.rst > Commit: [cbd26d3ca965a86c1ca286e0291ea0b7cd6faf31](https://github.com/dOpensource/dsiprouter/commit/cbd26d3ca965a86c1ca286e0291ea0b7cd6faf31) > Date: Fri, 14 Dec 2018 15:41:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cbd26d3ca965a86c1ca286e0291ea0b7cd6faf31) [//]: # (START_SECTION ce8adf1b5273038f21dfc5dd8b7b74249e395a95) ### Update upgrade.rst > Commit: [ce8adf1b5273038f21dfc5dd8b7b74249e395a95](https://github.com/dOpensource/dsiprouter/commit/ce8adf1b5273038f21dfc5dd8b7b74249e395a95) > Date: Fri, 14 Dec 2018 15:35:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ce8adf1b5273038f21dfc5dd8b7b74249e395a95) [//]: # (START_SECTION 3dc44779f8e0875e678ea03c20e17c9d445024ad) ### Update upgrade.rst > Commit: [3dc44779f8e0875e678ea03c20e17c9d445024ad](https://github.com/dOpensource/dsiprouter/commit/3dc44779f8e0875e678ea03c20e17c9d445024ad) > Date: Fri, 14 Dec 2018 15:15:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3dc44779f8e0875e678ea03c20e17c9d445024ad) [//]: # (START_SECTION 6237b951c3b52feae6841d91aa8e10a25f583921) ### Update upgrade.rst > Commit: [6237b951c3b52feae6841d91aa8e10a25f583921](https://github.com/dOpensource/dsiprouter/commit/6237b951c3b52feae6841d91aa8e10a25f583921) > Date: Fri, 14 Dec 2018 15:13:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6237b951c3b52feae6841d91aa8e10a25f583921) [//]: # (START_SECTION b8346484331f3344c4b5c1f9caa75cdc542861e2) ### Add files via upload > Commit: [b8346484331f3344c4b5c1f9caa75cdc542861e2](https://github.com/dOpensource/dsiprouter/commit/b8346484331f3344c4b5c1f9caa75cdc542861e2) > Date: Fri, 14 Dec 2018 15:12:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b8346484331f3344c4b5c1f9caa75cdc542861e2) [//]: # (START_SECTION 03adc3156bd16c5fcdbb83619bc0770524930361) ### Update upgrade.rst > Commit: [03adc3156bd16c5fcdbb83619bc0770524930361](https://github.com/dOpensource/dsiprouter/commit/03adc3156bd16c5fcdbb83619bc0770524930361) > Date: Fri, 14 Dec 2018 15:11:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 03adc3156bd16c5fcdbb83619bc0770524930361) [//]: # (START_SECTION 3cb41569351838ab46ff9f533d06d9b4ae3d89fc) ### Update upgrade.rst > Commit: [3cb41569351838ab46ff9f533d06d9b4ae3d89fc](https://github.com/dOpensource/dsiprouter/commit/3cb41569351838ab46ff9f533d06d9b4ae3d89fc) > Date: Fri, 14 Dec 2018 15:01:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3cb41569351838ab46ff9f533d06d9b4ae3d89fc) [//]: # (START_SECTION f01c855e592c9851b9c55d7db90b5969fd426868) ### Update upgrade.rst > Commit: [f01c855e592c9851b9c55d7db90b5969fd426868](https://github.com/dOpensource/dsiprouter/commit/f01c855e592c9851b9c55d7db90b5969fd426868) > Date: Fri, 14 Dec 2018 14:59:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f01c855e592c9851b9c55d7db90b5969fd426868) [//]: # (START_SECTION 57395c0f477f7c865266322d3556c7583a5e72a6) ### Update upgrade.rst > Commit: [57395c0f477f7c865266322d3556c7583a5e72a6](https://github.com/dOpensource/dsiprouter/commit/57395c0f477f7c865266322d3556c7583a5e72a6) > Date: Fri, 14 Dec 2018 14:51:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 57395c0f477f7c865266322d3556c7583a5e72a6) [//]: # (START_SECTION 8aa1830a3e65eb9e093a055d59110c69094d6242) ### Update upgrade.rst > Commit: [8aa1830a3e65eb9e093a055d59110c69094d6242](https://github.com/dOpensource/dsiprouter/commit/8aa1830a3e65eb9e093a055d59110c69094d6242) > Date: Fri, 14 Dec 2018 14:49:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8aa1830a3e65eb9e093a055d59110c69094d6242) [//]: # (START_SECTION 9f19dcce147e3b82ba5d8a3fea49332c13503366) ### Update upgrade.rst > Commit: [9f19dcce147e3b82ba5d8a3fea49332c13503366](https://github.com/dOpensource/dsiprouter/commit/9f19dcce147e3b82ba5d8a3fea49332c13503366) > Date: Fri, 14 Dec 2018 14:21:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9f19dcce147e3b82ba5d8a3fea49332c13503366) [//]: # (START_SECTION 746ec4c40b56b3f5cf26d83e5c1bc11a4f21359b) ### Update index.rst > Commit: [746ec4c40b56b3f5cf26d83e5c1bc11a4f21359b](https://github.com/dOpensource/dsiprouter/commit/746ec4c40b56b3f5cf26d83e5c1bc11a4f21359b) > Date: Fri, 14 Dec 2018 14:18:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 746ec4c40b56b3f5cf26d83e5c1bc11a4f21359b) [//]: # (START_SECTION 3c1c4292af8ec1c90c5b070aa9048bd430f1844b) ### Create upgrade.rst > Commit: [3c1c4292af8ec1c90c5b070aa9048bd430f1844b](https://github.com/dOpensource/dsiprouter/commit/3c1c4292af8ec1c90c5b070aa9048bd430f1844b) > Date: Fri, 14 Dec 2018 14:14:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3c1c4292af8ec1c90c5b070aa9048bd430f1844b) [//]: # (START_SECTION a59a27bda0a5ba38bc4221b9e7855319e7ab36c1) ### Update index.rst > Commit: [a59a27bda0a5ba38bc4221b9e7855319e7ab36c1](https://github.com/dOpensource/dsiprouter/commit/a59a27bda0a5ba38bc4221b9e7855319e7ab36c1) > Date: Fri, 14 Dec 2018 14:12:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a59a27bda0a5ba38bc4221b9e7855319e7ab36c1) [//]: # (START_SECTION 09bf9da137d19cbb57e15cdece55bff490238364) ### Update README.md > Commit: [09bf9da137d19cbb57e15cdece55bff490238364](https://github.com/dOpensource/dsiprouter/commit/09bf9da137d19cbb57e15cdece55bff490238364) > Date: Fri, 14 Dec 2018 13:40:17 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 09bf9da137d19cbb57e15cdece55bff490238364) [//]: # (START_SECTION f9afd41bcdc50468b2b12333789f3ecaa598443c) ### Update README.md > Commit: [f9afd41bcdc50468b2b12333789f3ecaa598443c](https://github.com/dOpensource/dsiprouter/commit/f9afd41bcdc50468b2b12333789f3ecaa598443c) > Date: Fri, 14 Dec 2018 13:39:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f9afd41bcdc50468b2b12333789f3ecaa598443c) [//]: # (START_SECTION 0835acd461d8403200f1dcbdc6840146651d3dc5) ### Update index.rst > Commit: [0835acd461d8403200f1dcbdc6840146651d3dc5](https://github.com/dOpensource/dsiprouter/commit/0835acd461d8403200f1dcbdc6840146651d3dc5) > Date: Fri, 14 Dec 2018 13:13:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0835acd461d8403200f1dcbdc6840146651d3dc5) [//]: # (START_SECTION 0bc8459a29b18b69b630ff293e1b5cbdfe240353) ### Update resources.rst > Commit: [0bc8459a29b18b69b630ff293e1b5cbdfe240353](https://github.com/dOpensource/dsiprouter/commit/0bc8459a29b18b69b630ff293e1b5cbdfe240353) > Date: Fri, 14 Dec 2018 13:11:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0bc8459a29b18b69b630ff293e1b5cbdfe240353) [//]: # (START_SECTION 0d81ecee5c630442421dd4bef7825de9841e189e) ### Update configuring.rst > Commit: [0d81ecee5c630442421dd4bef7825de9841e189e](https://github.com/dOpensource/dsiprouter/commit/0d81ecee5c630442421dd4bef7825de9841e189e) > Date: Fri, 14 Dec 2018 13:08:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0d81ecee5c630442421dd4bef7825de9841e189e) [//]: # (START_SECTION 392844aa937cc2abb498a179debd22f8b48b8072) ### Update resources.rst > Commit: [392844aa937cc2abb498a179debd22f8b48b8072](https://github.com/dOpensource/dsiprouter/commit/392844aa937cc2abb498a179debd22f8b48b8072) > Date: Fri, 14 Dec 2018 13:06:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 392844aa937cc2abb498a179debd22f8b48b8072) [//]: # (START_SECTION 92f73f2a4e887e1a18c4fbbf58e6e29f0672990e) ### Update configuring.rst > Commit: [92f73f2a4e887e1a18c4fbbf58e6e29f0672990e](https://github.com/dOpensource/dsiprouter/commit/92f73f2a4e887e1a18c4fbbf58e6e29f0672990e) > Date: Fri, 14 Dec 2018 13:04:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92f73f2a4e887e1a18c4fbbf58e6e29f0672990e) [//]: # (START_SECTION 9d43e76dd04598dff4e2d90dc086ce1126c75a8f) ### Update index.rst > Commit: [9d43e76dd04598dff4e2d90dc086ce1126c75a8f](https://github.com/dOpensource/dsiprouter/commit/9d43e76dd04598dff4e2d90dc086ce1126c75a8f) > Date: Fri, 14 Dec 2018 12:55:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9d43e76dd04598dff4e2d90dc086ce1126c75a8f) [//]: # (START_SECTION 94d678fd6d46196d9c35a0eef6ba84ae9b794b5b) ### Fixed the Global Outbound Route issue that prevented routes from being saved > Commit: [94d678fd6d46196d9c35a0eef6ba84ae9b794b5b](https://github.com/dOpensource/dsiprouter/commit/94d678fd6d46196d9c35a0eef6ba84ae9b794b5b) > Date: Fri, 14 Dec 2018 17:50:50 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 94d678fd6d46196d9c35a0eef6ba84ae9b794b5b) [//]: # (START_SECTION 1c49f296e9184f7e2bcbfada9de19d2dca8d315f) ### Update configuring.rst > Commit: [1c49f296e9184f7e2bcbfada9de19d2dca8d315f](https://github.com/dOpensource/dsiprouter/commit/1c49f296e9184f7e2bcbfada9de19d2dca8d315f) > Date: Fri, 14 Dec 2018 12:50:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c49f296e9184f7e2bcbfada9de19d2dca8d315f) [//]: # (START_SECTION 0434d161c35d7fd70eb1a28a9376fdd55e8af2ea) ### Update index.rst > Commit: [0434d161c35d7fd70eb1a28a9376fdd55e8af2ea](https://github.com/dOpensource/dsiprouter/commit/0434d161c35d7fd70eb1a28a9376fdd55e8af2ea) > Date: Fri, 14 Dec 2018 12:48:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0434d161c35d7fd70eb1a28a9376fdd55e8af2ea) [//]: # (START_SECTION 4922aad478a5f06bdacec005c741dd4fef76a13f) ### Update configuring.rst > Commit: [4922aad478a5f06bdacec005c741dd4fef76a13f](https://github.com/dOpensource/dsiprouter/commit/4922aad478a5f06bdacec005c741dd4fef76a13f) > Date: Fri, 14 Dec 2018 12:02:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4922aad478a5f06bdacec005c741dd4fef76a13f) [//]: # (START_SECTION a490a980257e7fbd63b67accfbecfaeab2ee6150) ### Update index.rst > Commit: [a490a980257e7fbd63b67accfbecfaeab2ee6150](https://github.com/dOpensource/dsiprouter/commit/a490a980257e7fbd63b67accfbecfaeab2ee6150) > Date: Fri, 14 Dec 2018 11:58:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a490a980257e7fbd63b67accfbecfaeab2ee6150) [//]: # (START_SECTION c1200780b220e4b1793e392ef033325e1feb371c) ### Update use-cases.rst > Commit: [c1200780b220e4b1793e392ef033325e1feb371c](https://github.com/dOpensource/dsiprouter/commit/c1200780b220e4b1793e392ef033325e1feb371c) > Date: Fri, 14 Dec 2018 11:29:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c1200780b220e4b1793e392ef033325e1feb371c) [//]: # (START_SECTION a725efdb0090b911f25c5d59c2c80fc2fc4e71f5) ### Update use-cases.rst > Commit: [a725efdb0090b911f25c5d59c2c80fc2fc4e71f5](https://github.com/dOpensource/dsiprouter/commit/a725efdb0090b911f25c5d59c2c80fc2fc4e71f5) > Date: Fri, 14 Dec 2018 11:25:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a725efdb0090b911f25c5d59c2c80fc2fc4e71f5) [//]: # (START_SECTION 09aa41c30a0f6f4f22f4f10680ddb540acfbe892) ### Update use-cases.rst > Commit: [09aa41c30a0f6f4f22f4f10680ddb540acfbe892](https://github.com/dOpensource/dsiprouter/commit/09aa41c30a0f6f4f22f4f10680ddb540acfbe892) > Date: Fri, 14 Dec 2018 11:25:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 09aa41c30a0f6f4f22f4f10680ddb540acfbe892) [//]: # (START_SECTION f4f8ab64127207693ac48d3ff6cf3ddfdfa41aea) ### Update use-cases.rst > Commit: [f4f8ab64127207693ac48d3ff6cf3ddfdfa41aea](https://github.com/dOpensource/dsiprouter/commit/f4f8ab64127207693ac48d3ff6cf3ddfdfa41aea) > Date: Fri, 14 Dec 2018 11:22:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f4f8ab64127207693ac48d3ff6cf3ddfdfa41aea) [//]: # (START_SECTION 8f68f34dfaffc9a8f17f12472a5e5fb8581e5219) ### Add files via upload > Commit: [8f68f34dfaffc9a8f17f12472a5e5fb8581e5219](https://github.com/dOpensource/dsiprouter/commit/8f68f34dfaffc9a8f17f12472a5e5fb8581e5219) > Date: Fri, 14 Dec 2018 11:21:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8f68f34dfaffc9a8f17f12472a5e5fb8581e5219) [//]: # (START_SECTION 96a104220f748e43a43efd15249e1ee2fe932144) ### Update use-cases.rst > Commit: [96a104220f748e43a43efd15249e1ee2fe932144](https://github.com/dOpensource/dsiprouter/commit/96a104220f748e43a43efd15249e1ee2fe932144) > Date: Fri, 14 Dec 2018 10:34:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 96a104220f748e43a43efd15249e1ee2fe932144) [//]: # (START_SECTION cda111f1c8a28c8abdc93ce2843b2c5193173b89) ### Update use-cases.rst > Commit: [cda111f1c8a28c8abdc93ce2843b2c5193173b89](https://github.com/dOpensource/dsiprouter/commit/cda111f1c8a28c8abdc93ce2843b2c5193173b89) > Date: Thu, 13 Dec 2018 21:34:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cda111f1c8a28c8abdc93ce2843b2c5193173b89) [//]: # (START_SECTION 10d61b696779138984cd6393a3de536d90c4102e) ### Update use-cases.rst > Commit: [10d61b696779138984cd6393a3de536d90c4102e](https://github.com/dOpensource/dsiprouter/commit/10d61b696779138984cd6393a3de536d90c4102e) > Date: Thu, 13 Dec 2018 21:33:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 10d61b696779138984cd6393a3de536d90c4102e) [//]: # (START_SECTION 60cebcc2a64b4b9bd3e80cbdeffc0b8ffa6ddd24) ### Update use-cases.rst > Commit: [60cebcc2a64b4b9bd3e80cbdeffc0b8ffa6ddd24](https://github.com/dOpensource/dsiprouter/commit/60cebcc2a64b4b9bd3e80cbdeffc0b8ffa6ddd24) > Date: Thu, 13 Dec 2018 21:11:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 60cebcc2a64b4b9bd3e80cbdeffc0b8ffa6ddd24) [//]: # (START_SECTION 0b6851cee7a0aca9664ec2f09091514374cdd819) ### Update use-cases.rst > Commit: [0b6851cee7a0aca9664ec2f09091514374cdd819](https://github.com/dOpensource/dsiprouter/commit/0b6851cee7a0aca9664ec2f09091514374cdd819) > Date: Thu, 13 Dec 2018 21:10:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0b6851cee7a0aca9664ec2f09091514374cdd819) [//]: # (START_SECTION 2b7aedc107c5b7e8cf17d71e96ee760f6d0b38f3) ### Update use-cases.rst > Commit: [2b7aedc107c5b7e8cf17d71e96ee760f6d0b38f3](https://github.com/dOpensource/dsiprouter/commit/2b7aedc107c5b7e8cf17d71e96ee760f6d0b38f3) > Date: Thu, 13 Dec 2018 21:05:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2b7aedc107c5b7e8cf17d71e96ee760f6d0b38f3) [//]: # (START_SECTION 663bc588faa0d3c7773adc1d841bee6379b9d46f) ### Add files via upload > Commit: [663bc588faa0d3c7773adc1d841bee6379b9d46f](https://github.com/dOpensource/dsiprouter/commit/663bc588faa0d3c7773adc1d841bee6379b9d46f) > Date: Thu, 13 Dec 2018 21:04:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 663bc588faa0d3c7773adc1d841bee6379b9d46f) [//]: # (START_SECTION 60f17c1538c2db3020056e6ef23c76456f738b90) ### Update use-cases.rst > Commit: [60f17c1538c2db3020056e6ef23c76456f738b90](https://github.com/dOpensource/dsiprouter/commit/60f17c1538c2db3020056e6ef23c76456f738b90) > Date: Thu, 13 Dec 2018 21:03:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 60f17c1538c2db3020056e6ef23c76456f738b90) [//]: # (START_SECTION 55e5e7ea66be6e2f2b0a2f98e6ea78312ba3dc1c) ### Add files via upload > Commit: [55e5e7ea66be6e2f2b0a2f98e6ea78312ba3dc1c](https://github.com/dOpensource/dsiprouter/commit/55e5e7ea66be6e2f2b0a2f98e6ea78312ba3dc1c) > Date: Thu, 13 Dec 2018 20:51:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 55e5e7ea66be6e2f2b0a2f98e6ea78312ba3dc1c) [//]: # (START_SECTION 922f88419d1403bc1e08e30b49070594081ce187) ### Update use-cases.rst > Commit: [922f88419d1403bc1e08e30b49070594081ce187](https://github.com/dOpensource/dsiprouter/commit/922f88419d1403bc1e08e30b49070594081ce187) > Date: Thu, 13 Dec 2018 20:50:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 922f88419d1403bc1e08e30b49070594081ce187) [//]: # (START_SECTION 08da76f6d09eb31073cc12c71bca1f645a66cec3) ### Update use-cases.rst > Commit: [08da76f6d09eb31073cc12c71bca1f645a66cec3](https://github.com/dOpensource/dsiprouter/commit/08da76f6d09eb31073cc12c71bca1f645a66cec3) > Date: Thu, 13 Dec 2018 20:45:03 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 08da76f6d09eb31073cc12c71bca1f645a66cec3) [//]: # (START_SECTION a56861ee45b5ff1b1d90fff3f905785ba6484a9e) ### Update use-cases.rst > Commit: [a56861ee45b5ff1b1d90fff3f905785ba6484a9e](https://github.com/dOpensource/dsiprouter/commit/a56861ee45b5ff1b1d90fff3f905785ba6484a9e) > Date: Thu, 13 Dec 2018 20:43:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a56861ee45b5ff1b1d90fff3f905785ba6484a9e) [//]: # (START_SECTION 2a5e280ea37fdb94fcc120d03369b5ede10e128d) ### Update use-cases.rst > Commit: [2a5e280ea37fdb94fcc120d03369b5ede10e128d](https://github.com/dOpensource/dsiprouter/commit/2a5e280ea37fdb94fcc120d03369b5ede10e128d) > Date: Thu, 13 Dec 2018 20:41:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2a5e280ea37fdb94fcc120d03369b5ede10e128d) [//]: # (START_SECTION d1aa338f6885a69c3e695e3cb3efe83d62caaa09) ### Update use-cases.rst > Commit: [d1aa338f6885a69c3e695e3cb3efe83d62caaa09](https://github.com/dOpensource/dsiprouter/commit/d1aa338f6885a69c3e695e3cb3efe83d62caaa09) > Date: Thu, 13 Dec 2018 20:38:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d1aa338f6885a69c3e695e3cb3efe83d62caaa09) [//]: # (START_SECTION 6ba9817c4b49b3d3d5624d8f28026b4ff4a2cddd) ### Update use-cases.rst > Commit: [6ba9817c4b49b3d3d5624d8f28026b4ff4a2cddd](https://github.com/dOpensource/dsiprouter/commit/6ba9817c4b49b3d3d5624d8f28026b4ff4a2cddd) > Date: Thu, 13 Dec 2018 20:36:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6ba9817c4b49b3d3d5624d8f28026b4ff4a2cddd) [//]: # (START_SECTION 39f04ba4cee1077a03d713be74b12e1d72040060) ### Update use-cases.rst > Commit: [39f04ba4cee1077a03d713be74b12e1d72040060](https://github.com/dOpensource/dsiprouter/commit/39f04ba4cee1077a03d713be74b12e1d72040060) > Date: Thu, 13 Dec 2018 20:32:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 39f04ba4cee1077a03d713be74b12e1d72040060) [//]: # (START_SECTION e215925241dd1e34b67b1cc5f948096769ac2ada) ### Update use-cases.rst > Commit: [e215925241dd1e34b67b1cc5f948096769ac2ada](https://github.com/dOpensource/dsiprouter/commit/e215925241dd1e34b67b1cc5f948096769ac2ada) > Date: Thu, 13 Dec 2018 20:19:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e215925241dd1e34b67b1cc5f948096769ac2ada) [//]: # (START_SECTION 3486102b7ce136fe13d4fcaeba2e8af2b9a15e63) ### Update use-cases.rst > Commit: [3486102b7ce136fe13d4fcaeba2e8af2b9a15e63](https://github.com/dOpensource/dsiprouter/commit/3486102b7ce136fe13d4fcaeba2e8af2b9a15e63) > Date: Thu, 13 Dec 2018 20:17:17 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3486102b7ce136fe13d4fcaeba2e8af2b9a15e63) [//]: # (START_SECTION 115df31ed442fdc46f586ad953306fa5ae3d375e) ### Add files via upload > Commit: [115df31ed442fdc46f586ad953306fa5ae3d375e](https://github.com/dOpensource/dsiprouter/commit/115df31ed442fdc46f586ad953306fa5ae3d375e) > Date: Thu, 13 Dec 2018 20:15:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 115df31ed442fdc46f586ad953306fa5ae3d375e) [//]: # (START_SECTION 060b00fc8acc7fc52e3b6d944e5b306170393850) ### Update use-cases.rst > Commit: [060b00fc8acc7fc52e3b6d944e5b306170393850](https://github.com/dOpensource/dsiprouter/commit/060b00fc8acc7fc52e3b6d944e5b306170393850) > Date: Thu, 13 Dec 2018 20:11:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 060b00fc8acc7fc52e3b6d944e5b306170393850) [//]: # (START_SECTION 5069e7ddda117ad784c8124d6d29081ab8ad53a4) ### Update pbxs_and_endpoints.rst > Commit: [5069e7ddda117ad784c8124d6d29081ab8ad53a4](https://github.com/dOpensource/dsiprouter/commit/5069e7ddda117ad784c8124d6d29081ab8ad53a4) > Date: Thu, 13 Dec 2018 13:06:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5069e7ddda117ad784c8124d6d29081ab8ad53a4) [//]: # (START_SECTION 6598db3e26ae44c4ac858e846963c4a24c209dfa) ### Update pbxs_and_endpoints.rst > Commit: [6598db3e26ae44c4ac858e846963c4a24c209dfa](https://github.com/dOpensource/dsiprouter/commit/6598db3e26ae44c4ac858e846963c4a24c209dfa) > Date: Thu, 13 Dec 2018 13:03:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6598db3e26ae44c4ac858e846963c4a24c209dfa) [//]: # (START_SECTION b39e2701253246c1ddbb41a7c4ace9bdbf88c587) ### Update pbxs_and_endpoints.rst > Commit: [b39e2701253246c1ddbb41a7c4ace9bdbf88c587](https://github.com/dOpensource/dsiprouter/commit/b39e2701253246c1ddbb41a7c4ace9bdbf88c587) > Date: Thu, 13 Dec 2018 13:00:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b39e2701253246c1ddbb41a7c4ace9bdbf88c587) [//]: # (START_SECTION cf978ec560f604e6d9e2ac00c94af68ab8321620) ### Update use-cases.rst > Commit: [cf978ec560f604e6d9e2ac00c94af68ab8321620](https://github.com/dOpensource/dsiprouter/commit/cf978ec560f604e6d9e2ac00c94af68ab8321620) > Date: Thu, 13 Dec 2018 11:17:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cf978ec560f604e6d9e2ac00c94af68ab8321620) [//]: # (START_SECTION 8973ac89c3fbb08f72f46fd6785bcdd3f4cac8dd) ### Update use-cases.rst > Commit: [8973ac89c3fbb08f72f46fd6785bcdd3f4cac8dd](https://github.com/dOpensource/dsiprouter/commit/8973ac89c3fbb08f72f46fd6785bcdd3f4cac8dd) > Date: Thu, 13 Dec 2018 10:43:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8973ac89c3fbb08f72f46fd6785bcdd3f4cac8dd) [//]: # (START_SECTION b30a139ad0c87fb0f02c90f29481f40f12ca808b) ### Update use-cases.rst > Commit: [b30a139ad0c87fb0f02c90f29481f40f12ca808b](https://github.com/dOpensource/dsiprouter/commit/b30a139ad0c87fb0f02c90f29481f40f12ca808b) > Date: Thu, 13 Dec 2018 10:16:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b30a139ad0c87fb0f02c90f29481f40f12ca808b) [//]: # (START_SECTION db3bc54cf0fee96e609fd94e8714f02a064a91b7) ### Update use-cases.rst > Commit: [db3bc54cf0fee96e609fd94e8714f02a064a91b7](https://github.com/dOpensource/dsiprouter/commit/db3bc54cf0fee96e609fd94e8714f02a064a91b7) > Date: Thu, 13 Dec 2018 10:12:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION db3bc54cf0fee96e609fd94e8714f02a064a91b7) [//]: # (START_SECTION f3353970df8f5514290769b898a9368d369b7f4a) ### Update use-cases.rst > Commit: [f3353970df8f5514290769b898a9368d369b7f4a](https://github.com/dOpensource/dsiprouter/commit/f3353970df8f5514290769b898a9368d369b7f4a) > Date: Thu, 13 Dec 2018 10:08:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f3353970df8f5514290769b898a9368d369b7f4a) [//]: # (START_SECTION af41bfe6cb1e93304b43a2b806ce9de97d0a3677) ### Update use-cases.rst > Commit: [af41bfe6cb1e93304b43a2b806ce9de97d0a3677](https://github.com/dOpensource/dsiprouter/commit/af41bfe6cb1e93304b43a2b806ce9de97d0a3677) > Date: Thu, 13 Dec 2018 10:06:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION af41bfe6cb1e93304b43a2b806ce9de97d0a3677) [//]: # (START_SECTION 1fbcbd0f6fbf67d8f84918621833e1d6e63de439) ### Update use-cases.rst > Commit: [1fbcbd0f6fbf67d8f84918621833e1d6e63de439](https://github.com/dOpensource/dsiprouter/commit/1fbcbd0f6fbf67d8f84918621833e1d6e63de439) > Date: Thu, 13 Dec 2018 10:02:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1fbcbd0f6fbf67d8f84918621833e1d6e63de439) [//]: # (START_SECTION 20933ae40f783684e90147b154933d86028867f5) ### Update use-cases.rst > Commit: [20933ae40f783684e90147b154933d86028867f5](https://github.com/dOpensource/dsiprouter/commit/20933ae40f783684e90147b154933d86028867f5) > Date: Thu, 13 Dec 2018 10:01:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 20933ae40f783684e90147b154933d86028867f5) [//]: # (START_SECTION 9441c946a1b67c621e7647c0bf93c3ecbfc2ad38) ### Update use-cases.rst > Commit: [9441c946a1b67c621e7647c0bf93c3ecbfc2ad38](https://github.com/dOpensource/dsiprouter/commit/9441c946a1b67c621e7647c0bf93c3ecbfc2ad38) > Date: Thu, 13 Dec 2018 09:58:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9441c946a1b67c621e7647c0bf93c3ecbfc2ad38) [//]: # (START_SECTION 3ff193d807dfd3b4511a567bcaa5db5ccf717d39) ### Update use-cases.rst > Commit: [3ff193d807dfd3b4511a567bcaa5db5ccf717d39](https://github.com/dOpensource/dsiprouter/commit/3ff193d807dfd3b4511a567bcaa5db5ccf717d39) > Date: Thu, 13 Dec 2018 09:57:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3ff193d807dfd3b4511a567bcaa5db5ccf717d39) [//]: # (START_SECTION ff658fa38a9f9e038357decb831e35181656b0a7) ### Update use-cases.rst > Commit: [ff658fa38a9f9e038357decb831e35181656b0a7](https://github.com/dOpensource/dsiprouter/commit/ff658fa38a9f9e038357decb831e35181656b0a7) > Date: Thu, 13 Dec 2018 09:55:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ff658fa38a9f9e038357decb831e35181656b0a7) [//]: # (START_SECTION bc5465e7b036b3f8b15200103f8006ba54207617) ### Update use-cases.rst > Commit: [bc5465e7b036b3f8b15200103f8006ba54207617](https://github.com/dOpensource/dsiprouter/commit/bc5465e7b036b3f8b15200103f8006ba54207617) > Date: Thu, 13 Dec 2018 09:54:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bc5465e7b036b3f8b15200103f8006ba54207617) [//]: # (START_SECTION 5928ca15b6f45e8fafd468d09000c4da765b3267) ### Fixed Javascript error > Commit: [5928ca15b6f45e8fafd468d09000c4da765b3267](https://github.com/dOpensource/dsiprouter/commit/5928ca15b6f45e8fafd468d09000c4da765b3267) > Date: Thu, 13 Dec 2018 14:22:16 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 5928ca15b6f45e8fafd468d09000c4da765b3267) [//]: # (START_SECTION 65afd1f6e7515175ba2ecd3b6d86e34c39a3a143) ### Rename Resources.rst to resources.rst > Commit: [65afd1f6e7515175ba2ecd3b6d86e34c39a3a143](https://github.com/dOpensource/dsiprouter/commit/65afd1f6e7515175ba2ecd3b6d86e34c39a3a143) > Date: Wed, 12 Dec 2018 15:20:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 65afd1f6e7515175ba2ecd3b6d86e34c39a3a143) [//]: # (START_SECTION 7d1c22dc0e55a181f232f58d52ac0adca49f856c) ### Update Resources.rst > Commit: [7d1c22dc0e55a181f232f58d52ac0adca49f856c](https://github.com/dOpensource/dsiprouter/commit/7d1c22dc0e55a181f232f58d52ac0adca49f856c) > Date: Wed, 12 Dec 2018 15:19:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7d1c22dc0e55a181f232f58d52ac0adca49f856c) [//]: # (START_SECTION 12b76eb311777c6b1671e6f475b40205d7b804b2) ### Update Resources.rst > Commit: [12b76eb311777c6b1671e6f475b40205d7b804b2](https://github.com/dOpensource/dsiprouter/commit/12b76eb311777c6b1671e6f475b40205d7b804b2) > Date: Wed, 12 Dec 2018 13:58:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 12b76eb311777c6b1671e6f475b40205d7b804b2) [//]: # (START_SECTION a721002c199cc70bc0c18557cba3e8c537d6744b) ### Update configuring.rst > Commit: [a721002c199cc70bc0c18557cba3e8c537d6744b](https://github.com/dOpensource/dsiprouter/commit/a721002c199cc70bc0c18557cba3e8c537d6744b) > Date: Wed, 12 Dec 2018 13:58:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a721002c199cc70bc0c18557cba3e8c537d6744b) [//]: # (START_SECTION 2642451f9b3d61c5bc1f9a247f38cc695e043ff4) ### Update Resources.rst > Commit: [2642451f9b3d61c5bc1f9a247f38cc695e043ff4](https://github.com/dOpensource/dsiprouter/commit/2642451f9b3d61c5bc1f9a247f38cc695e043ff4) > Date: Wed, 12 Dec 2018 13:53:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2642451f9b3d61c5bc1f9a247f38cc695e043ff4) [//]: # (START_SECTION 3717ad63180779f1f3119ce38b8b29c0b637c827) ### Create Resources.rst > Commit: [3717ad63180779f1f3119ce38b8b29c0b637c827](https://github.com/dOpensource/dsiprouter/commit/3717ad63180779f1f3119ce38b8b29c0b637c827) > Date: Wed, 12 Dec 2018 13:46:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3717ad63180779f1f3119ce38b8b29c0b637c827) [//]: # (START_SECTION cf20c150ef0acb3c2c61b4a8fbe6015c8a924ea9) ### Add files via upload > Commit: [cf20c150ef0acb3c2c61b4a8fbe6015c8a924ea9](https://github.com/dOpensource/dsiprouter/commit/cf20c150ef0acb3c2c61b4a8fbe6015c8a924ea9) > Date: Wed, 12 Dec 2018 13:24:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cf20c150ef0acb3c2c61b4a8fbe6015c8a924ea9) [//]: # (START_SECTION 584eb58f48b8ce5365a92c75407db692001878db) ### Update pbxs_and_endpoints.rst > Commit: [584eb58f48b8ce5365a92c75407db692001878db](https://github.com/dOpensource/dsiprouter/commit/584eb58f48b8ce5365a92c75407db692001878db) > Date: Wed, 12 Dec 2018 13:17:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 584eb58f48b8ce5365a92c75407db692001878db) [//]: # (START_SECTION 7555cb78caca688287a315ac3ae44e92e2717488) ### Fixed issues with FusionPBX Sync and the ability to delete PBX's > Commit: [7555cb78caca688287a315ac3ae44e92e2717488](https://github.com/dOpensource/dsiprouter/commit/7555cb78caca688287a315ac3ae44e92e2717488) > Date: Tue, 11 Dec 2018 22:13:47 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 7555cb78caca688287a315ac3ae44e92e2717488) [//]: # (START_SECTION 0f214bed7a7cb4fce58819d54234dc67538f897a) ### Update use-cases.rst > Commit: [0f214bed7a7cb4fce58819d54234dc67538f897a](https://github.com/dOpensource/dsiprouter/commit/0f214bed7a7cb4fce58819d54234dc67538f897a) > Date: Tue, 11 Dec 2018 12:16:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0f214bed7a7cb4fce58819d54234dc67538f897a) [//]: # (START_SECTION 1c7454dac153ab89baec68da1e74cdff26a5e476) ### Add files via upload > Commit: [1c7454dac153ab89baec68da1e74cdff26a5e476](https://github.com/dOpensource/dsiprouter/commit/1c7454dac153ab89baec68da1e74cdff26a5e476) > Date: Tue, 11 Dec 2018 12:15:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c7454dac153ab89baec68da1e74cdff26a5e476) [//]: # (START_SECTION 59181b7efd513109142736c54594559c20b99759) ### Update use-cases.rst > Commit: [59181b7efd513109142736c54594559c20b99759](https://github.com/dOpensource/dsiprouter/commit/59181b7efd513109142736c54594559c20b99759) > Date: Tue, 11 Dec 2018 12:13:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 59181b7efd513109142736c54594559c20b99759) [//]: # (START_SECTION 7132baa5bd7686de6190e63dcd538aa0392db08a) ### Update use-cases.rst > Commit: [7132baa5bd7686de6190e63dcd538aa0392db08a](https://github.com/dOpensource/dsiprouter/commit/7132baa5bd7686de6190e63dcd538aa0392db08a) > Date: Tue, 11 Dec 2018 12:11:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7132baa5bd7686de6190e63dcd538aa0392db08a) [//]: # (START_SECTION e2070fe65954daff51a8bc6839b499b3eef6bbe4) ### Update use-cases.rst > Commit: [e2070fe65954daff51a8bc6839b499b3eef6bbe4](https://github.com/dOpensource/dsiprouter/commit/e2070fe65954daff51a8bc6839b499b3eef6bbe4) > Date: Tue, 11 Dec 2018 12:09:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e2070fe65954daff51a8bc6839b499b3eef6bbe4) [//]: # (START_SECTION ac1a77631ec67159ddffc6009e23606b1803d9f1) ### Add files via upload > Commit: [ac1a77631ec67159ddffc6009e23606b1803d9f1](https://github.com/dOpensource/dsiprouter/commit/ac1a77631ec67159ddffc6009e23606b1803d9f1) > Date: Tue, 11 Dec 2018 12:05:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ac1a77631ec67159ddffc6009e23606b1803d9f1) [//]: # (START_SECTION 75b3dfb19728dbe76654eb4f633bae7a2dcafe85) ### Update use-cases.rst > Commit: [75b3dfb19728dbe76654eb4f633bae7a2dcafe85](https://github.com/dOpensource/dsiprouter/commit/75b3dfb19728dbe76654eb4f633bae7a2dcafe85) > Date: Tue, 11 Dec 2018 09:34:03 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 75b3dfb19728dbe76654eb4f633bae7a2dcafe85) [//]: # (START_SECTION 7fa9cee31dd7c3cad756282f7ff64ea8d120c2fa) ### Update use-cases.rst > Commit: [7fa9cee31dd7c3cad756282f7ff64ea8d120c2fa](https://github.com/dOpensource/dsiprouter/commit/7fa9cee31dd7c3cad756282f7ff64ea8d120c2fa) > Date: Mon, 10 Dec 2018 15:30:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7fa9cee31dd7c3cad756282f7ff64ea8d120c2fa) [//]: # (START_SECTION 1e65dda2670080e312ad1553169b1d3ea4976632) ### Fixed the creation of static routes > Commit: [1e65dda2670080e312ad1553169b1d3ea4976632](https://github.com/dOpensource/dsiprouter/commit/1e65dda2670080e312ad1553169b1d3ea4976632) > Date: Sun, 9 Dec 2018 13:08:25 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 1e65dda2670080e312ad1553169b1d3ea4976632) [//]: # (START_SECTION 9a87f3a42bb3f2456840d4d98a9dd51d2e3418f4) ### Simplfied the Multidomain support > Commit: [9a87f3a42bb3f2456840d4d98a9dd51d2e3418f4](https://github.com/dOpensource/dsiprouter/commit/9a87f3a42bb3f2456840d4d98a9dd51d2e3418f4) > Date: Sat, 8 Dec 2018 19:56:09 +0000 > Author: root (root@debian-dsip-test.localdomain) > Committer: root (root@debian-dsip-test.localdomain) > Signed: --- [//]: # (END_SECTION 9a87f3a42bb3f2456840d4d98a9dd51d2e3418f4) [//]: # (START_SECTION 1e7644e62946bf52d247663ff2514226fde46138) ### Update installing.rst > Commit: [1e7644e62946bf52d247663ff2514226fde46138](https://github.com/dOpensource/dsiprouter/commit/1e7644e62946bf52d247663ff2514226fde46138) > Date: Sat, 8 Dec 2018 12:24:48 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1e7644e62946bf52d247663ff2514226fde46138) [//]: # (START_SECTION 9a6f944d187ce553b84ee511d1ea936e1770d6e9) ### Update installing.rst > Commit: [9a6f944d187ce553b84ee511d1ea936e1770d6e9](https://github.com/dOpensource/dsiprouter/commit/9a6f944d187ce553b84ee511d1ea936e1770d6e9) > Date: Sat, 8 Dec 2018 12:21:50 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9a6f944d187ce553b84ee511d1ea936e1770d6e9) [//]: # (START_SECTION aadc0547f85cc28ac730b88b809d8b6b58d99f13) ### Changes to fix the GUI > Commit: [aadc0547f85cc28ac730b88b809d8b6b58d99f13](https://github.com/dOpensource/dsiprouter/commit/aadc0547f85cc28ac730b88b809d8b6b58d99f13) > Date: Sat, 8 Dec 2018 16:58:59 +0000 > Author: root (root@debian-v51.localdomain) > Committer: root (root@debian-v51.localdomain) > Signed: --- [//]: # (END_SECTION aadc0547f85cc28ac730b88b809d8b6b58d99f13) [//]: # (START_SECTION 3e99df7b4ccc1fd40a5739257186ea24ac292c4e) ### Fixed an issue Javascript error that was preventing Fusion Support toggle button from working > Commit: [3e99df7b4ccc1fd40a5739257186ea24ac292c4e](https://github.com/dOpensource/dsiprouter/commit/3e99df7b4ccc1fd40a5739257186ea24ac292c4e) > Date: Sat, 8 Dec 2018 15:49:54 +0000 > Author: root (root@debian-v51.localdomain) > Committer: root (root@debian-v51.localdomain) > Signed: --- [//]: # (END_SECTION 3e99df7b4ccc1fd40a5739257186ea24ac292c4e) [//]: # (START_SECTION 916b4adc6c3af117a9a7992b872d6b95047adc63) ### Fixed an issue with datatables that was causing a JS error > Commit: [916b4adc6c3af117a9a7992b872d6b95047adc63](https://github.com/dOpensource/dsiprouter/commit/916b4adc6c3af117a9a7992b872d6b95047adc63) > Date: Sat, 8 Dec 2018 14:46:09 +0000 > Author: root (root@debian-v51.localdomain) > Committer: root (root@debian-v51.localdomain) > Signed: --- [//]: # (END_SECTION 916b4adc6c3af117a9a7992b872d6b95047adc63) [//]: # (START_SECTION cf311b3f8941a68b155e4190c2f00a5fbd5f53fa) ### Update command_line_options.rst > Commit: [cf311b3f8941a68b155e4190c2f00a5fbd5f53fa](https://github.com/dOpensource/dsiprouter/commit/cf311b3f8941a68b155e4190c2f00a5fbd5f53fa) > Date: Fri, 7 Dec 2018 22:54:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cf311b3f8941a68b155e4190c2f00a5fbd5f53fa) [//]: # (START_SECTION ed16d248270e4aa1a5cab2af65b009c685ee1c65) ### Update command_line_options.rst > Commit: [ed16d248270e4aa1a5cab2af65b009c685ee1c65](https://github.com/dOpensource/dsiprouter/commit/ed16d248270e4aa1a5cab2af65b009c685ee1c65) > Date: Fri, 7 Dec 2018 22:44:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ed16d248270e4aa1a5cab2af65b009c685ee1c65) [//]: # (START_SECTION 050b95e0f8ad833d5233a6691bea89b6fc6168dd) ### Update command_line_options.rst > Commit: [050b95e0f8ad833d5233a6691bea89b6fc6168dd](https://github.com/dOpensource/dsiprouter/commit/050b95e0f8ad833d5233a6691bea89b6fc6168dd) > Date: Fri, 7 Dec 2018 22:43:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 050b95e0f8ad833d5233a6691bea89b6fc6168dd) [//]: # (START_SECTION 638fcacb4433104acaf7d59b0c29a34a9f063681) ### Update command_line_options.rst > Commit: [638fcacb4433104acaf7d59b0c29a34a9f063681](https://github.com/dOpensource/dsiprouter/commit/638fcacb4433104acaf7d59b0c29a34a9f063681) > Date: Fri, 7 Dec 2018 22:37:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 638fcacb4433104acaf7d59b0c29a34a9f063681) [//]: # (START_SECTION 79e0aa07c41d4f4c94d80184af13f9f8fbbd89a2) ### Update install_option > Commit: [79e0aa07c41d4f4c94d80184af13f9f8fbbd89a2](https://github.com/dOpensource/dsiprouter/commit/79e0aa07c41d4f4c94d80184af13f9f8fbbd89a2) > Date: Fri, 7 Dec 2018 22:33:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79e0aa07c41d4f4c94d80184af13f9f8fbbd89a2) [//]: # (START_SECTION 5fd7d73cb7ab0f751e5ecddce95d0f85bcb30bb0) ### Update command_line_options.rst > Commit: [5fd7d73cb7ab0f751e5ecddce95d0f85bcb30bb0](https://github.com/dOpensource/dsiprouter/commit/5fd7d73cb7ab0f751e5ecddce95d0f85bcb30bb0) > Date: Fri, 7 Dec 2018 22:30:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5fd7d73cb7ab0f751e5ecddce95d0f85bcb30bb0) [//]: # (START_SECTION a95423bc1dfb8763f4d702e9e15995e2634a44dc) ### Create install_option > Commit: [a95423bc1dfb8763f4d702e9e15995e2634a44dc](https://github.com/dOpensource/dsiprouter/commit/a95423bc1dfb8763f4d702e9e15995e2634a44dc) > Date: Fri, 7 Dec 2018 22:29:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a95423bc1dfb8763f4d702e9e15995e2634a44dc) [//]: # (START_SECTION 33477e607dfcbe3d005b919de124af3ae1b7701c) ### Update command_line_options.rst > Commit: [33477e607dfcbe3d005b919de124af3ae1b7701c](https://github.com/dOpensource/dsiprouter/commit/33477e607dfcbe3d005b919de124af3ae1b7701c) > Date: Fri, 7 Dec 2018 19:30:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 33477e607dfcbe3d005b919de124af3ae1b7701c) [//]: # (START_SECTION a3cb688818101f47ef7adc65e0a5aa189c89cf3d) ### Update command_line_options.rst > Commit: [a3cb688818101f47ef7adc65e0a5aa189c89cf3d](https://github.com/dOpensource/dsiprouter/commit/a3cb688818101f47ef7adc65e0a5aa189c89cf3d) > Date: Fri, 7 Dec 2018 19:25:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a3cb688818101f47ef7adc65e0a5aa189c89cf3d) [//]: # (START_SECTION 85494ea42dba92740cad9829b0231e303a5db0d9) ### Update command_line_options.rst > Commit: [85494ea42dba92740cad9829b0231e303a5db0d9](https://github.com/dOpensource/dsiprouter/commit/85494ea42dba92740cad9829b0231e303a5db0d9) > Date: Fri, 7 Dec 2018 17:52:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 85494ea42dba92740cad9829b0231e303a5db0d9) [//]: # (START_SECTION 89da2706398db81653a1a5b6509e6c0ec990cc6d) ### Update command_line_options.rst > Commit: [89da2706398db81653a1a5b6509e6c0ec990cc6d](https://github.com/dOpensource/dsiprouter/commit/89da2706398db81653a1a5b6509e6c0ec990cc6d) > Date: Fri, 7 Dec 2018 15:37:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 89da2706398db81653a1a5b6509e6c0ec990cc6d) [//]: # (START_SECTION c72fda263ead57ae45ccf2eb28ab1ce92962bced) ### Update command_line_options.rst > Commit: [c72fda263ead57ae45ccf2eb28ab1ce92962bced](https://github.com/dOpensource/dsiprouter/commit/c72fda263ead57ae45ccf2eb28ab1ce92962bced) > Date: Fri, 7 Dec 2018 15:35:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c72fda263ead57ae45ccf2eb28ab1ce92962bced) [//]: # (START_SECTION 765375ae09d8e3cc08b8c389ec856f75f1f7ba4d) ### Update command_line_options.rst > Commit: [765375ae09d8e3cc08b8c389ec856f75f1f7ba4d](https://github.com/dOpensource/dsiprouter/commit/765375ae09d8e3cc08b8c389ec856f75f1f7ba4d) > Date: Fri, 7 Dec 2018 14:48:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 765375ae09d8e3cc08b8c389ec856f75f1f7ba4d) [//]: # (START_SECTION 61c5967288d6a33d938d769c02d25f68d9ba4926) ### Update command_line_options.rst > Commit: [61c5967288d6a33d938d769c02d25f68d9ba4926](https://github.com/dOpensource/dsiprouter/commit/61c5967288d6a33d938d769c02d25f68d9ba4926) > Date: Fri, 7 Dec 2018 14:40:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 61c5967288d6a33d938d769c02d25f68d9ba4926) [//]: # (START_SECTION ca7f16cd52c720561b40ccdd6beec26f88cbbe5e) ### Update command_line_options.rst > Commit: [ca7f16cd52c720561b40ccdd6beec26f88cbbe5e](https://github.com/dOpensource/dsiprouter/commit/ca7f16cd52c720561b40ccdd6beec26f88cbbe5e) > Date: Fri, 7 Dec 2018 14:37:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ca7f16cd52c720561b40ccdd6beec26f88cbbe5e) [//]: # (START_SECTION f23b864a3cb3d5526a99a2d75ca3a69ac795bd3d) ### Update use-cases.rst > Commit: [f23b864a3cb3d5526a99a2d75ca3a69ac795bd3d](https://github.com/dOpensource/dsiprouter/commit/f23b864a3cb3d5526a99a2d75ca3a69ac795bd3d) > Date: Fri, 7 Dec 2018 10:54:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f23b864a3cb3d5526a99a2d75ca3a69ac795bd3d) [//]: # (START_SECTION 76f8d5f07325ef4942885103453f986ea3782f14) ### Update use-cases.rst > Commit: [76f8d5f07325ef4942885103453f986ea3782f14](https://github.com/dOpensource/dsiprouter/commit/76f8d5f07325ef4942885103453f986ea3782f14) > Date: Fri, 7 Dec 2018 10:53:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 76f8d5f07325ef4942885103453f986ea3782f14) [//]: # (START_SECTION ce8acb42553ccf7eb76360b5f65ce778bd6d099f) ### Update use-cases.rst > Commit: [ce8acb42553ccf7eb76360b5f65ce778bd6d099f](https://github.com/dOpensource/dsiprouter/commit/ce8acb42553ccf7eb76360b5f65ce778bd6d099f) > Date: Fri, 7 Dec 2018 10:51:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ce8acb42553ccf7eb76360b5f65ce778bd6d099f) [//]: # (START_SECTION fe9ffc06953ccbe5c65ec134efd7070dbf6310f9) ### Update use-cases.rst > Commit: [fe9ffc06953ccbe5c65ec134efd7070dbf6310f9](https://github.com/dOpensource/dsiprouter/commit/fe9ffc06953ccbe5c65ec134efd7070dbf6310f9) > Date: Fri, 7 Dec 2018 10:48:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fe9ffc06953ccbe5c65ec134efd7070dbf6310f9) [//]: # (START_SECTION 62c0b4fae5e8b9cc366a7de5d3b349d0fc263065) ### Update use-cases.rst > Commit: [62c0b4fae5e8b9cc366a7de5d3b349d0fc263065](https://github.com/dOpensource/dsiprouter/commit/62c0b4fae5e8b9cc366a7de5d3b349d0fc263065) > Date: Fri, 7 Dec 2018 10:48:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62c0b4fae5e8b9cc366a7de5d3b349d0fc263065) [//]: # (START_SECTION 9c1b112fbb2b12dfca47b63646b77af1eaecea8e) ### Delete list_of_domains1.PNG > Commit: [9c1b112fbb2b12dfca47b63646b77af1eaecea8e](https://github.com/dOpensource/dsiprouter/commit/9c1b112fbb2b12dfca47b63646b77af1eaecea8e) > Date: Fri, 7 Dec 2018 10:46:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9c1b112fbb2b12dfca47b63646b77af1eaecea8e) [//]: # (START_SECTION ac1ebe0c86d4bb1b5d21db0eb29e1313defa8f41) ### Update use-cases.rst > Commit: [ac1ebe0c86d4bb1b5d21db0eb29e1313defa8f41](https://github.com/dOpensource/dsiprouter/commit/ac1ebe0c86d4bb1b5d21db0eb29e1313defa8f41) > Date: Fri, 7 Dec 2018 10:43:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ac1ebe0c86d4bb1b5d21db0eb29e1313defa8f41) [//]: # (START_SECTION 8a7f7ba79856092d3d1202eb41ed585cd294cc52) ### Update use-cases.rst > Commit: [8a7f7ba79856092d3d1202eb41ed585cd294cc52](https://github.com/dOpensource/dsiprouter/commit/8a7f7ba79856092d3d1202eb41ed585cd294cc52) > Date: Fri, 7 Dec 2018 10:41:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8a7f7ba79856092d3d1202eb41ed585cd294cc52) [//]: # (START_SECTION aea234264abda28edee90db41d92dd6df1dfcc6b) ### Update use-cases.rst > Commit: [aea234264abda28edee90db41d92dd6df1dfcc6b](https://github.com/dOpensource/dsiprouter/commit/aea234264abda28edee90db41d92dd6df1dfcc6b) > Date: Fri, 7 Dec 2018 10:38:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aea234264abda28edee90db41d92dd6df1dfcc6b) [//]: # (START_SECTION 148e79032178755f8fbd7dd6dd15840ae02e96fa) ### Update command_line_options.rst > Commit: [148e79032178755f8fbd7dd6dd15840ae02e96fa](https://github.com/dOpensource/dsiprouter/commit/148e79032178755f8fbd7dd6dd15840ae02e96fa) > Date: Fri, 7 Dec 2018 09:11:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 148e79032178755f8fbd7dd6dd15840ae02e96fa) [//]: # (START_SECTION 9019969a7522958aab1a4bbc30140a5f1f5df983) ### Update command_line_options.rst > Commit: [9019969a7522958aab1a4bbc30140a5f1f5df983](https://github.com/dOpensource/dsiprouter/commit/9019969a7522958aab1a4bbc30140a5f1f5df983) > Date: Fri, 7 Dec 2018 09:09:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9019969a7522958aab1a4bbc30140a5f1f5df983) [//]: # (START_SECTION 6ceb6692fc981af823fe43e16e59a6fa1410a398) ### Update command_line_options.rst > Commit: [6ceb6692fc981af823fe43e16e59a6fa1410a398](https://github.com/dOpensource/dsiprouter/commit/6ceb6692fc981af823fe43e16e59a6fa1410a398) > Date: Fri, 7 Dec 2018 09:09:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6ceb6692fc981af823fe43e16e59a6fa1410a398) [//]: # (START_SECTION f15008333f58a883ef8e5caa27ef99bca6de1611) ### Update command_line_options.rst > Commit: [f15008333f58a883ef8e5caa27ef99bca6de1611](https://github.com/dOpensource/dsiprouter/commit/f15008333f58a883ef8e5caa27ef99bca6de1611) > Date: Fri, 7 Dec 2018 09:01:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f15008333f58a883ef8e5caa27ef99bca6de1611) [//]: # (START_SECTION ec07d7c56028a9076dd834a835a76ebcaf570d94) ### Update command_line_options.rst > Commit: [ec07d7c56028a9076dd834a835a76ebcaf570d94](https://github.com/dOpensource/dsiprouter/commit/ec07d7c56028a9076dd834a835a76ebcaf570d94) > Date: Fri, 7 Dec 2018 09:00:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ec07d7c56028a9076dd834a835a76ebcaf570d94) [//]: # (START_SECTION d97d0cbaa0ca851eec37585bea0e5357e4d6544e) ### Update command_line_options.rst > Commit: [d97d0cbaa0ca851eec37585bea0e5357e4d6544e](https://github.com/dOpensource/dsiprouter/commit/d97d0cbaa0ca851eec37585bea0e5357e4d6544e) > Date: Fri, 7 Dec 2018 08:59:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d97d0cbaa0ca851eec37585bea0e5357e4d6544e) [//]: # (START_SECTION 289886ee032f60c41fee15f3b5bba6e671e327e9) ### Update use-cases.rst > Commit: [289886ee032f60c41fee15f3b5bba6e671e327e9](https://github.com/dOpensource/dsiprouter/commit/289886ee032f60c41fee15f3b5bba6e671e327e9) > Date: Thu, 6 Dec 2018 15:17:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 289886ee032f60c41fee15f3b5bba6e671e327e9) [//]: # (START_SECTION f5cc37a37fc4b71a37dacdb993ce07b0e0c2ef79) ### Update use-cases.rst > Commit: [f5cc37a37fc4b71a37dacdb993ce07b0e0c2ef79](https://github.com/dOpensource/dsiprouter/commit/f5cc37a37fc4b71a37dacdb993ce07b0e0c2ef79) > Date: Thu, 6 Dec 2018 15:17:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f5cc37a37fc4b71a37dacdb993ce07b0e0c2ef79) [//]: # (START_SECTION d6fb1c05de68b63a38282b87d220cb5736d07fcb) ### Update use-cases.rst > Commit: [d6fb1c05de68b63a38282b87d220cb5736d07fcb](https://github.com/dOpensource/dsiprouter/commit/d6fb1c05de68b63a38282b87d220cb5736d07fcb) > Date: Thu, 6 Dec 2018 15:14:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d6fb1c05de68b63a38282b87d220cb5736d07fcb) [//]: # (START_SECTION bfc9a572d9ad25e1755002f8d03b31edf7b5fc9b) ### Update use-cases.rst > Commit: [bfc9a572d9ad25e1755002f8d03b31edf7b5fc9b](https://github.com/dOpensource/dsiprouter/commit/bfc9a572d9ad25e1755002f8d03b31edf7b5fc9b) > Date: Thu, 6 Dec 2018 15:12:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bfc9a572d9ad25e1755002f8d03b31edf7b5fc9b) [//]: # (START_SECTION 6b70420a51f716b3ab5842db978d551dfef88434) ### Update use-cases.rst > Commit: [6b70420a51f716b3ab5842db978d551dfef88434](https://github.com/dOpensource/dsiprouter/commit/6b70420a51f716b3ab5842db978d551dfef88434) > Date: Thu, 6 Dec 2018 15:11:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6b70420a51f716b3ab5842db978d551dfef88434) [//]: # (START_SECTION 393344f5571828dc6e58888245f3718035bdcae3) ### Update use-cases.rst > Commit: [393344f5571828dc6e58888245f3718035bdcae3](https://github.com/dOpensource/dsiprouter/commit/393344f5571828dc6e58888245f3718035bdcae3) > Date: Thu, 6 Dec 2018 15:10:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 393344f5571828dc6e58888245f3718035bdcae3) [//]: # (START_SECTION d5ecbcd2b61f92b5cc10a863f6e95ed8b0b96898) ### Update use-cases.rst > Commit: [d5ecbcd2b61f92b5cc10a863f6e95ed8b0b96898](https://github.com/dOpensource/dsiprouter/commit/d5ecbcd2b61f92b5cc10a863f6e95ed8b0b96898) > Date: Thu, 6 Dec 2018 15:08:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d5ecbcd2b61f92b5cc10a863f6e95ed8b0b96898) [//]: # (START_SECTION ff2a454b25c17ebbd4428ea41992bfdd71096fba) ### Update use-cases.rst > Commit: [ff2a454b25c17ebbd4428ea41992bfdd71096fba](https://github.com/dOpensource/dsiprouter/commit/ff2a454b25c17ebbd4428ea41992bfdd71096fba) > Date: Thu, 6 Dec 2018 15:06:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ff2a454b25c17ebbd4428ea41992bfdd71096fba) [//]: # (START_SECTION 98046aaaea3c7c203fe80e1c8d2f61618ba7b63e) ### Update command_line_options.rst > Commit: [98046aaaea3c7c203fe80e1c8d2f61618ba7b63e](https://github.com/dOpensource/dsiprouter/commit/98046aaaea3c7c203fe80e1c8d2f61618ba7b63e) > Date: Thu, 6 Dec 2018 15:05:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 98046aaaea3c7c203fe80e1c8d2f61618ba7b63e) [//]: # (START_SECTION 667b9288e5ee41e2e7cfcf2d4fb7a428503c549f) ### Update command_line_options.rst > Commit: [667b9288e5ee41e2e7cfcf2d4fb7a428503c549f](https://github.com/dOpensource/dsiprouter/commit/667b9288e5ee41e2e7cfcf2d4fb7a428503c549f) > Date: Thu, 6 Dec 2018 15:05:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 667b9288e5ee41e2e7cfcf2d4fb7a428503c549f) [//]: # (START_SECTION cd06d7a2a94f1abbf466516694c2cfe2149bf56d) ### Update command_line_options.rst > Commit: [cd06d7a2a94f1abbf466516694c2cfe2149bf56d](https://github.com/dOpensource/dsiprouter/commit/cd06d7a2a94f1abbf466516694c2cfe2149bf56d) > Date: Thu, 6 Dec 2018 15:04:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cd06d7a2a94f1abbf466516694c2cfe2149bf56d) [//]: # (START_SECTION b7178b68ba8542d149578874d7fbd5290786ffb4) ### Update command_line_options.rst > Commit: [b7178b68ba8542d149578874d7fbd5290786ffb4](https://github.com/dOpensource/dsiprouter/commit/b7178b68ba8542d149578874d7fbd5290786ffb4) > Date: Thu, 6 Dec 2018 15:03:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b7178b68ba8542d149578874d7fbd5290786ffb4) [//]: # (START_SECTION fd2328046e3788c440fa48192a63dbbbc1c79264) ### Update use-cases.rst > Commit: [fd2328046e3788c440fa48192a63dbbbc1c79264](https://github.com/dOpensource/dsiprouter/commit/fd2328046e3788c440fa48192a63dbbbc1c79264) > Date: Thu, 6 Dec 2018 15:01:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fd2328046e3788c440fa48192a63dbbbc1c79264) [//]: # (START_SECTION dd099c2ff2f187389bd5ed69a5a6e462490c2c86) ### Update use-cases.rst > Commit: [dd099c2ff2f187389bd5ed69a5a6e462490c2c86](https://github.com/dOpensource/dsiprouter/commit/dd099c2ff2f187389bd5ed69a5a6e462490c2c86) > Date: Thu, 6 Dec 2018 14:56:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dd099c2ff2f187389bd5ed69a5a6e462490c2c86) [//]: # (START_SECTION 28f686349a9d05b79cf399468027ad5936a20bc1) ### Update use-cases.rst > Commit: [28f686349a9d05b79cf399468027ad5936a20bc1](https://github.com/dOpensource/dsiprouter/commit/28f686349a9d05b79cf399468027ad5936a20bc1) > Date: Thu, 6 Dec 2018 14:55:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 28f686349a9d05b79cf399468027ad5936a20bc1) [//]: # (START_SECTION 9e4033dba0d8728cc8de62cd453027b01af2020e) ### Add files via upload > Commit: [9e4033dba0d8728cc8de62cd453027b01af2020e](https://github.com/dOpensource/dsiprouter/commit/9e4033dba0d8728cc8de62cd453027b01af2020e) > Date: Thu, 6 Dec 2018 14:54:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9e4033dba0d8728cc8de62cd453027b01af2020e) [//]: # (START_SECTION 16a8b5a62341f818a591d178760850fc8bd283a0) ### Delete zoiper_example.PNG > Commit: [16a8b5a62341f818a591d178760850fc8bd283a0](https://github.com/dOpensource/dsiprouter/commit/16a8b5a62341f818a591d178760850fc8bd283a0) > Date: Thu, 6 Dec 2018 14:54:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 16a8b5a62341f818a591d178760850fc8bd283a0) [//]: # (START_SECTION 588cb3c20dfa3ec6ed041cfce7aa391e37b75f16) ### Update use-cases.rst > Commit: [588cb3c20dfa3ec6ed041cfce7aa391e37b75f16](https://github.com/dOpensource/dsiprouter/commit/588cb3c20dfa3ec6ed041cfce7aa391e37b75f16) > Date: Thu, 6 Dec 2018 14:53:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 588cb3c20dfa3ec6ed041cfce7aa391e37b75f16) [//]: # (START_SECTION 5d1fbdfde5a0296973a89f90794e14985e89386a) ### Update use-cases.rst > Commit: [5d1fbdfde5a0296973a89f90794e14985e89386a](https://github.com/dOpensource/dsiprouter/commit/5d1fbdfde5a0296973a89f90794e14985e89386a) > Date: Thu, 6 Dec 2018 14:49:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5d1fbdfde5a0296973a89f90794e14985e89386a) [//]: # (START_SECTION 344be156abed478f0065bef75c3be54679183204) ### Update use-cases.rst > Commit: [344be156abed478f0065bef75c3be54679183204](https://github.com/dOpensource/dsiprouter/commit/344be156abed478f0065bef75c3be54679183204) > Date: Thu, 6 Dec 2018 14:47:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 344be156abed478f0065bef75c3be54679183204) [//]: # (START_SECTION 8a878d60cfed669c02afb1eda3d0f6cc8c9d4948) ### Update use-cases.rst > Commit: [8a878d60cfed669c02afb1eda3d0f6cc8c9d4948](https://github.com/dOpensource/dsiprouter/commit/8a878d60cfed669c02afb1eda3d0f6cc8c9d4948) > Date: Thu, 6 Dec 2018 14:46:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8a878d60cfed669c02afb1eda3d0f6cc8c9d4948) [//]: # (START_SECTION 5aab7b87b93d0326fb5e15630ce01aee03c30741) ### Add files via upload > Commit: [5aab7b87b93d0326fb5e15630ce01aee03c30741](https://github.com/dOpensource/dsiprouter/commit/5aab7b87b93d0326fb5e15630ce01aee03c30741) > Date: Thu, 6 Dec 2018 14:44:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5aab7b87b93d0326fb5e15630ce01aee03c30741) [//]: # (START_SECTION 42c6aea2f3ca18499804d48de4bcb70023f37a72) ### Update use-cases.rst > Commit: [42c6aea2f3ca18499804d48de4bcb70023f37a72](https://github.com/dOpensource/dsiprouter/commit/42c6aea2f3ca18499804d48de4bcb70023f37a72) > Date: Thu, 6 Dec 2018 14:26:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 42c6aea2f3ca18499804d48de4bcb70023f37a72) [//]: # (START_SECTION 74223b3c883d1f7aa53469454f0a1c5e3e5681f5) ### Update use-cases.rst > Commit: [74223b3c883d1f7aa53469454f0a1c5e3e5681f5](https://github.com/dOpensource/dsiprouter/commit/74223b3c883d1f7aa53469454f0a1c5e3e5681f5) > Date: Thu, 6 Dec 2018 14:12:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 74223b3c883d1f7aa53469454f0a1c5e3e5681f5) [//]: # (START_SECTION 47f956b156d70d77dd477dc8e92aa99b4858d4dc) ### Update use-cases.rst > Commit: [47f956b156d70d77dd477dc8e92aa99b4858d4dc](https://github.com/dOpensource/dsiprouter/commit/47f956b156d70d77dd477dc8e92aa99b4858d4dc) > Date: Thu, 6 Dec 2018 14:06:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 47f956b156d70d77dd477dc8e92aa99b4858d4dc) [//]: # (START_SECTION bb15224b49be4422bac517b6e18f6aa410ee2571) ### Add files via upload > Commit: [bb15224b49be4422bac517b6e18f6aa410ee2571](https://github.com/dOpensource/dsiprouter/commit/bb15224b49be4422bac517b6e18f6aa410ee2571) > Date: Thu, 6 Dec 2018 14:05:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bb15224b49be4422bac517b6e18f6aa410ee2571) [//]: # (START_SECTION 5d0b75705955ed383f676fe3bb4dc966cecd8583) ### Update use-cases.rst > Commit: [5d0b75705955ed383f676fe3bb4dc966cecd8583](https://github.com/dOpensource/dsiprouter/commit/5d0b75705955ed383f676fe3bb4dc966cecd8583) > Date: Thu, 6 Dec 2018 14:05:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5d0b75705955ed383f676fe3bb4dc966cecd8583) [//]: # (START_SECTION f29f670b6a9d4454d42679475c05d5933084344c) ### Update command_line_options.rst > Commit: [f29f670b6a9d4454d42679475c05d5933084344c](https://github.com/dOpensource/dsiprouter/commit/f29f670b6a9d4454d42679475c05d5933084344c) > Date: Thu, 6 Dec 2018 13:12:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f29f670b6a9d4454d42679475c05d5933084344c) [//]: # (START_SECTION 0aeb7ee3f572865c6ed1d00cb7d94830ee435fef) ### Update command_line_options.rst > Commit: [0aeb7ee3f572865c6ed1d00cb7d94830ee435fef](https://github.com/dOpensource/dsiprouter/commit/0aeb7ee3f572865c6ed1d00cb7d94830ee435fef) > Date: Thu, 6 Dec 2018 13:11:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0aeb7ee3f572865c6ed1d00cb7d94830ee435fef) [//]: # (START_SECTION 2daab6b71f884cccd363b6b05a19293cb3fee2b2) ### Update command_line_options.rst > Commit: [2daab6b71f884cccd363b6b05a19293cb3fee2b2](https://github.com/dOpensource/dsiprouter/commit/2daab6b71f884cccd363b6b05a19293cb3fee2b2) > Date: Thu, 6 Dec 2018 13:11:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2daab6b71f884cccd363b6b05a19293cb3fee2b2) [//]: # (START_SECTION 602ad423fccd8eb6d7f4ebd297fbc68cd2b08d01) ### Update command_line_options.rst > Commit: [602ad423fccd8eb6d7f4ebd297fbc68cd2b08d01](https://github.com/dOpensource/dsiprouter/commit/602ad423fccd8eb6d7f4ebd297fbc68cd2b08d01) > Date: Thu, 6 Dec 2018 13:07:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 602ad423fccd8eb6d7f4ebd297fbc68cd2b08d01) [//]: # (START_SECTION 983eee72f935a7ce70d28a7b6d35e6c263fc2fdf) ### Update command_line_options.rst > Commit: [983eee72f935a7ce70d28a7b6d35e6c263fc2fdf](https://github.com/dOpensource/dsiprouter/commit/983eee72f935a7ce70d28a7b6d35e6c263fc2fdf) > Date: Thu, 6 Dec 2018 13:06:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 983eee72f935a7ce70d28a7b6d35e6c263fc2fdf) [//]: # (START_SECTION 085797c33219df01c930dbe27c29cb11398f03d3) ### Update command_line_options.rst > Commit: [085797c33219df01c930dbe27c29cb11398f03d3](https://github.com/dOpensource/dsiprouter/commit/085797c33219df01c930dbe27c29cb11398f03d3) > Date: Thu, 6 Dec 2018 13:06:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 085797c33219df01c930dbe27c29cb11398f03d3) [//]: # (START_SECTION ef332f0ee789177c64c847302f1725cc42d558ec) ### Update command_line_options.rst > Commit: [ef332f0ee789177c64c847302f1725cc42d558ec](https://github.com/dOpensource/dsiprouter/commit/ef332f0ee789177c64c847302f1725cc42d558ec) > Date: Thu, 6 Dec 2018 13:05:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ef332f0ee789177c64c847302f1725cc42d558ec) [//]: # (START_SECTION 0695cd31a90572587772fbc3f45dac8d678c8c4e) ### Update command_line_options.rst > Commit: [0695cd31a90572587772fbc3f45dac8d678c8c4e](https://github.com/dOpensource/dsiprouter/commit/0695cd31a90572587772fbc3f45dac8d678c8c4e) > Date: Thu, 6 Dec 2018 13:05:17 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0695cd31a90572587772fbc3f45dac8d678c8c4e) [//]: # (START_SECTION 33815785d90440d319fb6efb7866118f310fd419) ### Update command_line_options.rst > Commit: [33815785d90440d319fb6efb7866118f310fd419](https://github.com/dOpensource/dsiprouter/commit/33815785d90440d319fb6efb7866118f310fd419) > Date: Thu, 6 Dec 2018 13:04:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 33815785d90440d319fb6efb7866118f310fd419) [//]: # (START_SECTION 21a43c9900c59302cbce48fd2b0d8bf34469cc7c) ### Update use-cases.rst > Commit: [21a43c9900c59302cbce48fd2b0d8bf34469cc7c](https://github.com/dOpensource/dsiprouter/commit/21a43c9900c59302cbce48fd2b0d8bf34469cc7c) > Date: Thu, 6 Dec 2018 12:57:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 21a43c9900c59302cbce48fd2b0d8bf34469cc7c) [//]: # (START_SECTION bc7d7326829a388463a5a7e41c99c7801269d2ad) ### Update use-cases.rst > Commit: [bc7d7326829a388463a5a7e41c99c7801269d2ad](https://github.com/dOpensource/dsiprouter/commit/bc7d7326829a388463a5a7e41c99c7801269d2ad) > Date: Thu, 6 Dec 2018 12:55:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bc7d7326829a388463a5a7e41c99c7801269d2ad) [//]: # (START_SECTION cce8539090b295ca6ae86ca7a0900960354798c9) ### Add files via upload > Commit: [cce8539090b295ca6ae86ca7a0900960354798c9](https://github.com/dOpensource/dsiprouter/commit/cce8539090b295ca6ae86ca7a0900960354798c9) > Date: Thu, 6 Dec 2018 12:54:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cce8539090b295ca6ae86ca7a0900960354798c9) [//]: # (START_SECTION 9c0fd79d14bdf43cbc69f752624d30e586e9564e) ### Delete 11d_dialplan2.PNG > Commit: [9c0fd79d14bdf43cbc69f752624d30e586e9564e](https://github.com/dOpensource/dsiprouter/commit/9c0fd79d14bdf43cbc69f752624d30e586e9564e) > Date: Thu, 6 Dec 2018 12:53:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9c0fd79d14bdf43cbc69f752624d30e586e9564e) [//]: # (START_SECTION f501053662ee2c79336012b5cf3eeb86539bbadd) ### Delete dialplan_11.PNG > Commit: [f501053662ee2c79336012b5cf3eeb86539bbadd](https://github.com/dOpensource/dsiprouter/commit/f501053662ee2c79336012b5cf3eeb86539bbadd) > Date: Thu, 6 Dec 2018 12:53:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f501053662ee2c79336012b5cf3eeb86539bbadd) [//]: # (START_SECTION b879fa2bdc4a4388efc07e8c9e3f4004abe15e98) ### Update use-cases.rst > Commit: [b879fa2bdc4a4388efc07e8c9e3f4004abe15e98](https://github.com/dOpensource/dsiprouter/commit/b879fa2bdc4a4388efc07e8c9e3f4004abe15e98) > Date: Thu, 6 Dec 2018 12:52:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b879fa2bdc4a4388efc07e8c9e3f4004abe15e98) [//]: # (START_SECTION 79318e3ef70daf15dad5b620857efa857b2066ed) ### Update use-cases.rst > Commit: [79318e3ef70daf15dad5b620857efa857b2066ed](https://github.com/dOpensource/dsiprouter/commit/79318e3ef70daf15dad5b620857efa857b2066ed) > Date: Thu, 6 Dec 2018 12:51:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79318e3ef70daf15dad5b620857efa857b2066ed) [//]: # (START_SECTION f8e012438b1cb25abbcc2ff90caf8ebc2ce14ada) ### Update use-cases.rst > Commit: [f8e012438b1cb25abbcc2ff90caf8ebc2ce14ada](https://github.com/dOpensource/dsiprouter/commit/f8e012438b1cb25abbcc2ff90caf8ebc2ce14ada) > Date: Thu, 6 Dec 2018 12:50:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f8e012438b1cb25abbcc2ff90caf8ebc2ce14ada) [//]: # (START_SECTION 2ec4f03b7976b65d99845fbc0950379a9f6b5a59) ### Update use-cases.rst > Commit: [2ec4f03b7976b65d99845fbc0950379a9f6b5a59](https://github.com/dOpensource/dsiprouter/commit/2ec4f03b7976b65d99845fbc0950379a9f6b5a59) > Date: Thu, 6 Dec 2018 12:49:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2ec4f03b7976b65d99845fbc0950379a9f6b5a59) [//]: # (START_SECTION 13a73ea3062ca6e8977b6257dc0a0e80e9ff0d2f) ### Update use-cases.rst > Commit: [13a73ea3062ca6e8977b6257dc0a0e80e9ff0d2f](https://github.com/dOpensource/dsiprouter/commit/13a73ea3062ca6e8977b6257dc0a0e80e9ff0d2f) > Date: Thu, 6 Dec 2018 12:45:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 13a73ea3062ca6e8977b6257dc0a0e80e9ff0d2f) [//]: # (START_SECTION dbb03ebac0d3391ecb09345d7cf45ddc509104ff) ### Add files via upload > Commit: [dbb03ebac0d3391ecb09345d7cf45ddc509104ff](https://github.com/dOpensource/dsiprouter/commit/dbb03ebac0d3391ecb09345d7cf45ddc509104ff) > Date: Thu, 6 Dec 2018 12:43:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dbb03ebac0d3391ecb09345d7cf45ddc509104ff) [//]: # (START_SECTION 7535484f98c0098f8358cbdb3d7a82834272f566) ### Update use-cases.rst > Commit: [7535484f98c0098f8358cbdb3d7a82834272f566](https://github.com/dOpensource/dsiprouter/commit/7535484f98c0098f8358cbdb3d7a82834272f566) > Date: Thu, 6 Dec 2018 12:42:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7535484f98c0098f8358cbdb3d7a82834272f566) [//]: # (START_SECTION ae3d0c71561ce754a77e4b958de94cb494c17ba7) ### Update command_line_options.rst > Commit: [ae3d0c71561ce754a77e4b958de94cb494c17ba7](https://github.com/dOpensource/dsiprouter/commit/ae3d0c71561ce754a77e4b958de94cb494c17ba7) > Date: Thu, 6 Dec 2018 12:36:03 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ae3d0c71561ce754a77e4b958de94cb494c17ba7) [//]: # (START_SECTION 6475fee6ea6534f2e5641a56510bb36256951327) ### Update command_line_options.rst > Commit: [6475fee6ea6534f2e5641a56510bb36256951327](https://github.com/dOpensource/dsiprouter/commit/6475fee6ea6534f2e5641a56510bb36256951327) > Date: Thu, 6 Dec 2018 12:34:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6475fee6ea6534f2e5641a56510bb36256951327) [//]: # (START_SECTION 89cbb67300162eea463f9ac40cf533eed2caa6aa) ### Update command_line_options.rst > Commit: [89cbb67300162eea463f9ac40cf533eed2caa6aa](https://github.com/dOpensource/dsiprouter/commit/89cbb67300162eea463f9ac40cf533eed2caa6aa) > Date: Thu, 6 Dec 2018 12:30:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 89cbb67300162eea463f9ac40cf533eed2caa6aa) [//]: # (START_SECTION ca0d3756116e607ea96d3165151f590dbf605d4a) ### Update command_line_options.rst > Commit: [ca0d3756116e607ea96d3165151f590dbf605d4a](https://github.com/dOpensource/dsiprouter/commit/ca0d3756116e607ea96d3165151f590dbf605d4a) > Date: Thu, 6 Dec 2018 12:26:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ca0d3756116e607ea96d3165151f590dbf605d4a) [//]: # (START_SECTION c81cc5897f336db0c8889c06af72db2b3bca30e7) ### Update command_line_options.rst > Commit: [c81cc5897f336db0c8889c06af72db2b3bca30e7](https://github.com/dOpensource/dsiprouter/commit/c81cc5897f336db0c8889c06af72db2b3bca30e7) > Date: Thu, 6 Dec 2018 12:20:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c81cc5897f336db0c8889c06af72db2b3bca30e7) [//]: # (START_SECTION 1b983f297226e77b2800630ad2335da42ba976f3) ### Update command_line_options.rst > Commit: [1b983f297226e77b2800630ad2335da42ba976f3](https://github.com/dOpensource/dsiprouter/commit/1b983f297226e77b2800630ad2335da42ba976f3) > Date: Thu, 6 Dec 2018 12:20:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1b983f297226e77b2800630ad2335da42ba976f3) [//]: # (START_SECTION 3596c39c740ef83457b75261b774a48894c4420a) ### Update command_line_options.rst > Commit: [3596c39c740ef83457b75261b774a48894c4420a](https://github.com/dOpensource/dsiprouter/commit/3596c39c740ef83457b75261b774a48894c4420a) > Date: Thu, 6 Dec 2018 12:19:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3596c39c740ef83457b75261b774a48894c4420a) [//]: # (START_SECTION 22d83a156b253fc2f203a2e08d0538a7241675cc) ### Update command_line_options.rst > Commit: [22d83a156b253fc2f203a2e08d0538a7241675cc](https://github.com/dOpensource/dsiprouter/commit/22d83a156b253fc2f203a2e08d0538a7241675cc) > Date: Thu, 6 Dec 2018 11:56:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 22d83a156b253fc2f203a2e08d0538a7241675cc) [//]: # (START_SECTION 41d9b947ac9c7c7a2bf53d488b84c31ff436953f) ### Update command_line_options.rst > Commit: [41d9b947ac9c7c7a2bf53d488b84c31ff436953f](https://github.com/dOpensource/dsiprouter/commit/41d9b947ac9c7c7a2bf53d488b84c31ff436953f) > Date: Thu, 6 Dec 2018 11:19:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 41d9b947ac9c7c7a2bf53d488b84c31ff436953f) [//]: # (START_SECTION 79621819717360f647e693c07704ba1d7c30b16c) ### Update command_line_options.rst > Commit: [79621819717360f647e693c07704ba1d7c30b16c](https://github.com/dOpensource/dsiprouter/commit/79621819717360f647e693c07704ba1d7c30b16c) > Date: Thu, 6 Dec 2018 11:17:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79621819717360f647e693c07704ba1d7c30b16c) [//]: # (START_SECTION e7d0e8562eb97199081bb7b9f84c68e218d0b557) ### Update command_line_options.rst > Commit: [e7d0e8562eb97199081bb7b9f84c68e218d0b557](https://github.com/dOpensource/dsiprouter/commit/e7d0e8562eb97199081bb7b9f84c68e218d0b557) > Date: Thu, 6 Dec 2018 11:17:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e7d0e8562eb97199081bb7b9f84c68e218d0b557) [//]: # (START_SECTION b5ebe5ff4f5d9a1e2f5158ee99e1d2bd08f0582f) ### Update command_line_options.rst > Commit: [b5ebe5ff4f5d9a1e2f5158ee99e1d2bd08f0582f](https://github.com/dOpensource/dsiprouter/commit/b5ebe5ff4f5d9a1e2f5158ee99e1d2bd08f0582f) > Date: Thu, 6 Dec 2018 11:16:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b5ebe5ff4f5d9a1e2f5158ee99e1d2bd08f0582f) [//]: # (START_SECTION b02dbcb157172a3c6ccd5eadfb2a78477557799a) ### Update command_line_options.rst > Commit: [b02dbcb157172a3c6ccd5eadfb2a78477557799a](https://github.com/dOpensource/dsiprouter/commit/b02dbcb157172a3c6ccd5eadfb2a78477557799a) > Date: Thu, 6 Dec 2018 11:15:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b02dbcb157172a3c6ccd5eadfb2a78477557799a) [//]: # (START_SECTION 6098b9f4d04820329d615ec01f8332748132a6de) ### Update command_line_options.rst > Commit: [6098b9f4d04820329d615ec01f8332748132a6de](https://github.com/dOpensource/dsiprouter/commit/6098b9f4d04820329d615ec01f8332748132a6de) > Date: Thu, 6 Dec 2018 11:13:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6098b9f4d04820329d615ec01f8332748132a6de) [//]: # (START_SECTION dab249698fd794f5c70475ec326165f2733107a6) ### Update command_line_options.rst > Commit: [dab249698fd794f5c70475ec326165f2733107a6](https://github.com/dOpensource/dsiprouter/commit/dab249698fd794f5c70475ec326165f2733107a6) > Date: Thu, 6 Dec 2018 11:11:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dab249698fd794f5c70475ec326165f2733107a6) [//]: # (START_SECTION 7e0faf08240b1026784dab219fc93fbfc88ddab9) ### Update command_line_options.rst > Commit: [7e0faf08240b1026784dab219fc93fbfc88ddab9](https://github.com/dOpensource/dsiprouter/commit/7e0faf08240b1026784dab219fc93fbfc88ddab9) > Date: Thu, 6 Dec 2018 11:11:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7e0faf08240b1026784dab219fc93fbfc88ddab9) [//]: # (START_SECTION 5caadc2dd4f99cdb4055f94e82247ac7b4644c82) ### Update command_line_options.rst > Commit: [5caadc2dd4f99cdb4055f94e82247ac7b4644c82](https://github.com/dOpensource/dsiprouter/commit/5caadc2dd4f99cdb4055f94e82247ac7b4644c82) > Date: Thu, 6 Dec 2018 11:11:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5caadc2dd4f99cdb4055f94e82247ac7b4644c82) [//]: # (START_SECTION eeae319e6fba19122867592373a6cf625ead92a0) ### Update command_line_options.rst > Commit: [eeae319e6fba19122867592373a6cf625ead92a0](https://github.com/dOpensource/dsiprouter/commit/eeae319e6fba19122867592373a6cf625ead92a0) > Date: Thu, 6 Dec 2018 11:09:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eeae319e6fba19122867592373a6cf625ead92a0) [//]: # (START_SECTION 151d86f75adc877b7d11cc35540cc848fce6d4a2) ### Update command_line_options.rst > Commit: [151d86f75adc877b7d11cc35540cc848fce6d4a2](https://github.com/dOpensource/dsiprouter/commit/151d86f75adc877b7d11cc35540cc848fce6d4a2) > Date: Thu, 6 Dec 2018 11:08:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 151d86f75adc877b7d11cc35540cc848fce6d4a2) [//]: # (START_SECTION 54b9e6b4096e25aa496d37571a8528afc7f6d586) ### Update command_line_options.rst > Commit: [54b9e6b4096e25aa496d37571a8528afc7f6d586](https://github.com/dOpensource/dsiprouter/commit/54b9e6b4096e25aa496d37571a8528afc7f6d586) > Date: Thu, 6 Dec 2018 11:07:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 54b9e6b4096e25aa496d37571a8528afc7f6d586) [//]: # (START_SECTION c3d0d364edf4d383ff904e23008821396e3cbf28) ### Update command_line_options.rst > Commit: [c3d0d364edf4d383ff904e23008821396e3cbf28](https://github.com/dOpensource/dsiprouter/commit/c3d0d364edf4d383ff904e23008821396e3cbf28) > Date: Thu, 6 Dec 2018 11:06:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c3d0d364edf4d383ff904e23008821396e3cbf28) [//]: # (START_SECTION 202ecb65234c571f146226d3c8421f7ec53011b1) ### Update command_line_options.rst > Commit: [202ecb65234c571f146226d3c8421f7ec53011b1](https://github.com/dOpensource/dsiprouter/commit/202ecb65234c571f146226d3c8421f7ec53011b1) > Date: Thu, 6 Dec 2018 11:05:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 202ecb65234c571f146226d3c8421f7ec53011b1) [//]: # (START_SECTION 8f4e543faab2ba15f0609321876286d3d98149b5) ### Update command_line_options.rst > Commit: [8f4e543faab2ba15f0609321876286d3d98149b5](https://github.com/dOpensource/dsiprouter/commit/8f4e543faab2ba15f0609321876286d3d98149b5) > Date: Thu, 6 Dec 2018 11:04:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8f4e543faab2ba15f0609321876286d3d98149b5) [//]: # (START_SECTION b7e8dff20e515cb91bcc486e3573714d5f53e68b) ### Update command_line_options.rst > Commit: [b7e8dff20e515cb91bcc486e3573714d5f53e68b](https://github.com/dOpensource/dsiprouter/commit/b7e8dff20e515cb91bcc486e3573714d5f53e68b) > Date: Thu, 6 Dec 2018 11:02:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b7e8dff20e515cb91bcc486e3573714d5f53e68b) [//]: # (START_SECTION d5258fa6c34c0b4d238f6f9c484495b07e42bc70) ### Update command_line_options.rst > Commit: [d5258fa6c34c0b4d238f6f9c484495b07e42bc70](https://github.com/dOpensource/dsiprouter/commit/d5258fa6c34c0b4d238f6f9c484495b07e42bc70) > Date: Thu, 6 Dec 2018 11:00:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d5258fa6c34c0b4d238f6f9c484495b07e42bc70) [//]: # (START_SECTION d1f5701490e421feacd1f2f83526c343524a126e) ### Update command_line_options.rst > Commit: [d1f5701490e421feacd1f2f83526c343524a126e](https://github.com/dOpensource/dsiprouter/commit/d1f5701490e421feacd1f2f83526c343524a126e) > Date: Thu, 6 Dec 2018 10:56:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d1f5701490e421feacd1f2f83526c343524a126e) [//]: # (START_SECTION 16ba0130857f157b8ca2d25ca970628b2d6e81b6) ### Update use-cases.rst > Commit: [16ba0130857f157b8ca2d25ca970628b2d6e81b6](https://github.com/dOpensource/dsiprouter/commit/16ba0130857f157b8ca2d25ca970628b2d6e81b6) > Date: Thu, 6 Dec 2018 10:29:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 16ba0130857f157b8ca2d25ca970628b2d6e81b6) [//]: # (START_SECTION b876bb46ad10a9e6b835dbf8017e36716fe566e9) ### Update use-cases.rst > Commit: [b876bb46ad10a9e6b835dbf8017e36716fe566e9](https://github.com/dOpensource/dsiprouter/commit/b876bb46ad10a9e6b835dbf8017e36716fe566e9) > Date: Thu, 6 Dec 2018 10:28:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b876bb46ad10a9e6b835dbf8017e36716fe566e9) [//]: # (START_SECTION b286809dea53c2a2ea82ffec535bd4519f5c9e8a) ### Add files via upload > Commit: [b286809dea53c2a2ea82ffec535bd4519f5c9e8a](https://github.com/dOpensource/dsiprouter/commit/b286809dea53c2a2ea82ffec535bd4519f5c9e8a) > Date: Thu, 6 Dec 2018 10:27:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b286809dea53c2a2ea82ffec535bd4519f5c9e8a) [//]: # (START_SECTION 61f9af2883329b93f18c7878535f9f97a6b9461c) ### Update use-cases.rst > Commit: [61f9af2883329b93f18c7878535f9f97a6b9461c](https://github.com/dOpensource/dsiprouter/commit/61f9af2883329b93f18c7878535f9f97a6b9461c) > Date: Thu, 6 Dec 2018 10:22:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 61f9af2883329b93f18c7878535f9f97a6b9461c) [//]: # (START_SECTION 003a349e1cd36f53ee8c76d0aa7f451b14d09fff) ### Add files via upload > Commit: [003a349e1cd36f53ee8c76d0aa7f451b14d09fff](https://github.com/dOpensource/dsiprouter/commit/003a349e1cd36f53ee8c76d0aa7f451b14d09fff) > Date: Thu, 6 Dec 2018 10:21:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 003a349e1cd36f53ee8c76d0aa7f451b14d09fff) [//]: # (START_SECTION 725ceeb3a0a11c0395f58b8524db7a5dc482f3b1) ### Delete 11d_dialplan2.PNG > Commit: [725ceeb3a0a11c0395f58b8524db7a5dc482f3b1](https://github.com/dOpensource/dsiprouter/commit/725ceeb3a0a11c0395f58b8524db7a5dc482f3b1) > Date: Thu, 6 Dec 2018 10:19:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 725ceeb3a0a11c0395f58b8524db7a5dc482f3b1) [//]: # (START_SECTION b36d14fb3160a67bef18ef1e98f81822d9f97ebe) ### Update command_line_options.rst > Commit: [b36d14fb3160a67bef18ef1e98f81822d9f97ebe](https://github.com/dOpensource/dsiprouter/commit/b36d14fb3160a67bef18ef1e98f81822d9f97ebe) > Date: Thu, 6 Dec 2018 09:42:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b36d14fb3160a67bef18ef1e98f81822d9f97ebe) [//]: # (START_SECTION 04439585a81b86bc2b6efa84ed95c22ed6b4aa2b) ### Fixed an issue with sync'ing with FusionPBX servers > Commit: [04439585a81b86bc2b6efa84ed95c22ed6b4aa2b](https://github.com/dOpensource/dsiprouter/commit/04439585a81b86bc2b6efa84ed95c22ed6b4aa2b) > Date: Wed, 5 Dec 2018 21:40:25 +0000 > Author: root (root@debian-v51.localdomain) > Committer: root (root@debian-v51.localdomain) > Signed: --- [//]: # (END_SECTION 04439585a81b86bc2b6efa84ed95c22ed6b4aa2b) [//]: # (START_SECTION 817cdea62dfacc1a2b26d76a5585473bdbdf80b2) ### Update command_line_options.rst > Commit: [817cdea62dfacc1a2b26d76a5585473bdbdf80b2](https://github.com/dOpensource/dsiprouter/commit/817cdea62dfacc1a2b26d76a5585473bdbdf80b2) > Date: Wed, 5 Dec 2018 16:10:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 817cdea62dfacc1a2b26d76a5585473bdbdf80b2) [//]: # (START_SECTION a67b89861bdcd4e46669fddaf9c9e7c4447a7c75) ### Update command_line_options.rst > Commit: [a67b89861bdcd4e46669fddaf9c9e7c4447a7c75](https://github.com/dOpensource/dsiprouter/commit/a67b89861bdcd4e46669fddaf9c9e7c4447a7c75) > Date: Wed, 5 Dec 2018 16:09:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a67b89861bdcd4e46669fddaf9c9e7c4447a7c75) [//]: # (START_SECTION 2fe18f670331c8858287495c2440c218a480eaf4) ### Update command_line_options.rst > Commit: [2fe18f670331c8858287495c2440c218a480eaf4](https://github.com/dOpensource/dsiprouter/commit/2fe18f670331c8858287495c2440c218a480eaf4) > Date: Wed, 5 Dec 2018 16:08:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2fe18f670331c8858287495c2440c218a480eaf4) [//]: # (START_SECTION 70fe86b3691b34c50faf1aa9d78f0d7bd29826f4) ### Update command_line_options.rst > Commit: [70fe86b3691b34c50faf1aa9d78f0d7bd29826f4](https://github.com/dOpensource/dsiprouter/commit/70fe86b3691b34c50faf1aa9d78f0d7bd29826f4) > Date: Wed, 5 Dec 2018 16:08:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 70fe86b3691b34c50faf1aa9d78f0d7bd29826f4) [//]: # (START_SECTION 176580d083e975678c1cb0204af07ec1f4d1e377) ### Update command_line_options.rst > Commit: [176580d083e975678c1cb0204af07ec1f4d1e377](https://github.com/dOpensource/dsiprouter/commit/176580d083e975678c1cb0204af07ec1f4d1e377) > Date: Wed, 5 Dec 2018 16:07:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 176580d083e975678c1cb0204af07ec1f4d1e377) [//]: # (START_SECTION 2031fbe4e584fd9ff239912990afb64bd8465206) ### Update command_line_options.rst > Commit: [2031fbe4e584fd9ff239912990afb64bd8465206](https://github.com/dOpensource/dsiprouter/commit/2031fbe4e584fd9ff239912990afb64bd8465206) > Date: Wed, 5 Dec 2018 16:06:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2031fbe4e584fd9ff239912990afb64bd8465206) [//]: # (START_SECTION 60e2fdab00f8f20d4204e4822909fbf15f2b80a9) ### Update command_line_options.rst > Commit: [60e2fdab00f8f20d4204e4822909fbf15f2b80a9](https://github.com/dOpensource/dsiprouter/commit/60e2fdab00f8f20d4204e4822909fbf15f2b80a9) > Date: Wed, 5 Dec 2018 16:06:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 60e2fdab00f8f20d4204e4822909fbf15f2b80a9) [//]: # (START_SECTION ace41e37436366b3b48f28150875d1da8683f971) ### Update command_line_options.rst > Commit: [ace41e37436366b3b48f28150875d1da8683f971](https://github.com/dOpensource/dsiprouter/commit/ace41e37436366b3b48f28150875d1da8683f971) > Date: Wed, 5 Dec 2018 16:05:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ace41e37436366b3b48f28150875d1da8683f971) [//]: # (START_SECTION c190e513d8762ca6cbd34203d4e7f34409718350) ### Update command_line_options.rst > Commit: [c190e513d8762ca6cbd34203d4e7f34409718350](https://github.com/dOpensource/dsiprouter/commit/c190e513d8762ca6cbd34203d4e7f34409718350) > Date: Wed, 5 Dec 2018 16:05:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c190e513d8762ca6cbd34203d4e7f34409718350) [//]: # (START_SECTION dbe86a1d276ffe751749d78b7644bf53d727b235) ### Update command_line_options.rst > Commit: [dbe86a1d276ffe751749d78b7644bf53d727b235](https://github.com/dOpensource/dsiprouter/commit/dbe86a1d276ffe751749d78b7644bf53d727b235) > Date: Wed, 5 Dec 2018 16:04:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dbe86a1d276ffe751749d78b7644bf53d727b235) [//]: # (START_SECTION 92f54bd32c64650744622d29e7639e8495298709) ### Update command_line_options.rst > Commit: [92f54bd32c64650744622d29e7639e8495298709](https://github.com/dOpensource/dsiprouter/commit/92f54bd32c64650744622d29e7639e8495298709) > Date: Wed, 5 Dec 2018 16:03:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92f54bd32c64650744622d29e7639e8495298709) [//]: # (START_SECTION 974cede9e228fb9f90ea1f02b56762ca3e343a6a) ### Update command_line_options.rst > Commit: [974cede9e228fb9f90ea1f02b56762ca3e343a6a](https://github.com/dOpensource/dsiprouter/commit/974cede9e228fb9f90ea1f02b56762ca3e343a6a) > Date: Wed, 5 Dec 2018 16:02:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 974cede9e228fb9f90ea1f02b56762ca3e343a6a) [//]: # (START_SECTION 1c5f9ef05f2ac1df72a2c2790e0ce4a85196f899) ### Update command_line_options.rst > Commit: [1c5f9ef05f2ac1df72a2c2790e0ce4a85196f899](https://github.com/dOpensource/dsiprouter/commit/1c5f9ef05f2ac1df72a2c2790e0ce4a85196f899) > Date: Wed, 5 Dec 2018 16:02:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c5f9ef05f2ac1df72a2c2790e0ce4a85196f899) [//]: # (START_SECTION ad56241386f50a1853c92bd6bc9de657d460e228) ### Update command_line_options.rst > Commit: [ad56241386f50a1853c92bd6bc9de657d460e228](https://github.com/dOpensource/dsiprouter/commit/ad56241386f50a1853c92bd6bc9de657d460e228) > Date: Wed, 5 Dec 2018 16:01:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ad56241386f50a1853c92bd6bc9de657d460e228) [//]: # (START_SECTION ea5e2e2a8a5282c92355f12c5c12210eeb7993d6) ### Update command_line_options.rst > Commit: [ea5e2e2a8a5282c92355f12c5c12210eeb7993d6](https://github.com/dOpensource/dsiprouter/commit/ea5e2e2a8a5282c92355f12c5c12210eeb7993d6) > Date: Wed, 5 Dec 2018 16:01:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ea5e2e2a8a5282c92355f12c5c12210eeb7993d6) [//]: # (START_SECTION 14c551b7293d696bfdcafd1c662291f094a1bfaf) ### Update command_line_options.rst > Commit: [14c551b7293d696bfdcafd1c662291f094a1bfaf](https://github.com/dOpensource/dsiprouter/commit/14c551b7293d696bfdcafd1c662291f094a1bfaf) > Date: Wed, 5 Dec 2018 16:01:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 14c551b7293d696bfdcafd1c662291f094a1bfaf) [//]: # (START_SECTION 25fc05cbf29e3dd7978c5c3ff98c2af837fe6393) ### Update command_line_options.rst > Commit: [25fc05cbf29e3dd7978c5c3ff98c2af837fe6393](https://github.com/dOpensource/dsiprouter/commit/25fc05cbf29e3dd7978c5c3ff98c2af837fe6393) > Date: Wed, 5 Dec 2018 16:00:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 25fc05cbf29e3dd7978c5c3ff98c2af837fe6393) [//]: # (START_SECTION 44dd4bdf4e26c1dd93494f1aff23a31f1f7a9e79) ### Update command_line_options.rst > Commit: [44dd4bdf4e26c1dd93494f1aff23a31f1f7a9e79](https://github.com/dOpensource/dsiprouter/commit/44dd4bdf4e26c1dd93494f1aff23a31f1f7a9e79) > Date: Wed, 5 Dec 2018 15:59:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 44dd4bdf4e26c1dd93494f1aff23a31f1f7a9e79) [//]: # (START_SECTION 09821b0afdefd29f0a2d8f1d16d07bfbf4993d65) ### Update command_line_options.rst > Commit: [09821b0afdefd29f0a2d8f1d16d07bfbf4993d65](https://github.com/dOpensource/dsiprouter/commit/09821b0afdefd29f0a2d8f1d16d07bfbf4993d65) > Date: Wed, 5 Dec 2018 15:50:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 09821b0afdefd29f0a2d8f1d16d07bfbf4993d65) [//]: # (START_SECTION b190587fe5a11c9bd30338dca34d8ffc5a3cb9a5) ### Update command_line_options.rst > Commit: [b190587fe5a11c9bd30338dca34d8ffc5a3cb9a5](https://github.com/dOpensource/dsiprouter/commit/b190587fe5a11c9bd30338dca34d8ffc5a3cb9a5) > Date: Wed, 5 Dec 2018 15:49:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b190587fe5a11c9bd30338dca34d8ffc5a3cb9a5) [//]: # (START_SECTION 5b3223187d5d3c66ba4ac176a28d9d445af3a05d) ### Update command_line_options.rst > Commit: [5b3223187d5d3c66ba4ac176a28d9d445af3a05d](https://github.com/dOpensource/dsiprouter/commit/5b3223187d5d3c66ba4ac176a28d9d445af3a05d) > Date: Wed, 5 Dec 2018 15:49:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5b3223187d5d3c66ba4ac176a28d9d445af3a05d) [//]: # (START_SECTION 30711d7e9e349183490fb405f8309d463dfe75c5) ### Update command_line_options.rst > Commit: [30711d7e9e349183490fb405f8309d463dfe75c5](https://github.com/dOpensource/dsiprouter/commit/30711d7e9e349183490fb405f8309d463dfe75c5) > Date: Wed, 5 Dec 2018 15:48:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 30711d7e9e349183490fb405f8309d463dfe75c5) [//]: # (START_SECTION 466d3cbed9a7de0143d2c409e39e477c2c437665) ### Update command_line_options.rst > Commit: [466d3cbed9a7de0143d2c409e39e477c2c437665](https://github.com/dOpensource/dsiprouter/commit/466d3cbed9a7de0143d2c409e39e477c2c437665) > Date: Wed, 5 Dec 2018 15:46:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 466d3cbed9a7de0143d2c409e39e477c2c437665) [//]: # (START_SECTION 87ffe7fe3b93ee0a27d4d22c89869325573a7386) ### Update command_line_options.rst > Commit: [87ffe7fe3b93ee0a27d4d22c89869325573a7386](https://github.com/dOpensource/dsiprouter/commit/87ffe7fe3b93ee0a27d4d22c89869325573a7386) > Date: Wed, 5 Dec 2018 15:45:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 87ffe7fe3b93ee0a27d4d22c89869325573a7386) [//]: # (START_SECTION a825793487ed70e3969d94660ddf17905edd3d78) ### Update command_line_options.rst > Commit: [a825793487ed70e3969d94660ddf17905edd3d78](https://github.com/dOpensource/dsiprouter/commit/a825793487ed70e3969d94660ddf17905edd3d78) > Date: Wed, 5 Dec 2018 15:44:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a825793487ed70e3969d94660ddf17905edd3d78) [//]: # (START_SECTION 127fb5fea981bda64d6d17c298789f1187fcecfd) ### Update command_line_options.rst > Commit: [127fb5fea981bda64d6d17c298789f1187fcecfd](https://github.com/dOpensource/dsiprouter/commit/127fb5fea981bda64d6d17c298789f1187fcecfd) > Date: Wed, 5 Dec 2018 15:40:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 127fb5fea981bda64d6d17c298789f1187fcecfd) [//]: # (START_SECTION 63814bd3e27245dcfc5d4ba11f765c6ecd727838) ### Update command_line_options.rst > Commit: [63814bd3e27245dcfc5d4ba11f765c6ecd727838](https://github.com/dOpensource/dsiprouter/commit/63814bd3e27245dcfc5d4ba11f765c6ecd727838) > Date: Wed, 5 Dec 2018 15:40:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 63814bd3e27245dcfc5d4ba11f765c6ecd727838) [//]: # (START_SECTION 2461fc8fd838e233301fd6268c73e227ea812483) ### Update command_line_options.rst > Commit: [2461fc8fd838e233301fd6268c73e227ea812483](https://github.com/dOpensource/dsiprouter/commit/2461fc8fd838e233301fd6268c73e227ea812483) > Date: Wed, 5 Dec 2018 15:39:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2461fc8fd838e233301fd6268c73e227ea812483) [//]: # (START_SECTION 39a8cbf850140154611de7842acbe94da5ebf3d5) ### Update command_line_options.rst > Commit: [39a8cbf850140154611de7842acbe94da5ebf3d5](https://github.com/dOpensource/dsiprouter/commit/39a8cbf850140154611de7842acbe94da5ebf3d5) > Date: Wed, 5 Dec 2018 15:38:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 39a8cbf850140154611de7842acbe94da5ebf3d5) [//]: # (START_SECTION 23b0452d3f57e7e1f1a04e9b65ac79ebf45fe94c) ### Update command_line_options.rst > Commit: [23b0452d3f57e7e1f1a04e9b65ac79ebf45fe94c](https://github.com/dOpensource/dsiprouter/commit/23b0452d3f57e7e1f1a04e9b65ac79ebf45fe94c) > Date: Wed, 5 Dec 2018 15:37:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 23b0452d3f57e7e1f1a04e9b65ac79ebf45fe94c) [//]: # (START_SECTION 63a6cedf489fe45966aa661ba42ef14cfb50af7f) ### Update command_line_options.rst > Commit: [63a6cedf489fe45966aa661ba42ef14cfb50af7f](https://github.com/dOpensource/dsiprouter/commit/63a6cedf489fe45966aa661ba42ef14cfb50af7f) > Date: Wed, 5 Dec 2018 15:33:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 63a6cedf489fe45966aa661ba42ef14cfb50af7f) [//]: # (START_SECTION 8861b4b932ea90f9ed891893eecfd4b6e360ad32) ### Update command_line_options.rst > Commit: [8861b4b932ea90f9ed891893eecfd4b6e360ad32](https://github.com/dOpensource/dsiprouter/commit/8861b4b932ea90f9ed891893eecfd4b6e360ad32) > Date: Wed, 5 Dec 2018 15:33:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8861b4b932ea90f9ed891893eecfd4b6e360ad32) [//]: # (START_SECTION e47c3301943a66984671a3fc238fc8d2c48804d7) ### Update command_line_options.rst > Commit: [e47c3301943a66984671a3fc238fc8d2c48804d7](https://github.com/dOpensource/dsiprouter/commit/e47c3301943a66984671a3fc238fc8d2c48804d7) > Date: Wed, 5 Dec 2018 15:32:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e47c3301943a66984671a3fc238fc8d2c48804d7) [//]: # (START_SECTION b5551d43be75252f3f08d9ba1984be8af9b0161b) ### Update command_line_options.rst > Commit: [b5551d43be75252f3f08d9ba1984be8af9b0161b](https://github.com/dOpensource/dsiprouter/commit/b5551d43be75252f3f08d9ba1984be8af9b0161b) > Date: Wed, 5 Dec 2018 15:27:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b5551d43be75252f3f08d9ba1984be8af9b0161b) [//]: # (START_SECTION ca51e56e4fad9d83d97dd625061a726c6737a127) ### Update command_line_options.rst > Commit: [ca51e56e4fad9d83d97dd625061a726c6737a127](https://github.com/dOpensource/dsiprouter/commit/ca51e56e4fad9d83d97dd625061a726c6737a127) > Date: Wed, 5 Dec 2018 15:26:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ca51e56e4fad9d83d97dd625061a726c6737a127) [//]: # (START_SECTION cb77b48b62a1149bbf016cacb19529328700942e) ### Update command_line_options.rst > Commit: [cb77b48b62a1149bbf016cacb19529328700942e](https://github.com/dOpensource/dsiprouter/commit/cb77b48b62a1149bbf016cacb19529328700942e) > Date: Wed, 5 Dec 2018 15:25:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cb77b48b62a1149bbf016cacb19529328700942e) [//]: # (START_SECTION e0cb8ca5c167da59831038ac75b1896648b844ad) ### Update command_line_options.rst > Commit: [e0cb8ca5c167da59831038ac75b1896648b844ad](https://github.com/dOpensource/dsiprouter/commit/e0cb8ca5c167da59831038ac75b1896648b844ad) > Date: Wed, 5 Dec 2018 15:24:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e0cb8ca5c167da59831038ac75b1896648b844ad) [//]: # (START_SECTION 56ed04546303de03c403ca658c16909edea8e265) ### Update and rename uninstalling.rst to command_line_options.rst > Commit: [56ed04546303de03c403ca658c16909edea8e265](https://github.com/dOpensource/dsiprouter/commit/56ed04546303de03c403ca658c16909edea8e265) > Date: Wed, 5 Dec 2018 12:39:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 56ed04546303de03c403ca658c16909edea8e265) [//]: # (START_SECTION 2ebbf629ddc5fbe7141f961c7a5d819a62d5f194) ### Update configuring.rst > Commit: [2ebbf629ddc5fbe7141f961c7a5d819a62d5f194](https://github.com/dOpensource/dsiprouter/commit/2ebbf629ddc5fbe7141f961c7a5d819a62d5f194) > Date: Wed, 5 Dec 2018 12:38:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2ebbf629ddc5fbe7141f961c7a5d819a62d5f194) [//]: # (START_SECTION f5ed526604dda2ea27d924e008fb8822a40b3b40) ### Update configuring.rst > Commit: [f5ed526604dda2ea27d924e008fb8822a40b3b40](https://github.com/dOpensource/dsiprouter/commit/f5ed526604dda2ea27d924e008fb8822a40b3b40) > Date: Wed, 5 Dec 2018 12:09:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f5ed526604dda2ea27d924e008fb8822a40b3b40) [//]: # (START_SECTION 68dbc78ea23bc3c094df7de434fb829cf5f71291) ### Update uninstalling.rst > Commit: [68dbc78ea23bc3c094df7de434fb829cf5f71291](https://github.com/dOpensource/dsiprouter/commit/68dbc78ea23bc3c094df7de434fb829cf5f71291) > Date: Wed, 5 Dec 2018 12:05:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 68dbc78ea23bc3c094df7de434fb829cf5f71291) [//]: # (START_SECTION d7ae04bc83484c23119be8595a4b02dc8fca604d) ### Update uninstalling.rst > Commit: [d7ae04bc83484c23119be8595a4b02dc8fca604d](https://github.com/dOpensource/dsiprouter/commit/d7ae04bc83484c23119be8595a4b02dc8fca604d) > Date: Wed, 5 Dec 2018 12:04:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d7ae04bc83484c23119be8595a4b02dc8fca604d) [//]: # (START_SECTION 747a95f491ea34c7e070d8f277b1385df9e4e233) ### Update and rename uninstalling dSIPRouter.rst to uninstalling.rst > Commit: [747a95f491ea34c7e070d8f277b1385df9e4e233](https://github.com/dOpensource/dsiprouter/commit/747a95f491ea34c7e070d8f277b1385df9e4e233) > Date: Wed, 5 Dec 2018 11:54:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 747a95f491ea34c7e070d8f277b1385df9e4e233) [//]: # (START_SECTION 1e46a6d9596d8f7b37f5fe4b532c45bd534d9341) ### Create uninstalling dSIPRouter.rst > Commit: [1e46a6d9596d8f7b37f5fe4b532c45bd534d9341](https://github.com/dOpensource/dsiprouter/commit/1e46a6d9596d8f7b37f5fe4b532c45bd534d9341) > Date: Wed, 5 Dec 2018 11:52:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1e46a6d9596d8f7b37f5fe4b532c45bd534d9341) [//]: # (START_SECTION 042000aa2f4103516b675429826aae27ee630cff) ### Update use-cases.rst > Commit: [042000aa2f4103516b675429826aae27ee630cff](https://github.com/dOpensource/dsiprouter/commit/042000aa2f4103516b675429826aae27ee630cff) > Date: Wed, 5 Dec 2018 11:50:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 042000aa2f4103516b675429826aae27ee630cff) [//]: # (START_SECTION 063cf906081d3894fb0dbe20e2233075d290b259) ### Update use-cases.rst > Commit: [063cf906081d3894fb0dbe20e2233075d290b259](https://github.com/dOpensource/dsiprouter/commit/063cf906081d3894fb0dbe20e2233075d290b259) > Date: Wed, 5 Dec 2018 11:49:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 063cf906081d3894fb0dbe20e2233075d290b259) [//]: # (START_SECTION 9e1a6b9565e264379026199507a5f12e9b18d244) ### Update use-cases.rst > Commit: [9e1a6b9565e264379026199507a5f12e9b18d244](https://github.com/dOpensource/dsiprouter/commit/9e1a6b9565e264379026199507a5f12e9b18d244) > Date: Wed, 5 Dec 2018 11:48:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9e1a6b9565e264379026199507a5f12e9b18d244) [//]: # (START_SECTION 8805ecf84743bc0cfeae3becde69e9b930a41bbc) ### Update use-cases.rst > Commit: [8805ecf84743bc0cfeae3becde69e9b930a41bbc](https://github.com/dOpensource/dsiprouter/commit/8805ecf84743bc0cfeae3becde69e9b930a41bbc) > Date: Tue, 4 Dec 2018 14:59:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8805ecf84743bc0cfeae3becde69e9b930a41bbc) [//]: # (START_SECTION 90b4ac84b052fd1eb963ae9ef179de54d17b9339) ### Update use-cases.rst > Commit: [90b4ac84b052fd1eb963ae9ef179de54d17b9339](https://github.com/dOpensource/dsiprouter/commit/90b4ac84b052fd1eb963ae9ef179de54d17b9339) > Date: Tue, 4 Dec 2018 14:57:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 90b4ac84b052fd1eb963ae9ef179de54d17b9339) [//]: # (START_SECTION 20bb8fad54023738be95050b43c584376eb38547) ### Update use-cases.rst > Commit: [20bb8fad54023738be95050b43c584376eb38547](https://github.com/dOpensource/dsiprouter/commit/20bb8fad54023738be95050b43c584376eb38547) > Date: Tue, 4 Dec 2018 14:47:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 20bb8fad54023738be95050b43c584376eb38547) [//]: # (START_SECTION c764685364cda45cd7f76ee61026fa92b3435e32) ### Delete dialplan_11d.PNG > Commit: [c764685364cda45cd7f76ee61026fa92b3435e32](https://github.com/dOpensource/dsiprouter/commit/c764685364cda45cd7f76ee61026fa92b3435e32) > Date: Tue, 4 Dec 2018 14:41:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c764685364cda45cd7f76ee61026fa92b3435e32) [//]: # (START_SECTION 7be3b97d5edc74d655caee605512830105737e9b) ### Update use-cases.rst > Commit: [7be3b97d5edc74d655caee605512830105737e9b](https://github.com/dOpensource/dsiprouter/commit/7be3b97d5edc74d655caee605512830105737e9b) > Date: Tue, 4 Dec 2018 14:36:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7be3b97d5edc74d655caee605512830105737e9b) [//]: # (START_SECTION 6e258008f3fe925994e072daaa9d75c9dbb7760a) ### Add files via upload > Commit: [6e258008f3fe925994e072daaa9d75c9dbb7760a](https://github.com/dOpensource/dsiprouter/commit/6e258008f3fe925994e072daaa9d75c9dbb7760a) > Date: Tue, 4 Dec 2018 14:35:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6e258008f3fe925994e072daaa9d75c9dbb7760a) [//]: # (START_SECTION c7ff8ae6f595b6655eac2bb180b6eb7eb157445a) ### Update use-cases.rst > Commit: [c7ff8ae6f595b6655eac2bb180b6eb7eb157445a](https://github.com/dOpensource/dsiprouter/commit/c7ff8ae6f595b6655eac2bb180b6eb7eb157445a) > Date: Tue, 4 Dec 2018 14:27:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c7ff8ae6f595b6655eac2bb180b6eb7eb157445a) [//]: # (START_SECTION ab13610276804818c8fc149180fbfe5f4529dbe4) ### Update use-cases.rst > Commit: [ab13610276804818c8fc149180fbfe5f4529dbe4](https://github.com/dOpensource/dsiprouter/commit/ab13610276804818c8fc149180fbfe5f4529dbe4) > Date: Tue, 4 Dec 2018 10:26:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ab13610276804818c8fc149180fbfe5f4529dbe4) [//]: # (START_SECTION dabb36bfa7ea0d68ad7f36e2d15a3f603ad86ac9) ### Update use-cases.rst > Commit: [dabb36bfa7ea0d68ad7f36e2d15a3f603ad86ac9](https://github.com/dOpensource/dsiprouter/commit/dabb36bfa7ea0d68ad7f36e2d15a3f603ad86ac9) > Date: Tue, 4 Dec 2018 10:24:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dabb36bfa7ea0d68ad7f36e2d15a3f603ad86ac9) [//]: # (START_SECTION 66f3b4e7aa86a9192c64fe6d1122ae5a29339b9f) ### Update use-cases.rst > Commit: [66f3b4e7aa86a9192c64fe6d1122ae5a29339b9f](https://github.com/dOpensource/dsiprouter/commit/66f3b4e7aa86a9192c64fe6d1122ae5a29339b9f) > Date: Tue, 4 Dec 2018 10:20:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 66f3b4e7aa86a9192c64fe6d1122ae5a29339b9f) [//]: # (START_SECTION a0d919e4c908cc6ac5720f4b147ed8733ebe9608) ### Update use-cases.rst > Commit: [a0d919e4c908cc6ac5720f4b147ed8733ebe9608](https://github.com/dOpensource/dsiprouter/commit/a0d919e4c908cc6ac5720f4b147ed8733ebe9608) > Date: Tue, 4 Dec 2018 10:17:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a0d919e4c908cc6ac5720f4b147ed8733ebe9608) [//]: # (START_SECTION a7c1280523780c351501ca0e25bd897de61c81a6) ### Update use-cases.rst > Commit: [a7c1280523780c351501ca0e25bd897de61c81a6](https://github.com/dOpensource/dsiprouter/commit/a7c1280523780c351501ca0e25bd897de61c81a6) > Date: Tue, 4 Dec 2018 10:04:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a7c1280523780c351501ca0e25bd897de61c81a6) [//]: # (START_SECTION 16e58ef3a16052bdcc77f8f7379dbfa8ce60571a) ### Add files via upload > Commit: [16e58ef3a16052bdcc77f8f7379dbfa8ce60571a](https://github.com/dOpensource/dsiprouter/commit/16e58ef3a16052bdcc77f8f7379dbfa8ce60571a) > Date: Tue, 4 Dec 2018 10:02:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 16e58ef3a16052bdcc77f8f7379dbfa8ce60571a) [//]: # (START_SECTION 66d404c23738071205eba79bf92da94c49f8618d) ### Update use-cases.rst > Commit: [66d404c23738071205eba79bf92da94c49f8618d](https://github.com/dOpensource/dsiprouter/commit/66d404c23738071205eba79bf92da94c49f8618d) > Date: Tue, 4 Dec 2018 09:46:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 66d404c23738071205eba79bf92da94c49f8618d) [//]: # (START_SECTION 29721571915c3304053c2a6564255d40fcb09e5f) ### Update use-cases.rst > Commit: [29721571915c3304053c2a6564255d40fcb09e5f](https://github.com/dOpensource/dsiprouter/commit/29721571915c3304053c2a6564255d40fcb09e5f) > Date: Tue, 4 Dec 2018 09:43:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 29721571915c3304053c2a6564255d40fcb09e5f) [//]: # (START_SECTION fc6564b7b42dcdc3b00384909c4b21ef8fcb3993) ### Update use-cases.rst > Commit: [fc6564b7b42dcdc3b00384909c4b21ef8fcb3993](https://github.com/dOpensource/dsiprouter/commit/fc6564b7b42dcdc3b00384909c4b21ef8fcb3993) > Date: Mon, 3 Dec 2018 14:48:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fc6564b7b42dcdc3b00384909c4b21ef8fcb3993) [//]: # (START_SECTION a28988b075950add016891cb1c0d76f28dfbeb21) ### Update use-cases.rst > Commit: [a28988b075950add016891cb1c0d76f28dfbeb21](https://github.com/dOpensource/dsiprouter/commit/a28988b075950add016891cb1c0d76f28dfbeb21) > Date: Mon, 3 Dec 2018 14:18:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a28988b075950add016891cb1c0d76f28dfbeb21) [//]: # (START_SECTION 3d083afdf332473516db6956c822b751574d059b) ### Update use-cases.rst > Commit: [3d083afdf332473516db6956c822b751574d059b](https://github.com/dOpensource/dsiprouter/commit/3d083afdf332473516db6956c822b751574d059b) > Date: Mon, 3 Dec 2018 14:16:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d083afdf332473516db6956c822b751574d059b) [//]: # (START_SECTION 539c024c671d37fa48ab31cbb977af73913825aa) ### Update use-cases.rst > Commit: [539c024c671d37fa48ab31cbb977af73913825aa](https://github.com/dOpensource/dsiprouter/commit/539c024c671d37fa48ab31cbb977af73913825aa) > Date: Mon, 3 Dec 2018 14:15:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 539c024c671d37fa48ab31cbb977af73913825aa) [//]: # (START_SECTION 8be239fc61a6cba28bea5190d8885e1d1e25b598) ### Update use-cases.rst > Commit: [8be239fc61a6cba28bea5190d8885e1d1e25b598](https://github.com/dOpensource/dsiprouter/commit/8be239fc61a6cba28bea5190d8885e1d1e25b598) > Date: Mon, 3 Dec 2018 14:13:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8be239fc61a6cba28bea5190d8885e1d1e25b598) [//]: # (START_SECTION 69e69bd2d6d42ded7df8e31e78581df9167d35ef) ### Update use-cases.rst > Commit: [69e69bd2d6d42ded7df8e31e78581df9167d35ef](https://github.com/dOpensource/dsiprouter/commit/69e69bd2d6d42ded7df8e31e78581df9167d35ef) > Date: Mon, 3 Dec 2018 14:11:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 69e69bd2d6d42ded7df8e31e78581df9167d35ef) [//]: # (START_SECTION 018693e7ebd8c5cb202964177d5de298de356ec9) ### Add files via upload > Commit: [018693e7ebd8c5cb202964177d5de298de356ec9](https://github.com/dOpensource/dsiprouter/commit/018693e7ebd8c5cb202964177d5de298de356ec9) > Date: Mon, 3 Dec 2018 14:10:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 018693e7ebd8c5cb202964177d5de298de356ec9) [//]: # (START_SECTION 2373ea2a423fbf98ce4dd0c556bf1c7797ad14b2) ### Delete fusionpbx_hosting2.PNG > Commit: [2373ea2a423fbf98ce4dd0c556bf1c7797ad14b2](https://github.com/dOpensource/dsiprouter/commit/2373ea2a423fbf98ce4dd0c556bf1c7797ad14b2) > Date: Mon, 3 Dec 2018 14:09:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2373ea2a423fbf98ce4dd0c556bf1c7797ad14b2) [//]: # (START_SECTION 6e9f2b2e48b22d581ba1f26630c7f442e9ae5f82) ### Update use-cases.rst > Commit: [6e9f2b2e48b22d581ba1f26630c7f442e9ae5f82](https://github.com/dOpensource/dsiprouter/commit/6e9f2b2e48b22d581ba1f26630c7f442e9ae5f82) > Date: Mon, 3 Dec 2018 13:59:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6e9f2b2e48b22d581ba1f26630c7f442e9ae5f82) [//]: # (START_SECTION 2358e58442d261aa832a397f09f62d71ad1c9771) ### Update use-cases.rst > Commit: [2358e58442d261aa832a397f09f62d71ad1c9771](https://github.com/dOpensource/dsiprouter/commit/2358e58442d261aa832a397f09f62d71ad1c9771) > Date: Mon, 3 Dec 2018 13:58:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2358e58442d261aa832a397f09f62d71ad1c9771) [//]: # (START_SECTION 9a156aa2b8e8249e65999f5b08f81d6fe4ca53e2) ### Update use-cases.rst > Commit: [9a156aa2b8e8249e65999f5b08f81d6fe4ca53e2](https://github.com/dOpensource/dsiprouter/commit/9a156aa2b8e8249e65999f5b08f81d6fe4ca53e2) > Date: Mon, 3 Dec 2018 13:56:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9a156aa2b8e8249e65999f5b08f81d6fe4ca53e2) [//]: # (START_SECTION 6290ec80b465320bbd966c90f4bbc38a55a7bc76) ### Update use-cases.rst > Commit: [6290ec80b465320bbd966c90f4bbc38a55a7bc76](https://github.com/dOpensource/dsiprouter/commit/6290ec80b465320bbd966c90f4bbc38a55a7bc76) > Date: Mon, 3 Dec 2018 13:55:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6290ec80b465320bbd966c90f4bbc38a55a7bc76) [//]: # (START_SECTION dab065f980de93e5d53e031a1cb95a980b90b814) ### Update use-cases.rst > Commit: [dab065f980de93e5d53e031a1cb95a980b90b814](https://github.com/dOpensource/dsiprouter/commit/dab065f980de93e5d53e031a1cb95a980b90b814) > Date: Mon, 3 Dec 2018 13:53:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dab065f980de93e5d53e031a1cb95a980b90b814) [//]: # (START_SECTION 9bc941fa9f3270a5c8db6bad9574f30482f2fc10) ### Update use-cases.rst > Commit: [9bc941fa9f3270a5c8db6bad9574f30482f2fc10](https://github.com/dOpensource/dsiprouter/commit/9bc941fa9f3270a5c8db6bad9574f30482f2fc10) > Date: Mon, 3 Dec 2018 13:50:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9bc941fa9f3270a5c8db6bad9574f30482f2fc10) [//]: # (START_SECTION aa092df23f44030cc5996d6ccd8df0cffe4e8cfe) ### Update use-cases.rst > Commit: [aa092df23f44030cc5996d6ccd8df0cffe4e8cfe](https://github.com/dOpensource/dsiprouter/commit/aa092df23f44030cc5996d6ccd8df0cffe4e8cfe) > Date: Mon, 3 Dec 2018 13:49:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aa092df23f44030cc5996d6ccd8df0cffe4e8cfe) [//]: # (START_SECTION 3160ab71dadf831e17091cc7215dd9f90d7d96af) ### Update use-cases.rst > Commit: [3160ab71dadf831e17091cc7215dd9f90d7d96af](https://github.com/dOpensource/dsiprouter/commit/3160ab71dadf831e17091cc7215dd9f90d7d96af) > Date: Mon, 3 Dec 2018 13:47:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3160ab71dadf831e17091cc7215dd9f90d7d96af) [//]: # (START_SECTION 92811fc87431326cb07f661db5d9ef72d9ce4e9a) ### Update use-cases.rst > Commit: [92811fc87431326cb07f661db5d9ef72d9ce4e9a](https://github.com/dOpensource/dsiprouter/commit/92811fc87431326cb07f661db5d9ef72d9ce4e9a) > Date: Mon, 3 Dec 2018 13:45:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92811fc87431326cb07f661db5d9ef72d9ce4e9a) [//]: # (START_SECTION 3320d2b23e9e9d8b31d199dbb83c87351a1325a0) ### Update use-cases.rst > Commit: [3320d2b23e9e9d8b31d199dbb83c87351a1325a0](https://github.com/dOpensource/dsiprouter/commit/3320d2b23e9e9d8b31d199dbb83c87351a1325a0) > Date: Mon, 3 Dec 2018 13:44:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3320d2b23e9e9d8b31d199dbb83c87351a1325a0) [//]: # (START_SECTION a23762fe4157e4cae2715b063624ebb9be06a450) ### Update use-cases.rst > Commit: [a23762fe4157e4cae2715b063624ebb9be06a450](https://github.com/dOpensource/dsiprouter/commit/a23762fe4157e4cae2715b063624ebb9be06a450) > Date: Mon, 3 Dec 2018 13:43:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a23762fe4157e4cae2715b063624ebb9be06a450) [//]: # (START_SECTION a0a5a63f38340dce64acae8f23ef26ed1e247064) ### Update use-cases.rst > Commit: [a0a5a63f38340dce64acae8f23ef26ed1e247064](https://github.com/dOpensource/dsiprouter/commit/a0a5a63f38340dce64acae8f23ef26ed1e247064) > Date: Mon, 3 Dec 2018 13:42:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a0a5a63f38340dce64acae8f23ef26ed1e247064) [//]: # (START_SECTION 4e035bebb3622c07f57e392da8c6f2afaebc3261) ### Update use-cases.rst > Commit: [4e035bebb3622c07f57e392da8c6f2afaebc3261](https://github.com/dOpensource/dsiprouter/commit/4e035bebb3622c07f57e392da8c6f2afaebc3261) > Date: Mon, 3 Dec 2018 13:39:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4e035bebb3622c07f57e392da8c6f2afaebc3261) [//]: # (START_SECTION 22b1a37803ecbb339f016f881f26bbbe5f2f8c46) ### Update use-cases.rst > Commit: [22b1a37803ecbb339f016f881f26bbbe5f2f8c46](https://github.com/dOpensource/dsiprouter/commit/22b1a37803ecbb339f016f881f26bbbe5f2f8c46) > Date: Mon, 3 Dec 2018 13:34:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 22b1a37803ecbb339f016f881f26bbbe5f2f8c46) [//]: # (START_SECTION c312d8fb2f94a0f765ef40a7085da271ad8f3601) ### Update use-cases.rst > Commit: [c312d8fb2f94a0f765ef40a7085da271ad8f3601](https://github.com/dOpensource/dsiprouter/commit/c312d8fb2f94a0f765ef40a7085da271ad8f3601) > Date: Mon, 3 Dec 2018 13:33:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c312d8fb2f94a0f765ef40a7085da271ad8f3601) [//]: # (START_SECTION e83477e70105080f6daa89859888b9c788b64831) ### Update use-cases.rst > Commit: [e83477e70105080f6daa89859888b9c788b64831](https://github.com/dOpensource/dsiprouter/commit/e83477e70105080f6daa89859888b9c788b64831) > Date: Mon, 3 Dec 2018 13:30:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e83477e70105080f6daa89859888b9c788b64831) [//]: # (START_SECTION c40947ea2137dd384adb22367ddf1831d293df15) ### Update use-cases.rst > Commit: [c40947ea2137dd384adb22367ddf1831d293df15](https://github.com/dOpensource/dsiprouter/commit/c40947ea2137dd384adb22367ddf1831d293df15) > Date: Mon, 3 Dec 2018 13:28:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c40947ea2137dd384adb22367ddf1831d293df15) [//]: # (START_SECTION 5625d5f865c3760152f1ec90a35d57c22fdf0c0a) ### Update use-cases.rst > Commit: [5625d5f865c3760152f1ec90a35d57c22fdf0c0a](https://github.com/dOpensource/dsiprouter/commit/5625d5f865c3760152f1ec90a35d57c22fdf0c0a) > Date: Mon, 3 Dec 2018 13:26:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5625d5f865c3760152f1ec90a35d57c22fdf0c0a) [//]: # (START_SECTION 3e39fd2ac22d59097105e019c60ef1998508dd7d) ### Add files via upload > Commit: [3e39fd2ac22d59097105e019c60ef1998508dd7d](https://github.com/dOpensource/dsiprouter/commit/3e39fd2ac22d59097105e019c60ef1998508dd7d) > Date: Mon, 3 Dec 2018 13:22:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3e39fd2ac22d59097105e019c60ef1998508dd7d) [//]: # (START_SECTION dd0049686de8b640ca1123d4dc8f4194021ee887) ### Update use-cases.rst > Commit: [dd0049686de8b640ca1123d4dc8f4194021ee887](https://github.com/dOpensource/dsiprouter/commit/dd0049686de8b640ca1123d4dc8f4194021ee887) > Date: Mon, 3 Dec 2018 13:22:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dd0049686de8b640ca1123d4dc8f4194021ee887) [//]: # (START_SECTION af1c60e897334324e3d6dd1a7787d50d2e1ee973) ### Update use-cases.rst > Commit: [af1c60e897334324e3d6dd1a7787d50d2e1ee973](https://github.com/dOpensource/dsiprouter/commit/af1c60e897334324e3d6dd1a7787d50d2e1ee973) > Date: Mon, 3 Dec 2018 13:20:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION af1c60e897334324e3d6dd1a7787d50d2e1ee973) [//]: # (START_SECTION a3f8c6157c4263813331935a2635df856de10c27) ### Add files via upload > Commit: [a3f8c6157c4263813331935a2635df856de10c27](https://github.com/dOpensource/dsiprouter/commit/a3f8c6157c4263813331935a2635df856de10c27) > Date: Mon, 3 Dec 2018 13:19:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a3f8c6157c4263813331935a2635df856de10c27) [//]: # (START_SECTION 7b2da29bd66fec284a8ae0ed6e7c1fb58d8a75e5) ### Delete 11d_dialplan_2.PNG > Commit: [7b2da29bd66fec284a8ae0ed6e7c1fb58d8a75e5](https://github.com/dOpensource/dsiprouter/commit/7b2da29bd66fec284a8ae0ed6e7c1fb58d8a75e5) > Date: Mon, 3 Dec 2018 13:19:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7b2da29bd66fec284a8ae0ed6e7c1fb58d8a75e5) [//]: # (START_SECTION 126d16e9a6419ebc946d76283549d48edf8f577a) ### Update use-cases.rst > Commit: [126d16e9a6419ebc946d76283549d48edf8f577a](https://github.com/dOpensource/dsiprouter/commit/126d16e9a6419ebc946d76283549d48edf8f577a) > Date: Mon, 3 Dec 2018 13:18:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 126d16e9a6419ebc946d76283549d48edf8f577a) [//]: # (START_SECTION 6c2a6d5e2a602bca0e066a4338820cf2520300e5) ### Update use-cases.rst > Commit: [6c2a6d5e2a602bca0e066a4338820cf2520300e5](https://github.com/dOpensource/dsiprouter/commit/6c2a6d5e2a602bca0e066a4338820cf2520300e5) > Date: Mon, 3 Dec 2018 12:29:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6c2a6d5e2a602bca0e066a4338820cf2520300e5) [//]: # (START_SECTION b2500941ec0845017e78a79b376305197de374c1) ### Update use-cases.rst > Commit: [b2500941ec0845017e78a79b376305197de374c1](https://github.com/dOpensource/dsiprouter/commit/b2500941ec0845017e78a79b376305197de374c1) > Date: Mon, 3 Dec 2018 12:28:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b2500941ec0845017e78a79b376305197de374c1) [//]: # (START_SECTION 53d909fc90bed86c508e9602dd3dff0998a87fb4) ### Update use-cases.rst > Commit: [53d909fc90bed86c508e9602dd3dff0998a87fb4](https://github.com/dOpensource/dsiprouter/commit/53d909fc90bed86c508e9602dd3dff0998a87fb4) > Date: Mon, 3 Dec 2018 12:03:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 53d909fc90bed86c508e9602dd3dff0998a87fb4) [//]: # (START_SECTION 95673cb589c08f7b9b91f0565161fee9b89d252b) ### Update use-cases.rst > Commit: [95673cb589c08f7b9b91f0565161fee9b89d252b](https://github.com/dOpensource/dsiprouter/commit/95673cb589c08f7b9b91f0565161fee9b89d252b) > Date: Mon, 3 Dec 2018 11:54:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 95673cb589c08f7b9b91f0565161fee9b89d252b) [//]: # (START_SECTION 283bec8c2dd843bd9f89e4fea6ee6e001877c803) ### Update use-cases.rst > Commit: [283bec8c2dd843bd9f89e4fea6ee6e001877c803](https://github.com/dOpensource/dsiprouter/commit/283bec8c2dd843bd9f89e4fea6ee6e001877c803) > Date: Mon, 3 Dec 2018 11:52:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 283bec8c2dd843bd9f89e4fea6ee6e001877c803) [//]: # (START_SECTION 8ba974019ac339517ab15b654cccd3c9882d3d34) ### Update use-cases.rst > Commit: [8ba974019ac339517ab15b654cccd3c9882d3d34](https://github.com/dOpensource/dsiprouter/commit/8ba974019ac339517ab15b654cccd3c9882d3d34) > Date: Mon, 3 Dec 2018 11:48:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8ba974019ac339517ab15b654cccd3c9882d3d34) [//]: # (START_SECTION 77635a5a66d8aaf180316c16152ba6aa245f8996) ### Add files via upload > Commit: [77635a5a66d8aaf180316c16152ba6aa245f8996](https://github.com/dOpensource/dsiprouter/commit/77635a5a66d8aaf180316c16152ba6aa245f8996) > Date: Mon, 3 Dec 2018 11:38:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 77635a5a66d8aaf180316c16152ba6aa245f8996) [//]: # (START_SECTION 6dd381f8cd69e953a5598a0c26a1088649507964) ### Update use-cases.rst > Commit: [6dd381f8cd69e953a5598a0c26a1088649507964](https://github.com/dOpensource/dsiprouter/commit/6dd381f8cd69e953a5598a0c26a1088649507964) > Date: Mon, 3 Dec 2018 11:23:17 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6dd381f8cd69e953a5598a0c26a1088649507964) [//]: # (START_SECTION 80760c1ddc981f6e94f4e3dba1c98ac109806232) ### Update use-cases.rst > Commit: [80760c1ddc981f6e94f4e3dba1c98ac109806232](https://github.com/dOpensource/dsiprouter/commit/80760c1ddc981f6e94f4e3dba1c98ac109806232) > Date: Fri, 30 Nov 2018 11:34:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 80760c1ddc981f6e94f4e3dba1c98ac109806232) [//]: # (START_SECTION e045ec2409c0502e9b049e5566d5dbaf7d6d8788) ### Update use-cases.rst > Commit: [e045ec2409c0502e9b049e5566d5dbaf7d6d8788](https://github.com/dOpensource/dsiprouter/commit/e045ec2409c0502e9b049e5566d5dbaf7d6d8788) > Date: Fri, 30 Nov 2018 11:30:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e045ec2409c0502e9b049e5566d5dbaf7d6d8788) [//]: # (START_SECTION 69bd7624b90d527ed23406b956ec43dc451ccadf) ### Update use-cases.rst > Commit: [69bd7624b90d527ed23406b956ec43dc451ccadf](https://github.com/dOpensource/dsiprouter/commit/69bd7624b90d527ed23406b956ec43dc451ccadf) > Date: Fri, 30 Nov 2018 11:28:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 69bd7624b90d527ed23406b956ec43dc451ccadf) [//]: # (START_SECTION c371d7af384e8716ef3e0be02e2f8da6551e84dd) ### Update use-cases.rst > Commit: [c371d7af384e8716ef3e0be02e2f8da6551e84dd](https://github.com/dOpensource/dsiprouter/commit/c371d7af384e8716ef3e0be02e2f8da6551e84dd) > Date: Fri, 30 Nov 2018 11:07:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c371d7af384e8716ef3e0be02e2f8da6551e84dd) [//]: # (START_SECTION 041abb29901cd235202e0f22755b7db6b2596932) ### Add files via upload > Commit: [041abb29901cd235202e0f22755b7db6b2596932](https://github.com/dOpensource/dsiprouter/commit/041abb29901cd235202e0f22755b7db6b2596932) > Date: Fri, 30 Nov 2018 11:03:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 041abb29901cd235202e0f22755b7db6b2596932) [//]: # (START_SECTION 0c2b2794dc45b2566c1baa8b061bd60be3530cbe) ### Update use-cases.rst > Commit: [0c2b2794dc45b2566c1baa8b061bd60be3530cbe](https://github.com/dOpensource/dsiprouter/commit/0c2b2794dc45b2566c1baa8b061bd60be3530cbe) > Date: Fri, 30 Nov 2018 11:01:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0c2b2794dc45b2566c1baa8b061bd60be3530cbe) [//]: # (START_SECTION aaedb6a45fa3f154aefdd12b68ca3b10daa45d26) ### Update use-cases.rst > Commit: [aaedb6a45fa3f154aefdd12b68ca3b10daa45d26](https://github.com/dOpensource/dsiprouter/commit/aaedb6a45fa3f154aefdd12b68ca3b10daa45d26) > Date: Thu, 29 Nov 2018 14:20:03 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aaedb6a45fa3f154aefdd12b68ca3b10daa45d26) [//]: # (START_SECTION 3dfd12b73b4c30337f0f007ba299b6c550667f52) ### Add files via upload > Commit: [3dfd12b73b4c30337f0f007ba299b6c550667f52](https://github.com/dOpensource/dsiprouter/commit/3dfd12b73b4c30337f0f007ba299b6c550667f52) > Date: Thu, 29 Nov 2018 14:17:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3dfd12b73b4c30337f0f007ba299b6c550667f52) [//]: # (START_SECTION 4a02d86bc5c0cb808bf8b8962fea0425521d3977) ### Update use-cases.rst > Commit: [4a02d86bc5c0cb808bf8b8962fea0425521d3977](https://github.com/dOpensource/dsiprouter/commit/4a02d86bc5c0cb808bf8b8962fea0425521d3977) > Date: Thu, 29 Nov 2018 14:17:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4a02d86bc5c0cb808bf8b8962fea0425521d3977) [//]: # (START_SECTION eb6c2b821e0f03e4976a073124f91c3025ca2d06) ### Update use-cases.rst > Commit: [eb6c2b821e0f03e4976a073124f91c3025ca2d06](https://github.com/dOpensource/dsiprouter/commit/eb6c2b821e0f03e4976a073124f91c3025ca2d06) > Date: Thu, 29 Nov 2018 13:42:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb6c2b821e0f03e4976a073124f91c3025ca2d06) [//]: # (START_SECTION 7859611f7fe33530e181b48b558af5827a4022dd) ### Update use-cases.rst > Commit: [7859611f7fe33530e181b48b558af5827a4022dd](https://github.com/dOpensource/dsiprouter/commit/7859611f7fe33530e181b48b558af5827a4022dd) > Date: Thu, 29 Nov 2018 13:41:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7859611f7fe33530e181b48b558af5827a4022dd) [//]: # (START_SECTION e6ab0944d595e85ca3bdb683b42def15d7fa4ddc) ### Update use-cases.rst > Commit: [e6ab0944d595e85ca3bdb683b42def15d7fa4ddc](https://github.com/dOpensource/dsiprouter/commit/e6ab0944d595e85ca3bdb683b42def15d7fa4ddc) > Date: Thu, 29 Nov 2018 13:37:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e6ab0944d595e85ca3bdb683b42def15d7fa4ddc) [//]: # (START_SECTION c1955b27adfa21f3ae38b54178c69cb604e73ced) ### Update use-cases.rst > Commit: [c1955b27adfa21f3ae38b54178c69cb604e73ced](https://github.com/dOpensource/dsiprouter/commit/c1955b27adfa21f3ae38b54178c69cb604e73ced) > Date: Thu, 29 Nov 2018 13:36:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c1955b27adfa21f3ae38b54178c69cb604e73ced) [//]: # (START_SECTION 53ba73e1220feab3a4e6b54265338051fa7c9f91) ### Update use-cases.rst > Commit: [53ba73e1220feab3a4e6b54265338051fa7c9f91](https://github.com/dOpensource/dsiprouter/commit/53ba73e1220feab3a4e6b54265338051fa7c9f91) > Date: Thu, 29 Nov 2018 13:33:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 53ba73e1220feab3a4e6b54265338051fa7c9f91) [//]: # (START_SECTION 60594ed03141479578c6a07d11b5bf76753dece0) ### Add files via upload > Commit: [60594ed03141479578c6a07d11b5bf76753dece0](https://github.com/dOpensource/dsiprouter/commit/60594ed03141479578c6a07d11b5bf76753dece0) > Date: Thu, 29 Nov 2018 13:31:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 60594ed03141479578c6a07d11b5bf76753dece0) [//]: # (START_SECTION 038b5e5b1e4e9bb1e6f7d8d7969ce07279d474ec) ### Update use-cases.rst > Commit: [038b5e5b1e4e9bb1e6f7d8d7969ce07279d474ec](https://github.com/dOpensource/dsiprouter/commit/038b5e5b1e4e9bb1e6f7d8d7969ce07279d474ec) > Date: Thu, 29 Nov 2018 13:31:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 038b5e5b1e4e9bb1e6f7d8d7969ce07279d474ec) [//]: # (START_SECTION eafa71ff350fe9a4cc5b2ef66fa38117bfe094f7) ### Update use-cases.rst > Commit: [eafa71ff350fe9a4cc5b2ef66fa38117bfe094f7](https://github.com/dOpensource/dsiprouter/commit/eafa71ff350fe9a4cc5b2ef66fa38117bfe094f7) > Date: Thu, 29 Nov 2018 13:26:54 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eafa71ff350fe9a4cc5b2ef66fa38117bfe094f7) [//]: # (START_SECTION a370bcdbeace0ae48d100a2fcd2801bd4085126c) ### Update use-cases.rst > Commit: [a370bcdbeace0ae48d100a2fcd2801bd4085126c](https://github.com/dOpensource/dsiprouter/commit/a370bcdbeace0ae48d100a2fcd2801bd4085126c) > Date: Thu, 29 Nov 2018 13:23:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a370bcdbeace0ae48d100a2fcd2801bd4085126c) [//]: # (START_SECTION 0739c358539a0629bfcb26a661610748909efea1) ### Update use-cases.rst > Commit: [0739c358539a0629bfcb26a661610748909efea1](https://github.com/dOpensource/dsiprouter/commit/0739c358539a0629bfcb26a661610748909efea1) > Date: Thu, 29 Nov 2018 13:02:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0739c358539a0629bfcb26a661610748909efea1) [//]: # (START_SECTION 8c110668e59f3566b16296b91ea496f20aeecb18) ### Update use-cases.rst > Commit: [8c110668e59f3566b16296b91ea496f20aeecb18](https://github.com/dOpensource/dsiprouter/commit/8c110668e59f3566b16296b91ea496f20aeecb18) > Date: Thu, 29 Nov 2018 12:57:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8c110668e59f3566b16296b91ea496f20aeecb18) [//]: # (START_SECTION aac8b80f73e9b221e7dc4370aefe2e43a16df286) ### Update use-cases.rst > Commit: [aac8b80f73e9b221e7dc4370aefe2e43a16df286](https://github.com/dOpensource/dsiprouter/commit/aac8b80f73e9b221e7dc4370aefe2e43a16df286) > Date: Thu, 29 Nov 2018 12:56:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aac8b80f73e9b221e7dc4370aefe2e43a16df286) [//]: # (START_SECTION b935064fa71c603cf65127440c336fef96683426) ### Update use-cases.rst > Commit: [b935064fa71c603cf65127440c336fef96683426](https://github.com/dOpensource/dsiprouter/commit/b935064fa71c603cf65127440c336fef96683426) > Date: Thu, 29 Nov 2018 12:55:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b935064fa71c603cf65127440c336fef96683426) [//]: # (START_SECTION 8cc818b9927be4dc0091eb9b93d86477d6c62c00) ### Update use-cases.rst > Commit: [8cc818b9927be4dc0091eb9b93d86477d6c62c00](https://github.com/dOpensource/dsiprouter/commit/8cc818b9927be4dc0091eb9b93d86477d6c62c00) > Date: Thu, 29 Nov 2018 12:54:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8cc818b9927be4dc0091eb9b93d86477d6c62c00) [//]: # (START_SECTION cc507c7d912a95f3f01b43188b422a6f268454e7) ### Update use-cases.rst > Commit: [cc507c7d912a95f3f01b43188b422a6f268454e7](https://github.com/dOpensource/dsiprouter/commit/cc507c7d912a95f3f01b43188b422a6f268454e7) > Date: Thu, 29 Nov 2018 12:51:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cc507c7d912a95f3f01b43188b422a6f268454e7) [//]: # (START_SECTION e4886f3453e9cdb71203ee66f2487d60ca4203ed) ### Update use-cases.rst > Commit: [e4886f3453e9cdb71203ee66f2487d60ca4203ed](https://github.com/dOpensource/dsiprouter/commit/e4886f3453e9cdb71203ee66f2487d60ca4203ed) > Date: Thu, 29 Nov 2018 12:50:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e4886f3453e9cdb71203ee66f2487d60ca4203ed) [//]: # (START_SECTION 1c6203c29dfa8ad426e201dfdd19c09b21fdc834) ### Update use-cases.rst > Commit: [1c6203c29dfa8ad426e201dfdd19c09b21fdc834](https://github.com/dOpensource/dsiprouter/commit/1c6203c29dfa8ad426e201dfdd19c09b21fdc834) > Date: Thu, 29 Nov 2018 12:48:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c6203c29dfa8ad426e201dfdd19c09b21fdc834) [//]: # (START_SECTION 260133601bf69ecf226350be74a5fbfd20c85861) ### Update use-cases.rst > Commit: [260133601bf69ecf226350be74a5fbfd20c85861](https://github.com/dOpensource/dsiprouter/commit/260133601bf69ecf226350be74a5fbfd20c85861) > Date: Thu, 29 Nov 2018 12:45:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 260133601bf69ecf226350be74a5fbfd20c85861) [//]: # (START_SECTION 011becafa6b96e37fc37f9944c63a5b4ce2e4af4) ### Update use-cases.rst > Commit: [011becafa6b96e37fc37f9944c63a5b4ce2e4af4](https://github.com/dOpensource/dsiprouter/commit/011becafa6b96e37fc37f9944c63a5b4ce2e4af4) > Date: Thu, 29 Nov 2018 12:43:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 011becafa6b96e37fc37f9944c63a5b4ce2e4af4) [//]: # (START_SECTION 4c6e863cdc20372bed0b306a76443541989f04cb) ### Update use-cases.rst > Commit: [4c6e863cdc20372bed0b306a76443541989f04cb](https://github.com/dOpensource/dsiprouter/commit/4c6e863cdc20372bed0b306a76443541989f04cb) > Date: Thu, 29 Nov 2018 12:41:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4c6e863cdc20372bed0b306a76443541989f04cb) [//]: # (START_SECTION 8e3dddd53488518eb2af7aa822408cae39a60ca5) ### Update use-cases.rst > Commit: [8e3dddd53488518eb2af7aa822408cae39a60ca5](https://github.com/dOpensource/dsiprouter/commit/8e3dddd53488518eb2af7aa822408cae39a60ca5) > Date: Thu, 29 Nov 2018 12:38:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8e3dddd53488518eb2af7aa822408cae39a60ca5) [//]: # (START_SECTION 77f8d5efb19a381b5488a846362aff40ece9a3de) ### Update use-cases.rst > Commit: [77f8d5efb19a381b5488a846362aff40ece9a3de](https://github.com/dOpensource/dsiprouter/commit/77f8d5efb19a381b5488a846362aff40ece9a3de) > Date: Thu, 29 Nov 2018 12:35:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 77f8d5efb19a381b5488a846362aff40ece9a3de) [//]: # (START_SECTION 5eb05ac1c1e72234a30f04a7f52d4604bc8b6c06) ### Update use-cases.rst > Commit: [5eb05ac1c1e72234a30f04a7f52d4604bc8b6c06](https://github.com/dOpensource/dsiprouter/commit/5eb05ac1c1e72234a30f04a7f52d4604bc8b6c06) > Date: Thu, 29 Nov 2018 12:34:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5eb05ac1c1e72234a30f04a7f52d4604bc8b6c06) [//]: # (START_SECTION a11b72e26dc9a71430fb3fd5c4bfd5591409906c) ### Update use-cases.rst > Commit: [a11b72e26dc9a71430fb3fd5c4bfd5591409906c](https://github.com/dOpensource/dsiprouter/commit/a11b72e26dc9a71430fb3fd5c4bfd5591409906c) > Date: Thu, 29 Nov 2018 12:33:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a11b72e26dc9a71430fb3fd5c4bfd5591409906c) [//]: # (START_SECTION e37e821ee0e5e768c60afe17f0ef3959a7136f3f) ### Update use-cases.rst > Commit: [e37e821ee0e5e768c60afe17f0ef3959a7136f3f](https://github.com/dOpensource/dsiprouter/commit/e37e821ee0e5e768c60afe17f0ef3959a7136f3f) > Date: Thu, 29 Nov 2018 12:30:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e37e821ee0e5e768c60afe17f0ef3959a7136f3f) [//]: # (START_SECTION 81023b24f5a899da647ae7ce00669c040592430f) ### Update use-cases.rst > Commit: [81023b24f5a899da647ae7ce00669c040592430f](https://github.com/dOpensource/dsiprouter/commit/81023b24f5a899da647ae7ce00669c040592430f) > Date: Thu, 29 Nov 2018 12:25:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 81023b24f5a899da647ae7ce00669c040592430f) [//]: # (START_SECTION 2245c608ed6b13c94406c9d8b64626ab9dfd3d98) ### Update use-cases.rst > Commit: [2245c608ed6b13c94406c9d8b64626ab9dfd3d98](https://github.com/dOpensource/dsiprouter/commit/2245c608ed6b13c94406c9d8b64626ab9dfd3d98) > Date: Thu, 29 Nov 2018 12:23:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2245c608ed6b13c94406c9d8b64626ab9dfd3d98) [//]: # (START_SECTION 77bd401ef2b996e1709999668c2cf3852a691723) ### Add files via upload > Commit: [77bd401ef2b996e1709999668c2cf3852a691723](https://github.com/dOpensource/dsiprouter/commit/77bd401ef2b996e1709999668c2cf3852a691723) > Date: Thu, 29 Nov 2018 12:22:30 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 77bd401ef2b996e1709999668c2cf3852a691723) [//]: # (START_SECTION 2ed683ae1298506449b325df1ef81334f94bc79f) ### Update use-cases.rst > Commit: [2ed683ae1298506449b325df1ef81334f94bc79f](https://github.com/dOpensource/dsiprouter/commit/2ed683ae1298506449b325df1ef81334f94bc79f) > Date: Thu, 29 Nov 2018 12:21:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2ed683ae1298506449b325df1ef81334f94bc79f) [//]: # (START_SECTION 4f8689ee5d22cb18bc88f9d583dc1b083adc0cc1) ### Update configuring.rst > Commit: [4f8689ee5d22cb18bc88f9d583dc1b083adc0cc1](https://github.com/dOpensource/dsiprouter/commit/4f8689ee5d22cb18bc88f9d583dc1b083adc0cc1) > Date: Thu, 29 Nov 2018 10:42:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4f8689ee5d22cb18bc88f9d583dc1b083adc0cc1) [//]: # (START_SECTION aa6939f2229a12ef75fe06e5a9476a60fc07f066) ### Update pbxs_and_endpoints.rst > Commit: [aa6939f2229a12ef75fe06e5a9476a60fc07f066](https://github.com/dOpensource/dsiprouter/commit/aa6939f2229a12ef75fe06e5a9476a60fc07f066) > Date: Thu, 29 Nov 2018 10:40:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aa6939f2229a12ef75fe06e5a9476a60fc07f066) [//]: # (START_SECTION 902370faf90e28437c058e8dcbba8e54b0c5cadd) ### Update use-cases.rst > Commit: [902370faf90e28437c058e8dcbba8e54b0c5cadd](https://github.com/dOpensource/dsiprouter/commit/902370faf90e28437c058e8dcbba8e54b0c5cadd) > Date: Wed, 28 Nov 2018 16:02:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 902370faf90e28437c058e8dcbba8e54b0c5cadd) [//]: # (START_SECTION f694ac7f54a186bdf9ddc123879c97fcb85e8e3f) ### Update global_outbound_routes.rst > Commit: [f694ac7f54a186bdf9ddc123879c97fcb85e8e3f](https://github.com/dOpensource/dsiprouter/commit/f694ac7f54a186bdf9ddc123879c97fcb85e8e3f) > Date: Wed, 28 Nov 2018 16:00:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f694ac7f54a186bdf9ddc123879c97fcb85e8e3f) [//]: # (START_SECTION ff660123dca05e16b49e4dc31e55e078dc584110) ### Update use-cases.rst > Commit: [ff660123dca05e16b49e4dc31e55e078dc584110](https://github.com/dOpensource/dsiprouter/commit/ff660123dca05e16b49e4dc31e55e078dc584110) > Date: Wed, 28 Nov 2018 15:53:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ff660123dca05e16b49e4dc31e55e078dc584110) [//]: # (START_SECTION 2a605297459206702f8f3a713577961f49d68b18) ### Update use-cases.rst > Commit: [2a605297459206702f8f3a713577961f49d68b18](https://github.com/dOpensource/dsiprouter/commit/2a605297459206702f8f3a713577961f49d68b18) > Date: Wed, 28 Nov 2018 15:35:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2a605297459206702f8f3a713577961f49d68b18) [//]: # (START_SECTION 42be0e853e6e504790761eb655f6028771cee855) ### Update use-cases.rst > Commit: [42be0e853e6e504790761eb655f6028771cee855](https://github.com/dOpensource/dsiprouter/commit/42be0e853e6e504790761eb655f6028771cee855) > Date: Wed, 28 Nov 2018 15:35:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 42be0e853e6e504790761eb655f6028771cee855) [//]: # (START_SECTION e388cae6f1a14f729f86d57ddf430678ac298f43) ### Update use-cases.rst > Commit: [e388cae6f1a14f729f86d57ddf430678ac298f43](https://github.com/dOpensource/dsiprouter/commit/e388cae6f1a14f729f86d57ddf430678ac298f43) > Date: Wed, 28 Nov 2018 15:32:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e388cae6f1a14f729f86d57ddf430678ac298f43) [//]: # (START_SECTION 5762ec24ff2ccd9a5731a5204015d347ef3b1dd9) ### Update use-cases.rst > Commit: [5762ec24ff2ccd9a5731a5204015d347ef3b1dd9](https://github.com/dOpensource/dsiprouter/commit/5762ec24ff2ccd9a5731a5204015d347ef3b1dd9) > Date: Wed, 28 Nov 2018 15:31:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5762ec24ff2ccd9a5731a5204015d347ef3b1dd9) [//]: # (START_SECTION 91e1ea02b1fbf3f0066af5f6161b11298fdf5feb) ### Update use-cases.rst > Commit: [91e1ea02b1fbf3f0066af5f6161b11298fdf5feb](https://github.com/dOpensource/dsiprouter/commit/91e1ea02b1fbf3f0066af5f6161b11298fdf5feb) > Date: Wed, 28 Nov 2018 15:26:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 91e1ea02b1fbf3f0066af5f6161b11298fdf5feb) [//]: # (START_SECTION b0dae40725f2be1ada240392874748c7f8f5ecb1) ### Update use-cases.rst > Commit: [b0dae40725f2be1ada240392874748c7f8f5ecb1](https://github.com/dOpensource/dsiprouter/commit/b0dae40725f2be1ada240392874748c7f8f5ecb1) > Date: Wed, 28 Nov 2018 15:25:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b0dae40725f2be1ada240392874748c7f8f5ecb1) [//]: # (START_SECTION 141dacddf82b531a609d64023e44a0b78db94ad5) ### Update use-cases.rst > Commit: [141dacddf82b531a609d64023e44a0b78db94ad5](https://github.com/dOpensource/dsiprouter/commit/141dacddf82b531a609d64023e44a0b78db94ad5) > Date: Wed, 28 Nov 2018 15:24:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 141dacddf82b531a609d64023e44a0b78db94ad5) [//]: # (START_SECTION 90b11bf5c894ecb81c395235ef3b036282c648f6) ### Update use-cases.rst > Commit: [90b11bf5c894ecb81c395235ef3b036282c648f6](https://github.com/dOpensource/dsiprouter/commit/90b11bf5c894ecb81c395235ef3b036282c648f6) > Date: Wed, 28 Nov 2018 15:23:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 90b11bf5c894ecb81c395235ef3b036282c648f6) [//]: # (START_SECTION c0856b90f4271b5e2281a15dad9fc9edbb75452c) ### Update use-cases.rst > Commit: [c0856b90f4271b5e2281a15dad9fc9edbb75452c](https://github.com/dOpensource/dsiprouter/commit/c0856b90f4271b5e2281a15dad9fc9edbb75452c) > Date: Wed, 28 Nov 2018 15:21:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c0856b90f4271b5e2281a15dad9fc9edbb75452c) [//]: # (START_SECTION 9e752dfd6a77d89fef094c0b3b852f1a2e945b82) ### Update use-cases.rst > Commit: [9e752dfd6a77d89fef094c0b3b852f1a2e945b82](https://github.com/dOpensource/dsiprouter/commit/9e752dfd6a77d89fef094c0b3b852f1a2e945b82) > Date: Wed, 28 Nov 2018 15:20:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9e752dfd6a77d89fef094c0b3b852f1a2e945b82) [//]: # (START_SECTION f6b39da05d9d0c96d73b4b9bb733f70c5a892c1c) ### Update use-cases.rst > Commit: [f6b39da05d9d0c96d73b4b9bb733f70c5a892c1c](https://github.com/dOpensource/dsiprouter/commit/f6b39da05d9d0c96d73b4b9bb733f70c5a892c1c) > Date: Wed, 28 Nov 2018 15:19:03 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f6b39da05d9d0c96d73b4b9bb733f70c5a892c1c) [//]: # (START_SECTION f8b22d9a12a9cc43e70086231388c4bff00a9b77) ### Update use-cases.rst > Commit: [f8b22d9a12a9cc43e70086231388c4bff00a9b77](https://github.com/dOpensource/dsiprouter/commit/f8b22d9a12a9cc43e70086231388c4bff00a9b77) > Date: Wed, 28 Nov 2018 15:15:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f8b22d9a12a9cc43e70086231388c4bff00a9b77) [//]: # (START_SECTION a95d1faa9b969ffe0dbcc8033e3c0da37840560d) ### Update use-cases.rst > Commit: [a95d1faa9b969ffe0dbcc8033e3c0da37840560d](https://github.com/dOpensource/dsiprouter/commit/a95d1faa9b969ffe0dbcc8033e3c0da37840560d) > Date: Wed, 28 Nov 2018 15:09:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a95d1faa9b969ffe0dbcc8033e3c0da37840560d) [//]: # (START_SECTION 33433486d2acc9e1aa4dee8c39e2478a2a5596f7) ### Update use-cases.rst > Commit: [33433486d2acc9e1aa4dee8c39e2478a2a5596f7](https://github.com/dOpensource/dsiprouter/commit/33433486d2acc9e1aa4dee8c39e2478a2a5596f7) > Date: Wed, 28 Nov 2018 14:23:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 33433486d2acc9e1aa4dee8c39e2478a2a5596f7) [//]: # (START_SECTION e3c410eb88521e5453d127fae15cb4ef8e98e3b6) ### Update use-cases.rst > Commit: [e3c410eb88521e5453d127fae15cb4ef8e98e3b6](https://github.com/dOpensource/dsiprouter/commit/e3c410eb88521e5453d127fae15cb4ef8e98e3b6) > Date: Wed, 28 Nov 2018 14:19:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e3c410eb88521e5453d127fae15cb4ef8e98e3b6) [//]: # (START_SECTION 5aed2da88eef6ca84b46e16f5085b77bfbcb98bb) ### Update use-cases.rst > Commit: [5aed2da88eef6ca84b46e16f5085b77bfbcb98bb](https://github.com/dOpensource/dsiprouter/commit/5aed2da88eef6ca84b46e16f5085b77bfbcb98bb) > Date: Wed, 28 Nov 2018 14:05:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5aed2da88eef6ca84b46e16f5085b77bfbcb98bb) [//]: # (START_SECTION ed285c33d2b968def5bd3e3e8445d294fa24f244) ### Update use-cases.rst > Commit: [ed285c33d2b968def5bd3e3e8445d294fa24f244](https://github.com/dOpensource/dsiprouter/commit/ed285c33d2b968def5bd3e3e8445d294fa24f244) > Date: Wed, 28 Nov 2018 14:03:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ed285c33d2b968def5bd3e3e8445d294fa24f244) [//]: # (START_SECTION c9aee0b1847dbc71fdaaa33be01e7d3a65c5408f) ### Update use-cases.rst > Commit: [c9aee0b1847dbc71fdaaa33be01e7d3a65c5408f](https://github.com/dOpensource/dsiprouter/commit/c9aee0b1847dbc71fdaaa33be01e7d3a65c5408f) > Date: Wed, 28 Nov 2018 13:01:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c9aee0b1847dbc71fdaaa33be01e7d3a65c5408f) [//]: # (START_SECTION c4dbb41e7b37e9e0fed01bf48eb4c4698bd6456a) ### Update use-cases.rst > Commit: [c4dbb41e7b37e9e0fed01bf48eb4c4698bd6456a](https://github.com/dOpensource/dsiprouter/commit/c4dbb41e7b37e9e0fed01bf48eb4c4698bd6456a) > Date: Wed, 28 Nov 2018 13:00:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c4dbb41e7b37e9e0fed01bf48eb4c4698bd6456a) [//]: # (START_SECTION b7c8216d1611dddd849b19920d1407fe68f3a7e0) ### Update use-cases.rst > Commit: [b7c8216d1611dddd849b19920d1407fe68f3a7e0](https://github.com/dOpensource/dsiprouter/commit/b7c8216d1611dddd849b19920d1407fe68f3a7e0) > Date: Wed, 28 Nov 2018 12:58:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b7c8216d1611dddd849b19920d1407fe68f3a7e0) [//]: # (START_SECTION 30ec734b5ff705a6b7a18dd7cdd6c5ef0ebd5231) ### Update use-cases.rst > Commit: [30ec734b5ff705a6b7a18dd7cdd6c5ef0ebd5231](https://github.com/dOpensource/dsiprouter/commit/30ec734b5ff705a6b7a18dd7cdd6c5ef0ebd5231) > Date: Wed, 28 Nov 2018 12:57:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 30ec734b5ff705a6b7a18dd7cdd6c5ef0ebd5231) [//]: # (START_SECTION 4e9375a0ab4f304a1fee44b01d68d9e30cf0c02a) ### Update use-cases.rst > Commit: [4e9375a0ab4f304a1fee44b01d68d9e30cf0c02a](https://github.com/dOpensource/dsiprouter/commit/4e9375a0ab4f304a1fee44b01d68d9e30cf0c02a) > Date: Wed, 28 Nov 2018 12:52:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4e9375a0ab4f304a1fee44b01d68d9e30cf0c02a) [//]: # (START_SECTION 4bf3482f0b7032d8b42df232d85ffaac68d9b0e8) ### Update use-cases.rst > Commit: [4bf3482f0b7032d8b42df232d85ffaac68d9b0e8](https://github.com/dOpensource/dsiprouter/commit/4bf3482f0b7032d8b42df232d85ffaac68d9b0e8) > Date: Wed, 28 Nov 2018 12:51:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4bf3482f0b7032d8b42df232d85ffaac68d9b0e8) [//]: # (START_SECTION fb3da587b3194dbc450734af89119ad69bda4456) ### Update use-cases.rst > Commit: [fb3da587b3194dbc450734af89119ad69bda4456](https://github.com/dOpensource/dsiprouter/commit/fb3da587b3194dbc450734af89119ad69bda4456) > Date: Wed, 28 Nov 2018 12:47:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fb3da587b3194dbc450734af89119ad69bda4456) [//]: # (START_SECTION eff9bf8e4c3b1ddc6619b6b6ce74238cce74bffb) ### Update use-cases.rst > Commit: [eff9bf8e4c3b1ddc6619b6b6ce74238cce74bffb](https://github.com/dOpensource/dsiprouter/commit/eff9bf8e4c3b1ddc6619b6b6ce74238cce74bffb) > Date: Wed, 28 Nov 2018 12:45:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eff9bf8e4c3b1ddc6619b6b6ce74238cce74bffb) [//]: # (START_SECTION d8926c0360032655ffb5bab4cd1fb6f34d9025de) ### Update use-cases.rst > Commit: [d8926c0360032655ffb5bab4cd1fb6f34d9025de](https://github.com/dOpensource/dsiprouter/commit/d8926c0360032655ffb5bab4cd1fb6f34d9025de) > Date: Wed, 28 Nov 2018 12:40:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d8926c0360032655ffb5bab4cd1fb6f34d9025de) [//]: # (START_SECTION 0d3ac2dc82d3d2cbd18381aa7ba3c924114d1650) ### Update use-cases.rst > Commit: [0d3ac2dc82d3d2cbd18381aa7ba3c924114d1650](https://github.com/dOpensource/dsiprouter/commit/0d3ac2dc82d3d2cbd18381aa7ba3c924114d1650) > Date: Wed, 28 Nov 2018 12:33:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0d3ac2dc82d3d2cbd18381aa7ba3c924114d1650) [//]: # (START_SECTION 17e888fb78f4c94277dd9be49b8913966ce80361) ### Update use-cases.rst > Commit: [17e888fb78f4c94277dd9be49b8913966ce80361](https://github.com/dOpensource/dsiprouter/commit/17e888fb78f4c94277dd9be49b8913966ce80361) > Date: Wed, 28 Nov 2018 12:28:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 17e888fb78f4c94277dd9be49b8913966ce80361) [//]: # (START_SECTION 92e94b9a2f2a4daa7d6174b2a19156ce5e3242f4) ### Update use-cases.rst > Commit: [92e94b9a2f2a4daa7d6174b2a19156ce5e3242f4](https://github.com/dOpensource/dsiprouter/commit/92e94b9a2f2a4daa7d6174b2a19156ce5e3242f4) > Date: Wed, 28 Nov 2018 12:27:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92e94b9a2f2a4daa7d6174b2a19156ce5e3242f4) [//]: # (START_SECTION 15aeaacc15e86c6fe879d131853c4e2aafd404e2) ### Update use-cases.rst > Commit: [15aeaacc15e86c6fe879d131853c4e2aafd404e2](https://github.com/dOpensource/dsiprouter/commit/15aeaacc15e86c6fe879d131853c4e2aafd404e2) > Date: Wed, 28 Nov 2018 09:34:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 15aeaacc15e86c6fe879d131853c4e2aafd404e2) [//]: # (START_SECTION 9b7a09695d8906d7760c4f8a603ee6834639145a) ### Update carrier_groups.rst > Commit: [9b7a09695d8906d7760c4f8a603ee6834639145a](https://github.com/dOpensource/dsiprouter/commit/9b7a09695d8906d7760c4f8a603ee6834639145a) > Date: Tue, 27 Nov 2018 15:43:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9b7a09695d8906d7760c4f8a603ee6834639145a) [//]: # (START_SECTION d254d07482d160713f4962ee83b2ab3ab0a061a5) ### Update carrier_groups.rst > Commit: [d254d07482d160713f4962ee83b2ab3ab0a061a5](https://github.com/dOpensource/dsiprouter/commit/d254d07482d160713f4962ee83b2ab3ab0a061a5) > Date: Tue, 27 Nov 2018 15:42:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d254d07482d160713f4962ee83b2ab3ab0a061a5) [//]: # (START_SECTION d981beee8af23eb1a88959f273c265893706c36a) ### Update carrier_groups.rst > Commit: [d981beee8af23eb1a88959f273c265893706c36a](https://github.com/dOpensource/dsiprouter/commit/d981beee8af23eb1a88959f273c265893706c36a) > Date: Tue, 27 Nov 2018 15:38:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d981beee8af23eb1a88959f273c265893706c36a) [//]: # (START_SECTION 0e829d781bc79e9aafaf07112a252d3989d07ae1) ### Update carrier_groups.rst > Commit: [0e829d781bc79e9aafaf07112a252d3989d07ae1](https://github.com/dOpensource/dsiprouter/commit/0e829d781bc79e9aafaf07112a252d3989d07ae1) > Date: Tue, 27 Nov 2018 15:37:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0e829d781bc79e9aafaf07112a252d3989d07ae1) [//]: # (START_SECTION 59b8313b93e359e2e953f5759cfbee35555fc6f3) ### Update carrier_groups.rst > Commit: [59b8313b93e359e2e953f5759cfbee35555fc6f3](https://github.com/dOpensource/dsiprouter/commit/59b8313b93e359e2e953f5759cfbee35555fc6f3) > Date: Tue, 27 Nov 2018 15:36:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 59b8313b93e359e2e953f5759cfbee35555fc6f3) [//]: # (START_SECTION a927825f41807b8ba3fe834bb4cdf9b19f380160) ### Add files via upload > Commit: [a927825f41807b8ba3fe834bb4cdf9b19f380160](https://github.com/dOpensource/dsiprouter/commit/a927825f41807b8ba3fe834bb4cdf9b19f380160) > Date: Tue, 27 Nov 2018 15:34:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a927825f41807b8ba3fe834bb4cdf9b19f380160) [//]: # (START_SECTION 956943487a928049235d880e5756392f7b646766) ### Delete IP authenication.PNG > Commit: [956943487a928049235d880e5756392f7b646766](https://github.com/dOpensource/dsiprouter/commit/956943487a928049235d880e5756392f7b646766) > Date: Tue, 27 Nov 2018 15:34:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 956943487a928049235d880e5756392f7b646766) [//]: # (START_SECTION 942b24602598af530f0b853e5e99a06786a870bd) ### Update carrier_groups.rst > Commit: [942b24602598af530f0b853e5e99a06786a870bd](https://github.com/dOpensource/dsiprouter/commit/942b24602598af530f0b853e5e99a06786a870bd) > Date: Tue, 27 Nov 2018 15:34:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 942b24602598af530f0b853e5e99a06786a870bd) [//]: # (START_SECTION 203831d712b3691c1b9167048326e433475bef56) ### Add files via upload > Commit: [203831d712b3691c1b9167048326e433475bef56](https://github.com/dOpensource/dsiprouter/commit/203831d712b3691c1b9167048326e433475bef56) > Date: Tue, 27 Nov 2018 15:30:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 203831d712b3691c1b9167048326e433475bef56) [//]: # (START_SECTION b77e88e29d7f10ba18566064095cf43575c7ef33) ### Update use-cases.rst > Commit: [b77e88e29d7f10ba18566064095cf43575c7ef33](https://github.com/dOpensource/dsiprouter/commit/b77e88e29d7f10ba18566064095cf43575c7ef33) > Date: Tue, 27 Nov 2018 15:08:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b77e88e29d7f10ba18566064095cf43575c7ef33) [//]: # (START_SECTION 9665f3c663cae93a060a8d42345493467b2afaf4) ### Update use-cases.rst > Commit: [9665f3c663cae93a060a8d42345493467b2afaf4](https://github.com/dOpensource/dsiprouter/commit/9665f3c663cae93a060a8d42345493467b2afaf4) > Date: Tue, 27 Nov 2018 15:07:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9665f3c663cae93a060a8d42345493467b2afaf4) [//]: # (START_SECTION 75e44d879c72f2d5fe03dcfd1be1730d191c34f1) ### Update use-cases.rst > Commit: [75e44d879c72f2d5fe03dcfd1be1730d191c34f1](https://github.com/dOpensource/dsiprouter/commit/75e44d879c72f2d5fe03dcfd1be1730d191c34f1) > Date: Tue, 27 Nov 2018 15:05:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 75e44d879c72f2d5fe03dcfd1be1730d191c34f1) [//]: # (START_SECTION 9241e581a68df76911cb74c0d241cdfb98abb97c) ### Update use-cases.rst > Commit: [9241e581a68df76911cb74c0d241cdfb98abb97c](https://github.com/dOpensource/dsiprouter/commit/9241e581a68df76911cb74c0d241cdfb98abb97c) > Date: Tue, 27 Nov 2018 14:56:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9241e581a68df76911cb74c0d241cdfb98abb97c) [//]: # (START_SECTION 589fd170829697735036d0baa90cdcc2c9461e2c) ### Update use-cases.rst > Commit: [589fd170829697735036d0baa90cdcc2c9461e2c](https://github.com/dOpensource/dsiprouter/commit/589fd170829697735036d0baa90cdcc2c9461e2c) > Date: Tue, 27 Nov 2018 10:43:19 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 589fd170829697735036d0baa90cdcc2c9461e2c) [//]: # (START_SECTION 42d9fb33b7cac00cb0bf98dc91556db5978c07b9) ### Update global_outbound_routes.rst > Commit: [42d9fb33b7cac00cb0bf98dc91556db5978c07b9](https://github.com/dOpensource/dsiprouter/commit/42d9fb33b7cac00cb0bf98dc91556db5978c07b9) > Date: Tue, 27 Nov 2018 10:03:20 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 42d9fb33b7cac00cb0bf98dc91556db5978c07b9) [//]: # (START_SECTION 4c46c9c2c6507485b069901b400ff2fbfdeb58a4) ### Create global_outbound_routes.rst > Commit: [4c46c9c2c6507485b069901b400ff2fbfdeb58a4](https://github.com/dOpensource/dsiprouter/commit/4c46c9c2c6507485b069901b400ff2fbfdeb58a4) > Date: Tue, 27 Nov 2018 10:02:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4c46c9c2c6507485b069901b400ff2fbfdeb58a4) [//]: # (START_SECTION 058c2fd0fbe424772c4ac8da958b108b149fe665) ### Update use-cases.rst > Commit: [058c2fd0fbe424772c4ac8da958b108b149fe665](https://github.com/dOpensource/dsiprouter/commit/058c2fd0fbe424772c4ac8da958b108b149fe665) > Date: Tue, 27 Nov 2018 10:01:52 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 058c2fd0fbe424772c4ac8da958b108b149fe665) [//]: # (START_SECTION 2779721a299d975eb9b449f9fe7d5b933ad4c541) ### Update carrier_groups.rst > Commit: [2779721a299d975eb9b449f9fe7d5b933ad4c541](https://github.com/dOpensource/dsiprouter/commit/2779721a299d975eb9b449f9fe7d5b933ad4c541) > Date: Tue, 27 Nov 2018 09:58:34 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2779721a299d975eb9b449f9fe7d5b933ad4c541) [//]: # (START_SECTION 6dbc1d7982c8727e149a2c026ab1ebe88d4007a5) ### Update use-cases.rst > Commit: [6dbc1d7982c8727e149a2c026ab1ebe88d4007a5](https://github.com/dOpensource/dsiprouter/commit/6dbc1d7982c8727e149a2c026ab1ebe88d4007a5) > Date: Tue, 27 Nov 2018 09:45:20 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6dbc1d7982c8727e149a2c026ab1ebe88d4007a5) [//]: # (START_SECTION 8b1b20920acf629cadbe99b4b06769e8e63912d9) ### Update carrier_groups.rst > Commit: [8b1b20920acf629cadbe99b4b06769e8e63912d9](https://github.com/dOpensource/dsiprouter/commit/8b1b20920acf629cadbe99b4b06769e8e63912d9) > Date: Tue, 27 Nov 2018 09:43:18 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8b1b20920acf629cadbe99b4b06769e8e63912d9) [//]: # (START_SECTION 3c602d135d6f170f910c7893c96487db949637af) ### Update use-cases.rst > Commit: [3c602d135d6f170f910c7893c96487db949637af](https://github.com/dOpensource/dsiprouter/commit/3c602d135d6f170f910c7893c96487db949637af) > Date: Tue, 27 Nov 2018 09:26:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3c602d135d6f170f910c7893c96487db949637af) [//]: # (START_SECTION ce76652240bca2e369fb7d09cf7d2c90a465e53e) ### Letsencrypt will not work since the machine doesn't have a routeable domain name > Commit: [ce76652240bca2e369fb7d09cf7d2c90a465e53e](https://github.com/dOpensource/dsiprouter/commit/ce76652240bca2e369fb7d09cf7d2c90a465e53e) > Date: Tue, 27 Nov 2018 00:22:51 +0000 > Author: mhendricks (root@debian-dsip-51-build.localdomain) > Committer: mhendricks (root@debian-dsip-51-build.localdomain) > Signed: --- [//]: # (END_SECTION ce76652240bca2e369fb7d09cf7d2c90a465e53e) [//]: # (START_SECTION b88fcccee58017d8eca65fca4188a9d8aeef4721) ### Fixed some more conflicts with datatables.js > Commit: [b88fcccee58017d8eca65fca4188a9d8aeef4721](https://github.com/dOpensource/dsiprouter/commit/b88fcccee58017d8eca65fca4188a9d8aeef4721) > Date: Mon, 26 Nov 2018 20:54:13 +0000 > Author: mhendricks (root@debian-dsip-51-build.localdomain) > Committer: mhendricks (root@debian-dsip-51-build.localdomain) > Signed: --- [//]: # (END_SECTION b88fcccee58017d8eca65fca4188a9d8aeef4721) [//]: # (START_SECTION 59044ef87ccf8565906b5fd8a1df49a4d450c621) ### Update use-cases.rst > Commit: [59044ef87ccf8565906b5fd8a1df49a4d450c621](https://github.com/dOpensource/dsiprouter/commit/59044ef87ccf8565906b5fd8a1df49a4d450c621) > Date: Mon, 26 Nov 2018 15:07:45 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 59044ef87ccf8565906b5fd8a1df49a4d450c621) [//]: # (START_SECTION 4a42678222549258646340aae4e1cde768f7abc1) ### Update use-cases.rst > Commit: [4a42678222549258646340aae4e1cde768f7abc1](https://github.com/dOpensource/dsiprouter/commit/4a42678222549258646340aae4e1cde768f7abc1) > Date: Mon, 26 Nov 2018 15:00:14 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4a42678222549258646340aae4e1cde768f7abc1) [//]: # (START_SECTION 13b6151582c0f91a91b3af81a79a5c5b71bdbe82) ### Add files via upload > Commit: [13b6151582c0f91a91b3af81a79a5c5b71bdbe82](https://github.com/dOpensource/dsiprouter/commit/13b6151582c0f91a91b3af81a79a5c5b71bdbe82) > Date: Mon, 26 Nov 2018 14:40:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 13b6151582c0f91a91b3af81a79a5c5b71bdbe82) [//]: # (START_SECTION 16dcdaaf7183e24e0244c572f87567afd3ee43b9) ### Delete sip_trunking_freepbx_pjsip.png > Commit: [16dcdaaf7183e24e0244c572f87567afd3ee43b9](https://github.com/dOpensource/dsiprouter/commit/16dcdaaf7183e24e0244c572f87567afd3ee43b9) > Date: Mon, 26 Nov 2018 14:34:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 16dcdaaf7183e24e0244c572f87567afd3ee43b9) [//]: # (START_SECTION 513a30c8e55f18e73dae972ce10d9db9563352e8) ### Add files via upload > Commit: [513a30c8e55f18e73dae972ce10d9db9563352e8](https://github.com/dOpensource/dsiprouter/commit/513a30c8e55f18e73dae972ce10d9db9563352e8) > Date: Sat, 24 Nov 2018 14:50:49 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 513a30c8e55f18e73dae972ce10d9db9563352e8) [//]: # (START_SECTION f59d5160a28f8f4407c2c989f4d41b8b8312b123) ### Update use-cases.rst > Commit: [f59d5160a28f8f4407c2c989f4d41b8b8312b123](https://github.com/dOpensource/dsiprouter/commit/f59d5160a28f8f4407c2c989f4d41b8b8312b123) > Date: Sat, 24 Nov 2018 14:50:11 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f59d5160a28f8f4407c2c989f4d41b8b8312b123) [//]: # (START_SECTION 3fa8411d14a191f218d4d13edef3d9858bc2e1f4) ### Update use-cases.rst > Commit: [3fa8411d14a191f218d4d13edef3d9858bc2e1f4](https://github.com/dOpensource/dsiprouter/commit/3fa8411d14a191f218d4d13edef3d9858bc2e1f4) > Date: Sat, 24 Nov 2018 14:49:29 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3fa8411d14a191f218d4d13edef3d9858bc2e1f4) [//]: # (START_SECTION c6976fc95170d3f8ff7ae5b6ffc261b3a0c8ebbf) ### Update use-cases.rst > Commit: [c6976fc95170d3f8ff7ae5b6ffc261b3a0c8ebbf](https://github.com/dOpensource/dsiprouter/commit/c6976fc95170d3f8ff7ae5b6ffc261b3a0c8ebbf) > Date: Sat, 24 Nov 2018 13:21:59 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c6976fc95170d3f8ff7ae5b6ffc261b3a0c8ebbf) [//]: # (START_SECTION e7e5b1a40b91e62a5c4b3407035d6eadde539374) ### Update use-cases.rst > Commit: [e7e5b1a40b91e62a5c4b3407035d6eadde539374](https://github.com/dOpensource/dsiprouter/commit/e7e5b1a40b91e62a5c4b3407035d6eadde539374) > Date: Sat, 24 Nov 2018 13:19:48 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e7e5b1a40b91e62a5c4b3407035d6eadde539374) [//]: # (START_SECTION 3bf9b70afc3ba297a2e384a431867f5b0ed00e54) ### Update use-cases.rst > Commit: [3bf9b70afc3ba297a2e384a431867f5b0ed00e54](https://github.com/dOpensource/dsiprouter/commit/3bf9b70afc3ba297a2e384a431867f5b0ed00e54) > Date: Sat, 24 Nov 2018 13:15:29 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3bf9b70afc3ba297a2e384a431867f5b0ed00e54) [//]: # (START_SECTION ce56e4d5705b73accffbff60048fb92f75b106bd) ### Update use-cases.rst > Commit: [ce56e4d5705b73accffbff60048fb92f75b106bd](https://github.com/dOpensource/dsiprouter/commit/ce56e4d5705b73accffbff60048fb92f75b106bd) > Date: Sat, 24 Nov 2018 13:14:37 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ce56e4d5705b73accffbff60048fb92f75b106bd) [//]: # (START_SECTION c04f2724aed299780444fe36da320baf4cd58f6b) ### Update use-cases.rst > Commit: [c04f2724aed299780444fe36da320baf4cd58f6b](https://github.com/dOpensource/dsiprouter/commit/c04f2724aed299780444fe36da320baf4cd58f6b) > Date: Sat, 24 Nov 2018 13:12:52 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c04f2724aed299780444fe36da320baf4cd58f6b) [//]: # (START_SECTION eb5d6336b9f7acfbdc5110ada01bfd8946fc4d73) ### Update use-cases.rst > Commit: [eb5d6336b9f7acfbdc5110ada01bfd8946fc4d73](https://github.com/dOpensource/dsiprouter/commit/eb5d6336b9f7acfbdc5110ada01bfd8946fc4d73) > Date: Sat, 24 Nov 2018 08:37:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb5d6336b9f7acfbdc5110ada01bfd8946fc4d73) [//]: # (START_SECTION 3c014f58a59a8b657652c9c4babd71ff79f3d7c9) ### Applied a patch to deal with the stale database connections, Fixed Carrier Registraton so that the Registrar Server IP is addeded to the Address table, Fixed a conflict with the datatables javascript file that was preventing other javascript from operating correctly > Commit: [3c014f58a59a8b657652c9c4babd71ff79f3d7c9](https://github.com/dOpensource/dsiprouter/commit/3c014f58a59a8b657652c9c4babd71ff79f3d7c9) > Date: Mon, 19 Nov 2018 04:00:41 +0000 > Author: mhendricks (root@debian-dsip-51-build.localdomain) > Committer: mhendricks (root@debian-dsip-51-build.localdomain) > Signed: --- [//]: # (END_SECTION 3c014f58a59a8b657652c9c4babd71ff79f3d7c9) [//]: # (START_SECTION 457938c59d0867580e96ae978aab47d10424ab2c) ### Add files via upload > Commit: [457938c59d0867580e96ae978aab47d10424ab2c](https://github.com/dOpensource/dsiprouter/commit/457938c59d0867580e96ae978aab47d10424ab2c) > Date: Fri, 16 Nov 2018 14:46:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 457938c59d0867580e96ae978aab47d10424ab2c) [//]: # (START_SECTION 83ecc170dca6746831ee32deeaa692ec5d97fa74) ### Update use-cases.rst > Commit: [83ecc170dca6746831ee32deeaa692ec5d97fa74](https://github.com/dOpensource/dsiprouter/commit/83ecc170dca6746831ee32deeaa692ec5d97fa74) > Date: Fri, 16 Nov 2018 13:10:42 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 83ecc170dca6746831ee32deeaa692ec5d97fa74) [//]: # (START_SECTION 793e678e8c54db07602dc70f5d52112e5e186f62) ### Update use-cases.rst > Commit: [793e678e8c54db07602dc70f5d52112e5e186f62](https://github.com/dOpensource/dsiprouter/commit/793e678e8c54db07602dc70f5d52112e5e186f62) > Date: Fri, 16 Nov 2018 13:04:50 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 793e678e8c54db07602dc70f5d52112e5e186f62) [//]: # (START_SECTION 86b349224e745fbd675d56e113138227e0574e9e) ### Update use-cases.rst > Commit: [86b349224e745fbd675d56e113138227e0574e9e](https://github.com/dOpensource/dsiprouter/commit/86b349224e745fbd675d56e113138227e0574e9e) > Date: Fri, 16 Nov 2018 13:02:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 86b349224e745fbd675d56e113138227e0574e9e) [//]: # (START_SECTION 4fdcf23e77468179dafa74a2850e0ff0b3cc72b7) ### Add files via upload > Commit: [4fdcf23e77468179dafa74a2850e0ff0b3cc72b7](https://github.com/dOpensource/dsiprouter/commit/4fdcf23e77468179dafa74a2850e0ff0b3cc72b7) > Date: Fri, 16 Nov 2018 13:00:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4fdcf23e77468179dafa74a2850e0ff0b3cc72b7) [//]: # (START_SECTION 9b2c3db2e020b5728ff8047c254b476008f1a4de) ### Update use-cases.rst > Commit: [9b2c3db2e020b5728ff8047c254b476008f1a4de](https://github.com/dOpensource/dsiprouter/commit/9b2c3db2e020b5728ff8047c254b476008f1a4de) > Date: Fri, 16 Nov 2018 13:00:02 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9b2c3db2e020b5728ff8047c254b476008f1a4de) [//]: # (START_SECTION bdf63dd83891ea9cddd40d02c2278cc8f4ed009f) ### Add files via upload > Commit: [bdf63dd83891ea9cddd40d02c2278cc8f4ed009f](https://github.com/dOpensource/dsiprouter/commit/bdf63dd83891ea9cddd40d02c2278cc8f4ed009f) > Date: Fri, 16 Nov 2018 12:35:30 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bdf63dd83891ea9cddd40d02c2278cc8f4ed009f) [//]: # (START_SECTION 84cc1f485cdb313c0e8f2085711f4307bcd931dc) ### Create use-cases.rst > Commit: [84cc1f485cdb313c0e8f2085711f4307bcd931dc](https://github.com/dOpensource/dsiprouter/commit/84cc1f485cdb313c0e8f2085711f4307bcd931dc) > Date: Fri, 16 Nov 2018 12:33:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 84cc1f485cdb313c0e8f2085711f4307bcd931dc) [//]: # (START_SECTION 1209735bc4779ed18949ef75074c7f6f2468d51e) ### Add SSL configuratoin to install script > Commit: [1209735bc4779ed18949ef75074c7f6f2468d51e](https://github.com/dOpensource/dsiprouter/commit/1209735bc4779ed18949ef75074c7f6f2468d51e) > Date: Thu, 15 Nov 2018 18:29:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - -- added ssl default configuration to install process --- [//]: # (END_SECTION 1209735bc4779ed18949ef75074c7f6f2468d51e) [//]: # (START_SECTION f5a18b7cbe4263f45718a257c2a45f574e8af619) ### Fixed an issue that prevented the nginx docker image from starting after the server is rebooted > Commit: [f5a18b7cbe4263f45718a257c2a45f574e8af619](https://github.com/dOpensource/dsiprouter/commit/f5a18b7cbe4263f45718a257c2a45f574e8af619) > Date: Thu, 15 Nov 2018 22:54:23 +0000 > Author: root (root@debian-dsip-51-build.localdomain) > Committer: root (root@debian-dsip-51-build.localdomain) > Signed: --- [//]: # (END_SECTION f5a18b7cbe4263f45718a257c2a45f574e8af619) [//]: # (START_SECTION 82eca5ccc5d8b09ce7ab6fe8ebbd95119fdc9484) ### Update installing.rst > Commit: [82eca5ccc5d8b09ce7ab6fe8ebbd95119fdc9484](https://github.com/dOpensource/dsiprouter/commit/82eca5ccc5d8b09ce7ab6fe8ebbd95119fdc9484) > Date: Thu, 15 Nov 2018 14:58:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 82eca5ccc5d8b09ce7ab6fe8ebbd95119fdc9484) [//]: # (START_SECTION ab1d6631e5a193b7ef8902ff2201c4f4618043fa) ### Update installing.rst > Commit: [ab1d6631e5a193b7ef8902ff2201c4f4618043fa](https://github.com/dOpensource/dsiprouter/commit/ab1d6631e5a193b7ef8902ff2201c4f4618043fa) > Date: Thu, 15 Nov 2018 14:56:47 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ab1d6631e5a193b7ef8902ff2201c4f4618043fa) [//]: # (START_SECTION 4d3b1682c6b865b5367ecc13e32b2338e2f15a1e) ### Update installing.rst > Commit: [4d3b1682c6b865b5367ecc13e32b2338e2f15a1e](https://github.com/dOpensource/dsiprouter/commit/4d3b1682c6b865b5367ecc13e32b2338e2f15a1e) > Date: Thu, 15 Nov 2018 14:54:40 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4d3b1682c6b865b5367ecc13e32b2338e2f15a1e) [//]: # (START_SECTION 8b592bcdcb4b112516600b4be426f20558492029) ### Update installing.rst > Commit: [8b592bcdcb4b112516600b4be426f20558492029](https://github.com/dOpensource/dsiprouter/commit/8b592bcdcb4b112516600b4be426f20558492029) > Date: Thu, 15 Nov 2018 14:53:00 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8b592bcdcb4b112516600b4be426f20558492029) [//]: # (START_SECTION a60b30d533ddff616416883e153ed1d9fec6a26b) ### Update installing.rst > Commit: [a60b30d533ddff616416883e153ed1d9fec6a26b](https://github.com/dOpensource/dsiprouter/commit/a60b30d533ddff616416883e153ed1d9fec6a26b) > Date: Thu, 15 Nov 2018 14:50:33 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a60b30d533ddff616416883e153ed1d9fec6a26b) [//]: # (START_SECTION 91030ad3b9e0b39481dc276fe243e55a7ddc219a) ### Turned off the debug statement > Commit: [91030ad3b9e0b39481dc276fe243e55a7ddc219a](https://github.com/dOpensource/dsiprouter/commit/91030ad3b9e0b39481dc276fe243e55a7ddc219a) > Date: Thu, 15 Nov 2018 11:57:05 +0000 > Author: root (root@dSIPRouter-v051-build.localdomain) > Committer: root (root@dSIPRouter-v051-build.localdomain) > Signed: --- [//]: # (END_SECTION 91030ad3b9e0b39481dc276fe243e55a7ddc219a) [//]: # (START_SECTION 1165bb26023a7b24757c7360657008438ffeed2e) ### Update dsiprouter.sh > Commit: [1165bb26023a7b24757c7360657008438ffeed2e](https://github.com/dOpensource/dsiprouter/commit/1165bb26023a7b24757c7360657008438ffeed2e) > Date: Thu, 15 Nov 2018 06:50:58 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1165bb26023a7b24757c7360657008438ffeed2e) [//]: # (START_SECTION b2e277da3de0961b6717a7cc08bc23aa49de6960) ### Fixed installer on Debian > Commit: [b2e277da3de0961b6717a7cc08bc23aa49de6960](https://github.com/dOpensource/dsiprouter/commit/b2e277da3de0961b6717a7cc08bc23aa49de6960) > Date: Thu, 15 Nov 2018 11:39:56 +0000 > Author: root (root@dSIPRouter-v051-build.localdomain) > Committer: root (root@dSIPRouter-v051-build.localdomain) > Signed: --- [//]: # (END_SECTION b2e277da3de0961b6717a7cc08bc23aa49de6960) [//]: # (START_SECTION 1f767694669f7b7484cf951f7057d05386900a49) ### Update installing.rst > Commit: [1f767694669f7b7484cf951f7057d05386900a49](https://github.com/dOpensource/dsiprouter/commit/1f767694669f7b7484cf951f7057d05386900a49) > Date: Tue, 13 Nov 2018 19:42:43 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1f767694669f7b7484cf951f7057d05386900a49) [//]: # (START_SECTION 778c025c71c48dede13c4da1fe117677a5f9033a) ### Update README.md > Commit: [778c025c71c48dede13c4da1fe117677a5f9033a](https://github.com/dOpensource/dsiprouter/commit/778c025c71c48dede13c4da1fe117677a5f9033a) > Date: Tue, 13 Nov 2018 19:37:33 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 778c025c71c48dede13c4da1fe117677a5f9033a) [//]: # (START_SECTION 5f93fa3171f93c0edc84806c5924d6361644852b) ### Update README.md > Commit: [5f93fa3171f93c0edc84806c5924d6361644852b](https://github.com/dOpensource/dsiprouter/commit/5f93fa3171f93c0edc84806c5924d6361644852b) > Date: Tue, 13 Nov 2018 19:12:03 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5f93fa3171f93c0edc84806c5924d6361644852b) [//]: # (START_SECTION a1e1a90f319fb0440c6c321e1ecfc6ea37002891) ### Update carrier_groups.rst > Commit: [a1e1a90f319fb0440c6c321e1ecfc6ea37002891](https://github.com/dOpensource/dsiprouter/commit/a1e1a90f319fb0440c6c321e1ecfc6ea37002891) > Date: Fri, 9 Nov 2018 14:56:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a1e1a90f319fb0440c6c321e1ecfc6ea37002891) [//]: # (START_SECTION 75b8116f0e2c8e32e5a1df7efe04582ac9ce46f3) ### Update carrier_groups.rst > Commit: [75b8116f0e2c8e32e5a1df7efe04582ac9ce46f3](https://github.com/dOpensource/dsiprouter/commit/75b8116f0e2c8e32e5a1df7efe04582ac9ce46f3) > Date: Fri, 9 Nov 2018 14:55:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 75b8116f0e2c8e32e5a1df7efe04582ac9ce46f3) [//]: # (START_SECTION 49a18e01a1dce832861babbd2c15fa2079618389) ### Update installing.rst > Commit: [49a18e01a1dce832861babbd2c15fa2079618389](https://github.com/dOpensource/dsiprouter/commit/49a18e01a1dce832861babbd2c15fa2079618389) > Date: Fri, 9 Nov 2018 14:14:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 49a18e01a1dce832861babbd2c15fa2079618389) [//]: # (START_SECTION 99ae7d5df176c47c5ea230f8392728cffbe02030) ### Update installing.rst > Commit: [99ae7d5df176c47c5ea230f8392728cffbe02030](https://github.com/dOpensource/dsiprouter/commit/99ae7d5df176c47c5ea230f8392728cffbe02030) > Date: Fri, 9 Nov 2018 14:10:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 99ae7d5df176c47c5ea230f8392728cffbe02030) [//]: # (START_SECTION 66040b97ed790dc5312dfbc9e8592a35e6910c4e) ### Update installing.rst > Commit: [66040b97ed790dc5312dfbc9e8592a35e6910c4e](https://github.com/dOpensource/dsiprouter/commit/66040b97ed790dc5312dfbc9e8592a35e6910c4e) > Date: Fri, 9 Nov 2018 14:09:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 66040b97ed790dc5312dfbc9e8592a35e6910c4e) [//]: # (START_SECTION 1583b45b01afe9434db335b92fa314a1e1b32f7f) ### Update installing.rst > Commit: [1583b45b01afe9434db335b92fa314a1e1b32f7f](https://github.com/dOpensource/dsiprouter/commit/1583b45b01afe9434db335b92fa314a1e1b32f7f) > Date: Fri, 9 Nov 2018 14:02:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1583b45b01afe9434db335b92fa314a1e1b32f7f) [//]: # (START_SECTION ed8da72e7c2efe2cab3c103e6e2c2645dc1150c7) ### Update installing.rst > Commit: [ed8da72e7c2efe2cab3c103e6e2c2645dc1150c7](https://github.com/dOpensource/dsiprouter/commit/ed8da72e7c2efe2cab3c103e6e2c2645dc1150c7) > Date: Fri, 9 Nov 2018 14:00:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ed8da72e7c2efe2cab3c103e6e2c2645dc1150c7) [//]: # (START_SECTION 15e06f183af7d908ad11e8b946f575d48323ebec) ### Update installing.rst > Commit: [15e06f183af7d908ad11e8b946f575d48323ebec](https://github.com/dOpensource/dsiprouter/commit/15e06f183af7d908ad11e8b946f575d48323ebec) > Date: Fri, 9 Nov 2018 13:57:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 15e06f183af7d908ad11e8b946f575d48323ebec) [//]: # (START_SECTION bd863c42a007fb2e991d5312b03ab16fca92efc3) ### Update installing.rst > Commit: [bd863c42a007fb2e991d5312b03ab16fca92efc3](https://github.com/dOpensource/dsiprouter/commit/bd863c42a007fb2e991d5312b03ab16fca92efc3) > Date: Fri, 9 Nov 2018 13:56:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bd863c42a007fb2e991d5312b03ab16fca92efc3) [//]: # (START_SECTION 5caf4a6578033a6e34c335b0b9ba6934eb9e0733) ### Update installing.rst > Commit: [5caf4a6578033a6e34c335b0b9ba6934eb9e0733](https://github.com/dOpensource/dsiprouter/commit/5caf4a6578033a6e34c335b0b9ba6934eb9e0733) > Date: Fri, 9 Nov 2018 13:54:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5caf4a6578033a6e34c335b0b9ba6934eb9e0733) [//]: # (START_SECTION c76115212f20709531696572e531d1808d06eaa7) ### Update installing.rst > Commit: [c76115212f20709531696572e531d1808d06eaa7](https://github.com/dOpensource/dsiprouter/commit/c76115212f20709531696572e531d1808d06eaa7) > Date: Fri, 9 Nov 2018 13:51:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c76115212f20709531696572e531d1808d06eaa7) [//]: # (START_SECTION 4c8b8da65f27daa006e41869fa6dbcea848134fc) ### Update installing.rst > Commit: [4c8b8da65f27daa006e41869fa6dbcea848134fc](https://github.com/dOpensource/dsiprouter/commit/4c8b8da65f27daa006e41869fa6dbcea848134fc) > Date: Fri, 9 Nov 2018 13:46:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4c8b8da65f27daa006e41869fa6dbcea848134fc) [//]: # (START_SECTION 99bed286b6b15f62e27a13e614c00ed6688eab1e) ### Update installing.rst > Commit: [99bed286b6b15f62e27a13e614c00ed6688eab1e](https://github.com/dOpensource/dsiprouter/commit/99bed286b6b15f62e27a13e614c00ed6688eab1e) > Date: Fri, 9 Nov 2018 13:44:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 99bed286b6b15f62e27a13e614c00ed6688eab1e) [//]: # (START_SECTION 9a98edc95087411000326c3ef635792bf2399b16) ### Update installing.rst > Commit: [9a98edc95087411000326c3ef635792bf2399b16](https://github.com/dOpensource/dsiprouter/commit/9a98edc95087411000326c3ef635792bf2399b16) > Date: Fri, 9 Nov 2018 13:43:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9a98edc95087411000326c3ef635792bf2399b16) [//]: # (START_SECTION 2cfd118ad8a498a6a59c04964d85c29d9c3bb99e) ### Update domains.rst > Commit: [2cfd118ad8a498a6a59c04964d85c29d9c3bb99e](https://github.com/dOpensource/dsiprouter/commit/2cfd118ad8a498a6a59c04964d85c29d9c3bb99e) > Date: Fri, 9 Nov 2018 13:18:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2cfd118ad8a498a6a59c04964d85c29d9c3bb99e) [//]: # (START_SECTION 5b3805a2715738d8cb999690f3b441c34de25a05) ### Update domains.rst > Commit: [5b3805a2715738d8cb999690f3b441c34de25a05](https://github.com/dOpensource/dsiprouter/commit/5b3805a2715738d8cb999690f3b441c34de25a05) > Date: Fri, 9 Nov 2018 13:16:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5b3805a2715738d8cb999690f3b441c34de25a05) [//]: # (START_SECTION c5dee805da8ec12b2c1cdeb67d371fe61f673ce6) ### Update domains.rst > Commit: [c5dee805da8ec12b2c1cdeb67d371fe61f673ce6](https://github.com/dOpensource/dsiprouter/commit/c5dee805da8ec12b2c1cdeb67d371fe61f673ce6) > Date: Fri, 9 Nov 2018 13:12:47 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c5dee805da8ec12b2c1cdeb67d371fe61f673ce6) [//]: # (START_SECTION 7c3d12fb7a2be0e0fe22e04b9947b299a700fc06) ### Update configuring.rst > Commit: [7c3d12fb7a2be0e0fe22e04b9947b299a700fc06](https://github.com/dOpensource/dsiprouter/commit/7c3d12fb7a2be0e0fe22e04b9947b299a700fc06) > Date: Fri, 9 Nov 2018 13:11:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7c3d12fb7a2be0e0fe22e04b9947b299a700fc06) [//]: # (START_SECTION b3e095f30f3b5498926016a3c6825e9982a468eb) ### Update domains.rst > Commit: [b3e095f30f3b5498926016a3c6825e9982a468eb](https://github.com/dOpensource/dsiprouter/commit/b3e095f30f3b5498926016a3c6825e9982a468eb) > Date: Fri, 9 Nov 2018 12:27:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b3e095f30f3b5498926016a3c6825e9982a468eb) [//]: # (START_SECTION 921324ddfa980fcc711de28abc906517ee4c40b6) ### Update domains.rst > Commit: [921324ddfa980fcc711de28abc906517ee4c40b6](https://github.com/dOpensource/dsiprouter/commit/921324ddfa980fcc711de28abc906517ee4c40b6) > Date: Fri, 9 Nov 2018 12:19:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 921324ddfa980fcc711de28abc906517ee4c40b6) [//]: # (START_SECTION 2ecea64d78def7c33892f5cea8d2cf28c6d27108) ### Add files via upload > Commit: [2ecea64d78def7c33892f5cea8d2cf28c6d27108](https://github.com/dOpensource/dsiprouter/commit/2ecea64d78def7c33892f5cea8d2cf28c6d27108) > Date: Fri, 9 Nov 2018 12:17:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2ecea64d78def7c33892f5cea8d2cf28c6d27108) [//]: # (START_SECTION 2593339f116791259429a6ad1cb1e9712bc19310) ### Delete list_of_domains.PNG > Commit: [2593339f116791259429a6ad1cb1e9712bc19310](https://github.com/dOpensource/dsiprouter/commit/2593339f116791259429a6ad1cb1e9712bc19310) > Date: Fri, 9 Nov 2018 12:16:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2593339f116791259429a6ad1cb1e9712bc19310) [//]: # (START_SECTION 7dd8a17b8153ac0622ba98c720ea260b30b6b194) ### Delete add_new_domain2.PNG > Commit: [7dd8a17b8153ac0622ba98c720ea260b30b6b194](https://github.com/dOpensource/dsiprouter/commit/7dd8a17b8153ac0622ba98c720ea260b30b6b194) > Date: Fri, 9 Nov 2018 12:16:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7dd8a17b8153ac0622ba98c720ea260b30b6b194) [//]: # (START_SECTION 1750b693ae732cdf6932c1e1b5d5faf57ed4ad94) ### Update carrier_groups.rst > Commit: [1750b693ae732cdf6932c1e1b5d5faf57ed4ad94](https://github.com/dOpensource/dsiprouter/commit/1750b693ae732cdf6932c1e1b5d5faf57ed4ad94) > Date: Fri, 9 Nov 2018 12:05:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1750b693ae732cdf6932c1e1b5d5faf57ed4ad94) [//]: # (START_SECTION cfb00c047adb0dcb0415d1ded819063bc2ea8d3e) ### Add files via upload > Commit: [cfb00c047adb0dcb0415d1ded819063bc2ea8d3e](https://github.com/dOpensource/dsiprouter/commit/cfb00c047adb0dcb0415d1ded819063bc2ea8d3e) > Date: Fri, 9 Nov 2018 12:04:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cfb00c047adb0dcb0415d1ded819063bc2ea8d3e) [//]: # (START_SECTION 93a98ff4aa0a6275db7ce16c01acabfb4968deb3) ### Update carrier_groups.rst > Commit: [93a98ff4aa0a6275db7ce16c01acabfb4968deb3](https://github.com/dOpensource/dsiprouter/commit/93a98ff4aa0a6275db7ce16c01acabfb4968deb3) > Date: Fri, 9 Nov 2018 12:01:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 93a98ff4aa0a6275db7ce16c01acabfb4968deb3) [//]: # (START_SECTION 0d5f2f340ce0326222c34c2d8613ff326ffc2345) ### Add files via upload > Commit: [0d5f2f340ce0326222c34c2d8613ff326ffc2345](https://github.com/dOpensource/dsiprouter/commit/0d5f2f340ce0326222c34c2d8613ff326ffc2345) > Date: Fri, 9 Nov 2018 11:57:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0d5f2f340ce0326222c34c2d8613ff326ffc2345) [//]: # (START_SECTION 099d3584c1f3118b18af460c01fad9f6b432cd32) ### Update carrier_groups.rst > Commit: [099d3584c1f3118b18af460c01fad9f6b432cd32](https://github.com/dOpensource/dsiprouter/commit/099d3584c1f3118b18af460c01fad9f6b432cd32) > Date: Fri, 9 Nov 2018 11:50:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 099d3584c1f3118b18af460c01fad9f6b432cd32) [//]: # (START_SECTION 882de80116bc399a484d6cd6afbfea244c6e3426) ### Update index.rst > Commit: [882de80116bc399a484d6cd6afbfea244c6e3426](https://github.com/dOpensource/dsiprouter/commit/882de80116bc399a484d6cd6afbfea244c6e3426) > Date: Thu, 8 Nov 2018 15:53:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 882de80116bc399a484d6cd6afbfea244c6e3426) [//]: # (START_SECTION 6157a28a60dc731c02ac8fb31cc8591c04eb918d) ### Update index.rst > Commit: [6157a28a60dc731c02ac8fb31cc8591c04eb918d](https://github.com/dOpensource/dsiprouter/commit/6157a28a60dc731c02ac8fb31cc8591c04eb918d) > Date: Thu, 8 Nov 2018 15:51:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6157a28a60dc731c02ac8fb31cc8591c04eb918d) [//]: # (START_SECTION 7558d895a2e25800ece0e181ed1b7788e800608b) ### Update index.rst > Commit: [7558d895a2e25800ece0e181ed1b7788e800608b](https://github.com/dOpensource/dsiprouter/commit/7558d895a2e25800ece0e181ed1b7788e800608b) > Date: Thu, 8 Nov 2018 15:49:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7558d895a2e25800ece0e181ed1b7788e800608b) [//]: # (START_SECTION aedfb0d1ee3b89213984234979f2137b2a59df0b) ### Update carrier_groups.rst > Commit: [aedfb0d1ee3b89213984234979f2137b2a59df0b](https://github.com/dOpensource/dsiprouter/commit/aedfb0d1ee3b89213984234979f2137b2a59df0b) > Date: Thu, 8 Nov 2018 15:46:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION aedfb0d1ee3b89213984234979f2137b2a59df0b) [//]: # (START_SECTION fa3e1f7bf740445e180fc574fee2a15265d7c08b) ### Update carrier_groups.rst > Commit: [fa3e1f7bf740445e180fc574fee2a15265d7c08b](https://github.com/dOpensource/dsiprouter/commit/fa3e1f7bf740445e180fc574fee2a15265d7c08b) > Date: Thu, 8 Nov 2018 15:45:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fa3e1f7bf740445e180fc574fee2a15265d7c08b) [//]: # (START_SECTION 9d794dd375b30004efa9c5d0301522ba80ceb6b0) ### Update domains.rst > Commit: [9d794dd375b30004efa9c5d0301522ba80ceb6b0](https://github.com/dOpensource/dsiprouter/commit/9d794dd375b30004efa9c5d0301522ba80ceb6b0) > Date: Thu, 8 Nov 2018 15:43:28 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9d794dd375b30004efa9c5d0301522ba80ceb6b0) [//]: # (START_SECTION 2b15d386650893591c23669c2ecbfb529556d518) ### Update domains.rst > Commit: [2b15d386650893591c23669c2ecbfb529556d518](https://github.com/dOpensource/dsiprouter/commit/2b15d386650893591c23669c2ecbfb529556d518) > Date: Thu, 8 Nov 2018 15:42:50 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2b15d386650893591c23669c2ecbfb529556d518) [//]: # (START_SECTION 73f6fea1d902e7a9abf341dde9d1725b17e854b9) ### Update domains.rst > Commit: [73f6fea1d902e7a9abf341dde9d1725b17e854b9](https://github.com/dOpensource/dsiprouter/commit/73f6fea1d902e7a9abf341dde9d1725b17e854b9) > Date: Thu, 8 Nov 2018 15:32:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 73f6fea1d902e7a9abf341dde9d1725b17e854b9) [//]: # (START_SECTION 2c9d64d46eee85049b66048fa9eef659d826f9a4) ### Update domains.rst > Commit: [2c9d64d46eee85049b66048fa9eef659d826f9a4](https://github.com/dOpensource/dsiprouter/commit/2c9d64d46eee85049b66048fa9eef659d826f9a4) > Date: Thu, 8 Nov 2018 15:32:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2c9d64d46eee85049b66048fa9eef659d826f9a4) [//]: # (START_SECTION 235f903c9b8c4897a03df7bfae30f1bf45ee2f1d) ### Update domains.rst > Commit: [235f903c9b8c4897a03df7bfae30f1bf45ee2f1d](https://github.com/dOpensource/dsiprouter/commit/235f903c9b8c4897a03df7bfae30f1bf45ee2f1d) > Date: Thu, 8 Nov 2018 15:31:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 235f903c9b8c4897a03df7bfae30f1bf45ee2f1d) [//]: # (START_SECTION 9e596e2ce492b51c134c3dad22a1ba4ca35c3b02) ### Update domains.rst > Commit: [9e596e2ce492b51c134c3dad22a1ba4ca35c3b02](https://github.com/dOpensource/dsiprouter/commit/9e596e2ce492b51c134c3dad22a1ba4ca35c3b02) > Date: Thu, 8 Nov 2018 15:30:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9e596e2ce492b51c134c3dad22a1ba4ca35c3b02) [//]: # (START_SECTION b6f2f27fffabc0b0a28774c450000e24d4567371) ### Update domains.rst > Commit: [b6f2f27fffabc0b0a28774c450000e24d4567371](https://github.com/dOpensource/dsiprouter/commit/b6f2f27fffabc0b0a28774c450000e24d4567371) > Date: Thu, 8 Nov 2018 15:29:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b6f2f27fffabc0b0a28774c450000e24d4567371) [//]: # (START_SECTION 7a2199cb5b30f0a8f08a5975aedbb718aae4778c) ### Update domains.rst > Commit: [7a2199cb5b30f0a8f08a5975aedbb718aae4778c](https://github.com/dOpensource/dsiprouter/commit/7a2199cb5b30f0a8f08a5975aedbb718aae4778c) > Date: Thu, 8 Nov 2018 15:29:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7a2199cb5b30f0a8f08a5975aedbb718aae4778c) [//]: # (START_SECTION 66c3fe9e2253315cb6b3150eb23f0ac01f99f6b8) ### Update domains.rst > Commit: [66c3fe9e2253315cb6b3150eb23f0ac01f99f6b8](https://github.com/dOpensource/dsiprouter/commit/66c3fe9e2253315cb6b3150eb23f0ac01f99f6b8) > Date: Thu, 8 Nov 2018 15:28:40 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 66c3fe9e2253315cb6b3150eb23f0ac01f99f6b8) [//]: # (START_SECTION 960074e6bf4996db7b0c73c908aae5fea252623e) ### Update domains.rst > Commit: [960074e6bf4996db7b0c73c908aae5fea252623e](https://github.com/dOpensource/dsiprouter/commit/960074e6bf4996db7b0c73c908aae5fea252623e) > Date: Thu, 8 Nov 2018 15:26:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 960074e6bf4996db7b0c73c908aae5fea252623e) [//]: # (START_SECTION 44393a6e59288da83f0611f92e67119cf55b11a4) ### Update domains.rst > Commit: [44393a6e59288da83f0611f92e67119cf55b11a4](https://github.com/dOpensource/dsiprouter/commit/44393a6e59288da83f0611f92e67119cf55b11a4) > Date: Thu, 8 Nov 2018 15:25:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 44393a6e59288da83f0611f92e67119cf55b11a4) [//]: # (START_SECTION 7048f82452877ded0f3aa5dd6c51dc3c9fe3078c) ### Update domains.rst > Commit: [7048f82452877ded0f3aa5dd6c51dc3c9fe3078c](https://github.com/dOpensource/dsiprouter/commit/7048f82452877ded0f3aa5dd6c51dc3c9fe3078c) > Date: Thu, 8 Nov 2018 15:24:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7048f82452877ded0f3aa5dd6c51dc3c9fe3078c) [//]: # (START_SECTION ba2fb8652c31e34ed00a7b447bbcbca3e17e0dd6) ### Add files via upload > Commit: [ba2fb8652c31e34ed00a7b447bbcbca3e17e0dd6](https://github.com/dOpensource/dsiprouter/commit/ba2fb8652c31e34ed00a7b447bbcbca3e17e0dd6) > Date: Thu, 8 Nov 2018 15:23:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ba2fb8652c31e34ed00a7b447bbcbca3e17e0dd6) [//]: # (START_SECTION 5b4c13a16c7ff45c7f7586ae9c2d0d8a9c1cf454) ### Update domains.rst > Commit: [5b4c13a16c7ff45c7f7586ae9c2d0d8a9c1cf454](https://github.com/dOpensource/dsiprouter/commit/5b4c13a16c7ff45c7f7586ae9c2d0d8a9c1cf454) > Date: Thu, 8 Nov 2018 15:22:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5b4c13a16c7ff45c7f7586ae9c2d0d8a9c1cf454) [//]: # (START_SECTION cada6f3b68096eabc8f62c8a0b6f52f2f2ac181a) ### Update carrier_groups.rst > Commit: [cada6f3b68096eabc8f62c8a0b6f52f2f2ac181a](https://github.com/dOpensource/dsiprouter/commit/cada6f3b68096eabc8f62c8a0b6f52f2f2ac181a) > Date: Thu, 8 Nov 2018 15:20:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cada6f3b68096eabc8f62c8a0b6f52f2f2ac181a) [//]: # (START_SECTION 906b33da57e3aacd23f255616057dd9da460f7c1) ### Update carrier_groups.rst > Commit: [906b33da57e3aacd23f255616057dd9da460f7c1](https://github.com/dOpensource/dsiprouter/commit/906b33da57e3aacd23f255616057dd9da460f7c1) > Date: Thu, 8 Nov 2018 15:19:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 906b33da57e3aacd23f255616057dd9da460f7c1) [//]: # (START_SECTION 6969d79f6b18b40cc065ffbc52191a5bb8e5f786) ### Update carrier_groups.rst > Commit: [6969d79f6b18b40cc065ffbc52191a5bb8e5f786](https://github.com/dOpensource/dsiprouter/commit/6969d79f6b18b40cc065ffbc52191a5bb8e5f786) > Date: Thu, 8 Nov 2018 15:11:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6969d79f6b18b40cc065ffbc52191a5bb8e5f786) [//]: # (START_SECTION 20ad3212c883e78ad15895069a6f23b4e5a46ae8) ### Update carrier_groups.rst > Commit: [20ad3212c883e78ad15895069a6f23b4e5a46ae8](https://github.com/dOpensource/dsiprouter/commit/20ad3212c883e78ad15895069a6f23b4e5a46ae8) > Date: Thu, 8 Nov 2018 15:10:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 20ad3212c883e78ad15895069a6f23b4e5a46ae8) [//]: # (START_SECTION 78b6265a9d3e0a2dc89cbf259b0317e8ebe22762) ### Update carrier_groups.rst > Commit: [78b6265a9d3e0a2dc89cbf259b0317e8ebe22762](https://github.com/dOpensource/dsiprouter/commit/78b6265a9d3e0a2dc89cbf259b0317e8ebe22762) > Date: Thu, 8 Nov 2018 15:10:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 78b6265a9d3e0a2dc89cbf259b0317e8ebe22762) [//]: # (START_SECTION 349e61f8d63ac3e021b9181f92e49c7503678f5f) ### Update carrier_groups.rst > Commit: [349e61f8d63ac3e021b9181f92e49c7503678f5f](https://github.com/dOpensource/dsiprouter/commit/349e61f8d63ac3e021b9181f92e49c7503678f5f) > Date: Thu, 8 Nov 2018 15:09:27 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 349e61f8d63ac3e021b9181f92e49c7503678f5f) [//]: # (START_SECTION 14e6cb436f44a7f265c7ed613a2a9d52d313f056) ### Update carrier_groups.rst > Commit: [14e6cb436f44a7f265c7ed613a2a9d52d313f056](https://github.com/dOpensource/dsiprouter/commit/14e6cb436f44a7f265c7ed613a2a9d52d313f056) > Date: Thu, 8 Nov 2018 14:51:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 14e6cb436f44a7f265c7ed613a2a9d52d313f056) [//]: # (START_SECTION f7ef55ffd6b038fa002c795613e37a1c040ea732) ### Update carrier_groups.rst > Commit: [f7ef55ffd6b038fa002c795613e37a1c040ea732](https://github.com/dOpensource/dsiprouter/commit/f7ef55ffd6b038fa002c795613e37a1c040ea732) > Date: Thu, 8 Nov 2018 14:50:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f7ef55ffd6b038fa002c795613e37a1c040ea732) [//]: # (START_SECTION 35388acaaa4b66c8f1347d494ec5e3f38f44a4f2) ### Update carrier_groups.rst > Commit: [35388acaaa4b66c8f1347d494ec5e3f38f44a4f2](https://github.com/dOpensource/dsiprouter/commit/35388acaaa4b66c8f1347d494ec5e3f38f44a4f2) > Date: Thu, 8 Nov 2018 14:50:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 35388acaaa4b66c8f1347d494ec5e3f38f44a4f2) [//]: # (START_SECTION ddcbba0a2c19a2ae786675ecbf2bb40ffdab3a19) ### Update carrier_groups.rst > Commit: [ddcbba0a2c19a2ae786675ecbf2bb40ffdab3a19](https://github.com/dOpensource/dsiprouter/commit/ddcbba0a2c19a2ae786675ecbf2bb40ffdab3a19) > Date: Thu, 8 Nov 2018 14:49:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ddcbba0a2c19a2ae786675ecbf2bb40ffdab3a19) [//]: # (START_SECTION 6380ca9f409b6ac30e59fcab658ace2587ab45d4) ### Update carrier_groups.rst > Commit: [6380ca9f409b6ac30e59fcab658ace2587ab45d4](https://github.com/dOpensource/dsiprouter/commit/6380ca9f409b6ac30e59fcab658ace2587ab45d4) > Date: Thu, 8 Nov 2018 14:47:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6380ca9f409b6ac30e59fcab658ace2587ab45d4) [//]: # (START_SECTION 8322b7b03295246732ec4334f8c66c9073990faa) ### Delete add_carrier_details.PNG > Commit: [8322b7b03295246732ec4334f8c66c9073990faa](https://github.com/dOpensource/dsiprouter/commit/8322b7b03295246732ec4334f8c66c9073990faa) > Date: Thu, 8 Nov 2018 14:46:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8322b7b03295246732ec4334f8c66c9073990faa) [//]: # (START_SECTION 659de73d00997d6ef6fbe9bace3a4c9939a5df3d) ### Delete add_new_carrier_details.JPG > Commit: [659de73d00997d6ef6fbe9bace3a4c9939a5df3d](https://github.com/dOpensource/dsiprouter/commit/659de73d00997d6ef6fbe9bace3a4c9939a5df3d) > Date: Thu, 8 Nov 2018 14:45:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 659de73d00997d6ef6fbe9bace3a4c9939a5df3d) [//]: # (START_SECTION e6ff5cf5cb93dae1a7355a6b495f94bd9c746d55) ### Update carrier_groups.rst > Commit: [e6ff5cf5cb93dae1a7355a6b495f94bd9c746d55](https://github.com/dOpensource/dsiprouter/commit/e6ff5cf5cb93dae1a7355a6b495f94bd9c746d55) > Date: Thu, 8 Nov 2018 14:44:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e6ff5cf5cb93dae1a7355a6b495f94bd9c746d55) [//]: # (START_SECTION 0bfb0af828d877f5e65004dc3780bda2649e80f6) ### Update carrier_groups.rst > Commit: [0bfb0af828d877f5e65004dc3780bda2649e80f6](https://github.com/dOpensource/dsiprouter/commit/0bfb0af828d877f5e65004dc3780bda2649e80f6) > Date: Thu, 8 Nov 2018 14:44:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0bfb0af828d877f5e65004dc3780bda2649e80f6) [//]: # (START_SECTION 45dccd21c2191002a5bed33c744a397b895c1b0a) ### Add files via upload > Commit: [45dccd21c2191002a5bed33c744a397b895c1b0a](https://github.com/dOpensource/dsiprouter/commit/45dccd21c2191002a5bed33c744a397b895c1b0a) > Date: Thu, 8 Nov 2018 14:42:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 45dccd21c2191002a5bed33c744a397b895c1b0a) [//]: # (START_SECTION d726380487cef794abd00e00b27d3c535e18d92b) ### Update carrier_groups.rst > Commit: [d726380487cef794abd00e00b27d3c535e18d92b](https://github.com/dOpensource/dsiprouter/commit/d726380487cef794abd00e00b27d3c535e18d92b) > Date: Thu, 8 Nov 2018 14:37:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d726380487cef794abd00e00b27d3c535e18d92b) [//]: # (START_SECTION 5357705e425b25400ee584bc84d56ede14667eec) ### Update carrier_groups.rst > Commit: [5357705e425b25400ee584bc84d56ede14667eec](https://github.com/dOpensource/dsiprouter/commit/5357705e425b25400ee584bc84d56ede14667eec) > Date: Thu, 8 Nov 2018 11:28:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5357705e425b25400ee584bc84d56ede14667eec) [//]: # (START_SECTION a1409c59deb3714ffab1cba0d486cb3ccb2fa51f) ### Update carrier_groups.rst > Commit: [a1409c59deb3714ffab1cba0d486cb3ccb2fa51f](https://github.com/dOpensource/dsiprouter/commit/a1409c59deb3714ffab1cba0d486cb3ccb2fa51f) > Date: Thu, 8 Nov 2018 11:25:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a1409c59deb3714ffab1cba0d486cb3ccb2fa51f) [//]: # (START_SECTION 013023749f30c305652c61d35ed73d4f58795809) ### Update index.rst > Commit: [013023749f30c305652c61d35ed73d4f58795809](https://github.com/dOpensource/dsiprouter/commit/013023749f30c305652c61d35ed73d4f58795809) > Date: Thu, 8 Nov 2018 11:20:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 013023749f30c305652c61d35ed73d4f58795809) [//]: # (START_SECTION c3f97845900139ba10929105a9c3e8dca34c6672) ### Update index.rst > Commit: [c3f97845900139ba10929105a9c3e8dca34c6672](https://github.com/dOpensource/dsiprouter/commit/c3f97845900139ba10929105a9c3e8dca34c6672) > Date: Thu, 8 Nov 2018 11:19:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c3f97845900139ba10929105a9c3e8dca34c6672) [//]: # (START_SECTION 3f7f2a13de3b202ed48ecce83c8f093bcf421bd9) ### Update index.rst > Commit: [3f7f2a13de3b202ed48ecce83c8f093bcf421bd9](https://github.com/dOpensource/dsiprouter/commit/3f7f2a13de3b202ed48ecce83c8f093bcf421bd9) > Date: Thu, 8 Nov 2018 11:18:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f7f2a13de3b202ed48ecce83c8f093bcf421bd9) [//]: # (START_SECTION 3d133ee7a6ae25903e1cc01eb8e6a9384b34ac44) ### Update index.rst > Commit: [3d133ee7a6ae25903e1cc01eb8e6a9384b34ac44](https://github.com/dOpensource/dsiprouter/commit/3d133ee7a6ae25903e1cc01eb8e6a9384b34ac44) > Date: Thu, 8 Nov 2018 11:16:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d133ee7a6ae25903e1cc01eb8e6a9384b34ac44) [//]: # (START_SECTION 4a0337f57b3727affc04511ce9547483986bfb77) ### Update index.rst > Commit: [4a0337f57b3727affc04511ce9547483986bfb77](https://github.com/dOpensource/dsiprouter/commit/4a0337f57b3727affc04511ce9547483986bfb77) > Date: Thu, 8 Nov 2018 11:15:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4a0337f57b3727affc04511ce9547483986bfb77) [//]: # (START_SECTION 3674dc9a515e798d00a84ac4dbf22f7198d9e0ba) ### Update index.rst > Commit: [3674dc9a515e798d00a84ac4dbf22f7198d9e0ba](https://github.com/dOpensource/dsiprouter/commit/3674dc9a515e798d00a84ac4dbf22f7198d9e0ba) > Date: Thu, 8 Nov 2018 11:02:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3674dc9a515e798d00a84ac4dbf22f7198d9e0ba) [//]: # (START_SECTION cef282028674bf43872059c47c179dd4b0170572) ### Update index.rst > Commit: [cef282028674bf43872059c47c179dd4b0170572](https://github.com/dOpensource/dsiprouter/commit/cef282028674bf43872059c47c179dd4b0170572) > Date: Thu, 8 Nov 2018 10:56:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cef282028674bf43872059c47c179dd4b0170572) [//]: # (START_SECTION 9c8404c23592bc4d5b6d932b55932b981a2f63e9) ### Update installing.rst > Commit: [9c8404c23592bc4d5b6d932b55932b981a2f63e9](https://github.com/dOpensource/dsiprouter/commit/9c8404c23592bc4d5b6d932b55932b981a2f63e9) > Date: Thu, 8 Nov 2018 07:19:02 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9c8404c23592bc4d5b6d932b55932b981a2f63e9) [//]: # (START_SECTION 67148356831d88c45e6879817903e8c24157b96d) ### Update installing.rst > Commit: [67148356831d88c45e6879817903e8c24157b96d](https://github.com/dOpensource/dsiprouter/commit/67148356831d88c45e6879817903e8c24157b96d) > Date: Thu, 8 Nov 2018 07:16:38 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 67148356831d88c45e6879817903e8c24157b96d) [//]: # (START_SECTION f7b184afe38ba624148fd86bc3ab5a4da05bd588) ### Update index.rst > Commit: [f7b184afe38ba624148fd86bc3ab5a4da05bd588](https://github.com/dOpensource/dsiprouter/commit/f7b184afe38ba624148fd86bc3ab5a4da05bd588) > Date: Thu, 8 Nov 2018 07:12:44 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f7b184afe38ba624148fd86bc3ab5a4da05bd588) [//]: # (START_SECTION 3d275be3ee2013f36441293189b6af4ded7c7a15) ### Update index.rst > Commit: [3d275be3ee2013f36441293189b6af4ded7c7a15](https://github.com/dOpensource/dsiprouter/commit/3d275be3ee2013f36441293189b6af4ded7c7a15) > Date: Thu, 8 Nov 2018 07:09:13 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d275be3ee2013f36441293189b6af4ded7c7a15) [//]: # (START_SECTION 0708b729cacfcad3fd4d28b10f228bc1c542af74) ### Update index.rst > Commit: [0708b729cacfcad3fd4d28b10f228bc1c542af74](https://github.com/dOpensource/dsiprouter/commit/0708b729cacfcad3fd4d28b10f228bc1c542af74) > Date: Thu, 8 Nov 2018 07:08:41 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0708b729cacfcad3fd4d28b10f228bc1c542af74) [//]: # (START_SECTION ae02588f5a9b9cd669949895c527aee4c70b2774) ### Update index.rst > Commit: [ae02588f5a9b9cd669949895c527aee4c70b2774](https://github.com/dOpensource/dsiprouter/commit/ae02588f5a9b9cd669949895c527aee4c70b2774) > Date: Thu, 8 Nov 2018 07:08:23 -0800 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ae02588f5a9b9cd669949895c527aee4c70b2774) [//]: # (START_SECTION b6936e2311fa65faf0c3a17393d57f8227581f8d) ### Update pbxs_and_endpoints.rst > Commit: [b6936e2311fa65faf0c3a17393d57f8227581f8d](https://github.com/dOpensource/dsiprouter/commit/b6936e2311fa65faf0c3a17393d57f8227581f8d) > Date: Thu, 8 Nov 2018 07:32:40 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b6936e2311fa65faf0c3a17393d57f8227581f8d) [//]: # (START_SECTION 24306942fc9cc6c9d87fbd8d92be46558af62cb2) ### Update pbxs_and_endpoints.rst > Commit: [24306942fc9cc6c9d87fbd8d92be46558af62cb2](https://github.com/dOpensource/dsiprouter/commit/24306942fc9cc6c9d87fbd8d92be46558af62cb2) > Date: Thu, 8 Nov 2018 07:23:15 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 24306942fc9cc6c9d87fbd8d92be46558af62cb2) [//]: # (START_SECTION 3d81b30cf8af7ba01385391d40474159300a46a2) ### Update pbxs_and_endpoints.rst > Commit: [3d81b30cf8af7ba01385391d40474159300a46a2](https://github.com/dOpensource/dsiprouter/commit/3d81b30cf8af7ba01385391d40474159300a46a2) > Date: Thu, 8 Nov 2018 07:06:50 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3d81b30cf8af7ba01385391d40474159300a46a2) [//]: # (START_SECTION 645597d5629e3f45fe86c4e3d053aca3733f5d65) ### Add files via upload > Commit: [645597d5629e3f45fe86c4e3d053aca3733f5d65](https://github.com/dOpensource/dsiprouter/commit/645597d5629e3f45fe86c4e3d053aca3733f5d65) > Date: Thu, 8 Nov 2018 06:58:12 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 645597d5629e3f45fe86c4e3d053aca3733f5d65) [//]: # (START_SECTION 8fcb849ece00cfcddd2c9f605edeff9a64c21ce6) ### Add files via upload > Commit: [8fcb849ece00cfcddd2c9f605edeff9a64c21ce6](https://github.com/dOpensource/dsiprouter/commit/8fcb849ece00cfcddd2c9f605edeff9a64c21ce6) > Date: Thu, 8 Nov 2018 06:55:24 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8fcb849ece00cfcddd2c9f605edeff9a64c21ce6) [//]: # (START_SECTION 05c43abafeff549ecbd9e53cc35116bcebde1504) ### Update domains.rst > Commit: [05c43abafeff549ecbd9e53cc35116bcebde1504](https://github.com/dOpensource/dsiprouter/commit/05c43abafeff549ecbd9e53cc35116bcebde1504) > Date: Wed, 7 Nov 2018 15:21:13 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 05c43abafeff549ecbd9e53cc35116bcebde1504) [//]: # (START_SECTION 6119a97e5eda62f0c9930266736db34df40d9ab5) ### Add files via upload > Commit: [6119a97e5eda62f0c9930266736db34df40d9ab5](https://github.com/dOpensource/dsiprouter/commit/6119a97e5eda62f0c9930266736db34df40d9ab5) > Date: Wed, 7 Nov 2018 15:19:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6119a97e5eda62f0c9930266736db34df40d9ab5) [//]: # (START_SECTION e6afe014e74faf0727ae620fdff2dd9b2620558f) ### Delete list_of_domains.PNG > Commit: [e6afe014e74faf0727ae620fdff2dd9b2620558f](https://github.com/dOpensource/dsiprouter/commit/e6afe014e74faf0727ae620fdff2dd9b2620558f) > Date: Wed, 7 Nov 2018 15:19:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e6afe014e74faf0727ae620fdff2dd9b2620558f) [//]: # (START_SECTION d824515e08296521080f0715d5b1a2cecab028bf) ### Update domains.rst > Commit: [d824515e08296521080f0715d5b1a2cecab028bf](https://github.com/dOpensource/dsiprouter/commit/d824515e08296521080f0715d5b1a2cecab028bf) > Date: Wed, 7 Nov 2018 15:16:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d824515e08296521080f0715d5b1a2cecab028bf) [//]: # (START_SECTION e84029b031eaabf3e7b428cac52edb1026e05f57) ### Update domains.rst > Commit: [e84029b031eaabf3e7b428cac52edb1026e05f57](https://github.com/dOpensource/dsiprouter/commit/e84029b031eaabf3e7b428cac52edb1026e05f57) > Date: Wed, 7 Nov 2018 15:15:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e84029b031eaabf3e7b428cac52edb1026e05f57) [//]: # (START_SECTION 84c26dff4642f9f148a8a4fb3a24d24e4ba4c842) ### Update domains.rst > Commit: [84c26dff4642f9f148a8a4fb3a24d24e4ba4c842](https://github.com/dOpensource/dsiprouter/commit/84c26dff4642f9f148a8a4fb3a24d24e4ba4c842) > Date: Wed, 7 Nov 2018 15:03:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 84c26dff4642f9f148a8a4fb3a24d24e4ba4c842) [//]: # (START_SECTION 36adeabb0282fa7aa704023be624704aeaa7737e) ### Update domains.rst > Commit: [36adeabb0282fa7aa704023be624704aeaa7737e](https://github.com/dOpensource/dsiprouter/commit/36adeabb0282fa7aa704023be624704aeaa7737e) > Date: Wed, 7 Nov 2018 15:02:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 36adeabb0282fa7aa704023be624704aeaa7737e) [//]: # (START_SECTION 8c5369e76acb622ae7c9e7b0a9f74a288777ee58) ### Add files via upload > Commit: [8c5369e76acb622ae7c9e7b0a9f74a288777ee58](https://github.com/dOpensource/dsiprouter/commit/8c5369e76acb622ae7c9e7b0a9f74a288777ee58) > Date: Wed, 7 Nov 2018 14:58:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8c5369e76acb622ae7c9e7b0a9f74a288777ee58) [//]: # (START_SECTION bf65ac599de2482fd4016aba17374dd4365c6ab4) ### Add files via upload > Commit: [bf65ac599de2482fd4016aba17374dd4365c6ab4](https://github.com/dOpensource/dsiprouter/commit/bf65ac599de2482fd4016aba17374dd4365c6ab4) > Date: Wed, 7 Nov 2018 14:56:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bf65ac599de2482fd4016aba17374dd4365c6ab4) [//]: # (START_SECTION ab0422df0420288dfd62d32b0da5f33abfa95058) ### Update domains.rst > Commit: [ab0422df0420288dfd62d32b0da5f33abfa95058](https://github.com/dOpensource/dsiprouter/commit/ab0422df0420288dfd62d32b0da5f33abfa95058) > Date: Wed, 7 Nov 2018 14:56:12 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ab0422df0420288dfd62d32b0da5f33abfa95058) [//]: # (START_SECTION 1c309d0d097ce264b2c749277e22f6afb96d31fa) ### Update domains.rst > Commit: [1c309d0d097ce264b2c749277e22f6afb96d31fa](https://github.com/dOpensource/dsiprouter/commit/1c309d0d097ce264b2c749277e22f6afb96d31fa) > Date: Wed, 7 Nov 2018 14:40:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c309d0d097ce264b2c749277e22f6afb96d31fa) [//]: # (START_SECTION f3c5d0af8f533c56483cc2c32dd59e55ef979553) ### Add files via upload > Commit: [f3c5d0af8f533c56483cc2c32dd59e55ef979553](https://github.com/dOpensource/dsiprouter/commit/f3c5d0af8f533c56483cc2c32dd59e55ef979553) > Date: Wed, 7 Nov 2018 14:39:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f3c5d0af8f533c56483cc2c32dd59e55ef979553) [//]: # (START_SECTION 9fe740e810c39f990d8281bc33cf6614d0a12aba) ### Update carrier_groups.rst > Commit: [9fe740e810c39f990d8281bc33cf6614d0a12aba](https://github.com/dOpensource/dsiprouter/commit/9fe740e810c39f990d8281bc33cf6614d0a12aba) > Date: Wed, 7 Nov 2018 14:38:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9fe740e810c39f990d8281bc33cf6614d0a12aba) [//]: # (START_SECTION 44345eab11a70553e1aa7c8561fe587279df20a6) ### Update carrier_groups.rst > Commit: [44345eab11a70553e1aa7c8561fe587279df20a6](https://github.com/dOpensource/dsiprouter/commit/44345eab11a70553e1aa7c8561fe587279df20a6) > Date: Wed, 7 Nov 2018 14:37:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 44345eab11a70553e1aa7c8561fe587279df20a6) [//]: # (START_SECTION 6a67776c44afc44deeb53558b02a76fdc9c176a4) ### Update domains.rst > Commit: [6a67776c44afc44deeb53558b02a76fdc9c176a4](https://github.com/dOpensource/dsiprouter/commit/6a67776c44afc44deeb53558b02a76fdc9c176a4) > Date: Wed, 7 Nov 2018 14:35:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a67776c44afc44deeb53558b02a76fdc9c176a4) [//]: # (START_SECTION da8c57e2686d704b3a51357a70176c412816c2f3) ### Update domains.rst > Commit: [da8c57e2686d704b3a51357a70176c412816c2f3](https://github.com/dOpensource/dsiprouter/commit/da8c57e2686d704b3a51357a70176c412816c2f3) > Date: Wed, 7 Nov 2018 14:20:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION da8c57e2686d704b3a51357a70176c412816c2f3) [//]: # (START_SECTION d9e62ccce9363a4e36520ddf53c92023c87fab33) ### Update domains.rst > Commit: [d9e62ccce9363a4e36520ddf53c92023c87fab33](https://github.com/dOpensource/dsiprouter/commit/d9e62ccce9363a4e36520ddf53c92023c87fab33) > Date: Wed, 7 Nov 2018 14:20:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d9e62ccce9363a4e36520ddf53c92023c87fab33) [//]: # (START_SECTION 838d4db662059e1b0a3dcd9895b33acf606b5d35) ### Update domains.rst > Commit: [838d4db662059e1b0a3dcd9895b33acf606b5d35](https://github.com/dOpensource/dsiprouter/commit/838d4db662059e1b0a3dcd9895b33acf606b5d35) > Date: Wed, 7 Nov 2018 14:20:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 838d4db662059e1b0a3dcd9895b33acf606b5d35) [//]: # (START_SECTION 1bfdb1e48899bfe083d359d5cf3a1641bde446fa) ### Add files via upload > Commit: [1bfdb1e48899bfe083d359d5cf3a1641bde446fa](https://github.com/dOpensource/dsiprouter/commit/1bfdb1e48899bfe083d359d5cf3a1641bde446fa) > Date: Wed, 7 Nov 2018 14:19:17 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1bfdb1e48899bfe083d359d5cf3a1641bde446fa) [//]: # (START_SECTION ec2ffb962fe60b2874cd281f5519ba2226620d88) ### Update domains.rst > Commit: [ec2ffb962fe60b2874cd281f5519ba2226620d88](https://github.com/dOpensource/dsiprouter/commit/ec2ffb962fe60b2874cd281f5519ba2226620d88) > Date: Wed, 7 Nov 2018 14:18:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ec2ffb962fe60b2874cd281f5519ba2226620d88) [//]: # (START_SECTION 087b3022cdd7d6fe2fbd47326f9a9bfdde8105ef) ### Update domains.rst > Commit: [087b3022cdd7d6fe2fbd47326f9a9bfdde8105ef](https://github.com/dOpensource/dsiprouter/commit/087b3022cdd7d6fe2fbd47326f9a9bfdde8105ef) > Date: Wed, 7 Nov 2018 14:17:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 087b3022cdd7d6fe2fbd47326f9a9bfdde8105ef) [//]: # (START_SECTION 3f6dec3f751e3625de3027551713e3a04af0940f) ### Update carrier_groups.rst > Commit: [3f6dec3f751e3625de3027551713e3a04af0940f](https://github.com/dOpensource/dsiprouter/commit/3f6dec3f751e3625de3027551713e3a04af0940f) > Date: Wed, 7 Nov 2018 14:14:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f6dec3f751e3625de3027551713e3a04af0940f) [//]: # (START_SECTION 77d4d74989a6cd745cfc20b016fd0cb28515ba1a) ### Update carrier_groups.rst > Commit: [77d4d74989a6cd745cfc20b016fd0cb28515ba1a](https://github.com/dOpensource/dsiprouter/commit/77d4d74989a6cd745cfc20b016fd0cb28515ba1a) > Date: Wed, 7 Nov 2018 14:08:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 77d4d74989a6cd745cfc20b016fd0cb28515ba1a) [//]: # (START_SECTION 6b77313ce0cacfb1dfaf08bbacd804652b763235) ### Update carrier_groups.rst > Commit: [6b77313ce0cacfb1dfaf08bbacd804652b763235](https://github.com/dOpensource/dsiprouter/commit/6b77313ce0cacfb1dfaf08bbacd804652b763235) > Date: Wed, 7 Nov 2018 14:03:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6b77313ce0cacfb1dfaf08bbacd804652b763235) [//]: # (START_SECTION 515cf15dfc88da8999fa60e018bacf4040f2e64b) ### Add files via upload > Commit: [515cf15dfc88da8999fa60e018bacf4040f2e64b](https://github.com/dOpensource/dsiprouter/commit/515cf15dfc88da8999fa60e018bacf4040f2e64b) > Date: Wed, 7 Nov 2018 14:02:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 515cf15dfc88da8999fa60e018bacf4040f2e64b) [//]: # (START_SECTION a56006401802aa6637d781fcb7216ed8d70d1d00) ### Update carrier_groups.rst > Commit: [a56006401802aa6637d781fcb7216ed8d70d1d00](https://github.com/dOpensource/dsiprouter/commit/a56006401802aa6637d781fcb7216ed8d70d1d00) > Date: Wed, 7 Nov 2018 13:59:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a56006401802aa6637d781fcb7216ed8d70d1d00) [//]: # (START_SECTION 6e3d80f7d4a3c6ab51e75d07a58944ce16a196f7) ### Update carrier_groups.rst > Commit: [6e3d80f7d4a3c6ab51e75d07a58944ce16a196f7](https://github.com/dOpensource/dsiprouter/commit/6e3d80f7d4a3c6ab51e75d07a58944ce16a196f7) > Date: Wed, 7 Nov 2018 13:58:33 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6e3d80f7d4a3c6ab51e75d07a58944ce16a196f7) [//]: # (START_SECTION e325034ee2f4ed1a4b7929b7a8100cd287d703ff) ### Update carrier_groups.rst > Commit: [e325034ee2f4ed1a4b7929b7a8100cd287d703ff](https://github.com/dOpensource/dsiprouter/commit/e325034ee2f4ed1a4b7929b7a8100cd287d703ff) > Date: Wed, 7 Nov 2018 13:57:09 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e325034ee2f4ed1a4b7929b7a8100cd287d703ff) [//]: # (START_SECTION 9ae76d535a2fc2e746b914fbf83dc3b097f2855a) ### Update carrier_groups.rst > Commit: [9ae76d535a2fc2e746b914fbf83dc3b097f2855a](https://github.com/dOpensource/dsiprouter/commit/9ae76d535a2fc2e746b914fbf83dc3b097f2855a) > Date: Wed, 7 Nov 2018 13:52:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9ae76d535a2fc2e746b914fbf83dc3b097f2855a) [//]: # (START_SECTION d807063563a63b7451fdbabe27127f786e4b3ad9) ### Update carrier_groups.rst > Commit: [d807063563a63b7451fdbabe27127f786e4b3ad9](https://github.com/dOpensource/dsiprouter/commit/d807063563a63b7451fdbabe27127f786e4b3ad9) > Date: Wed, 7 Nov 2018 13:51:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d807063563a63b7451fdbabe27127f786e4b3ad9) [//]: # (START_SECTION f2d8742473a1630d56bea75ac5dff0d01d9afa99) ### Update carrier_groups.rst > Commit: [f2d8742473a1630d56bea75ac5dff0d01d9afa99](https://github.com/dOpensource/dsiprouter/commit/f2d8742473a1630d56bea75ac5dff0d01d9afa99) > Date: Wed, 7 Nov 2018 13:47:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f2d8742473a1630d56bea75ac5dff0d01d9afa99) [//]: # (START_SECTION a26aee01aa92ab38bc22e8b71a9262a4b617a821) ### Update carrier_groups.rst > Commit: [a26aee01aa92ab38bc22e8b71a9262a4b617a821](https://github.com/dOpensource/dsiprouter/commit/a26aee01aa92ab38bc22e8b71a9262a4b617a821) > Date: Wed, 7 Nov 2018 13:46:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a26aee01aa92ab38bc22e8b71a9262a4b617a821) [//]: # (START_SECTION d317f3ee531139dd11203d344e6bbbf9f4487d39) ### Add files via upload > Commit: [d317f3ee531139dd11203d344e6bbbf9f4487d39](https://github.com/dOpensource/dsiprouter/commit/d317f3ee531139dd11203d344e6bbbf9f4487d39) > Date: Wed, 7 Nov 2018 13:45:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d317f3ee531139dd11203d344e6bbbf9f4487d39) [//]: # (START_SECTION e4b77fcd3992a422072251bbeae4132848d80216) ### Delete add_carrier_details.PNG > Commit: [e4b77fcd3992a422072251bbeae4132848d80216](https://github.com/dOpensource/dsiprouter/commit/e4b77fcd3992a422072251bbeae4132848d80216) > Date: Wed, 7 Nov 2018 13:13:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e4b77fcd3992a422072251bbeae4132848d80216) [//]: # (START_SECTION 2d2d33ee45056b4e43d52d731e47bb59f93c1f3f) ### Update carrier_groups.rst > Commit: [2d2d33ee45056b4e43d52d731e47bb59f93c1f3f](https://github.com/dOpensource/dsiprouter/commit/2d2d33ee45056b4e43d52d731e47bb59f93c1f3f) > Date: Wed, 7 Nov 2018 13:09:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2d2d33ee45056b4e43d52d731e47bb59f93c1f3f) [//]: # (START_SECTION 69c3f84f4636dc56fdec90e2b2ab7dc09ad70a10) ### Update carrier_groups.rst > Commit: [69c3f84f4636dc56fdec90e2b2ab7dc09ad70a10](https://github.com/dOpensource/dsiprouter/commit/69c3f84f4636dc56fdec90e2b2ab7dc09ad70a10) > Date: Wed, 7 Nov 2018 13:08:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 69c3f84f4636dc56fdec90e2b2ab7dc09ad70a10) [//]: # (START_SECTION 871ca3f67956ee0a8911c9378643e4acafdce45f) ### Update carrier_groups.rst > Commit: [871ca3f67956ee0a8911c9378643e4acafdce45f](https://github.com/dOpensource/dsiprouter/commit/871ca3f67956ee0a8911c9378643e4acafdce45f) > Date: Wed, 7 Nov 2018 13:01:46 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 871ca3f67956ee0a8911c9378643e4acafdce45f) [//]: # (START_SECTION 47c3c0f1ede32fbe67ae4136eee3a9ba20c6adc5) ### Update carrier_groups.rst > Commit: [47c3c0f1ede32fbe67ae4136eee3a9ba20c6adc5](https://github.com/dOpensource/dsiprouter/commit/47c3c0f1ede32fbe67ae4136eee3a9ba20c6adc5) > Date: Wed, 7 Nov 2018 13:00:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 47c3c0f1ede32fbe67ae4136eee3a9ba20c6adc5) [//]: # (START_SECTION 3a0ad942a884c78fc01fa88ca4cb46a58c85064c) ### Update carrier_groups.rst > Commit: [3a0ad942a884c78fc01fa88ca4cb46a58c85064c](https://github.com/dOpensource/dsiprouter/commit/3a0ad942a884c78fc01fa88ca4cb46a58c85064c) > Date: Wed, 7 Nov 2018 13:00:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3a0ad942a884c78fc01fa88ca4cb46a58c85064c) [//]: # (START_SECTION fcdff6652036f4ffef6051f1c9454c4527f5e12a) ### Add files via upload > Commit: [fcdff6652036f4ffef6051f1c9454c4527f5e12a](https://github.com/dOpensource/dsiprouter/commit/fcdff6652036f4ffef6051f1c9454c4527f5e12a) > Date: Wed, 7 Nov 2018 12:58:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fcdff6652036f4ffef6051f1c9454c4527f5e12a) [//]: # (START_SECTION efb20947688a815fd2fe6a80f195486fb12881d2) ### Add files via upload > Commit: [efb20947688a815fd2fe6a80f195486fb12881d2](https://github.com/dOpensource/dsiprouter/commit/efb20947688a815fd2fe6a80f195486fb12881d2) > Date: Wed, 7 Nov 2018 12:57:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION efb20947688a815fd2fe6a80f195486fb12881d2) [//]: # (START_SECTION 7dfc66fe597230c70477ab11cfdde39769ee4695) ### Update carrier_groups.rst > Commit: [7dfc66fe597230c70477ab11cfdde39769ee4695](https://github.com/dOpensource/dsiprouter/commit/7dfc66fe597230c70477ab11cfdde39769ee4695) > Date: Wed, 7 Nov 2018 12:57:22 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7dfc66fe597230c70477ab11cfdde39769ee4695) [//]: # (START_SECTION cc9c143fa472cdbd7805fc7f55b30773b67c1bbf) ### Update carrier_groups.rst > Commit: [cc9c143fa472cdbd7805fc7f55b30773b67c1bbf](https://github.com/dOpensource/dsiprouter/commit/cc9c143fa472cdbd7805fc7f55b30773b67c1bbf) > Date: Wed, 7 Nov 2018 12:52:17 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cc9c143fa472cdbd7805fc7f55b30773b67c1bbf) [//]: # (START_SECTION 50987ac70b7e56827635bbb7cdf47728d93f630c) ### Update carrier_groups.rst > Commit: [50987ac70b7e56827635bbb7cdf47728d93f630c](https://github.com/dOpensource/dsiprouter/commit/50987ac70b7e56827635bbb7cdf47728d93f630c) > Date: Wed, 7 Nov 2018 12:47:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 50987ac70b7e56827635bbb7cdf47728d93f630c) [//]: # (START_SECTION 28cf51f82d6992312b31bb44b6da8ce0524526d0) ### Add files via upload > Commit: [28cf51f82d6992312b31bb44b6da8ce0524526d0](https://github.com/dOpensource/dsiprouter/commit/28cf51f82d6992312b31bb44b6da8ce0524526d0) > Date: Wed, 7 Nov 2018 12:45:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 28cf51f82d6992312b31bb44b6da8ce0524526d0) [//]: # (START_SECTION 20d4b7cf6e1e9985858c4ee2d88980ab01d66808) ### Update carrier_groups.rst > Commit: [20d4b7cf6e1e9985858c4ee2d88980ab01d66808](https://github.com/dOpensource/dsiprouter/commit/20d4b7cf6e1e9985858c4ee2d88980ab01d66808) > Date: Wed, 7 Nov 2018 12:44:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 20d4b7cf6e1e9985858c4ee2d88980ab01d66808) [//]: # (START_SECTION 808b69afc436d118108029034614c19e52b872db) ### Update carrier_groups.rst > Commit: [808b69afc436d118108029034614c19e52b872db](https://github.com/dOpensource/dsiprouter/commit/808b69afc436d118108029034614c19e52b872db) > Date: Wed, 7 Nov 2018 12:43:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 808b69afc436d118108029034614c19e52b872db) [//]: # (START_SECTION 0cf97ee957242a8c63993253b38c6907507b413b) ### Add files via upload > Commit: [0cf97ee957242a8c63993253b38c6907507b413b](https://github.com/dOpensource/dsiprouter/commit/0cf97ee957242a8c63993253b38c6907507b413b) > Date: Wed, 7 Nov 2018 12:42:15 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0cf97ee957242a8c63993253b38c6907507b413b) [//]: # (START_SECTION d958c93d8bb186b6bf27b71b215bf5a43162efef) ### Update carrier_groups.rst > Commit: [d958c93d8bb186b6bf27b71b215bf5a43162efef](https://github.com/dOpensource/dsiprouter/commit/d958c93d8bb186b6bf27b71b215bf5a43162efef) > Date: Wed, 7 Nov 2018 12:38:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d958c93d8bb186b6bf27b71b215bf5a43162efef) [//]: # (START_SECTION c51b8d173491c8f031d228178411bc7f3c570be9) ### Update carrier_groups.rst > Commit: [c51b8d173491c8f031d228178411bc7f3c570be9](https://github.com/dOpensource/dsiprouter/commit/c51b8d173491c8f031d228178411bc7f3c570be9) > Date: Wed, 7 Nov 2018 12:35:37 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c51b8d173491c8f031d228178411bc7f3c570be9) [//]: # (START_SECTION fbe0b0368dbac3168e3b1344149a36f82bce698d) ### Update carrier_groups.rst > Commit: [fbe0b0368dbac3168e3b1344149a36f82bce698d](https://github.com/dOpensource/dsiprouter/commit/fbe0b0368dbac3168e3b1344149a36f82bce698d) > Date: Wed, 7 Nov 2018 12:31:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fbe0b0368dbac3168e3b1344149a36f82bce698d) [//]: # (START_SECTION 52e8475c0a63aaf90888339b8fc7c09d33b7d62e) ### Create domains.rst > Commit: [52e8475c0a63aaf90888339b8fc7c09d33b7d62e](https://github.com/dOpensource/dsiprouter/commit/52e8475c0a63aaf90888339b8fc7c09d33b7d62e) > Date: Wed, 7 Nov 2018 10:25:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 52e8475c0a63aaf90888339b8fc7c09d33b7d62e) [//]: # (START_SECTION 74d5d64ea961bd4e5c7c0fd3cc08af9702960a12) ### Update configuring.rst > Commit: [74d5d64ea961bd4e5c7c0fd3cc08af9702960a12](https://github.com/dOpensource/dsiprouter/commit/74d5d64ea961bd4e5c7c0fd3cc08af9702960a12) > Date: Wed, 7 Nov 2018 10:03:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 74d5d64ea961bd4e5c7c0fd3cc08af9702960a12) [//]: # (START_SECTION 1011dee05818fc5fcde8cbd554c495378333df72) ### Add files via upload > Commit: [1011dee05818fc5fcde8cbd554c495378333df72](https://github.com/dOpensource/dsiprouter/commit/1011dee05818fc5fcde8cbd554c495378333df72) > Date: Tue, 6 Nov 2018 21:04:54 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1011dee05818fc5fcde8cbd554c495378333df72) [//]: # (START_SECTION f5c077f3fb4013d56891810f4c6c3a44813d067a) ### Delete dSIP_PBX_ADD_New_PBX.png > Commit: [f5c077f3fb4013d56891810f4c6c3a44813d067a](https://github.com/dOpensource/dsiprouter/commit/f5c077f3fb4013d56891810f4c6c3a44813d067a) > Date: Tue, 6 Nov 2018 21:04:32 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f5c077f3fb4013d56891810f4c6c3a44813d067a) [//]: # (START_SECTION 63d466d3429e1c5e022da92b027ac6f972db6c66) ### Add files via upload > Commit: [63d466d3429e1c5e022da92b027ac6f972db6c66](https://github.com/dOpensource/dsiprouter/commit/63d466d3429e1c5e022da92b027ac6f972db6c66) > Date: Tue, 6 Nov 2018 21:02:41 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 63d466d3429e1c5e022da92b027ac6f972db6c66) [//]: # (START_SECTION 97e50e2c965a9f51aad3a29ff0a8d3da9098afb6) ### Delete dSIP_PBX_ADD_New_PBX.png > Commit: [97e50e2c965a9f51aad3a29ff0a8d3da9098afb6](https://github.com/dOpensource/dsiprouter/commit/97e50e2c965a9f51aad3a29ff0a8d3da9098afb6) > Date: Tue, 6 Nov 2018 21:02:17 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 97e50e2c965a9f51aad3a29ff0a8d3da9098afb6) [//]: # (START_SECTION cc0e3c88bd89802b937d7e6086a6efe1890fca4d) ### Add files via upload > Commit: [cc0e3c88bd89802b937d7e6086a6efe1890fca4d](https://github.com/dOpensource/dsiprouter/commit/cc0e3c88bd89802b937d7e6086a6efe1890fca4d) > Date: Tue, 6 Nov 2018 20:58:16 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cc0e3c88bd89802b937d7e6086a6efe1890fca4d) [//]: # (START_SECTION ed4e3511ad2d7d68dd8dc625c0cf369ee3b860aa) ### Delete dSIP_dashboard.png > Commit: [ed4e3511ad2d7d68dd8dc625c0cf369ee3b860aa](https://github.com/dOpensource/dsiprouter/commit/ed4e3511ad2d7d68dd8dc625c0cf369ee3b860aa) > Date: Tue, 6 Nov 2018 20:57:58 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ed4e3511ad2d7d68dd8dc625c0cf369ee3b860aa) [//]: # (START_SECTION 019ed3d873e8befadbddb1f56c43314e9d2cb4f5) ### Add files via upload > Commit: [019ed3d873e8befadbddb1f56c43314e9d2cb4f5](https://github.com/dOpensource/dsiprouter/commit/019ed3d873e8befadbddb1f56c43314e9d2cb4f5) > Date: Tue, 6 Nov 2018 20:57:31 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 019ed3d873e8befadbddb1f56c43314e9d2cb4f5) [//]: # (START_SECTION c99ae5928fcfa2ea35b455e3f9b3ff95aa80ebe7) ### Delete dSIP_PBX_Add.png > Commit: [c99ae5928fcfa2ea35b455e3f9b3ff95aa80ebe7](https://github.com/dOpensource/dsiprouter/commit/c99ae5928fcfa2ea35b455e3f9b3ff95aa80ebe7) > Date: Tue, 6 Nov 2018 20:57:13 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c99ae5928fcfa2ea35b455e3f9b3ff95aa80ebe7) [//]: # (START_SECTION 4c5061669b68ab639c5c1d541684722e6731e73f) ### Add files via upload > Commit: [4c5061669b68ab639c5c1d541684722e6731e73f](https://github.com/dOpensource/dsiprouter/commit/4c5061669b68ab639c5c1d541684722e6731e73f) > Date: Tue, 6 Nov 2018 20:56:42 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4c5061669b68ab639c5c1d541684722e6731e73f) [//]: # (START_SECTION e1b894edb52211d0f28c06e6f1144cac6b55440a) ### Delete dSIP_PBX_ADD_New_PBX.png > Commit: [e1b894edb52211d0f28c06e6f1144cac6b55440a](https://github.com/dOpensource/dsiprouter/commit/e1b894edb52211d0f28c06e6f1144cac6b55440a) > Date: Tue, 6 Nov 2018 20:56:21 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e1b894edb52211d0f28c06e6f1144cac6b55440a) [//]: # (START_SECTION ea91bf399e03c75ad6c87138eea3974bf7eacb1e) ### Add files via upload > Commit: [ea91bf399e03c75ad6c87138eea3974bf7eacb1e](https://github.com/dOpensource/dsiprouter/commit/ea91bf399e03c75ad6c87138eea3974bf7eacb1e) > Date: Tue, 6 Nov 2018 20:55:37 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ea91bf399e03c75ad6c87138eea3974bf7eacb1e) [//]: # (START_SECTION 4cfca2ca106590bfc26cab6d867d9330393106d1) ### Delete dSIP_IN_Manual_Add.png > Commit: [4cfca2ca106590bfc26cab6d867d9330393106d1](https://github.com/dOpensource/dsiprouter/commit/4cfca2ca106590bfc26cab6d867d9330393106d1) > Date: Tue, 6 Nov 2018 20:55:19 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4cfca2ca106590bfc26cab6d867d9330393106d1) [//]: # (START_SECTION 79fbf8685209d7cd2f796a132a6aa7296106ea65) ### Add files via upload > Commit: [79fbf8685209d7cd2f796a132a6aa7296106ea65](https://github.com/dOpensource/dsiprouter/commit/79fbf8685209d7cd2f796a132a6aa7296106ea65) > Date: Tue, 6 Nov 2018 20:54:42 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79fbf8685209d7cd2f796a132a6aa7296106ea65) [//]: # (START_SECTION 62e57d310d794ccae30840af16211fefdc7b92cc) ### Delete dSIP_IN_Import_DID.png > Commit: [62e57d310d794ccae30840af16211fefdc7b92cc](https://github.com/dOpensource/dsiprouter/commit/62e57d310d794ccae30840af16211fefdc7b92cc) > Date: Tue, 6 Nov 2018 20:54:15 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62e57d310d794ccae30840af16211fefdc7b92cc) [//]: # (START_SECTION a3bcc40ab1ea1243ffda1e621c15fd8d645998f1) ### Add files via upload > Commit: [a3bcc40ab1ea1243ffda1e621c15fd8d645998f1](https://github.com/dOpensource/dsiprouter/commit/a3bcc40ab1ea1243ffda1e621c15fd8d645998f1) > Date: Tue, 6 Nov 2018 20:53:28 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a3bcc40ab1ea1243ffda1e621c15fd8d645998f1) [//]: # (START_SECTION 97d82365edb7cb1c4169223f6046b9e7a9a7dbc4) ### Delete dSIP_IN_DID_Map.png > Commit: [97d82365edb7cb1c4169223f6046b9e7a9a7dbc4](https://github.com/dOpensource/dsiprouter/commit/97d82365edb7cb1c4169223f6046b9e7a9a7dbc4) > Date: Tue, 6 Nov 2018 20:53:03 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 97d82365edb7cb1c4169223f6046b9e7a9a7dbc4) [//]: # (START_SECTION e7e33fed434bd06a1c57287865e4187ce9237818) ### Update pbxs_and_endpoints.rst > Commit: [e7e33fed434bd06a1c57287865e4187ce9237818](https://github.com/dOpensource/dsiprouter/commit/e7e33fed434bd06a1c57287865e4187ce9237818) > Date: Tue, 6 Nov 2018 20:40:18 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e7e33fed434bd06a1c57287865e4187ce9237818) [//]: # (START_SECTION 689259043d83b6e3eb598d138adf6bfbcd18f38a) ### Update pbxs_and_endpoints.rst > Commit: [689259043d83b6e3eb598d138adf6bfbcd18f38a](https://github.com/dOpensource/dsiprouter/commit/689259043d83b6e3eb598d138adf6bfbcd18f38a) > Date: Tue, 6 Nov 2018 20:31:15 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 689259043d83b6e3eb598d138adf6bfbcd18f38a) [//]: # (START_SECTION 998b0be23813de5e4348296f0728e3eed9e51967) ### Add files via upload > Commit: [998b0be23813de5e4348296f0728e3eed9e51967](https://github.com/dOpensource/dsiprouter/commit/998b0be23813de5e4348296f0728e3eed9e51967) > Date: Tue, 6 Nov 2018 20:29:05 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 998b0be23813de5e4348296f0728e3eed9e51967) [//]: # (START_SECTION 2662c086013f5415753cce2c68e1b1b9af86154c) ### Add files via upload > Commit: [2662c086013f5415753cce2c68e1b1b9af86154c](https://github.com/dOpensource/dsiprouter/commit/2662c086013f5415753cce2c68e1b1b9af86154c) > Date: Tue, 6 Nov 2018 20:23:05 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2662c086013f5415753cce2c68e1b1b9af86154c) [//]: # (START_SECTION 50434336d76dd930cda3d78b5c783c5fe1fe9247) ### Update pbxs_and_endpoints.rst > Commit: [50434336d76dd930cda3d78b5c783c5fe1fe9247](https://github.com/dOpensource/dsiprouter/commit/50434336d76dd930cda3d78b5c783c5fe1fe9247) > Date: Tue, 6 Nov 2018 20:14:20 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 50434336d76dd930cda3d78b5c783c5fe1fe9247) [//]: # (START_SECTION 64e257b489b20a21a51825dc26270887ce65d7dc) ### Update pbxs_and_endpoints.rst > Commit: [64e257b489b20a21a51825dc26270887ce65d7dc](https://github.com/dOpensource/dsiprouter/commit/64e257b489b20a21a51825dc26270887ce65d7dc) > Date: Tue, 6 Nov 2018 20:07:53 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 64e257b489b20a21a51825dc26270887ce65d7dc) [//]: # (START_SECTION 8f403c25f510d4214b2b443ab70fda5dcb6e5112) ### Update pbxs_and_endpoints.rst > Commit: [8f403c25f510d4214b2b443ab70fda5dcb6e5112](https://github.com/dOpensource/dsiprouter/commit/8f403c25f510d4214b2b443ab70fda5dcb6e5112) > Date: Tue, 6 Nov 2018 19:56:22 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8f403c25f510d4214b2b443ab70fda5dcb6e5112) [//]: # (START_SECTION d1079ea98ed68b616e38d0399df138f32c4603e3) ### Add files via upload > Commit: [d1079ea98ed68b616e38d0399df138f32c4603e3](https://github.com/dOpensource/dsiprouter/commit/d1079ea98ed68b616e38d0399df138f32c4603e3) > Date: Tue, 6 Nov 2018 19:56:12 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d1079ea98ed68b616e38d0399df138f32c4603e3) [//]: # (START_SECTION 6a40809978d10ffa91f7be5d4dba309b99843b5e) ### Delete dSIP_IN_Manual_Add.png > Commit: [6a40809978d10ffa91f7be5d4dba309b99843b5e](https://github.com/dOpensource/dsiprouter/commit/6a40809978d10ffa91f7be5d4dba309b99843b5e) > Date: Tue, 6 Nov 2018 19:55:49 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a40809978d10ffa91f7be5d4dba309b99843b5e) [//]: # (START_SECTION 083930d4bdcfab64fa0dee2e5dff0f89a0f0cc00) ### Add files via upload > Commit: [083930d4bdcfab64fa0dee2e5dff0f89a0f0cc00](https://github.com/dOpensource/dsiprouter/commit/083930d4bdcfab64fa0dee2e5dff0f89a0f0cc00) > Date: Tue, 6 Nov 2018 19:46:07 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 083930d4bdcfab64fa0dee2e5dff0f89a0f0cc00) [//]: # (START_SECTION 56ebe63e308dad18280e711792b7b3639a445bdc) ### Update carrier_groups.rst > Commit: [56ebe63e308dad18280e711792b7b3639a445bdc](https://github.com/dOpensource/dsiprouter/commit/56ebe63e308dad18280e711792b7b3639a445bdc) > Date: Tue, 6 Nov 2018 16:32:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 56ebe63e308dad18280e711792b7b3639a445bdc) [//]: # (START_SECTION c6ebcbbe2152f441a7d2144378d44b8c241001a7) ### Update carrier_groups.rst > Commit: [c6ebcbbe2152f441a7d2144378d44b8c241001a7](https://github.com/dOpensource/dsiprouter/commit/c6ebcbbe2152f441a7d2144378d44b8c241001a7) > Date: Tue, 6 Nov 2018 16:23:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c6ebcbbe2152f441a7d2144378d44b8c241001a7) [//]: # (START_SECTION 55c6d3a8a0bbacbda24099fb569320f0c4e39acd) ### Update carrier_groups.rst > Commit: [55c6d3a8a0bbacbda24099fb569320f0c4e39acd](https://github.com/dOpensource/dsiprouter/commit/55c6d3a8a0bbacbda24099fb569320f0c4e39acd) > Date: Tue, 6 Nov 2018 16:22:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 55c6d3a8a0bbacbda24099fb569320f0c4e39acd) [//]: # (START_SECTION 505160dc6af62806f7b7c413686ed997e77ff93b) ### Update carrier_groups.rst > Commit: [505160dc6af62806f7b7c413686ed997e77ff93b](https://github.com/dOpensource/dsiprouter/commit/505160dc6af62806f7b7c413686ed997e77ff93b) > Date: Tue, 6 Nov 2018 16:21:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 505160dc6af62806f7b7c413686ed997e77ff93b) [//]: # (START_SECTION 610834d41f8405322ab34d03cfe980bdc2a56d84) ### Update carrier_groups.rst > Commit: [610834d41f8405322ab34d03cfe980bdc2a56d84](https://github.com/dOpensource/dsiprouter/commit/610834d41f8405322ab34d03cfe980bdc2a56d84) > Date: Tue, 6 Nov 2018 16:21:11 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 610834d41f8405322ab34d03cfe980bdc2a56d84) [//]: # (START_SECTION bdb7edec43840d9dc08ee617189f028a270bae93) ### Update carrier_groups.rst > Commit: [bdb7edec43840d9dc08ee617189f028a270bae93](https://github.com/dOpensource/dsiprouter/commit/bdb7edec43840d9dc08ee617189f028a270bae93) > Date: Tue, 6 Nov 2018 16:20:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bdb7edec43840d9dc08ee617189f028a270bae93) [//]: # (START_SECTION e68e584ba418c5666457e1bc32ac203f79f2cc72) ### Update carrier_groups.rst > Commit: [e68e584ba418c5666457e1bc32ac203f79f2cc72](https://github.com/dOpensource/dsiprouter/commit/e68e584ba418c5666457e1bc32ac203f79f2cc72) > Date: Tue, 6 Nov 2018 16:19:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e68e584ba418c5666457e1bc32ac203f79f2cc72) [//]: # (START_SECTION cde2c99ed797eca9cc70508049de0de621990375) ### Update carrier_groups.rst > Commit: [cde2c99ed797eca9cc70508049de0de621990375](https://github.com/dOpensource/dsiprouter/commit/cde2c99ed797eca9cc70508049de0de621990375) > Date: Tue, 6 Nov 2018 16:18:53 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cde2c99ed797eca9cc70508049de0de621990375) [//]: # (START_SECTION f4eba5be0915806967fd6e14ed3f2b7e3e43374a) ### Update carrier_groups.rst > Commit: [f4eba5be0915806967fd6e14ed3f2b7e3e43374a](https://github.com/dOpensource/dsiprouter/commit/f4eba5be0915806967fd6e14ed3f2b7e3e43374a) > Date: Tue, 6 Nov 2018 16:18:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f4eba5be0915806967fd6e14ed3f2b7e3e43374a) [//]: # (START_SECTION c5fe042405e48e100513c545191cf4a575dd1164) ### Update carrier_groups.rst > Commit: [c5fe042405e48e100513c545191cf4a575dd1164](https://github.com/dOpensource/dsiprouter/commit/c5fe042405e48e100513c545191cf4a575dd1164) > Date: Tue, 6 Nov 2018 16:16:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c5fe042405e48e100513c545191cf4a575dd1164) [//]: # (START_SECTION 8d4c4f9393dc9f301f81265d86383dd56af1ce7a) ### Update carrier_groups.rst > Commit: [8d4c4f9393dc9f301f81265d86383dd56af1ce7a](https://github.com/dOpensource/dsiprouter/commit/8d4c4f9393dc9f301f81265d86383dd56af1ce7a) > Date: Tue, 6 Nov 2018 16:14:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8d4c4f9393dc9f301f81265d86383dd56af1ce7a) [//]: # (START_SECTION 84fb9ac5f657b63463d0bafb64cbceefc8f801db) ### Update carrier_groups.rst > Commit: [84fb9ac5f657b63463d0bafb64cbceefc8f801db](https://github.com/dOpensource/dsiprouter/commit/84fb9ac5f657b63463d0bafb64cbceefc8f801db) > Date: Tue, 6 Nov 2018 16:11:44 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 84fb9ac5f657b63463d0bafb64cbceefc8f801db) [//]: # (START_SECTION ec2ebaa5bbcba32a227d91376fc372e0dcd51a72) ### Update carrier_groups.rst > Commit: [ec2ebaa5bbcba32a227d91376fc372e0dcd51a72](https://github.com/dOpensource/dsiprouter/commit/ec2ebaa5bbcba32a227d91376fc372e0dcd51a72) > Date: Tue, 6 Nov 2018 16:11:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ec2ebaa5bbcba32a227d91376fc372e0dcd51a72) [//]: # (START_SECTION c4f07ecf7334ebc66c2b74942cd8ad19c9b80bc1) ### Update carrier_groups.rst > Commit: [c4f07ecf7334ebc66c2b74942cd8ad19c9b80bc1](https://github.com/dOpensource/dsiprouter/commit/c4f07ecf7334ebc66c2b74942cd8ad19c9b80bc1) > Date: Tue, 6 Nov 2018 16:10:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c4f07ecf7334ebc66c2b74942cd8ad19c9b80bc1) [//]: # (START_SECTION 6a6244ae5ae66857116546a84c64aa70da9fef2c) ### Update carrier_groups.rst > Commit: [6a6244ae5ae66857116546a84c64aa70da9fef2c](https://github.com/dOpensource/dsiprouter/commit/6a6244ae5ae66857116546a84c64aa70da9fef2c) > Date: Tue, 6 Nov 2018 16:06:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a6244ae5ae66857116546a84c64aa70da9fef2c) [//]: # (START_SECTION e8c3513f99d1f734281c893f2357374893a689d9) ### Update pbxs_and_endpoints.rst > Commit: [e8c3513f99d1f734281c893f2357374893a689d9](https://github.com/dOpensource/dsiprouter/commit/e8c3513f99d1f734281c893f2357374893a689d9) > Date: Tue, 6 Nov 2018 15:55:10 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e8c3513f99d1f734281c893f2357374893a689d9) [//]: # (START_SECTION b389459485722e58c508fff3829687f038a14d87) ### Add files via upload > Commit: [b389459485722e58c508fff3829687f038a14d87](https://github.com/dOpensource/dsiprouter/commit/b389459485722e58c508fff3829687f038a14d87) > Date: Tue, 6 Nov 2018 15:46:15 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b389459485722e58c508fff3829687f038a14d87) [//]: # (START_SECTION eb75eaf1e8f52081d3ffa19737cf1e386a8e9a69) ### Add files via upload > Commit: [eb75eaf1e8f52081d3ffa19737cf1e386a8e9a69](https://github.com/dOpensource/dsiprouter/commit/eb75eaf1e8f52081d3ffa19737cf1e386a8e9a69) > Date: Tue, 6 Nov 2018 15:45:16 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb75eaf1e8f52081d3ffa19737cf1e386a8e9a69) [//]: # (START_SECTION 5de2b94560963733af93ea01cfb48fb3587c3817) ### Add files via upload > Commit: [5de2b94560963733af93ea01cfb48fb3587c3817](https://github.com/dOpensource/dsiprouter/commit/5de2b94560963733af93ea01cfb48fb3587c3817) > Date: Tue, 6 Nov 2018 15:44:02 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5de2b94560963733af93ea01cfb48fb3587c3817) [//]: # (START_SECTION 82d3f51b5a3f6517489599c678f815f19dbee1eb) ### Delete dsiprouter-carriers.jpg > Commit: [82d3f51b5a3f6517489599c678f815f19dbee1eb](https://github.com/dOpensource/dsiprouter/commit/82d3f51b5a3f6517489599c678f815f19dbee1eb) > Date: Tue, 6 Nov 2018 15:42:34 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 82d3f51b5a3f6517489599c678f815f19dbee1eb) [//]: # (START_SECTION 425983a27b432824a84b090c08b16ee3f2030659) ### Update pbxs_and_endpoints.rst > Commit: [425983a27b432824a84b090c08b16ee3f2030659](https://github.com/dOpensource/dsiprouter/commit/425983a27b432824a84b090c08b16ee3f2030659) > Date: Tue, 6 Nov 2018 15:40:41 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 425983a27b432824a84b090c08b16ee3f2030659) [//]: # (START_SECTION 22f15c2097c4b895ebc9ee9edcd69dcf4ed055b1) ### Update pbxs_and_endpoints.rst > Commit: [22f15c2097c4b895ebc9ee9edcd69dcf4ed055b1](https://github.com/dOpensource/dsiprouter/commit/22f15c2097c4b895ebc9ee9edcd69dcf4ed055b1) > Date: Tue, 6 Nov 2018 15:39:43 -0500 > Author: jornsby (44816622+jornsby@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 22f15c2097c4b895ebc9ee9edcd69dcf4ed055b1) [//]: # (START_SECTION 456b139ff2fdd8227774fd6e49bbf2249873f9ab) ### Update carrier_groups.rst > Commit: [456b139ff2fdd8227774fd6e49bbf2249873f9ab](https://github.com/dOpensource/dsiprouter/commit/456b139ff2fdd8227774fd6e49bbf2249873f9ab) > Date: Tue, 6 Nov 2018 14:36:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 456b139ff2fdd8227774fd6e49bbf2249873f9ab) [//]: # (START_SECTION cd6fb1e206dc29f01955896415b447752adc9eed) ### Update carrier_groups.rst > Commit: [cd6fb1e206dc29f01955896415b447752adc9eed](https://github.com/dOpensource/dsiprouter/commit/cd6fb1e206dc29f01955896415b447752adc9eed) > Date: Tue, 6 Nov 2018 14:35:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cd6fb1e206dc29f01955896415b447752adc9eed) [//]: # (START_SECTION 34c97dae0dd8e3c52a4736fef455eb5c221f6bcc) ### Update carrier_groups.rst > Commit: [34c97dae0dd8e3c52a4736fef455eb5c221f6bcc](https://github.com/dOpensource/dsiprouter/commit/34c97dae0dd8e3c52a4736fef455eb5c221f6bcc) > Date: Tue, 6 Nov 2018 14:29:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 34c97dae0dd8e3c52a4736fef455eb5c221f6bcc) [//]: # (START_SECTION e4176de1db50571fe0704de7ac66ae7b33683391) ### Delete config pic.PNG > Commit: [e4176de1db50571fe0704de7ac66ae7b33683391](https://github.com/dOpensource/dsiprouter/commit/e4176de1db50571fe0704de7ac66ae7b33683391) > Date: Tue, 6 Nov 2018 14:28:38 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e4176de1db50571fe0704de7ac66ae7b33683391) [//]: # (START_SECTION b63f23f6797f6b07bef9c3080bcc6049785af918) ### Update carrier_groups.rst > Commit: [b63f23f6797f6b07bef9c3080bcc6049785af918](https://github.com/dOpensource/dsiprouter/commit/b63f23f6797f6b07bef9c3080bcc6049785af918) > Date: Tue, 6 Nov 2018 14:26:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b63f23f6797f6b07bef9c3080bcc6049785af918) [//]: # (START_SECTION 6db1e3b0addf8721c2afa088efe6e07550413f6b) ### Update carrier_groups.rst > Commit: [6db1e3b0addf8721c2afa088efe6e07550413f6b](https://github.com/dOpensource/dsiprouter/commit/6db1e3b0addf8721c2afa088efe6e07550413f6b) > Date: Tue, 6 Nov 2018 14:25:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6db1e3b0addf8721c2afa088efe6e07550413f6b) [//]: # (START_SECTION b578812373655cd5ad6afcae139cea32dcb3350e) ### Add files via upload > Commit: [b578812373655cd5ad6afcae139cea32dcb3350e](https://github.com/dOpensource/dsiprouter/commit/b578812373655cd5ad6afcae139cea32dcb3350e) > Date: Tue, 6 Nov 2018 14:20:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b578812373655cd5ad6afcae139cea32dcb3350e) [//]: # (START_SECTION 15452463de0bc35b8cd8ba9602221cebbaf452d2) ### Update carrier_groups.rst > Commit: [15452463de0bc35b8cd8ba9602221cebbaf452d2](https://github.com/dOpensource/dsiprouter/commit/15452463de0bc35b8cd8ba9602221cebbaf452d2) > Date: Tue, 6 Nov 2018 14:13:24 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 15452463de0bc35b8cd8ba9602221cebbaf452d2) [//]: # (START_SECTION 46854615c9d3487716ee21defc65158fe9139998) ### Add files via upload > Commit: [46854615c9d3487716ee21defc65158fe9139998](https://github.com/dOpensource/dsiprouter/commit/46854615c9d3487716ee21defc65158fe9139998) > Date: Tue, 6 Nov 2018 14:12:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 46854615c9d3487716ee21defc65158fe9139998) [//]: # (START_SECTION 6098310670e6198e1dc71da0c19b47402b388f4d) ### Update carrier_groups.rst > Commit: [6098310670e6198e1dc71da0c19b47402b388f4d](https://github.com/dOpensource/dsiprouter/commit/6098310670e6198e1dc71da0c19b47402b388f4d) > Date: Tue, 6 Nov 2018 14:12:18 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6098310670e6198e1dc71da0c19b47402b388f4d) [//]: # (START_SECTION b7026ba379540cd5dbb1cab29b626e7878d69bd0) ### Update carrier_groups.rst > Commit: [b7026ba379540cd5dbb1cab29b626e7878d69bd0](https://github.com/dOpensource/dsiprouter/commit/b7026ba379540cd5dbb1cab29b626e7878d69bd0) > Date: Tue, 6 Nov 2018 14:11:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b7026ba379540cd5dbb1cab29b626e7878d69bd0) [//]: # (START_SECTION f95551465323eb8990cddce09f0928a3f20dc9fe) ### Update carrier_groups.rst > Commit: [f95551465323eb8990cddce09f0928a3f20dc9fe](https://github.com/dOpensource/dsiprouter/commit/f95551465323eb8990cddce09f0928a3f20dc9fe) > Date: Tue, 6 Nov 2018 14:10:26 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f95551465323eb8990cddce09f0928a3f20dc9fe) [//]: # (START_SECTION eaedd02a8c38094878bec06cccd5820ced6d729f) ### Update carrier_groups.rst > Commit: [eaedd02a8c38094878bec06cccd5820ced6d729f](https://github.com/dOpensource/dsiprouter/commit/eaedd02a8c38094878bec06cccd5820ced6d729f) > Date: Tue, 6 Nov 2018 14:05:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eaedd02a8c38094878bec06cccd5820ced6d729f) [//]: # (START_SECTION 20cb405ed58d8ea5797ab611a06589a02ab42472) ### Update carrier_groups.rst > Commit: [20cb405ed58d8ea5797ab611a06589a02ab42472](https://github.com/dOpensource/dsiprouter/commit/20cb405ed58d8ea5797ab611a06589a02ab42472) > Date: Tue, 6 Nov 2018 13:57:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 20cb405ed58d8ea5797ab611a06589a02ab42472) [//]: # (START_SECTION 341f1bb1b6ebd2f9e2a9e5bc80ecb8e924c9a16e) ### Update carrier_groups.rst > Commit: [341f1bb1b6ebd2f9e2a9e5bc80ecb8e924c9a16e](https://github.com/dOpensource/dsiprouter/commit/341f1bb1b6ebd2f9e2a9e5bc80ecb8e924c9a16e) > Date: Tue, 6 Nov 2018 13:15:55 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 341f1bb1b6ebd2f9e2a9e5bc80ecb8e924c9a16e) [//]: # (START_SECTION 74b14d739ff92554a2106839daddf0acd7aa4e37) ### Update carrier_groups.rst > Commit: [74b14d739ff92554a2106839daddf0acd7aa4e37](https://github.com/dOpensource/dsiprouter/commit/74b14d739ff92554a2106839daddf0acd7aa4e37) > Date: Tue, 6 Nov 2018 13:14:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 74b14d739ff92554a2106839daddf0acd7aa4e37) [//]: # (START_SECTION 7df26ba4d81a09c01aa26660744ff0a011d38086) ### Update carrier_groups.rst > Commit: [7df26ba4d81a09c01aa26660744ff0a011d38086](https://github.com/dOpensource/dsiprouter/commit/7df26ba4d81a09c01aa26660744ff0a011d38086) > Date: Tue, 6 Nov 2018 13:13:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7df26ba4d81a09c01aa26660744ff0a011d38086) [//]: # (START_SECTION 9fdf5e42cc004a5aa42d57872456c04b94dba276) ### Add files via upload > Commit: [9fdf5e42cc004a5aa42d57872456c04b94dba276](https://github.com/dOpensource/dsiprouter/commit/9fdf5e42cc004a5aa42d57872456c04b94dba276) > Date: Tue, 6 Nov 2018 13:11:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9fdf5e42cc004a5aa42d57872456c04b94dba276) [//]: # (START_SECTION f9713fb262681100a5b9e99222af4ce02ad87295) ### Update carrier_groups.rst > Commit: [f9713fb262681100a5b9e99222af4ce02ad87295](https://github.com/dOpensource/dsiprouter/commit/f9713fb262681100a5b9e99222af4ce02ad87295) > Date: Tue, 6 Nov 2018 13:09:14 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f9713fb262681100a5b9e99222af4ce02ad87295) [//]: # (START_SECTION 658695c588c634cab89ec3a65054f4d89f9af18e) ### Update carrier_groups.rst > Commit: [658695c588c634cab89ec3a65054f4d89f9af18e](https://github.com/dOpensource/dsiprouter/commit/658695c588c634cab89ec3a65054f4d89f9af18e) > Date: Tue, 6 Nov 2018 13:08:39 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 658695c588c634cab89ec3a65054f4d89f9af18e) [//]: # (START_SECTION 9e637b7c2da277d8629ab68dab753386d116a233) ### Update carrier_groups.rst > Commit: [9e637b7c2da277d8629ab68dab753386d116a233](https://github.com/dOpensource/dsiprouter/commit/9e637b7c2da277d8629ab68dab753386d116a233) > Date: Tue, 6 Nov 2018 13:07:59 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9e637b7c2da277d8629ab68dab753386d116a233) [//]: # (START_SECTION 60841ddeb6f74b3691a9969c5f20d28058675287) ### Update carrier_groups.rst > Commit: [60841ddeb6f74b3691a9969c5f20d28058675287](https://github.com/dOpensource/dsiprouter/commit/60841ddeb6f74b3691a9969c5f20d28058675287) > Date: Tue, 6 Nov 2018 12:51:20 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 60841ddeb6f74b3691a9969c5f20d28058675287) [//]: # (START_SECTION 0c9fb3ef6d12bf27f6c034c929a30e8213caf829) ### Update carrier_groups.rst > Commit: [0c9fb3ef6d12bf27f6c034c929a30e8213caf829](https://github.com/dOpensource/dsiprouter/commit/0c9fb3ef6d12bf27f6c034c929a30e8213caf829) > Date: Tue, 6 Nov 2018 12:50:43 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0c9fb3ef6d12bf27f6c034c929a30e8213caf829) [//]: # (START_SECTION 1c0045b2078d2dba2a87185d3665e8666c5b12b5) ### Update carrier_groups.rst > Commit: [1c0045b2078d2dba2a87185d3665e8666c5b12b5](https://github.com/dOpensource/dsiprouter/commit/1c0045b2078d2dba2a87185d3665e8666c5b12b5) > Date: Tue, 6 Nov 2018 12:49:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1c0045b2078d2dba2a87185d3665e8666c5b12b5) [//]: # (START_SECTION 62a95efd8e94072d5fd7a18448e1973407d144a4) ### Update carrier_groups.rst > Commit: [62a95efd8e94072d5fd7a18448e1973407d144a4](https://github.com/dOpensource/dsiprouter/commit/62a95efd8e94072d5fd7a18448e1973407d144a4) > Date: Tue, 6 Nov 2018 12:42:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62a95efd8e94072d5fd7a18448e1973407d144a4) [//]: # (START_SECTION 95a067a6f1cbf22c965fe6f015f1946d8247de53) ### Update carrier_groups.rst > Commit: [95a067a6f1cbf22c965fe6f015f1946d8247de53](https://github.com/dOpensource/dsiprouter/commit/95a067a6f1cbf22c965fe6f015f1946d8247de53) > Date: Tue, 6 Nov 2018 12:38:07 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 95a067a6f1cbf22c965fe6f015f1946d8247de53) [//]: # (START_SECTION 7c533fa92e67bf759c63705f3a2ba38bf17cade2) ### Update carrier_groups.rst > Commit: [7c533fa92e67bf759c63705f3a2ba38bf17cade2](https://github.com/dOpensource/dsiprouter/commit/7c533fa92e67bf759c63705f3a2ba38bf17cade2) > Date: Tue, 6 Nov 2018 12:35:21 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7c533fa92e67bf759c63705f3a2ba38bf17cade2) [//]: # (START_SECTION f60054085f8d8395cbf020a86f5aef13d172b696) ### Update carrier_groups.rst > Commit: [f60054085f8d8395cbf020a86f5aef13d172b696](https://github.com/dOpensource/dsiprouter/commit/f60054085f8d8395cbf020a86f5aef13d172b696) > Date: Tue, 6 Nov 2018 12:19:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f60054085f8d8395cbf020a86f5aef13d172b696) [//]: # (START_SECTION 067e979aff32592a34afc0daba1d4188ea9046c4) ### Fixed a number of GUI related issues and fixed issues with sort and search > Commit: [067e979aff32592a34afc0daba1d4188ea9046c4](https://github.com/dOpensource/dsiprouter/commit/067e979aff32592a34afc0daba1d4188ea9046c4) > Date: Tue, 6 Nov 2018 11:58:54 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 067e979aff32592a34afc0daba1d4188ea9046c4) [//]: # (START_SECTION 55020672413646463728c5abf4716302161896f9) ### Update configuring.rst > Commit: [55020672413646463728c5abf4716302161896f9](https://github.com/dOpensource/dsiprouter/commit/55020672413646463728c5abf4716302161896f9) > Date: Tue, 6 Nov 2018 06:45:33 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 55020672413646463728c5abf4716302161896f9) [//]: # (START_SECTION f282dfac1f383786c9e60c4c43974a0dba9c2a5c) ### Update carrier_groups.rst > Commit: [f282dfac1f383786c9e60c4c43974a0dba9c2a5c](https://github.com/dOpensource/dsiprouter/commit/f282dfac1f383786c9e60c4c43974a0dba9c2a5c) > Date: Tue, 6 Nov 2018 06:43:23 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f282dfac1f383786c9e60c4c43974a0dba9c2a5c) [//]: # (START_SECTION 09c22d175e78ea1bb8c08e55fd6b5c23cf798bd7) ### Update carrier_groups.rst > Commit: [09c22d175e78ea1bb8c08e55fd6b5c23cf798bd7](https://github.com/dOpensource/dsiprouter/commit/09c22d175e78ea1bb8c08e55fd6b5c23cf798bd7) > Date: Tue, 6 Nov 2018 06:42:56 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 09c22d175e78ea1bb8c08e55fd6b5c23cf798bd7) [//]: # (START_SECTION ce2273f8e4556060f2a349a657ab6e78874b45b9) ### Add files via upload > Commit: [ce2273f8e4556060f2a349a657ab6e78874b45b9](https://github.com/dOpensource/dsiprouter/commit/ce2273f8e4556060f2a349a657ab6e78874b45b9) > Date: Tue, 6 Nov 2018 06:42:16 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ce2273f8e4556060f2a349a657ab6e78874b45b9) [//]: # (START_SECTION ba3328fc3e2c5d0fe0aca3ead4d7abe4bcae0520) ### Update carrier_groups.rst > Commit: [ba3328fc3e2c5d0fe0aca3ead4d7abe4bcae0520](https://github.com/dOpensource/dsiprouter/commit/ba3328fc3e2c5d0fe0aca3ead4d7abe4bcae0520) > Date: Tue, 6 Nov 2018 06:34:47 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ba3328fc3e2c5d0fe0aca3ead4d7abe4bcae0520) [//]: # (START_SECTION c7ea4c6bfec6a648a34525726f804cdf018d8cb0) ### Create pbxs_and_endpoints.rst > Commit: [c7ea4c6bfec6a648a34525726f804cdf018d8cb0](https://github.com/dOpensource/dsiprouter/commit/c7ea4c6bfec6a648a34525726f804cdf018d8cb0) > Date: Tue, 6 Nov 2018 06:32:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c7ea4c6bfec6a648a34525726f804cdf018d8cb0) [//]: # (START_SECTION 6a21a1dd7725ba3f129d20028e5cd79453eb7f6f) ### Update configuring.rst > Commit: [6a21a1dd7725ba3f129d20028e5cd79453eb7f6f](https://github.com/dOpensource/dsiprouter/commit/6a21a1dd7725ba3f129d20028e5cd79453eb7f6f) > Date: Tue, 6 Nov 2018 06:29:25 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6a21a1dd7725ba3f129d20028e5cd79453eb7f6f) [//]: # (START_SECTION 0e82f5cf587a8ca2fac5229af6e1da04fbb3c458) ### Update configuring.rst > Commit: [0e82f5cf587a8ca2fac5229af6e1da04fbb3c458](https://github.com/dOpensource/dsiprouter/commit/0e82f5cf587a8ca2fac5229af6e1da04fbb3c458) > Date: Tue, 6 Nov 2018 06:27:25 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0e82f5cf587a8ca2fac5229af6e1da04fbb3c458) [//]: # (START_SECTION 99c76f0bd6098aa3172bdabc7ac0b4210c683d39) ### Create configuring.rst > Commit: [99c76f0bd6098aa3172bdabc7ac0b4210c683d39](https://github.com/dOpensource/dsiprouter/commit/99c76f0bd6098aa3172bdabc7ac0b4210c683d39) > Date: Tue, 6 Nov 2018 06:24:48 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 99c76f0bd6098aa3172bdabc7ac0b4210c683d39) [//]: # (START_SECTION 82b7b1a39a16fdfdf891096aac36bd6ea7a22849) ### Rename configuring.rst to carrier_groups.rst > Commit: [82b7b1a39a16fdfdf891096aac36bd6ea7a22849](https://github.com/dOpensource/dsiprouter/commit/82b7b1a39a16fdfdf891096aac36bd6ea7a22849) > Date: Tue, 6 Nov 2018 06:24:06 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 82b7b1a39a16fdfdf891096aac36bd6ea7a22849) [//]: # (START_SECTION d68e56df12b70ecf354177549c837deff39a10e4) ### Update configuring.rst > Commit: [d68e56df12b70ecf354177549c837deff39a10e4](https://github.com/dOpensource/dsiprouter/commit/d68e56df12b70ecf354177549c837deff39a10e4) > Date: Tue, 6 Nov 2018 06:20:11 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d68e56df12b70ecf354177549c837deff39a10e4) [//]: # (START_SECTION 61a9dccb2b41ea89350d8c9a7e361952207e5d47) ### Add files via upload > Commit: [61a9dccb2b41ea89350d8c9a7e361952207e5d47](https://github.com/dOpensource/dsiprouter/commit/61a9dccb2b41ea89350d8c9a7e361952207e5d47) > Date: Tue, 6 Nov 2018 06:17:50 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 61a9dccb2b41ea89350d8c9a7e361952207e5d47) [//]: # (START_SECTION cdd0d8a4c465e0abea94f38d4c2cf78326a3845f) ### Create configuring.rst > Commit: [cdd0d8a4c465e0abea94f38d4c2cf78326a3845f](https://github.com/dOpensource/dsiprouter/commit/cdd0d8a4c465e0abea94f38d4c2cf78326a3845f) > Date: Tue, 6 Nov 2018 06:12:18 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cdd0d8a4c465e0abea94f38d4c2cf78326a3845f) [//]: # (START_SECTION 05dfa018657d794d8678403fac08f7ac841eba8a) ### Update installing.rst > Commit: [05dfa018657d794d8678403fac08f7ac841eba8a](https://github.com/dOpensource/dsiprouter/commit/05dfa018657d794d8678403fac08f7ac841eba8a) > Date: Tue, 6 Nov 2018 06:01:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 05dfa018657d794d8678403fac08f7ac841eba8a) [//]: # (START_SECTION 852d06bbf8e641b8090d645d5744335f51ae8e13) ### Update installing.rst > Commit: [852d06bbf8e641b8090d645d5744335f51ae8e13](https://github.com/dOpensource/dsiprouter/commit/852d06bbf8e641b8090d645d5744335f51ae8e13) > Date: Tue, 6 Nov 2018 06:00:54 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 852d06bbf8e641b8090d645d5744335f51ae8e13) [//]: # (START_SECTION d3cf85d9f3ed1f2d51c557097800b01735e058f0) ### Update installing.rst > Commit: [d3cf85d9f3ed1f2d51c557097800b01735e058f0](https://github.com/dOpensource/dsiprouter/commit/d3cf85d9f3ed1f2d51c557097800b01735e058f0) > Date: Tue, 6 Nov 2018 05:59:05 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d3cf85d9f3ed1f2d51c557097800b01735e058f0) [//]: # (START_SECTION bf9af20b132e0e6c51db41fec889aa501601d9a9) ### Update installing.rst > Commit: [bf9af20b132e0e6c51db41fec889aa501601d9a9](https://github.com/dOpensource/dsiprouter/commit/bf9af20b132e0e6c51db41fec889aa501601d9a9) > Date: Tue, 6 Nov 2018 05:57:19 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bf9af20b132e0e6c51db41fec889aa501601d9a9) [//]: # (START_SECTION 3388a9d66fa6f1f3a6f6528c4b862c82c53c7f5d) ### Update installing.rst > Commit: [3388a9d66fa6f1f3a6f6528c4b862c82c53c7f5d](https://github.com/dOpensource/dsiprouter/commit/3388a9d66fa6f1f3a6f6528c4b862c82c53c7f5d) > Date: Tue, 6 Nov 2018 05:54:18 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3388a9d66fa6f1f3a6f6528c4b862c82c53c7f5d) [//]: # (START_SECTION 1b4f27e33f6f20e60ee1604935509db03caa03e3) ### Update index.rst > Commit: [1b4f27e33f6f20e60ee1604935509db03caa03e3](https://github.com/dOpensource/dsiprouter/commit/1b4f27e33f6f20e60ee1604935509db03caa03e3) > Date: Tue, 6 Nov 2018 05:52:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1b4f27e33f6f20e60ee1604935509db03caa03e3) [//]: # (START_SECTION 42592fea9dbd6d08eecfb82d5b88954ddfe4e31d) ### Update installing.rst > Commit: [42592fea9dbd6d08eecfb82d5b88954ddfe4e31d](https://github.com/dOpensource/dsiprouter/commit/42592fea9dbd6d08eecfb82d5b88954ddfe4e31d) > Date: Tue, 6 Nov 2018 05:46:33 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 42592fea9dbd6d08eecfb82d5b88954ddfe4e31d) [//]: # (START_SECTION 66c07ef48148c9a3ac21b1c34b6cee146dce63f4) ### Update installing.rst > Commit: [66c07ef48148c9a3ac21b1c34b6cee146dce63f4](https://github.com/dOpensource/dsiprouter/commit/66c07ef48148c9a3ac21b1c34b6cee146dce63f4) > Date: Tue, 6 Nov 2018 05:43:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 66c07ef48148c9a3ac21b1c34b6cee146dce63f4) [//]: # (START_SECTION 91b954a9ea9cb06507ac7eead1ff24349000bc1d) ### Update installing.rst > Commit: [91b954a9ea9cb06507ac7eead1ff24349000bc1d](https://github.com/dOpensource/dsiprouter/commit/91b954a9ea9cb06507ac7eead1ff24349000bc1d) > Date: Tue, 6 Nov 2018 05:37:44 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 91b954a9ea9cb06507ac7eead1ff24349000bc1d) [//]: # (START_SECTION 210b71f1b902564a4a6d869d9610a580b916c41e) ### Update installing.rst > Commit: [210b71f1b902564a4a6d869d9610a580b916c41e](https://github.com/dOpensource/dsiprouter/commit/210b71f1b902564a4a6d869d9610a580b916c41e) > Date: Tue, 6 Nov 2018 05:36:12 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 210b71f1b902564a4a6d869d9610a580b916c41e) [//]: # (START_SECTION 6e1a4b67aea0c2422a412a481bc999aa29352103) ### Update index.rst > Commit: [6e1a4b67aea0c2422a412a481bc999aa29352103](https://github.com/dOpensource/dsiprouter/commit/6e1a4b67aea0c2422a412a481bc999aa29352103) > Date: Tue, 6 Nov 2018 05:33:26 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6e1a4b67aea0c2422a412a481bc999aa29352103) [//]: # (START_SECTION 225f723393d2840c8de81236bc248ffb9d0bf2ef) ### Update installing.rst > Commit: [225f723393d2840c8de81236bc248ffb9d0bf2ef](https://github.com/dOpensource/dsiprouter/commit/225f723393d2840c8de81236bc248ffb9d0bf2ef) > Date: Tue, 6 Nov 2018 05:30:21 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 225f723393d2840c8de81236bc248ffb9d0bf2ef) [//]: # (START_SECTION c53130200a6ef06497df2296aad1e6c9ed27f292) ### Update installing.rst > Commit: [c53130200a6ef06497df2296aad1e6c9ed27f292](https://github.com/dOpensource/dsiprouter/commit/c53130200a6ef06497df2296aad1e6c9ed27f292) > Date: Tue, 6 Nov 2018 05:26:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c53130200a6ef06497df2296aad1e6c9ed27f292) [//]: # (START_SECTION 5050ffda7bc5977df6e071fe881a163387002a74) ### Update installing.rst > Commit: [5050ffda7bc5977df6e071fe881a163387002a74](https://github.com/dOpensource/dsiprouter/commit/5050ffda7bc5977df6e071fe881a163387002a74) > Date: Tue, 6 Nov 2018 05:16:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5050ffda7bc5977df6e071fe881a163387002a74) [//]: # (START_SECTION 47dcb3be33f32f85f442300fd434f2cb2b19aa88) ### Update index.rst > Commit: [47dcb3be33f32f85f442300fd434f2cb2b19aa88](https://github.com/dOpensource/dsiprouter/commit/47dcb3be33f32f85f442300fd434f2cb2b19aa88) > Date: Tue, 6 Nov 2018 05:16:01 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 47dcb3be33f32f85f442300fd434f2cb2b19aa88) [//]: # (START_SECTION b90b65e99ad68133c7b426eeb0972e77b55401d5) ### Create installing.rst > Commit: [b90b65e99ad68133c7b426eeb0972e77b55401d5](https://github.com/dOpensource/dsiprouter/commit/b90b65e99ad68133c7b426eeb0972e77b55401d5) > Date: Tue, 6 Nov 2018 05:15:20 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b90b65e99ad68133c7b426eeb0972e77b55401d5) [//]: # (START_SECTION a306484ce89e49fc17020e70f1e5485d3d77f07a) ### Update index.rst > Commit: [a306484ce89e49fc17020e70f1e5485d3d77f07a](https://github.com/dOpensource/dsiprouter/commit/a306484ce89e49fc17020e70f1e5485d3d77f07a) > Date: Mon, 5 Nov 2018 15:09:34 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a306484ce89e49fc17020e70f1e5485d3d77f07a) [//]: # (START_SECTION a4cd1c7a7b945a9b347e7e7b09518703ad5170cf) ### Update index.rst > Commit: [a4cd1c7a7b945a9b347e7e7b09518703ad5170cf](https://github.com/dOpensource/dsiprouter/commit/a4cd1c7a7b945a9b347e7e7b09518703ad5170cf) > Date: Mon, 5 Nov 2018 15:07:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a4cd1c7a7b945a9b347e7e7b09518703ad5170cf) [//]: # (START_SECTION ae2bd8ea022973b1d3cf74bffc0e420435aa0084) ### Update index.rst > Commit: [ae2bd8ea022973b1d3cf74bffc0e420435aa0084](https://github.com/dOpensource/dsiprouter/commit/ae2bd8ea022973b1d3cf74bffc0e420435aa0084) > Date: Mon, 5 Nov 2018 14:54:25 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ae2bd8ea022973b1d3cf74bffc0e420435aa0084) [//]: # (START_SECTION 65d9b412c8e628d5a59d6227f865e171d4d6af02) ### Update index.rst > Commit: [65d9b412c8e628d5a59d6227f865e171d4d6af02](https://github.com/dOpensource/dsiprouter/commit/65d9b412c8e628d5a59d6227f865e171d4d6af02) > Date: Mon, 5 Nov 2018 14:49:08 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 65d9b412c8e628d5a59d6227f865e171d4d6af02) [//]: # (START_SECTION a17c6206d8f41cfe18d7fdf53c9394f46a2d7549) ### Update index.rst > Commit: [a17c6206d8f41cfe18d7fdf53c9394f46a2d7549](https://github.com/dOpensource/dsiprouter/commit/a17c6206d8f41cfe18d7fdf53c9394f46a2d7549) > Date: Mon, 5 Nov 2018 14:41:10 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a17c6206d8f41cfe18d7fdf53c9394f46a2d7549) [//]: # (START_SECTION d9136035e52196f9a9443248085a7aaa2ea07177) ### Update index.rst > Commit: [d9136035e52196f9a9443248085a7aaa2ea07177](https://github.com/dOpensource/dsiprouter/commit/d9136035e52196f9a9443248085a7aaa2ea07177) > Date: Mon, 5 Nov 2018 14:39:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d9136035e52196f9a9443248085a7aaa2ea07177) [//]: # (START_SECTION a1aa7b630ccb45487d329623e182c7bcc41a8149) ### Update index.rst > Commit: [a1aa7b630ccb45487d329623e182c7bcc41a8149](https://github.com/dOpensource/dsiprouter/commit/a1aa7b630ccb45487d329623e182c7bcc41a8149) > Date: Mon, 5 Nov 2018 14:38:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a1aa7b630ccb45487d329623e182c7bcc41a8149) [//]: # (START_SECTION cd97478cba66bed7cdb99c7788b29f492acddef3) ### Update index.rst > Commit: [cd97478cba66bed7cdb99c7788b29f492acddef3](https://github.com/dOpensource/dsiprouter/commit/cd97478cba66bed7cdb99c7788b29f492acddef3) > Date: Mon, 5 Nov 2018 14:36:31 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cd97478cba66bed7cdb99c7788b29f492acddef3) [//]: # (START_SECTION 89aba6b54851b992020db17c3ab237a5d46ee27d) ### Update index.rst > Commit: [89aba6b54851b992020db17c3ab237a5d46ee27d](https://github.com/dOpensource/dsiprouter/commit/89aba6b54851b992020db17c3ab237a5d46ee27d) > Date: Mon, 5 Nov 2018 14:28:04 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 89aba6b54851b992020db17c3ab237a5d46ee27d) [//]: # (START_SECTION 656429533dd95fab33de88f043d0cc6480a4883d) ### Update index.rst > Commit: [656429533dd95fab33de88f043d0cc6480a4883d](https://github.com/dOpensource/dsiprouter/commit/656429533dd95fab33de88f043d0cc6480a4883d) > Date: Mon, 5 Nov 2018 14:26:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 656429533dd95fab33de88f043d0cc6480a4883d) [//]: # (START_SECTION eb68428a3b6993fda4f8570e8e5c1866ab7b6df7) ### Update index.rst > Commit: [eb68428a3b6993fda4f8570e8e5c1866ab7b6df7](https://github.com/dOpensource/dsiprouter/commit/eb68428a3b6993fda4f8570e8e5c1866ab7b6df7) > Date: Mon, 5 Nov 2018 14:23:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb68428a3b6993fda4f8570e8e5c1866ab7b6df7) [//]: # (START_SECTION 050707569fb1e92d9d90913feca2c5926791fe9f) ### Update index.rst > Commit: [050707569fb1e92d9d90913feca2c5926791fe9f](https://github.com/dOpensource/dsiprouter/commit/050707569fb1e92d9d90913feca2c5926791fe9f) > Date: Mon, 5 Nov 2018 14:20:36 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 050707569fb1e92d9d90913feca2c5926791fe9f) [//]: # (START_SECTION 4280b0277339b2dfc07456f7e8eb95df01825223) ### Update index.rst > Commit: [4280b0277339b2dfc07456f7e8eb95df01825223](https://github.com/dOpensource/dsiprouter/commit/4280b0277339b2dfc07456f7e8eb95df01825223) > Date: Mon, 5 Nov 2018 14:19:02 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4280b0277339b2dfc07456f7e8eb95df01825223) [//]: # (START_SECTION 3f8df7b2e0f6f44d385e36c9efa955cf1d313a5b) ### Update index.rst > Commit: [3f8df7b2e0f6f44d385e36c9efa955cf1d313a5b](https://github.com/dOpensource/dsiprouter/commit/3f8df7b2e0f6f44d385e36c9efa955cf1d313a5b) > Date: Mon, 5 Nov 2018 14:14:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f8df7b2e0f6f44d385e36c9efa955cf1d313a5b) [//]: # (START_SECTION eb2a30656434bd2f8c8689980e595c0662215a14) ### Update index.rst > Commit: [eb2a30656434bd2f8c8689980e595c0662215a14](https://github.com/dOpensource/dsiprouter/commit/eb2a30656434bd2f8c8689980e595c0662215a14) > Date: Mon, 5 Nov 2018 13:58:16 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb2a30656434bd2f8c8689980e595c0662215a14) [//]: # (START_SECTION bec7289e3e52786ebd365ab8dcce90c37535df78) ### Update index.rst > Commit: [bec7289e3e52786ebd365ab8dcce90c37535df78](https://github.com/dOpensource/dsiprouter/commit/bec7289e3e52786ebd365ab8dcce90c37535df78) > Date: Mon, 5 Nov 2018 13:53:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION bec7289e3e52786ebd365ab8dcce90c37535df78) [//]: # (START_SECTION 5f8de2d823a05755459bc1ae151f2be58439e757) ### Update index.rst > Commit: [5f8de2d823a05755459bc1ae151f2be58439e757](https://github.com/dOpensource/dsiprouter/commit/5f8de2d823a05755459bc1ae151f2be58439e757) > Date: Mon, 5 Nov 2018 13:51:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5f8de2d823a05755459bc1ae151f2be58439e757) [//]: # (START_SECTION 52d2339f02db3402a12198733055efc0b833cc09) ### Update index.rst > Commit: [52d2339f02db3402a12198733055efc0b833cc09](https://github.com/dOpensource/dsiprouter/commit/52d2339f02db3402a12198733055efc0b833cc09) > Date: Mon, 5 Nov 2018 13:44:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 52d2339f02db3402a12198733055efc0b833cc09) [//]: # (START_SECTION d3e1601c9e5d02c76f65997773a66f69e4c18ac6) ### Update index.rst > Commit: [d3e1601c9e5d02c76f65997773a66f69e4c18ac6](https://github.com/dOpensource/dsiprouter/commit/d3e1601c9e5d02c76f65997773a66f69e4c18ac6) > Date: Mon, 5 Nov 2018 13:33:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d3e1601c9e5d02c76f65997773a66f69e4c18ac6) [//]: # (START_SECTION 0367725739cf98cdf7fd954656b5f0e5f7dc498b) ### Update index.rst > Commit: [0367725739cf98cdf7fd954656b5f0e5f7dc498b](https://github.com/dOpensource/dsiprouter/commit/0367725739cf98cdf7fd954656b5f0e5f7dc498b) > Date: Mon, 5 Nov 2018 13:27:06 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0367725739cf98cdf7fd954656b5f0e5f7dc498b) [//]: # (START_SECTION d5555b5a88904146f037b1777b1ccbc3fd585976) ### Update index.rst > Commit: [d5555b5a88904146f037b1777b1ccbc3fd585976](https://github.com/dOpensource/dsiprouter/commit/d5555b5a88904146f037b1777b1ccbc3fd585976) > Date: Mon, 5 Nov 2018 12:45:23 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d5555b5a88904146f037b1777b1ccbc3fd585976) [//]: # (START_SECTION df6c9add73dde8126c0ac98cf21ff569c5a602e0) ### Update index.rst > Commit: [df6c9add73dde8126c0ac98cf21ff569c5a602e0](https://github.com/dOpensource/dsiprouter/commit/df6c9add73dde8126c0ac98cf21ff569c5a602e0) > Date: Mon, 5 Nov 2018 12:44:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION df6c9add73dde8126c0ac98cf21ff569c5a602e0) [//]: # (START_SECTION 7f9fb2325771005478fbf8c10f487149ac28e895) ### Update index.rst > Commit: [7f9fb2325771005478fbf8c10f487149ac28e895](https://github.com/dOpensource/dsiprouter/commit/7f9fb2325771005478fbf8c10f487149ac28e895) > Date: Mon, 5 Nov 2018 12:41:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7f9fb2325771005478fbf8c10f487149ac28e895) [//]: # (START_SECTION ac93fb29e458a49f2265b0a146165515df4f971a) ### Update index.rst > Commit: [ac93fb29e458a49f2265b0a146165515df4f971a](https://github.com/dOpensource/dsiprouter/commit/ac93fb29e458a49f2265b0a146165515df4f971a) > Date: Mon, 5 Nov 2018 12:38:42 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ac93fb29e458a49f2265b0a146165515df4f971a) [//]: # (START_SECTION ae06eeb651ac5ee3f36554102feb02d605726da5) ### Update index.rst > Commit: [ae06eeb651ac5ee3f36554102feb02d605726da5](https://github.com/dOpensource/dsiprouter/commit/ae06eeb651ac5ee3f36554102feb02d605726da5) > Date: Mon, 5 Nov 2018 12:35:32 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ae06eeb651ac5ee3f36554102feb02d605726da5) [//]: # (START_SECTION 7cfe90077c726788d79f663143bf1c7dc55d6cb7) ### Update index.rst > Commit: [7cfe90077c726788d79f663143bf1c7dc55d6cb7](https://github.com/dOpensource/dsiprouter/commit/7cfe90077c726788d79f663143bf1c7dc55d6cb7) > Date: Mon, 5 Nov 2018 12:34:57 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7cfe90077c726788d79f663143bf1c7dc55d6cb7) [//]: # (START_SECTION d614b77f9c7b1f712cd2e599688dee294a9bee55) ### Update index.rst > Commit: [d614b77f9c7b1f712cd2e599688dee294a9bee55](https://github.com/dOpensource/dsiprouter/commit/d614b77f9c7b1f712cd2e599688dee294a9bee55) > Date: Mon, 5 Nov 2018 12:32:51 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d614b77f9c7b1f712cd2e599688dee294a9bee55) [//]: # (START_SECTION 61074d151d4dd157290d7e7b0210570807a499be) ### Update index.rst > Commit: [61074d151d4dd157290d7e7b0210570807a499be](https://github.com/dOpensource/dsiprouter/commit/61074d151d4dd157290d7e7b0210570807a499be) > Date: Mon, 5 Nov 2018 12:30:00 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 61074d151d4dd157290d7e7b0210570807a499be) [//]: # (START_SECTION 27c00857bda1ec3dcd1d73d52fed8156be102825) ### Update index.rst > Commit: [27c00857bda1ec3dcd1d73d52fed8156be102825](https://github.com/dOpensource/dsiprouter/commit/27c00857bda1ec3dcd1d73d52fed8156be102825) > Date: Mon, 5 Nov 2018 12:13:41 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 27c00857bda1ec3dcd1d73d52fed8156be102825) [//]: # (START_SECTION fbf822d73f3a09e9588cf05c1c79e1deb8b03f3e) ### Update index.rst > Commit: [fbf822d73f3a09e9588cf05c1c79e1deb8b03f3e](https://github.com/dOpensource/dsiprouter/commit/fbf822d73f3a09e9588cf05c1c79e1deb8b03f3e) > Date: Mon, 5 Nov 2018 12:09:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fbf822d73f3a09e9588cf05c1c79e1deb8b03f3e) [//]: # (START_SECTION 31f9a5d9a334c0af187ddb8fedf3d6613b029351) ### Update index.rst > Commit: [31f9a5d9a334c0af187ddb8fedf3d6613b029351](https://github.com/dOpensource/dsiprouter/commit/31f9a5d9a334c0af187ddb8fedf3d6613b029351) > Date: Mon, 5 Nov 2018 12:08:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 31f9a5d9a334c0af187ddb8fedf3d6613b029351) [//]: # (START_SECTION 8d387c5bc356376b84696ec825e914a8e1eba605) ### Update index.rst > Commit: [8d387c5bc356376b84696ec825e914a8e1eba605](https://github.com/dOpensource/dsiprouter/commit/8d387c5bc356376b84696ec825e914a8e1eba605) > Date: Mon, 5 Nov 2018 12:05:52 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 8d387c5bc356376b84696ec825e914a8e1eba605) [//]: # (START_SECTION d06c51b6a55177e102e796aca34750d9f042ffea) ### Update README.md > Commit: [d06c51b6a55177e102e796aca34750d9f042ffea](https://github.com/dOpensource/dsiprouter/commit/d06c51b6a55177e102e796aca34750d9f042ffea) > Date: Mon, 5 Nov 2018 11:59:29 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d06c51b6a55177e102e796aca34750d9f042ffea) [//]: # (START_SECTION 69831efae2d540363e6685adce5599aeffd17e30) ### Update index.rst > Commit: [69831efae2d540363e6685adce5599aeffd17e30](https://github.com/dOpensource/dsiprouter/commit/69831efae2d540363e6685adce5599aeffd17e30) > Date: Mon, 5 Nov 2018 11:57:01 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 69831efae2d540363e6685adce5599aeffd17e30) [//]: # (START_SECTION a3d252949c0a867abdcdbc2fbeace5813875d50c) ### Update index.rst > Commit: [a3d252949c0a867abdcdbc2fbeace5813875d50c](https://github.com/dOpensource/dsiprouter/commit/a3d252949c0a867abdcdbc2fbeace5813875d50c) > Date: Mon, 5 Nov 2018 11:55:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a3d252949c0a867abdcdbc2fbeace5813875d50c) [//]: # (START_SECTION 2c7d51a7e874388f3b62f3000b57decc1d906703) ### Update index.rst > Commit: [2c7d51a7e874388f3b62f3000b57decc1d906703](https://github.com/dOpensource/dsiprouter/commit/2c7d51a7e874388f3b62f3000b57decc1d906703) > Date: Mon, 5 Nov 2018 11:54:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2c7d51a7e874388f3b62f3000b57decc1d906703) [//]: # (START_SECTION 08d10e0e628dda014c9fb1331e2b9700096ea2ca) ### Update index.rst > Commit: [08d10e0e628dda014c9fb1331e2b9700096ea2ca](https://github.com/dOpensource/dsiprouter/commit/08d10e0e628dda014c9fb1331e2b9700096ea2ca) > Date: Mon, 5 Nov 2018 11:51:58 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 08d10e0e628dda014c9fb1331e2b9700096ea2ca) [//]: # (START_SECTION 525b1f44557c90cbd98440092fb53321c3c0ff91) ### Update index.rst > Commit: [525b1f44557c90cbd98440092fb53321c3c0ff91](https://github.com/dOpensource/dsiprouter/commit/525b1f44557c90cbd98440092fb53321c3c0ff91) > Date: Mon, 5 Nov 2018 11:46:49 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 525b1f44557c90cbd98440092fb53321c3c0ff91) [//]: # (START_SECTION fdd79140e22e4802f865460df3a75a0ad50e66b3) ### Update index.rst > Commit: [fdd79140e22e4802f865460df3a75a0ad50e66b3](https://github.com/dOpensource/dsiprouter/commit/fdd79140e22e4802f865460df3a75a0ad50e66b3) > Date: Mon, 5 Nov 2018 11:33:19 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fdd79140e22e4802f865460df3a75a0ad50e66b3) [//]: # (START_SECTION 210a2ddc4420e829cf6a2d9c135724e05c1219c4) ### Update index.rst > Commit: [210a2ddc4420e829cf6a2d9c135724e05c1219c4](https://github.com/dOpensource/dsiprouter/commit/210a2ddc4420e829cf6a2d9c135724e05c1219c4) > Date: Mon, 5 Nov 2018 11:30:35 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 210a2ddc4420e829cf6a2d9c135724e05c1219c4) [//]: # (START_SECTION 25c9393d1160c74c3681b7588eb4f5391da2b806) ### Update index.rst > Commit: [25c9393d1160c74c3681b7588eb4f5391da2b806](https://github.com/dOpensource/dsiprouter/commit/25c9393d1160c74c3681b7588eb4f5391da2b806) > Date: Mon, 5 Nov 2018 11:27:45 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 25c9393d1160c74c3681b7588eb4f5391da2b806) [//]: # (START_SECTION 25fc7e99bb85bad0970c9d40d3c061c95f4fa04d) ### Update index.rst > Commit: [25fc7e99bb85bad0970c9d40d3c061c95f4fa04d](https://github.com/dOpensource/dsiprouter/commit/25fc7e99bb85bad0970c9d40d3c061c95f4fa04d) > Date: Mon, 5 Nov 2018 11:25:48 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 25fc7e99bb85bad0970c9d40d3c061c95f4fa04d) [//]: # (START_SECTION 32d177564f89e3267d6ee9f88b32128dc6f66435) ### Update index.rst > Commit: [32d177564f89e3267d6ee9f88b32128dc6f66435](https://github.com/dOpensource/dsiprouter/commit/32d177564f89e3267d6ee9f88b32128dc6f66435) > Date: Mon, 5 Nov 2018 11:23:05 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 32d177564f89e3267d6ee9f88b32128dc6f66435) [//]: # (START_SECTION 31dd5677af4d9640a92061f0fdf039adb647994d) ### Update index.rst > Commit: [31dd5677af4d9640a92061f0fdf039adb647994d](https://github.com/dOpensource/dsiprouter/commit/31dd5677af4d9640a92061f0fdf039adb647994d) > Date: Mon, 5 Nov 2018 10:38:56 -0500 > Author: ncannon01 (44709249+ncannon01@users.noreply.github.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 31dd5677af4d9640a92061f0fdf039adb647994d) [//]: # (START_SECTION ea93010271a1bef8480a93a87ea75e3e068957ea) ### Update index.rst > Commit: [ea93010271a1bef8480a93a87ea75e3e068957ea](https://github.com/dOpensource/dsiprouter/commit/ea93010271a1bef8480a93a87ea75e3e068957ea) > Date: Mon, 5 Nov 2018 09:33:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ea93010271a1bef8480a93a87ea75e3e068957ea) [//]: # (START_SECTION 332c0bb6b79b29fd56a283f6762e72e8133b755b) ### Update index.rst > Commit: [332c0bb6b79b29fd56a283f6762e72e8133b755b](https://github.com/dOpensource/dsiprouter/commit/332c0bb6b79b29fd56a283f6762e72e8133b755b) > Date: Mon, 5 Nov 2018 09:31:49 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 332c0bb6b79b29fd56a283f6762e72e8133b755b) [//]: # (START_SECTION e12ab1b48d452f1554142b63c5832e7be1058837) ### Update index.rst > Commit: [e12ab1b48d452f1554142b63c5832e7be1058837](https://github.com/dOpensource/dsiprouter/commit/e12ab1b48d452f1554142b63c5832e7be1058837) > Date: Mon, 5 Nov 2018 09:30:16 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e12ab1b48d452f1554142b63c5832e7be1058837) [//]: # (START_SECTION 3983711e773b9f6b41e4ed6df96f881c66a0586e) ### Update index.rst > Commit: [3983711e773b9f6b41e4ed6df96f881c66a0586e](https://github.com/dOpensource/dsiprouter/commit/3983711e773b9f6b41e4ed6df96f881c66a0586e) > Date: Mon, 5 Nov 2018 09:26:13 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3983711e773b9f6b41e4ed6df96f881c66a0586e) [//]: # (START_SECTION 2207add74d482c604cf70b68f7e61f72d235a96e) ### Update index.rst > Commit: [2207add74d482c604cf70b68f7e61f72d235a96e](https://github.com/dOpensource/dsiprouter/commit/2207add74d482c604cf70b68f7e61f72d235a96e) > Date: Mon, 5 Nov 2018 09:24:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2207add74d482c604cf70b68f7e61f72d235a96e) [//]: # (START_SECTION 860ab8ee1a68078c7f5d871fd27ac1348e3d47a6) ### Update index.rst > Commit: [860ab8ee1a68078c7f5d871fd27ac1348e3d47a6](https://github.com/dOpensource/dsiprouter/commit/860ab8ee1a68078c7f5d871fd27ac1348e3d47a6) > Date: Mon, 5 Nov 2018 09:24:17 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 860ab8ee1a68078c7f5d871fd27ac1348e3d47a6) [//]: # (START_SECTION a1b39356ab30428b96f3815adfc6b6592430dca0) ### Update index.rst > Commit: [a1b39356ab30428b96f3815adfc6b6592430dca0](https://github.com/dOpensource/dsiprouter/commit/a1b39356ab30428b96f3815adfc6b6592430dca0) > Date: Mon, 5 Nov 2018 09:20:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a1b39356ab30428b96f3815adfc6b6592430dca0) [//]: # (START_SECTION f50a31c8df66b3ac8cfb46c5fe8f291db60ebc87) ### Update index.rst > Commit: [f50a31c8df66b3ac8cfb46c5fe8f291db60ebc87](https://github.com/dOpensource/dsiprouter/commit/f50a31c8df66b3ac8cfb46c5fe8f291db60ebc87) > Date: Mon, 5 Nov 2018 09:16:01 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f50a31c8df66b3ac8cfb46c5fe8f291db60ebc87) [//]: # (START_SECTION 30d36d670bd96ace7dd33132387e01e73750021a) ### Update index.rst > Commit: [30d36d670bd96ace7dd33132387e01e73750021a](https://github.com/dOpensource/dsiprouter/commit/30d36d670bd96ace7dd33132387e01e73750021a) > Date: Fri, 2 Nov 2018 14:37:21 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 30d36d670bd96ace7dd33132387e01e73750021a) [//]: # (START_SECTION 84cafa37fe15f86334ad6c156aaff6b5fdbd196b) ### Update index.rst > Commit: [84cafa37fe15f86334ad6c156aaff6b5fdbd196b](https://github.com/dOpensource/dsiprouter/commit/84cafa37fe15f86334ad6c156aaff6b5fdbd196b) > Date: Fri, 2 Nov 2018 14:34:45 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 84cafa37fe15f86334ad6c156aaff6b5fdbd196b) [//]: # (START_SECTION 14d3c26782a92adf62dc26276f43e3b30839fce0) ### Update index.rst > Commit: [14d3c26782a92adf62dc26276f43e3b30839fce0](https://github.com/dOpensource/dsiprouter/commit/14d3c26782a92adf62dc26276f43e3b30839fce0) > Date: Fri, 2 Nov 2018 14:31:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 14d3c26782a92adf62dc26276f43e3b30839fce0) [//]: # (START_SECTION 2b9d89de6ee285b749028ffc6fd2544118ff484c) ### Update index.rst > Commit: [2b9d89de6ee285b749028ffc6fd2544118ff484c](https://github.com/dOpensource/dsiprouter/commit/2b9d89de6ee285b749028ffc6fd2544118ff484c) > Date: Fri, 2 Nov 2018 14:26:43 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 2b9d89de6ee285b749028ffc6fd2544118ff484c) [//]: # (START_SECTION ce9f450ea71a35b9e08a1b936431d7750c8d0ea3) ### Update index.rst > Commit: [ce9f450ea71a35b9e08a1b936431d7750c8d0ea3](https://github.com/dOpensource/dsiprouter/commit/ce9f450ea71a35b9e08a1b936431d7750c8d0ea3) > Date: Fri, 2 Nov 2018 14:26:03 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ce9f450ea71a35b9e08a1b936431d7750c8d0ea3) [//]: # (START_SECTION 79823053f92679a78799d946af4f76021e3d4884) ### Update index.rst > Commit: [79823053f92679a78799d946af4f76021e3d4884](https://github.com/dOpensource/dsiprouter/commit/79823053f92679a78799d946af4f76021e3d4884) > Date: Fri, 2 Nov 2018 14:24:07 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 79823053f92679a78799d946af4f76021e3d4884) [//]: # (START_SECTION c02abd219f4f5eaf295fe0da9b552190c68b62c4) ### Update index.rst > Commit: [c02abd219f4f5eaf295fe0da9b552190c68b62c4](https://github.com/dOpensource/dsiprouter/commit/c02abd219f4f5eaf295fe0da9b552190c68b62c4) > Date: Fri, 2 Nov 2018 14:18:50 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c02abd219f4f5eaf295fe0da9b552190c68b62c4) [//]: # (START_SECTION 179cae55486547db2fa5efe8101a9820437adc41) ### Create index.rst > Commit: [179cae55486547db2fa5efe8101a9820437adc41](https://github.com/dOpensource/dsiprouter/commit/179cae55486547db2fa5efe8101a9820437adc41) > Date: Fri, 2 Nov 2018 13:29:48 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 179cae55486547db2fa5efe8101a9820437adc41) [//]: # (START_SECTION 92e804b0f004a91c0e47d88b954b8bc1d882998b) ### Create index.rst > Commit: [92e804b0f004a91c0e47d88b954b8bc1d882998b](https://github.com/dOpensource/dsiprouter/commit/92e804b0f004a91c0e47d88b954b8bc1d882998b) > Date: Fri, 2 Nov 2018 13:10:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92e804b0f004a91c0e47d88b954b8bc1d882998b) [//]: # (START_SECTION 17680f44b08e8891659c7bc09bdfe637cf632127) ### Added the notes field to the add and edit modal's for Inbound Mappings > Commit: [17680f44b08e8891659c7bc09bdfe637cf632127](https://github.com/dOpensource/dsiprouter/commit/17680f44b08e8891659c7bc09bdfe637cf632127) > Date: Thu, 1 Nov 2018 11:55:07 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 17680f44b08e8891659c7bc09bdfe637cf632127) [//]: # (START_SECTION f96c161f8bab289573e1d4a6cf9dc0fbf3212276) ### Added support for importing one of more DID's Issue #84 > Commit: [f96c161f8bab289573e1d4a6cf9dc0fbf3212276](https://github.com/dOpensource/dsiprouter/commit/f96c161f8bab289573e1d4a6cf9dc0fbf3212276) > Date: Thu, 1 Nov 2018 04:31:47 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f96c161f8bab289573e1d4a6cf9dc0fbf3212276) [//]: # (START_SECTION 25b110bef1346ae693673afaa2a04553faf99952) ### Added support for sorting, searching and pagination to the domain page. This sort can also be added to other pages as well since the library is now added Issue #84 > Commit: [25b110bef1346ae693673afaa2a04553faf99952](https://github.com/dOpensource/dsiprouter/commit/25b110bef1346ae693673afaa2a04553faf99952) > Date: Tue, 30 Oct 2018 04:07:50 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 25b110bef1346ae693673afaa2a04553faf99952) [//]: # (START_SECTION 244bda0fd487d67b3d5e42c2b1b6a9f8e53dae97) ### Update CONTRIBUTING.md > Commit: [244bda0fd487d67b3d5e42c2b1b6a9f8e53dae97](https://github.com/dOpensource/dsiprouter/commit/244bda0fd487d67b3d5e42c2b1b6a9f8e53dae97) > Date: Wed, 24 Oct 2018 16:00:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 244bda0fd487d67b3d5e42c2b1b6a9f8e53dae97) [//]: # (START_SECTION 194beeaf4cb72efefce02a69c6c48f5e9463419e) ### Update CONTRIBUTING.md > Commit: [194beeaf4cb72efefce02a69c6c48f5e9463419e](https://github.com/dOpensource/dsiprouter/commit/194beeaf4cb72efefce02a69c6c48f5e9463419e) > Date: Wed, 24 Oct 2018 15:59:39 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 194beeaf4cb72efefce02a69c6c48f5e9463419e) [//]: # (START_SECTION bbe0919f659d081ddaf07890b41a56854bd8660d) ### Added Domain Management features and added a new approach to adding modules to dSIPRouter, which will be documented in the Contribution Guide. > Commit: [bbe0919f659d081ddaf07890b41a56854bd8660d](https://github.com/dOpensource/dsiprouter/commit/bbe0919f659d081ddaf07890b41a56854bd8660d) > Date: Mon, 22 Oct 2018 09:26:48 +0000 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: - - Static and Dynamic domains can be displayed in the GUI. - - Added a UnitTest to validate the Domain Management Services --- [//]: # (END_SECTION bbe0919f659d081ddaf07890b41a56854bd8660d) [//]: # (START_SECTION 789683f2bedd0a502e76e19ee2f7dce42023dcae) ### Merge asterisk-realtime and latest updates > Commit: [789683f2bedd0a502e76e19ee2f7dce42023dcae](https://github.com/dOpensource/dsiprouter/commit/789683f2bedd0a502e76e19ee2f7dce42023dcae) > Date: Sun, 30 Sep 2018 20:14:59 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - allow cluster db installation config - auto update cluster params in kamconfig - condense kamconfig files (using ifdefs) - merge current SIPWISE kamconfig - merge asterisk-realtime branch - fix NAT issues - auto update kamversion in kamconfig - upgrade version / release functions - improve install script util functions - add import library to install script - section out user configurable settings - make install script vars easier to use - remove placeholder in docs - add TODO statements - add current asterisk-realtime resources - add possible rtpengine fix in resources - add module install echo statements - allow SSL keys on debug - update OS dependencies - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION 789683f2bedd0a502e76e19ee2f7dce42023dcae) [//]: # (START_SECTION e39f4cb5aa9266b913b79089b6166a1c586a726d) ### Create CONTRIBUTING.md > Commit: [e39f4cb5aa9266b913b79089b6166a1c586a726d](https://github.com/dOpensource/dsiprouter/commit/e39f4cb5aa9266b913b79089b6166a1c586a726d) > Date: Sun, 30 Sep 2018 00:10:20 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: - initial guide --- [//]: # (END_SECTION e39f4cb5aa9266b913b79089b6166a1c586a726d) [//]: # (START_SECTION 1fd189ee59969884f6d0872df1aa30d3f39ce5b2) ### Added support for working with a Kamailio subscriber table and tested it against FreePBX > Commit: [1fd189ee59969884f6d0872df1aa30d3f39ce5b2](https://github.com/dOpensource/dsiprouter/commit/1fd189ee59969884f6d0872df1aa30d3f39ce5b2) > Date: Wed, 26 Sep 2018 14:17:05 -0400 > Author: root (root@kamailio3.kamailo3@lhsip.com) > Committer: root (root@kamailio3.kamailo3@lhsip.com) > Signed: --- [//]: # (END_SECTION 1fd189ee59969884f6d0872df1aa30d3f39ce5b2) [//]: # (START_SECTION 43246b5426badd72a5b2ac690f2d78ca14271666) ### Added support for enriching sip headers and added record_route support > Commit: [43246b5426badd72a5b2ac690f2d78ca14271666](https://github.com/dOpensource/dsiprouter/commit/43246b5426badd72a5b2ac690f2d78ca14271666) > Date: Mon, 24 Sep 2018 12:46:52 +0200 > Author: root (root@reg-01.voipmuch.com) > Committer: root (root@reg-01.voipmuch.com) > Signed: --- [//]: # (END_SECTION 43246b5426badd72a5b2ac690f2d78ca14271666) [//]: # (START_SECTION 4556d480be5c0c11d7c91152bbbbe08958b6f02d) ### Using sippasswd field within Asterisk Realtime to validate user passwords > Commit: [4556d480be5c0c11d7c91152bbbbe08958b6f02d](https://github.com/dOpensource/dsiprouter/commit/4556d480be5c0c11d7c91152bbbbe08958b6f02d) > Date: Mon, 24 Sep 2018 09:59:23 +0200 > Author: root (root@reg-01.voipmuch.com) > Committer: root (root@reg-01.voipmuch.com) > Signed: --- [//]: # (END_SECTION 4556d480be5c0c11d7c91152bbbbe08958b6f02d) [//]: # (START_SECTION 3027ebd0011ebac64e8d8665ae5078500c72e921) ### weezy was specified instead of stretch > Commit: [3027ebd0011ebac64e8d8665ae5078500c72e921](https://github.com/dOpensource/dsiprouter/commit/3027ebd0011ebac64e8d8665ae5078500c72e921) > Date: Sun, 23 Sep 2018 18:50:20 +0200 > Author: root (root@reg-01.voipmuch.com) > Committer: root (root@reg-01.voipmuch.com) > Signed: --- [//]: # (END_SECTION 3027ebd0011ebac64e8d8665ae5078500c72e921) [//]: # (START_SECTION 9e9755bde5835ae4d842741b6b4071b8e1c43799) ### Initial commit for Asterisk Realtime Support > Commit: [9e9755bde5835ae4d842741b6b4071b8e1c43799](https://github.com/dOpensource/dsiprouter/commit/9e9755bde5835ae4d842741b6b4071b8e1c43799) > Date: Sun, 23 Sep 2018 15:27:06 +0000 > Author: root (root@dsiprouter-dev.localdomain) > Committer: root (root@dsiprouter-dev.localdomain) > Signed: --- [//]: # (END_SECTION 9e9755bde5835ae4d842741b6b4071b8e1c43799) [//]: # (START_SECTION 83936bb0cbfff957a846d55acac27b2a2c1d8cd9) ### Add CentOS support v0.51 > Commit: [83936bb0cbfff957a846d55acac27b2a2c1d8cd9](https://github.com/dOpensource/dsiprouter/commit/83936bb0cbfff957a846d55acac27b2a2c1d8cd9) > Date: Mon, 10 Sep 2018 20:15:22 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #30 - Resolves #69 - merge changes with v5.1 - remove python prompt - change validate to allow centos v7 - add centos checks in install functions - fix distro / OS version checks - fix centos uninstall funcs - automate kamdbctl password prompt (edit config) - add module install support for centos - unset exported vars/funcs on exit (cleanup) - get rid of kamailio db prompt - allow debug in all commands (1st param only) - various fixes for install scripts - add util / library script - improve distro version checks - fixes for db error handling in dsiprouter.py - re-add serving https through ssl certs (settings.py) - improve file parsing in updateConfig() - fix default mysql csv's (broken from last commit) - enable cdrs by default - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION 83936bb0cbfff957a846d55acac27b2a2c1d8cd9) [//]: # (START_SECTION 0cb7af60b073f8ecd78beafdb13cc540e90a2a6d) ### Changed the default role in Kamailio to '' for all > Commit: [0cb7af60b073f8ecd78beafdb13cc540e90a2a6d](https://github.com/dOpensource/dsiprouter/commit/0cb7af60b073f8ecd78beafdb13cc540e90a2a6d) > Date: Fri, 7 Sep 2018 01:03:43 -0500 > Author: root (root@969092-extapp1.inemsoft.com) > Committer: root (root@969092-extapp1.inemsoft.com) > Signed: --- [//]: # (END_SECTION 0cb7af60b073f8ecd78beafdb13cc540e90a2a6d) [//]: # (START_SECTION 1c9d98e912dcd04069c0fed2d7680153a97dc42b) ### Raw fixes for centos 7 support > Commit: [1c9d98e912dcd04069c0fed2d7680153a97dc42b](https://github.com/dOpensource/dsiprouter/commit/1c9d98e912dcd04069c0fed2d7680153a97dc42b) > Date: Fri, 7 Sep 2018 00:05:37 -0500 > Author: root (root@969092-extapp1.inemsoft.com) > Committer: root (root@969092-extapp1.inemsoft.com) > Signed: --- [//]: # (END_SECTION 1c9d98e912dcd04069c0fed2d7680153a97dc42b) [//]: # (START_SECTION d0b5c970a246391e175cd1762138a35d35dc74da) ### Adding support for centos 7 > Commit: [d0b5c970a246391e175cd1762138a35d35dc74da](https://github.com/dOpensource/dsiprouter/commit/d0b5c970a246391e175cd1762138a35d35dc74da) > Date: Thu, 6 Sep 2018 17:54:35 -0500 > Author: root (root@969092-extapp1.inemsoft.com) > Committer: root (root@969092-extapp1.inemsoft.com) > Signed: --- [//]: # (END_SECTION d0b5c970a246391e175cd1762138a35d35dc74da) [//]: # (START_SECTION f06d26c7ec49e4cb3e44c3f4ce2696562cea8612) ### Added support for centos 7 > Commit: [f06d26c7ec49e4cb3e44c3f4ce2696562cea8612](https://github.com/dOpensource/dsiprouter/commit/f06d26c7ec49e4cb3e44c3f4ce2696562cea8612) > Date: Thu, 6 Sep 2018 17:20:57 -0500 > Author: root (root@969092-extapp1.inemsoft.com) > Committer: root (root@969092-extapp1.inemsoft.com) > Signed: --- [//]: # (END_SECTION f06d26c7ec49e4cb3e44c3f4ce2696562cea8612) [//]: # (START_SECTION 0aea024cd5bb0fdfbfc9a757c7a95e53977d27d7) ### Adding support back for centOS 7 > Commit: [0aea024cd5bb0fdfbfc9a757c7a95e53977d27d7](https://github.com/dOpensource/dsiprouter/commit/0aea024cd5bb0fdfbfc9a757c7a95e53977d27d7) > Date: Thu, 6 Sep 2018 17:03:58 -0500 > Author: root (root@969092-extapp1.inemsoft.com) > Committer: root (root@969092-extapp1.inemsoft.com) > Signed: --- [//]: # (END_SECTION 0aea024cd5bb0fdfbfc9a757c7a95e53977d27d7) [//]: # (START_SECTION 21818f07eadc647bf227fa3ba2ccad0fe81195f5) ### Provided comments in settings.py and added support for giving dSIPRouter roles > Commit: [21818f07eadc647bf227fa3ba2ccad0fe81195f5](https://github.com/dOpensource/dsiprouter/commit/21818f07eadc647bf227fa3ba2ccad0fe81195f5) > Date: Wed, 5 Sep 2018 06:54:27 -0400 > Author: root (root@kamailio3.kamailo3@lhsip.com) > Committer: root (root@kamailio3.kamailo3@lhsip.com) > Signed: --- [//]: # (END_SECTION 21818f07eadc647bf227fa3ba2ccad0fe81195f5) [//]: # (START_SECTION 0df96f063ed8583a16434c58a09761d65ab1aa45) ### Added support for Roles. Now a dSIPRouter instance can have a Role in the tolopology > Commit: [0df96f063ed8583a16434c58a09761d65ab1aa45](https://github.com/dOpensource/dsiprouter/commit/0df96f063ed8583a16434c58a09761d65ab1aa45) > Date: Tue, 4 Sep 2018 04:40:53 -0400 > Author: root (root@kamailio2.lhsip.com) > Committer: root (root@kamailio2.lhsip.com) > Signed: --- [//]: # (END_SECTION 0df96f063ed8583a16434c58a09761d65ab1aa45) [//]: # (START_SECTION 74fbcd830f062b37579daedb439cd29f8fc3426e) ### Fixed an issue with SSL properties not being pulled corrected from the settings.py file > Commit: [74fbcd830f062b37579daedb439cd29f8fc3426e](https://github.com/dOpensource/dsiprouter/commit/74fbcd830f062b37579daedb439cd29f8fc3426e) > Date: Tue, 4 Sep 2018 03:14:59 -0400 > Author: root (root@kamailio3.kamailo3@lhsip.com) > Committer: root (root@kamailio3.kamailo3@lhsip.com) > Signed: --- [//]: # (END_SECTION 74fbcd830f062b37579daedb439cd29f8fc3426e) [//]: # (START_SECTION 5d43060eae83e900f29ed87ec7ecc7c0970ddf36) ### Fixed an issue with SSL properties not being pulled corrected from the settings.py file > Commit: [5d43060eae83e900f29ed87ec7ecc7c0970ddf36](https://github.com/dOpensource/dsiprouter/commit/5d43060eae83e900f29ed87ec7ecc7c0970ddf36) > Date: Tue, 4 Sep 2018 03:11:02 -0400 > Author: root (root@kamailio3.kamailo3@lhsip.com) > Committer: root (root@kamailio3.kamailo3@lhsip.com) > Signed: --- [//]: # (END_SECTION 5d43060eae83e900f29ed87ec7ecc7c0970ddf36) [//]: # (START_SECTION e8ca9ef732a81609097a40e66cf4595607dc01ad) ### Changes to support single tenant > Commit: [e8ca9ef732a81609097a40e66cf4595607dc01ad](https://github.com/dOpensource/dsiprouter/commit/e8ca9ef732a81609097a40e66cf4595607dc01ad) > Date: Tue, 4 Sep 2018 03:00:25 -0400 > Author: root (root@kamailio3.kamailo3@lhsip.com) > Committer: root (root@kamailio3.kamailo3@lhsip.com) > Signed: --- [//]: # (END_SECTION e8ca9ef732a81609097a40e66cf4595607dc01ad) [//]: # (START_SECTION a227c1647381c5c70524fa225454e67bbf0eab51) ### Fixed #71 - Added support for GUI Session timeout activity Fixed #72 - Cleaned up exception code around database connection > Commit: [a227c1647381c5c70524fa225454e67bbf0eab51](https://github.com/dOpensource/dsiprouter/commit/a227c1647381c5c70524fa225454e67bbf0eab51) > Date: Sun, 2 Sep 2018 14:20:45 +0000 > Author: root (root@demo-dsiprouter.localdomain) > Committer: root (root@demo-dsiprouter.localdomain) > Signed: --- [//]: # (END_SECTION a227c1647381c5c70524fa225454e67bbf0eab51) [//]: # (START_SECTION 4482f38fa394bff1d040d7af6ffc3197c8cc2fe8) ### Freepbx & Flowroute Feature Release v0.51 > Commit: [4482f38fa394bff1d040d7af6ffc3197c8cc2fe8](https://github.com/dOpensource/dsiprouter/commit/4482f38fa394bff1d040d7af6ffc3197c8cc2fe8) > Date: Tue, 28 Aug 2018 23:59:18 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - Resolves #1 - Resolves #65 - -- add pbx registration forwarding - -- add single tenant pbx domain routing - -- add flowroute did importing - -- fix fusionpbx conflicts with domain table - -- add generic multidomain pbx support - -- add syntax highliting in blocks - -- add / optimize css vendor prefixes (autoprefix) - -- add combobox widget to inboundmapping view - -- add new icons for combobox widget - -- improved element disable functions - -- improved error view allowing debug messages - -- add custom domain tables to install script - -- fix nat reply bug in kamailio configs - -- merge new updates into kam44 config file - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION 4482f38fa394bff1d040d7af6ffc3197c8cc2fe8) [//]: # (START_SECTION ae9c8c4c1620295fefa2f6ed6c9204f64e796a5f) ### Updated the logo's > Commit: [ae9c8c4c1620295fefa2f6ed6c9204f64e796a5f](https://github.com/dOpensource/dsiprouter/commit/ae9c8c4c1620295fefa2f6ed6c9204f64e796a5f) > Date: Tue, 28 Aug 2018 14:00:15 +0000 > Author: root (root@dsiprouter-v50-final.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION ae9c8c4c1620295fefa2f6ed6c9204f64e796a5f) [//]: # (START_SECTION 34119ac35b82766fbb66f4a3fad07773c20ee08e) ### Fixed the PBX screen to ensure that ip auth is working, added fusionpbx as the default fusionpbx database username > Commit: [34119ac35b82766fbb66f4a3fad07773c20ee08e](https://github.com/dOpensource/dsiprouter/commit/34119ac35b82766fbb66f4a3fad07773c20ee08e) > Date: Tue, 28 Aug 2018 12:50:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 34119ac35b82766fbb66f4a3fad07773c20ee08e) [//]: # (START_SECTION ac97aa65f331a0007146d31343605d7cff480947) ### Fixed issue with main navigation not showing the the proper color when a navigation button is not clicked > Commit: [ac97aa65f331a0007146d31343605d7cff480947](https://github.com/dOpensource/dsiprouter/commit/ac97aa65f331a0007146d31343605d7cff480947) > Date: Mon, 27 Aug 2018 12:03:09 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION ac97aa65f331a0007146d31343605d7cff480947) [//]: # (START_SECTION 32eb94145b2c89bbcf1ac4059578d99a1905c64b) ### Updated the login screen > Commit: [32eb94145b2c89bbcf1ac4059578d99a1905c64b](https://github.com/dOpensource/dsiprouter/commit/32eb94145b2c89bbcf1ac4059578d99a1905c64b) > Date: Mon, 27 Aug 2018 11:15:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 32eb94145b2c89bbcf1ac4059578d99a1905c64b) [//]: # (START_SECTION 8e1cdf4240c9f196cc69263c247ee7815810b5e8) ### Fixed the issue with curl not returning the external ip address. I changed out the URL that was being used to get the external ip address > Commit: [8e1cdf4240c9f196cc69263c247ee7815810b5e8](https://github.com/dOpensource/dsiprouter/commit/8e1cdf4240c9f196cc69263c247ee7815810b5e8) > Date: Sun, 26 Aug 2018 02:50:35 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 8e1cdf4240c9f196cc69263c247ee7815810b5e8) [//]: # (START_SECTION 159d6005413754434b117140ab19ca173a115ab5) ### Cleaned up a duplicate install function > Commit: [159d6005413754434b117140ab19ca173a115ab5](https://github.com/dOpensource/dsiprouter/commit/159d6005413754434b117140ab19ca173a115ab5) > Date: Fri, 24 Aug 2018 11:56:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 159d6005413754434b117140ab19ca173a115ab5) [//]: # (START_SECTION 9db51f765cdf90024c8ecabba4031d8e5142717f) ### Revert "Revert "Add UI bug fix commits to v0.50"" > Commit: [9db51f765cdf90024c8ecabba4031d8e5142717f](https://github.com/dOpensource/dsiprouter/commit/9db51f765cdf90024c8ecabba4031d8e5142717f) > Date: Thu, 23 Aug 2018 17:00:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 9db51f765cdf90024c8ecabba4031d8e5142717f) [//]: # (START_SECTION f0c36be712f69718f3757c8bf3a349cc75f0b61d) ### Revert "Add UI bug fix commits to v0.50" > Commit: [f0c36be712f69718f3757c8bf3a349cc75f0b61d](https://github.com/dOpensource/dsiprouter/commit/f0c36be712f69718f3757c8bf3a349cc75f0b61d) > Date: Thu, 23 Aug 2018 10:50:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION f0c36be712f69718f3757c8bf3a349cc75f0b61d) [//]: # (START_SECTION d40bb4dea60fcbfae7e84a1e8b2669ab31d04944) ### Updated the logo's > Commit: [d40bb4dea60fcbfae7e84a1e8b2669ab31d04944](https://github.com/dOpensource/dsiprouter/commit/d40bb4dea60fcbfae7e84a1e8b2669ab31d04944) > Date: Tue, 28 Aug 2018 14:00:15 +0000 > Author: root (root@dsiprouter-v50-final.localdomain) > Committer: root (root@dsiprouter-v50-final.localdomain) > Signed: --- [//]: # (END_SECTION d40bb4dea60fcbfae7e84a1e8b2669ab31d04944) [//]: # (START_SECTION 2043e95408976a879bc153e9f72725fa43e79f71) ### Fixed the PBX screen to ensure that ip auth is working, added fusionpbx as the default fusionpbx database username > Commit: [2043e95408976a879bc153e9f72725fa43e79f71](https://github.com/dOpensource/dsiprouter/commit/2043e95408976a879bc153e9f72725fa43e79f71) > Date: Tue, 28 Aug 2018 12:50:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: root (root@dsiprouter-v050.localdomain) > Signed: --- [//]: # (END_SECTION 2043e95408976a879bc153e9f72725fa43e79f71) [//]: # (START_SECTION 8a86749753e83b9516beb9224629cba4c142bf9e) ### Fixed issue with main navigation not showing the the proper color when a navigation button is not clicked > Commit: [8a86749753e83b9516beb9224629cba4c142bf9e](https://github.com/dOpensource/dsiprouter/commit/8a86749753e83b9516beb9224629cba4c142bf9e) > Date: Mon, 27 Aug 2018 12:03:09 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: root (root@dsiprouter-v050.localdomain) > Signed: --- [//]: # (END_SECTION 8a86749753e83b9516beb9224629cba4c142bf9e) [//]: # (START_SECTION ce9ef24adf7c44c087c49b85cd1fb2c1f6fd074b) ### Updated the login screen > Commit: [ce9ef24adf7c44c087c49b85cd1fb2c1f6fd074b](https://github.com/dOpensource/dsiprouter/commit/ce9ef24adf7c44c087c49b85cd1fb2c1f6fd074b) > Date: Mon, 27 Aug 2018 11:15:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: root (root@dsiprouter-v050.localdomain) > Signed: --- [//]: # (END_SECTION ce9ef24adf7c44c087c49b85cd1fb2c1f6fd074b) [//]: # (START_SECTION 34b15687a0ee6fb6e529d3d652b714be9d53c230) ### Fixed the issue with curl not returning the external ip address. I changed out the URL that was being used to get the external ip address > Commit: [34b15687a0ee6fb6e529d3d652b714be9d53c230](https://github.com/dOpensource/dsiprouter/commit/34b15687a0ee6fb6e529d3d652b714be9d53c230) > Date: Sun, 26 Aug 2018 02:50:35 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: root (root@dsiprouter-v050.localdomain) > Signed: --- [//]: # (END_SECTION 34b15687a0ee6fb6e529d3d652b714be9d53c230) [//]: # (START_SECTION a76a8ec50d485591f24f1218773edb766d9feb4e) ### Cleaned up a duplicate install function > Commit: [a76a8ec50d485591f24f1218773edb766d9feb4e](https://github.com/dOpensource/dsiprouter/commit/a76a8ec50d485591f24f1218773edb766d9feb4e) > Date: Fri, 24 Aug 2018 11:56:03 +0000 > Author: root (root@dsiprouter-v050.localdomain) > Committer: root (root@dsiprouter-v050.localdomain) > Signed: --- [//]: # (END_SECTION a76a8ec50d485591f24f1218773edb766d9feb4e) [//]: # (START_SECTION 5ecec325b90e29462174bf36aec44fb3ac57bf13) ### Revert "Revert "Add UI bug fix commits to v0.50"" > Commit: [5ecec325b90e29462174bf36aec44fb3ac57bf13](https://github.com/dOpensource/dsiprouter/commit/5ecec325b90e29462174bf36aec44fb3ac57bf13) > Date: Thu, 23 Aug 2018 17:00:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5ecec325b90e29462174bf36aec44fb3ac57bf13) [//]: # (START_SECTION 62c1ba5ad7c5b74488f9f8c4707504202ecaa498) ### Revert "Add UI bug fix commits to v0.50" > Commit: [62c1ba5ad7c5b74488f9f8c4707504202ecaa498](https://github.com/dOpensource/dsiprouter/commit/62c1ba5ad7c5b74488f9f8c4707504202ecaa498) > Date: Thu, 23 Aug 2018 10:50:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 62c1ba5ad7c5b74488f9f8c4707504202ecaa498) [//]: # (START_SECTION 24af07468760a7d58cafadc94ff648a8d279aa34) ### UI Bug Fixes in v0.50 continued.. > Commit: [24af07468760a7d58cafadc94ff648a8d279aa34](https://github.com/dOpensource/dsiprouter/commit/24af07468760a7d58cafadc94ff648a8d279aa34) > Date: Mon, 13 Aug 2018 17:05:18 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - -- fixed login page styles - -- fixed table styles - -- fixed update on pbx endpoint - -- fixed auth radio toggle - -- fixed uacreg import csv values - -- fixed update on uac table to enable flag - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION 24af07468760a7d58cafadc94ff648a8d279aa34) [//]: # (START_SECTION faeec111a401183da874d0feaaa63104da45f07a) ### UI Bug Fixes in v0.50 > Commit: [faeec111a401183da874d0feaaa63104da45f07a](https://github.com/dOpensource/dsiprouter/commit/faeec111a401183da874d0feaaa63104da45f07a) > Date: Fri, 10 Aug 2018 19:23:30 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - -- fixed toggle button listeners - -- fixed pbx modal listener (not populating) - -- fixed reload button refreshing on ajax call - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION faeec111a401183da874d0feaaa63104da45f07a) [//]: # (START_SECTION eaa5c1d1256c2c700621d7ab9fbefb692217e93c) ### Fix runtime error > Commit: [eaa5c1d1256c2c700621d7ab9fbefb692217e93c](https://github.com/dOpensource/dsiprouter/commit/eaa5c1d1256c2c700621d7ab9fbefb692217e93c) > Date: Thu, 9 Aug 2018 14:07:11 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - -- change default server to use multi-threading - -- fix hanging comma in dsiprouter.py - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION eaa5c1d1256c2c700621d7ab9fbefb692217e93c) [//]: # (START_SECTION 604f8b1ed33a7c3fa93889ed77433fa8396348c2) ### Squash Commits and Merge with Master > Commit: [604f8b1ed33a7c3fa93889ed77433fa8396348c2](https://github.com/dOpensource/dsiprouter/commit/604f8b1ed33a7c3fa93889ed77433fa8396348c2) > Date: Thu, 9 Aug 2018 11:35:31 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: - commit 3eb7182214594dfbbe3701487bb959f0aec4b08d - Merge: 8582913 6bbc7de - Author: Tyler Moore - Date: Thu Aug 9 11:12:24 2018 -0400 - Merge remote-tracking branch 'origin/carrier-modal' into carrier-modal - # Conflicts: - # gui/settings.py - commit 85829134650ee4598f3c42db063b00f2ef16b4e8 - Author: Tyler Moore - Date: Wed Aug 8 22:33:01 2018 -0400 - v.50 Final Commit - Resolves #1 - Resolves #4 - Resolves #6 - Resolves #55 - Resolves #58 - Resolves #62 - Resolves #63 - -- various DB query fixes - -- carrier delete fix - -- debug defaults set for production - -- custom icons added - -- Jquery queries optimized - -- JS library source map files fixed - -- HTTP errors fixed - + resolving IP dynamically - -- HTML errors fixed - + toggle buttons fixed - + query selector shadowing id's fixed - + radio button listeners fixed - + modal nesting fixed - + modal scrolling fixed - + modal styles fixed - -- add exception handling throughout API - -- add endpoint debugging when debug enabled - -- finish carrier group modal features - -- jinja macro additions - -- add gateway group configuration defaults - + dr_gw_lists table - + uacreg table - + dr_gateways table (update) - + address table (update) - -- install script fixes for kamailio config added - -- added group name editing feature - -- add conversion functions - -- change version number - -- many more small bug fixes / tweaks.. see diff - Signed-off-by: Tyler Moore - commit 6bbc7defae59a6da1b8c146370621bb845fb9091 - Author: Tyler Moore - Date: Wed Aug 8 22:33:01 2018 -0400 - v.50 Final Merge - Resolves #1 - Resolves #4 - Resolves #6 - Resolves #55 - Resolves #58 - Resolves #62 - Resolves #63 - -- various DB query fixes - -- carrier delete fix - -- debug defaults set for production - -- custom icons added - -- Jquery queries optimized - -- JS library source map files fixed - -- HTTP errors fixed - + resolving IP dynamically - -- HTML errors fixed - + toggle buttons fixed - + query selector shadowing id's fixed - + radio button listeners fixed - + modal nesting fixed - + modal scrolling fixed - + modal styles fixed - -- add exception handling throughout API - -- add endpoint debugging when debug enabled - -- finish carrier group modal features - -- jinja macro additions - -- add gateway group configuration defaults - + dr_gw_lists table - + uacreg table - + dr_gateways table (update) - + address table (update) - -- install script fixes for kamailio config added - -- added group name editing feature - -- add conversion functions - -- many more small bug fixes / tweaks.. see diff - Signed-off-by: Tyler Moore - commit e14c688bd7b11145061d913a80236da0ad509eb6 - Author: Tyler Moore - Date: Sun Jul 29 16:31:29 2018 -0400 - Feature Addition: Carrier Groups - modifiy / resolves #57 - resolves #60 - resolves #9 - This commit is for v0.50 - https://github.com/dOpensource/dsiprouter/tree/v0.50 - Add new carrier gruop route - Add UAC carrier registration - Add Voxbone carrier to default - Various backend additions - Add support for gw_lists table in DB - Fix table border in GUI - Fix GUI hideen modal / input selection bugs - Fix GUI modal input field data populating - Add notes for frontend improvements - Fix update config file - Add dynamic ip / domain methods - Various other fixes in commit changes - commit 9e0e97755ad3f87b366b162c13761ab5fec21d38 - Author: Tyler Moore - Date: Tue Jul 10 20:05:33 2018 -0400 - [#57] Feature Addition: Unique Domain Name Per PBX - resolves #57 - This commit is for v0.50 https://github.com/dOpensource/dsiprouter/tree/v0.50 - See screenshots in: dsiprouter/docs/images/features/pbx_domain - commit b67161169bbba39abb6327c9cefb1ee28961e743 - Author: root - Date: Mon Jul 2 21:21:48 2018 +0000 - Removed the uk_cfk index - Signed-off-by: Tyler Moore --- [//]: # (END_SECTION 604f8b1ed33a7c3fa93889ed77433fa8396348c2) [//]: # (START_SECTION 72e7c725860c2656f2d24cb850b2535aed6c1f7d) ### Update README.md > Commit: [72e7c725860c2656f2d24cb850b2535aed6c1f7d](https://github.com/dOpensource/dsiprouter/commit/72e7c725860c2656f2d24cb850b2535aed6c1f7d) > Date: Fri, 6 Jul 2018 09:09:59 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 72e7c725860c2656f2d24cb850b2535aed6c1f7d) [//]: # (START_SECTION 65720cf08d35968c63c7b85b67fe7ad4f7c0b07a) ### Update README.md > Commit: [65720cf08d35968c63c7b85b67fe7ad4f7c0b07a](https://github.com/dOpensource/dsiprouter/commit/65720cf08d35968c63c7b85b67fe7ad4f7c0b07a) > Date: Fri, 6 Jul 2018 09:09:05 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 65720cf08d35968c63c7b85b67fe7ad4f7c0b07a) [//]: # (START_SECTION 0244a9e4fd8a186d2a232abe632e75bb33d946ba) ### Update kamailio51_dsiprouter.cfg > Commit: [0244a9e4fd8a186d2a232abe632e75bb33d946ba](https://github.com/dOpensource/dsiprouter/commit/0244a9e4fd8a186d2a232abe632e75bb33d946ba) > Date: Tue, 3 Jul 2018 17:09:44 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0244a9e4fd8a186d2a232abe632e75bb33d946ba) [//]: # (START_SECTION b67161169bbba39abb6327c9cefb1ee28961e743) ### Removed the uk_cfk index > Commit: [b67161169bbba39abb6327c9cefb1ee28961e743](https://github.com/dOpensource/dsiprouter/commit/b67161169bbba39abb6327c9cefb1ee28961e743) > Date: Mon, 2 Jul 2018 21:21:48 +0000 > Author: root (root@dsiprouter-dev.localdomain) > Committer: root (root@dsiprouter-dev.localdomain) > Signed: --- [//]: # (END_SECTION b67161169bbba39abb6327c9cefb1ee28961e743) [//]: # (START_SECTION 057a9c10c0a2da25d237e953f34bb05bf417fc4e) ### Removed the uk_cfk index > Commit: [057a9c10c0a2da25d237e953f34bb05bf417fc4e](https://github.com/dOpensource/dsiprouter/commit/057a9c10c0a2da25d237e953f34bb05bf417fc4e) > Date: Mon, 2 Jul 2018 21:21:48 +0000 > Author: root (root@dsiprouter-dev.localdomain) > Committer: root (root@dsiprouter-dev.localdomain) > Signed: --- [//]: # (END_SECTION 057a9c10c0a2da25d237e953f34bb05bf417fc4e) [//]: # (START_SECTION 7cfe35f51f54199b21541dd5d1d171c11d1bea3b) ### Update README.md > Commit: [7cfe35f51f54199b21541dd5d1d171c11d1bea3b](https://github.com/dOpensource/dsiprouter/commit/7cfe35f51f54199b21541dd5d1d171c11d1bea3b) > Date: Tue, 26 Jun 2018 04:04:27 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7cfe35f51f54199b21541dd5d1d171c11d1bea3b) [//]: # (START_SECTION 90033f6fe351f1862ad13e937442c10814f22ca9) ### Update README.md > Commit: [90033f6fe351f1862ad13e937442c10814f22ca9](https://github.com/dOpensource/dsiprouter/commit/90033f6fe351f1862ad13e937442c10814f22ca9) > Date: Tue, 26 Jun 2018 04:03:13 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 90033f6fe351f1862ad13e937442c10814f22ca9) [//]: # (START_SECTION d98c01c94caa8fb1196ecd99a133f147a9d77e32) ### Update README.md > Commit: [d98c01c94caa8fb1196ecd99a133f147a9d77e32](https://github.com/dOpensource/dsiprouter/commit/d98c01c94caa8fb1196ecd99a133f147a9d77e32) > Date: Tue, 26 Jun 2018 03:57:51 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION d98c01c94caa8fb1196ecd99a133f147a9d77e32) [//]: # (START_SECTION 533adb9aed63672d170d8e7c75c8ffb694249d10) ### Fixed the dSIPRouter logo > Commit: [533adb9aed63672d170d8e7c75c8ffb694249d10](https://github.com/dOpensource/dsiprouter/commit/533adb9aed63672d170d8e7c75c8ffb694249d10) > Date: Sun, 24 Jun 2018 23:47:17 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION 533adb9aed63672d170d8e7c75c8ffb694249d10) [//]: # (START_SECTION eb69b92f8964cf16740d8a4c6cc86f7c40e2a613) ### Removed install script logic out for right now > Commit: [eb69b92f8964cf16740d8a4c6cc86f7c40e2a613](https://github.com/dOpensource/dsiprouter/commit/eb69b92f8964cf16740d8a4c6cc86f7c40e2a613) > Date: Sun, 24 Jun 2018 22:37:43 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION eb69b92f8964cf16740d8a4c6cc86f7c40e2a613) [//]: # (START_SECTION fef2d3bf8aeffef583fc9f083e2fa5b7b2fec7b6) ### Fixed the script > Commit: [fef2d3bf8aeffef583fc9f083e2fa5b7b2fec7b6](https://github.com/dOpensource/dsiprouter/commit/fef2d3bf8aeffef583fc9f083e2fa5b7b2fec7b6) > Date: Sun, 24 Jun 2018 22:21:12 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION fef2d3bf8aeffef583fc9f083e2fa5b7b2fec7b6) [//]: # (START_SECTION 401acdfed4df133a6a02e753def5aa6c5283b630) ### Added dSIP ascii logo after the installation process > Commit: [401acdfed4df133a6a02e753def5aa6c5283b630](https://github.com/dOpensource/dsiprouter/commit/401acdfed4df133a6a02e753def5aa6c5283b630) > Date: Sun, 24 Jun 2018 22:19:51 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION 401acdfed4df133a6a02e753def5aa6c5283b630) [//]: # (START_SECTION a737580886187327c2e34c73d21c14fb054f82bc) ### Fixed an issue with the function that added the firewall rule > Commit: [a737580886187327c2e34c73d21c14fb054f82bc](https://github.com/dOpensource/dsiprouter/commit/a737580886187327c2e34c73d21c14fb054f82bc) > Date: Sat, 23 Jun 2018 00:02:24 +0000 > Author: root (root@p2.detroitpbx.com) > Committer: root (root@p2.detroitpbx.com) > Signed: --- [//]: # (END_SECTION a737580886187327c2e34c73d21c14fb054f82bc) [//]: # (START_SECTION e846c2ed168aa164bae78bac7a101d79d2728c0d) ### Fixed issues to support Domain Routing with FusionPBX and to support hosting images for endpoint devices like the Polycom > Commit: [e846c2ed168aa164bae78bac7a101d79d2728c0d](https://github.com/dOpensource/dsiprouter/commit/e846c2ed168aa164bae78bac7a101d79d2728c0d) > Date: Fri, 22 Jun 2018 15:57:56 +0000 > Author: root (root@p1.detrotpbx.com) > Committer: root (root@p1.detrotpbx.com) > Signed: --- [//]: # (END_SECTION e846c2ed168aa164bae78bac7a101d79d2728c0d) [//]: # (START_SECTION 1f4957ca82af70f81849d113ee848db38f37fff0) ### Added changed to support proper BYE propagation when using Domain Routing with FusionPBX > Commit: [1f4957ca82af70f81849d113ee848db38f37fff0](https://github.com/dOpensource/dsiprouter/commit/1f4957ca82af70f81849d113ee848db38f37fff0) > Date: Mon, 18 Jun 2018 01:00:15 +0000 > Author: root (root@p1.detrotpbx.com) > Committer: root (root@p1.detrotpbx.com) > Signed: --- [//]: # (END_SECTION 1f4957ca82af70f81849d113ee848db38f37fff0) [//]: # (START_SECTION 40b13f23a7fa7b0a9fc81002619cddc98019d1c2) ### Fixed an issue with a missing compiler directive and support for UPDATE SIP messages > Commit: [40b13f23a7fa7b0a9fc81002619cddc98019d1c2](https://github.com/dOpensource/dsiprouter/commit/40b13f23a7fa7b0a9fc81002619cddc98019d1c2) > Date: Sun, 17 Jun 2018 02:18:09 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION 40b13f23a7fa7b0a9fc81002619cddc98019d1c2) [//]: # (START_SECTION cc021b41837dcf733b79d92a2dfb31a63af81451) ### Disabled server NAT by default > Commit: [cc021b41837dcf733b79d92a2dfb31a63af81451](https://github.com/dOpensource/dsiprouter/commit/cc021b41837dcf733b79d92a2dfb31a63af81451) > Date: Sat, 16 Jun 2018 09:39:13 +0000 > Author: root (root@ip-172-31-53-160.ec2.internal) > Committer: root (root@ip-172-31-53-160.ec2.internal) > Signed: --- [//]: # (END_SECTION cc021b41837dcf733b79d92a2dfb31a63af81451) [//]: # (START_SECTION 4451243dad5172df0253b01c6cd4215859a4fb29) ### Fixed issues with SERVERNAT feature > Commit: [4451243dad5172df0253b01c6cd4215859a4fb29](https://github.com/dOpensource/dsiprouter/commit/4451243dad5172df0253b01c6cd4215859a4fb29) > Date: Thu, 14 Jun 2018 00:58:41 -0500 > Author: Mack (mack@dopensource.com) > Committer: Mack (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4451243dad5172df0253b01c6cd4215859a4fb29) [//]: # (START_SECTION ae4bccd75b75d6bf800cfeb8444788803c3ade2a) ### Fixed an issue with Outbound routes > Commit: [ae4bccd75b75d6bf800cfeb8444788803c3ade2a](https://github.com/dOpensource/dsiprouter/commit/ae4bccd75b75d6bf800cfeb8444788803c3ade2a) > Date: Wed, 13 Jun 2018 03:42:36 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION ae4bccd75b75d6bf800cfeb8444788803c3ade2a) [//]: # (START_SECTION c4f631d185987e98b17243f406af58941b0c0a64) ### Adding the javasript file for bootstrap validation > Commit: [c4f631d185987e98b17243f406af58941b0c0a64](https://github.com/dOpensource/dsiprouter/commit/c4f631d185987e98b17243f406af58941b0c0a64) > Date: Wed, 13 Jun 2018 07:18:43 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION c4f631d185987e98b17243f406af58941b0c0a64) [//]: # (START_SECTION e51f3bbcc586e59b0e7712448f9e9e1587d76b64) ### Fixed some issues with Javascript validation > Commit: [e51f3bbcc586e59b0e7712448f9e9e1587d76b64](https://github.com/dOpensource/dsiprouter/commit/e51f3bbcc586e59b0e7712448f9e9e1587d76b64) > Date: Tue, 12 Jun 2018 20:16:07 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION e51f3bbcc586e59b0e7712448f9e9e1587d76b64) [//]: # (START_SECTION 247a804eef2191cf740e6c01ef03ab7bdaaca0f7) ### Fixed the rtpengine parameter that specifies the protocol used to communicate between Kamailio and RTPEngine > Commit: [247a804eef2191cf740e6c01ef03ab7bdaaca0f7](https://github.com/dOpensource/dsiprouter/commit/247a804eef2191cf740e6c01ef03ab7bdaaca0f7) > Date: Tue, 12 Jun 2018 14:36:58 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 247a804eef2191cf740e6c01ef03ab7bdaaca0f7) [//]: # (START_SECTION 99eb299b51fa3a3e2d925472900e3c55ea2d545a) ### Fixes #44 issues with installer and logrotate > Commit: [99eb299b51fa3a3e2d925472900e3c55ea2d545a](https://github.com/dOpensource/dsiprouter/commit/99eb299b51fa3a3e2d925472900e3c55ea2d545a) > Date: Tue, 12 Jun 2018 14:16:22 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 99eb299b51fa3a3e2d925472900e3c55ea2d545a) [//]: # (START_SECTION afdbb834d28964461afeb3059b2363088692311a) ### Fixed issue with install of SERVERNET > Commit: [afdbb834d28964461afeb3059b2363088692311a](https://github.com/dOpensource/dsiprouter/commit/afdbb834d28964461afeb3059b2363088692311a) > Date: Tue, 12 Jun 2018 13:02:31 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION afdbb834d28964461afeb3059b2363088692311a) [//]: # (START_SECTION c087c50b9350c2a5e38107b9b37b7ea192c618c0) ### Added the 0.41 version > Commit: [c087c50b9350c2a5e38107b9b37b7ea192c618c0](https://github.com/dOpensource/dsiprouter/commit/c087c50b9350c2a5e38107b9b37b7ea192c618c0) > Date: Tue, 12 Jun 2018 12:05:33 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION c087c50b9350c2a5e38107b9b37b7ea192c618c0) [//]: # (START_SECTION 26b951e359896d96a4ce11cc279d26de9dff854d) ### Fixes 51 - Fixed the update logic when an existing LCR prefix is already defined, but you want to update it > Commit: [26b951e359896d96a4ce11cc279d26de9dff854d](https://github.com/dOpensource/dsiprouter/commit/26b951e359896d96a4ce11cc279d26de9dff854d) > Date: Sun, 10 Jun 2018 22:54:09 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION 26b951e359896d96a4ce11cc279d26de9dff854d) [//]: # (START_SECTION c877002e29b69b5a1e5d54318317124e8a4d66bc) ### Added some comments and a record_route() when routing to PBX's > Commit: [c877002e29b69b5a1e5d54318317124e8a4d66bc](https://github.com/dOpensource/dsiprouter/commit/c877002e29b69b5a1e5d54318317124e8a4d66bc) > Date: Sun, 10 Jun 2018 21:43:50 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION c877002e29b69b5a1e5d54318317124e8a4d66bc) [//]: # (START_SECTION b8f206a19ca75c25914dd7f7c1a246d9404dc235) ### Changed the URI to /provision > Commit: [b8f206a19ca75c25914dd7f7c1a246d9404dc235](https://github.com/dOpensource/dsiprouter/commit/b8f206a19ca75c25914dd7f7c1a246d9404dc235) > Date: Sun, 10 Jun 2018 17:07:25 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION b8f206a19ca75c25914dd7f7c1a246d9404dc235) [//]: # (START_SECTION e4413eee1fb2a137ff9628fe4cf7863c34d199e6) ### Fixed an issue that was preventing the docker engine to install properly. > Commit: [e4413eee1fb2a137ff9628fe4cf7863c34d199e6](https://github.com/dOpensource/dsiprouter/commit/e4413eee1fb2a137ff9628fe4cf7863c34d199e6) > Date: Fri, 8 Jun 2018 19:01:35 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION e4413eee1fb2a137ff9628fe4cf7863c34d199e6) [//]: # (START_SECTION ce18ec722cbd8e69d08178ef594768ee10afdc68) ### Fixed #51 - Added more exception handling to handle updates > Commit: [ce18ec722cbd8e69d08178ef594768ee10afdc68](https://github.com/dOpensource/dsiprouter/commit/ce18ec722cbd8e69d08178ef594768ee10afdc68) > Date: Wed, 6 Jun 2018 18:18:13 -0400 > Author: root (root@siprtr-1.mercury.net) > Committer: root (root@siprtr-1.mercury.net) > Signed: --- [//]: # (END_SECTION ce18ec722cbd8e69d08178ef594768ee10afdc68) [//]: # (START_SECTION 2efb34fed0631b19312fc14e958eb79520e3066a) ### Fixes #52 - Added iptables-save to the list of steps needed to active FusionPBX support. Without this option the iptables rule will not be added during the next reboot > Commit: [2efb34fed0631b19312fc14e958eb79520e3066a](https://github.com/dOpensource/dsiprouter/commit/2efb34fed0631b19312fc14e958eb79520e3066a) > Date: Tue, 5 Jun 2018 11:24:11 +0000 > Author: root (root@dsiprouter-v0.41-dev) > Committer: root (root@dsiprouter-v0.41-dev) > Signed: --- [//]: # (END_SECTION 2efb34fed0631b19312fc14e958eb79520e3066a) [//]: # (START_SECTION 68eceda6b12cb556d57d9a37387be9bc566ec353) ### Fixes #51 - The update logic for Outbound Routes was refactored > Commit: [68eceda6b12cb556d57d9a37387be9bc566ec353](https://github.com/dOpensource/dsiprouter/commit/68eceda6b12cb556d57d9a37387be9bc566ec353) > Date: Tue, 5 Jun 2018 07:17:27 -0400 > Author: root (root@siprtr-1.mercury.net) > Committer: root (root@siprtr-1.mercury.net) > Signed: --- [//]: # (END_SECTION 68eceda6b12cb556d57d9a37387be9bc566ec353) [//]: # (START_SECTION eb7bce74395a989ffa97021d37ac6876a6bf0211) ### Update README.md > Commit: [eb7bce74395a989ffa97021d37ac6876a6bf0211](https://github.com/dOpensource/dsiprouter/commit/eb7bce74395a989ffa97021d37ac6876a6bf0211) > Date: Sun, 27 May 2018 19:44:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eb7bce74395a989ffa97021d37ac6876a6bf0211) [//]: # (START_SECTION 4e481da4931c9dc31ddf420a78640910ebc88724) ### Update README.md > Commit: [4e481da4931c9dc31ddf420a78640910ebc88724](https://github.com/dOpensource/dsiprouter/commit/4e481da4931c9dc31ddf420a78640910ebc88724) > Date: Sun, 27 May 2018 19:42:49 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4e481da4931c9dc31ddf420a78640910ebc88724) [//]: # (START_SECTION 23d2d38febdcd7c166dbc3e0c54f6af0afe9580d) ### Fixes #49 - SIP OPTION messages will be handled by only replying to them is the source ip address is a defined carrier or pbx/endpoint > Commit: [23d2d38febdcd7c166dbc3e0c54f6af0afe9580d](https://github.com/dOpensource/dsiprouter/commit/23d2d38febdcd7c166dbc3e0c54f6af0afe9580d) > Date: Sun, 27 May 2018 07:39:59 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 23d2d38febdcd7c166dbc3e0c54f6af0afe9580d) [//]: # (START_SECTION ecada9a3a8c3bfb7f046a529511954787cfb374c) ### Added configuration files for logrotate so that log files are rotated > Commit: [ecada9a3a8c3bfb7f046a529511954787cfb374c](https://github.com/dOpensource/dsiprouter/commit/ecada9a3a8c3bfb7f046a529511954787cfb374c) > Date: Tue, 22 May 2018 15:14:56 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION ecada9a3a8c3bfb7f046a529511954787cfb374c) [//]: # (START_SECTION e9557065aec29b9b749007d8ce2e554bd667e385) ### Fixed an issue with dsiprouter command line > Commit: [e9557065aec29b9b749007d8ce2e554bd667e385](https://github.com/dOpensource/dsiprouter/commit/e9557065aec29b9b749007d8ce2e554bd667e385) > Date: Mon, 21 May 2018 11:46:15 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e9557065aec29b9b749007d8ce2e554bd667e385) [//]: # (START_SECTION cff11fb0915558adc02688aba563ad741b48bedc) ### Update README.md > Commit: [cff11fb0915558adc02688aba563ad741b48bedc](https://github.com/dOpensource/dsiprouter/commit/cff11fb0915558adc02688aba563ad741b48bedc) > Date: Thu, 17 May 2018 10:36:24 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cff11fb0915558adc02688aba563ad741b48bedc) [//]: # (START_SECTION 1e82263c7d8a89158791b024d8b09385a4a14c29) ### Fixed an error with the RTPEngine install > Commit: [1e82263c7d8a89158791b024d8b09385a4a14c29](https://github.com/dOpensource/dsiprouter/commit/1e82263c7d8a89158791b024d8b09385a4a14c29) > Date: Thu, 17 May 2018 03:40:09 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 1e82263c7d8a89158791b024d8b09385a4a14c29) [//]: # (START_SECTION 24e6db8e5157a6398a937aeef764fd3aba2eaad6) ### Set RTPEngine to start after it's installed > Commit: [24e6db8e5157a6398a937aeef764fd3aba2eaad6](https://github.com/dOpensource/dsiprouter/commit/24e6db8e5157a6398a937aeef764fd3aba2eaad6) > Date: Thu, 17 May 2018 03:29:12 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 24e6db8e5157a6398a937aeef764fd3aba2eaad6) [//]: # (START_SECTION 3dbe7b98c7ae4d59ca0228130a5f1628a9bad658) ### Fixed the configuration file for setting up RTP Engine on Debian > Commit: [3dbe7b98c7ae4d59ca0228130a5f1628a9bad658](https://github.com/dOpensource/dsiprouter/commit/3dbe7b98c7ae4d59ca0228130a5f1628a9bad658) > Date: Thu, 17 May 2018 03:09:46 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 3dbe7b98c7ae4d59ca0228130a5f1628a9bad658) [//]: # (START_SECTION 0695c19827c5c792e8be0e3df05f14c23922a318) ### Update README.md > Commit: [0695c19827c5c792e8be0e3df05f14c23922a318](https://github.com/dOpensource/dsiprouter/commit/0695c19827c5c792e8be0e3df05f14c23922a318) > Date: Thu, 17 May 2018 07:11:48 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0695c19827c5c792e8be0e3df05f14c23922a318) [//]: # (START_SECTION eea952b11083b9a651e568605fca196bd92ebe12) ### Update README.md > Commit: [eea952b11083b9a651e568605fca196bd92ebe12](https://github.com/dOpensource/dsiprouter/commit/eea952b11083b9a651e568605fca196bd92ebe12) > Date: Thu, 17 May 2018 07:07:40 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eea952b11083b9a651e568605fca196bd92ebe12) [//]: # (START_SECTION df392036e356dfe86b4941f576df8d69db7f75ee) ### Update README.md > Commit: [df392036e356dfe86b4941f576df8d69db7f75ee](https://github.com/dOpensource/dsiprouter/commit/df392036e356dfe86b4941f576df8d69db7f75ee) > Date: Wed, 16 May 2018 10:40:41 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION df392036e356dfe86b4941f576df8d69db7f75ee) [//]: # (START_SECTION 5952b9b095b21b5e7b98375ec0ca9eba63a5c6fc) ### Add files via upload > Commit: [5952b9b095b21b5e7b98375ec0ca9eba63a5c6fc](https://github.com/dOpensource/dsiprouter/commit/5952b9b095b21b5e7b98375ec0ca9eba63a5c6fc) > Date: Wed, 16 May 2018 10:39:42 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5952b9b095b21b5e7b98375ec0ca9eba63a5c6fc) [//]: # (START_SECTION 92d22847128a8ce2ac8c3e0b202400cf3d4bea16) ### Update README.md > Commit: [92d22847128a8ce2ac8c3e0b202400cf3d4bea16](https://github.com/dOpensource/dsiprouter/commit/92d22847128a8ce2ac8c3e0b202400cf3d4bea16) > Date: Wed, 16 May 2018 10:37:01 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 92d22847128a8ce2ac8c3e0b202400cf3d4bea16) [//]: # (START_SECTION 685a01df3f8e8b610704515029fc493d11b1b3af) ### Update README.md > Commit: [685a01df3f8e8b610704515029fc493d11b1b3af](https://github.com/dOpensource/dsiprouter/commit/685a01df3f8e8b610704515029fc493d11b1b3af) > Date: Wed, 16 May 2018 10:32:24 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 685a01df3f8e8b610704515029fc493d11b1b3af) [//]: # (START_SECTION 7ec83e8664298ec47f8c88b4860f31e6c34b7652) ### Update README.md > Commit: [7ec83e8664298ec47f8c88b4860f31e6c34b7652](https://github.com/dOpensource/dsiprouter/commit/7ec83e8664298ec47f8c88b4860f31e6c34b7652) > Date: Wed, 16 May 2018 10:19:35 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7ec83e8664298ec47f8c88b4860f31e6c34b7652) [//]: # (START_SECTION f1dc57887b223f9379e831583629734dd9786eaf) ### Fixed an issue with username/password auth Fixes #39 > Commit: [f1dc57887b223f9379e831583629734dd9786eaf](https://github.com/dOpensource/dsiprouter/commit/f1dc57887b223f9379e831583629734dd9786eaf) > Date: Wed, 16 May 2018 07:40:10 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION f1dc57887b223f9379e831583629734dd9786eaf) [//]: # (START_SECTION ec42725b41311be22dc6c4834763584a88382a5c) ### New Logo and GUI Fixes - Fixes #40 > Commit: [ec42725b41311be22dc6c4834763584a88382a5c](https://github.com/dOpensource/dsiprouter/commit/ec42725b41311be22dc6c4834763584a88382a5c) > Date: Wed, 16 May 2018 07:16:08 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION ec42725b41311be22dc6c4834763584a88382a5c) [//]: # (START_SECTION 537a9c2853e72dcade24a8cacbc095a1395ab5ba) ### Fixed the csv file so that each carrier contains a name: in the tags/notes column. This is used to manage the Gateways Fixes #41 > Commit: [537a9c2853e72dcade24a8cacbc095a1395ab5ba](https://github.com/dOpensource/dsiprouter/commit/537a9c2853e72dcade24a8cacbc095a1395ab5ba) > Date: Tue, 15 May 2018 23:04:42 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 537a9c2853e72dcade24a8cacbc095a1395ab5ba) [//]: # (START_SECTION d9bbc6e79b13a20b7f760d337bd8bbc59ca4b7ab) ### Added record routes when calling outbound via carriers to ensure that the BYE is routed back throught Kamailio > Commit: [d9bbc6e79b13a20b7f760d337bd8bbc59ca4b7ab](https://github.com/dOpensource/dsiprouter/commit/d9bbc6e79b13a20b7f760d337bd8bbc59ca4b7ab) > Date: Tue, 15 May 2018 22:59:36 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION d9bbc6e79b13a20b7f760d337bd8bbc59ca4b7ab) [//]: # (START_SECTION 30a055177292dcc3e3b23c99d469984b478d04d4) ### Update address.csv > Commit: [30a055177292dcc3e3b23c99d469984b478d04d4](https://github.com/dOpensource/dsiprouter/commit/30a055177292dcc3e3b23c99d469984b478d04d4) > Date: Tue, 15 May 2018 23:23:00 +0200 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 30a055177292dcc3e3b23c99d469984b478d04d4) [//]: # (START_SECTION 15f0f27b64f9f6bc5331945397d4b59b7fd3567c) ### Add Support for FusionPBX Provisioning Fixes #26 > Commit: [15f0f27b64f9f6bc5331945397d4b59b7fd3567c](https://github.com/dOpensource/dsiprouter/commit/15f0f27b64f9f6bc5331945397d4b59b7fd3567c) > Date: Tue, 15 May 2018 20:17:39 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 15f0f27b64f9f6bc5331945397d4b59b7fd3567c) [//]: # (START_SECTION 2f087482ff0428518cde1258be49d247180d26a7) ### Added the threaded option to allow the service to startup in multi-threaded mode > Commit: [2f087482ff0428518cde1258be49d247180d26a7](https://github.com/dOpensource/dsiprouter/commit/2f087482ff0428518cde1258be49d247180d26a7) > Date: Sun, 13 May 2018 23:26:03 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 2f087482ff0428518cde1258be49d247180d26a7) [//]: # (START_SECTION 5942ccef53964881d4497570c3d6c902eb906e6b) ### Fixed an issue that prevented the PBX password from being updated > Commit: [5942ccef53964881d4497570c3d6c902eb906e6b](https://github.com/dOpensource/dsiprouter/commit/5942ccef53964881d4497570c3d6c902eb906e6b) > Date: Wed, 9 May 2018 16:22:10 -0400 > Author: root (release@dopensource.com) > Committer: root (release@dopensource.com) > Signed: --- [//]: # (END_SECTION 5942ccef53964881d4497570c3d6c902eb906e6b) [//]: # (START_SECTION bc07bb51a36a32ade8d3ffda0ddec66c04283934) ### Added support for automatically adding the PBX ip, port and transport when it registers. This means that it automatically gets added to the drouting.gateway table and the table is reloaded in real time > Commit: [bc07bb51a36a32ade8d3ffda0ddec66c04283934](https://github.com/dOpensource/dsiprouter/commit/bc07bb51a36a32ade8d3ffda0ddec66c04283934) > Date: Sun, 29 Apr 2018 18:32:49 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION bc07bb51a36a32ade8d3ffda0ddec66c04283934) [//]: # (START_SECTION cb161110adb2fcedcdf9ce0cce875037b1b4cabb) ### Update settings.py > Commit: [cb161110adb2fcedcdf9ce0cce875037b1b4cabb](https://github.com/dOpensource/dsiprouter/commit/cb161110adb2fcedcdf9ce0cce875037b1b4cabb) > Date: Tue, 24 Apr 2018 16:38:47 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION cb161110adb2fcedcdf9ce0cce875037b1b4cabb) [//]: # (START_SECTION 89ff2191ca0782707302f6978f66261be38e8847) ### Change the description of the default outbound routes > Commit: [89ff2191ca0782707302f6978f66261be38e8847](https://github.com/dOpensource/dsiprouter/commit/89ff2191ca0782707302f6978f66261be38e8847) > Date: Tue, 24 Apr 2018 15:59:53 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 89ff2191ca0782707302f6978f66261be38e8847) [//]: # (START_SECTION bdfc4db674b9b4524bf91fcf84ff97f98e7472c6) ### Fixed an issue with reloading the htable that support the new outbound route logic > Commit: [bdfc4db674b9b4524bf91fcf84ff97f98e7472c6](https://github.com/dOpensource/dsiprouter/commit/bdfc4db674b9b4524bf91fcf84ff97f98e7472c6) > Date: Mon, 23 Apr 2018 07:10:18 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION bdfc4db674b9b4524bf91fcf84ff97f98e7472c6) [//]: # (START_SECTION 77ac196b2a3270f7ad259f1aa5602b02695d3e07) ### Added a flag to make te built-in web server multi-threaded > Commit: [77ac196b2a3270f7ad259f1aa5602b02695d3e07](https://github.com/dOpensource/dsiprouter/commit/77ac196b2a3270f7ad259f1aa5602b02695d3e07) > Date: Sat, 14 Apr 2018 08:06:26 -0400 > Author: root (release@dopensource.com) > Committer: root (release@dopensource.com) > Signed: --- [//]: # (END_SECTION 77ac196b2a3270f7ad259f1aa5602b02695d3e07) [//]: # (START_SECTION 24a9407b2a713ba31bc76c8958cc92cf993ee98e) ### Fixed issue with update and save for LCR > Commit: [24a9407b2a713ba31bc76c8958cc92cf993ee98e](https://github.com/dOpensource/dsiprouter/commit/24a9407b2a713ba31bc76c8958cc92cf993ee98e) > Date: Fri, 6 Apr 2018 11:48:41 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 24a9407b2a713ba31bc76c8958cc92cf993ee98e) [//]: # (START_SECTION 8fe5ea6b04a20ebadbb3be2f439c63a5257c0c07) ### Completed the development of some light weight LCR funcationality > Commit: [8fe5ea6b04a20ebadbb3be2f439c63a5257c0c07](https://github.com/dOpensource/dsiprouter/commit/8fe5ea6b04a20ebadbb3be2f439c63a5257c0c07) > Date: Fri, 6 Apr 2018 03:34:32 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 8fe5ea6b04a20ebadbb3be2f439c63a5257c0c07) [//]: # (START_SECTION b634c0cc239500f8420d84c2751db1b1a44b6fb8) ### Added support for support LCR from a Kamailio prespective > Commit: [b634c0cc239500f8420d84c2751db1b1a44b6fb8](https://github.com/dOpensource/dsiprouter/commit/b634c0cc239500f8420d84c2751db1b1a44b6fb8) > Date: Thu, 5 Apr 2018 05:01:00 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION b634c0cc239500f8420d84c2751db1b1a44b6fb8) [//]: # (START_SECTION 63da09ec9d97a23a789d99601de00f8445a6ece6) ### add header check feature in teleblock route > Commit: [63da09ec9d97a23a789d99601de00f8445a6ece6](https://github.com/dOpensource/dsiprouter/commit/63da09ec9d97a23a789d99601de00f8445a6ece6) > Date: Sun, 1 Apr 2018 21:32:16 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 63da09ec9d97a23a789d99601de00f8445a6ece6) [//]: # (START_SECTION 321e790f57468ae335d2a0fd321c441f3100f38a) ### add current work on dynamic routing and LCR features > Commit: [321e790f57468ae335d2a0fd321c441f3100f38a](https://github.com/dOpensource/dsiprouter/commit/321e790f57468ae335d2a0fd321c441f3100f38a) > Date: Sun, 1 Apr 2018 21:03:13 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 321e790f57468ae335d2a0fd321c441f3100f38a) [//]: # (START_SECTION 87742ca2d658a60048d1c61f8d410b625021af7d) ### reformat messy code, fix html errors throughout, complete overhaul of front-end, add multiple outbound routes feature added, started adding backend capablities for dynamic routing, fixed 200 reply bug (endpoint now waits for 200 from carrier) > Commit: [87742ca2d658a60048d1c61f8d410b625021af7d](https://github.com/dOpensource/dsiprouter/commit/87742ca2d658a60048d1c61f8d410b625021af7d) > Date: Tue, 27 Mar 2018 20:01:23 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 87742ca2d658a60048d1c61f8d410b625021af7d) [//]: # (START_SECTION fbaa02fd24237c9c4d809f64581696b35784943b) ### Fixed issue with rtpengine not starting after installation > Commit: [fbaa02fd24237c9c4d809f64581696b35784943b](https://github.com/dOpensource/dsiprouter/commit/fbaa02fd24237c9c4d809f64581696b35784943b) > Date: Sat, 24 Mar 2018 22:43:13 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION fbaa02fd24237c9c4d809f64581696b35784943b) [//]: # (START_SECTION bc813f49869bc621d16585d7648ae5e2c4bfe81a) ### Fixed typo with VI carriers > Commit: [bc813f49869bc621d16585d7648ae5e2c4bfe81a](https://github.com/dOpensource/dsiprouter/commit/bc813f49869bc621d16585d7648ae5e2c4bfe81a) > Date: Sat, 24 Mar 2018 20:03:01 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION bc813f49869bc621d16585d7648ae5e2c4bfe81a) [//]: # (START_SECTION 1a6728d9a5a2bc8e6f3febf66414c4dd6562df11) ### Added a fix to resolve firewall issues > Commit: [1a6728d9a5a2bc8e6f3febf66414c4dd6562df11](https://github.com/dOpensource/dsiprouter/commit/1a6728d9a5a2bc8e6f3febf66414c4dd6562df11) > Date: Sat, 24 Mar 2018 19:59:13 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 1a6728d9a5a2bc8e6f3febf66414c4dd6562df11) [//]: # (START_SECTION 4389700a37b7c41741ad9e6f25743072c3b41889) ### Fixed an issue that prevented port 5060 from being added and removed during the install and uninstall process, respectively > Commit: [4389700a37b7c41741ad9e6f25743072c3b41889](https://github.com/dOpensource/dsiprouter/commit/4389700a37b7c41741ad9e6f25743072c3b41889) > Date: Sat, 24 Mar 2018 18:52:49 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 4389700a37b7c41741ad9e6f25743072c3b41889) [//]: # (START_SECTION 3c3ba20ad9f923cfd9be6fec74a5100fcee432ed) ### fixed uninstall cmd, add support for debian jessie dsiprouter installation > Commit: [3c3ba20ad9f923cfd9be6fec74a5100fcee432ed](https://github.com/dOpensource/dsiprouter/commit/3c3ba20ad9f923cfd9be6fec74a5100fcee432ed) > Date: Sat, 24 Mar 2018 02:17:16 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 3c3ba20ad9f923cfd9be6fec74a5100fcee432ed) [//]: # (START_SECTION 0cebcae69d28faebb58e1f37f9c9c16ee8b097b1) ### Fixed issues with Deb 8.9 installer > Commit: [0cebcae69d28faebb58e1f37f9c9c16ee8b097b1](https://github.com/dOpensource/dsiprouter/commit/0cebcae69d28faebb58e1f37f9c9c16ee8b097b1) > Date: Sat, 24 Mar 2018 12:55:35 +1100 > Author: root (root@debian.vixtel.com.au) > Committer: root (root@debian.vixtel.com.au) > Signed: --- [//]: # (END_SECTION 0cebcae69d28faebb58e1f37f9c9c16ee8b097b1) [//]: # (START_SECTION f3bd49a0c980055d373007a7eca60aa24aa1464c) ### Fixed issues with Deb 8.9 installer > Commit: [f3bd49a0c980055d373007a7eca60aa24aa1464c](https://github.com/dOpensource/dsiprouter/commit/f3bd49a0c980055d373007a7eca60aa24aa1464c) > Date: Sat, 24 Mar 2018 12:54:22 +1100 > Author: root (root@debian.vixtel.com.au) > Committer: root (root@debian.vixtel.com.au) > Signed: --- [//]: # (END_SECTION f3bd49a0c980055d373007a7eca60aa24aa1464c) [//]: # (START_SECTION a72d5ace87979b2b1647ef627be4ea8cc40d562d) ### fix broken debian jessie installation issues > Commit: [a72d5ace87979b2b1647ef627be4ea8cc40d562d](https://github.com/dOpensource/dsiprouter/commit/a72d5ace87979b2b1647ef627be4ea8cc40d562d) > Date: Sat, 24 Mar 2018 01:16:53 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION a72d5ace87979b2b1647ef627be4ea8cc40d562d) [//]: # (START_SECTION 3f6ea6ed37ee0948cfa574897261f181ab0af0f8) ### Update README.md > Commit: [3f6ea6ed37ee0948cfa574897261f181ab0af0f8](https://github.com/dOpensource/dsiprouter/commit/3f6ea6ed37ee0948cfa574897261f181ab0af0f8) > Date: Fri, 23 Mar 2018 06:31:35 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f6ea6ed37ee0948cfa574897261f181ab0af0f8) [//]: # (START_SECTION 3f8691614b484f0060b72cf67442bc1a1b79844b) ### Update README.md > Commit: [3f8691614b484f0060b72cf67442bc1a1b79844b](https://github.com/dOpensource/dsiprouter/commit/3f8691614b484f0060b72cf67442bc1a1b79844b) > Date: Fri, 23 Mar 2018 06:30:27 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3f8691614b484f0060b72cf67442bc1a1b79844b) [//]: # (START_SECTION f19f87d7d4325eedb4a266db47cf9a495f6e1ca2) ### Update README.md > Commit: [f19f87d7d4325eedb4a266db47cf9a495f6e1ca2](https://github.com/dOpensource/dsiprouter/commit/f19f87d7d4325eedb4a266db47cf9a495f6e1ca2) > Date: Fri, 23 Mar 2018 06:30:03 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION f19f87d7d4325eedb4a266db47cf9a495f6e1ca2) [//]: # (START_SECTION fa9806ab3573a9c353a222da85b041fcc1d87a7c) ### Update README.md > Commit: [fa9806ab3573a9c353a222da85b041fcc1d87a7c](https://github.com/dOpensource/dsiprouter/commit/fa9806ab3573a9c353a222da85b041fcc1d87a7c) > Date: Fri, 23 Mar 2018 06:25:38 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fa9806ab3573a9c353a222da85b041fcc1d87a7c) [//]: # (START_SECTION 7f96b230c12f91c3f83238608fb07ff53a32c08c) ### Updated README and validated the install on Debian 9.4 (Stretch) > Commit: [7f96b230c12f91c3f83238608fb07ff53a32c08c](https://github.com/dOpensource/dsiprouter/commit/7f96b230c12f91c3f83238608fb07ff53a32c08c) > Date: Fri, 23 Mar 2018 06:15:44 -0400 > Author: root (root@dsiprouter.dopensource.com) > Committer: root (root@dsiprouter.dopensource.com) > Signed: --- [//]: # (END_SECTION 7f96b230c12f91c3f83238608fb07ff53a32c08c) [//]: # (START_SECTION 461c71c97f1e017f0f8827570d5f95f2c741bcef) ### Fixed the installer issues for Debian 9.x (stretch) > Commit: [461c71c97f1e017f0f8827570d5f95f2c741bcef](https://github.com/dOpensource/dsiprouter/commit/461c71c97f1e017f0f8827570d5f95f2c741bcef) > Date: Fri, 23 Mar 2018 05:19:52 -0400 > Author: root (root@dsiprouter-kam5.dopensource.com) > Committer: root (root@dsiprouter-kam5.dopensource.com) > Signed: --- [//]: # (END_SECTION 461c71c97f1e017f0f8827570d5f95f2c741bcef) [//]: # (START_SECTION 51f8a8cad4f6faae2f98ca47f47de6670b765786) ### Fixed RTPProxy issue with Debian > Commit: [51f8a8cad4f6faae2f98ca47f47de6670b765786](https://github.com/dOpensource/dsiprouter/commit/51f8a8cad4f6faae2f98ca47f47de6670b765786) > Date: Thu, 22 Mar 2018 00:00:53 -0400 > Author: root (release@dopensource.com) > Committer: root (release@dopensource.com) > Signed: --- [//]: # (END_SECTION 51f8a8cad4f6faae2f98ca47f47de6670b765786) [//]: # (START_SECTION 6b4608c72aa90e30910087cff27b192f18e7c9d1) ### Fixed a missing curly brackets > Commit: [6b4608c72aa90e30910087cff27b192f18e7c9d1](https://github.com/dOpensource/dsiprouter/commit/6b4608c72aa90e30910087cff27b192f18e7c9d1) > Date: Tue, 20 Mar 2018 22:40:20 -0400 > Author: root (release@dopensource.com) > Committer: root (release@dopensource.com) > Signed: --- [//]: # (END_SECTION 6b4608c72aa90e30910087cff27b192f18e7c9d1) [//]: # (START_SECTION e682af00fb9e952212cb0eaf63dd5d9b7d2a420b) ### Fixed a bug with teleblock media enablement > Commit: [e682af00fb9e952212cb0eaf63dd5d9b7d2a420b](https://github.com/dOpensource/dsiprouter/commit/e682af00fb9e952212cb0eaf63dd5d9b7d2a420b) > Date: Tue, 20 Mar 2018 17:42:13 -0600 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e682af00fb9e952212cb0eaf63dd5d9b7d2a420b) [//]: # (START_SECTION 17b89eea8920d9feec83e8ebbcad6c5063a6a608) ### Fixed a bug that prevented the media server from being enabled > Commit: [17b89eea8920d9feec83e8ebbcad6c5063a6a608](https://github.com/dOpensource/dsiprouter/commit/17b89eea8920d9feec83e8ebbcad6c5063a6a608) > Date: Tue, 20 Mar 2018 16:44:30 -0600 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 17b89eea8920d9feec83e8ebbcad6c5063a6a608) [//]: # (START_SECTION 864cb5b4b1b86e05dce86410deb6971af9ec9d53) ### Fixed the default settings in the Kam 4.4 version of the configuration file > Commit: [864cb5b4b1b86e05dce86410deb6971af9ec9d53](https://github.com/dOpensource/dsiprouter/commit/864cb5b4b1b86e05dce86410deb6971af9ec9d53) > Date: Tue, 20 Mar 2018 04:48:01 -0600 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 864cb5b4b1b86e05dce86410deb6971af9ec9d53) [//]: # (START_SECTION 093f4b1b000707456dd95dca2d21b34aa0090af4) ### Changed the port back to the default 5000 > Commit: [093f4b1b000707456dd95dca2d21b34aa0090af4](https://github.com/dOpensource/dsiprouter/commit/093f4b1b000707456dd95dca2d21b34aa0090af4) > Date: Tue, 20 Mar 2018 04:23:55 -0600 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 093f4b1b000707456dd95dca2d21b34aa0090af4) [//]: # (START_SECTION 574f0d60efa09a5f736ab0cc096ba30114ce5b7d) ### Update settings.py > Commit: [574f0d60efa09a5f736ab0cc096ba30114ce5b7d](https://github.com/dOpensource/dsiprouter/commit/574f0d60efa09a5f736ab0cc096ba30114ce5b7d) > Date: Mon, 19 Mar 2018 06:01:04 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 574f0d60efa09a5f736ab0cc096ba30114ce5b7d) [//]: # (START_SECTION ee9b1c68d5c3770a80c178d0c967ce55f299d096) ### Update README.md > Commit: [ee9b1c68d5c3770a80c178d0c967ce55f299d096](https://github.com/dOpensource/dsiprouter/commit/ee9b1c68d5c3770a80c178d0c967ce55f299d096) > Date: Mon, 19 Mar 2018 06:00:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION ee9b1c68d5c3770a80c178d0c967ce55f299d096) [//]: # (START_SECTION a5a92ac439283a2876d39bce2e38348441451c99) ### Update README.md > Commit: [a5a92ac439283a2876d39bce2e38348441451c99](https://github.com/dOpensource/dsiprouter/commit/a5a92ac439283a2876d39bce2e38348441451c99) > Date: Mon, 19 Mar 2018 05:57:05 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a5a92ac439283a2876d39bce2e38348441451c99) [//]: # (START_SECTION 6f7ec9398b602574396786567bb735ff00d954f8) ### Add files via upload > Commit: [6f7ec9398b602574396786567bb735ff00d954f8](https://github.com/dOpensource/dsiprouter/commit/6f7ec9398b602574396786567bb735ff00d954f8) > Date: Mon, 19 Mar 2018 05:53:56 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6f7ec9398b602574396786567bb735ff00d954f8) [//]: # (START_SECTION 270505741eaf8f7325be7ab3277c76abf58e9045) ### Completed support for Teleblock Service > Commit: [270505741eaf8f7325be7ab3277c76abf58e9045](https://github.com/dOpensource/dsiprouter/commit/270505741eaf8f7325be7ab3277c76abf58e9045) > Date: Mon, 19 Mar 2018 09:51:06 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 270505741eaf8f7325be7ab3277c76abf58e9045) [//]: # (START_SECTION a8b26db33bfb995c2f8298654c183eac11174b17) ### Added GUI Support for Gryphon Teleblock Support > Commit: [a8b26db33bfb995c2f8298654c183eac11174b17](https://github.com/dOpensource/dsiprouter/commit/a8b26db33bfb995c2f8298654c183eac11174b17) > Date: Sun, 18 Mar 2018 13:30:25 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION a8b26db33bfb995c2f8298654c183eac11174b17) [//]: # (START_SECTION bac4034ad784e796bc013d874a874e0c38777bcd) ### Create CNAME > Commit: [bac4034ad784e796bc013d874a874e0c38777bcd](https://github.com/dOpensource/dsiprouter/commit/bac4034ad784e796bc013d874a874e0c38777bcd) > Date: Sat, 17 Mar 2018 20:04:06 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bac4034ad784e796bc013d874a874e0c38777bcd) [//]: # (START_SECTION e36449722cb15fbf56b65179c8ea6793ec8be906) ### Set theme jekyll-theme-architect > Commit: [e36449722cb15fbf56b65179c8ea6793ec8be906](https://github.com/dOpensource/dsiprouter/commit/e36449722cb15fbf56b65179c8ea6793ec8be906) > Date: Sat, 17 Mar 2018 19:51:32 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION e36449722cb15fbf56b65179c8ea6793ec8be906) [//]: # (START_SECTION 47333b5cde16838ba7c4a8c64e99445f53c4e7f3) ### Removed a legacy script for stopping dsiprouter > Commit: [47333b5cde16838ba7c4a8c64e99445f53c4e7f3](https://github.com/dOpensource/dsiprouter/commit/47333b5cde16838ba7c4a8c64e99445f53c4e7f3) > Date: Sat, 17 Mar 2018 14:50:44 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 47333b5cde16838ba7c4a8c64e99445f53c4e7f3) [//]: # (START_SECTION 4d7d4e84888eeeefd12f3692320badb19040fe4d) ### Added support for Teleblock > Commit: [4d7d4e84888eeeefd12f3692320badb19040fe4d](https://github.com/dOpensource/dsiprouter/commit/4d7d4e84888eeeefd12f3692320badb19040fe4d) > Date: Sat, 17 Mar 2018 14:48:59 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 4d7d4e84888eeeefd12f3692320badb19040fe4d) [//]: # (START_SECTION 6117b3fc5b30dcec0e3a162d6df5f976db6d1bdb) ### got rid of uneeded replies, fixed formatting > Commit: [6117b3fc5b30dcec0e3a162d6df5f976db6d1bdb](https://github.com/dOpensource/dsiprouter/commit/6117b3fc5b30dcec0e3a162d6df5f976db6d1bdb) > Date: Wed, 14 Mar 2018 14:48:09 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 6117b3fc5b30dcec0e3a162d6df5f976db6d1bdb) [//]: # (START_SECTION 0ad302940d9e883763df68fe40fbb560aaaaccd6) ### fixed the "500" reply bug and check status bug > Commit: [0ad302940d9e883763df68fe40fbb560aaaaccd6](https://github.com/dOpensource/dsiprouter/commit/0ad302940d9e883763df68fe40fbb560aaaaccd6) > Date: Tue, 13 Mar 2018 15:30:54 -0400 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION 0ad302940d9e883763df68fe40fbb560aaaaccd6) [//]: # (START_SECTION c3f8fe6d4d980230dc9a32b6d20079c86bcfdd51) ### Update kamailio51_dsiprouter.cfg > Commit: [c3f8fe6d4d980230dc9a32b6d20079c86bcfdd51](https://github.com/dOpensource/dsiprouter/commit/c3f8fe6d4d980230dc9a32b6d20079c86bcfdd51) > Date: Mon, 12 Mar 2018 21:04:00 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c3f8fe6d4d980230dc9a32b6d20079c86bcfdd51) [//]: # (START_SECTION eec8c7ae6a3c49b1fd7a58fa1edb4e0a4fbdaef8) ### Update kamailio51_dsiprouter.cfg > Commit: [eec8c7ae6a3c49b1fd7a58fa1edb4e0a4fbdaef8](https://github.com/dOpensource/dsiprouter/commit/eec8c7ae6a3c49b1fd7a58fa1edb4e0a4fbdaef8) > Date: Mon, 12 Mar 2018 21:03:30 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION eec8c7ae6a3c49b1fd7a58fa1edb4e0a4fbdaef8) [//]: # (START_SECTION 97e3ea8d6762476226e00e110d8a33e5630a3a6f) ### Update stretch.sh > Commit: [97e3ea8d6762476226e00e110d8a33e5630a3a6f](https://github.com/dOpensource/dsiprouter/commit/97e3ea8d6762476226e00e110d8a33e5630a3a6f) > Date: Sun, 11 Mar 2018 21:55:28 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 97e3ea8d6762476226e00e110d8a33e5630a3a6f) [//]: # (START_SECTION 5f36971c01d9a3bc52ee999454c0199eb27a7ff8) ### Update README.md > Commit: [5f36971c01d9a3bc52ee999454c0199eb27a7ff8](https://github.com/dOpensource/dsiprouter/commit/5f36971c01d9a3bc52ee999454c0199eb27a7ff8) > Date: Sun, 11 Mar 2018 21:43:45 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 5f36971c01d9a3bc52ee999454c0199eb27a7ff8) [//]: # (START_SECTION b07a5e065a16755cba48d638d72b28c2bd111dac) ### Update README.md > Commit: [b07a5e065a16755cba48d638d72b28c2bd111dac](https://github.com/dOpensource/dsiprouter/commit/b07a5e065a16755cba48d638d72b28c2bd111dac) > Date: Sun, 11 Mar 2018 21:34:51 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b07a5e065a16755cba48d638d72b28c2bd111dac) [//]: # (START_SECTION dbec72854ac57ee4f2937b09eedf373bd49e6e19) ### Update README.md > Commit: [dbec72854ac57ee4f2937b09eedf373bd49e6e19](https://github.com/dOpensource/dsiprouter/commit/dbec72854ac57ee4f2937b09eedf373bd49e6e19) > Date: Sun, 11 Mar 2018 21:29:02 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION dbec72854ac57ee4f2937b09eedf373bd49e6e19) [//]: # (START_SECTION 4b5a7621be2f99cb6ba75476ea9299e2155b9d16) ### Update README.md > Commit: [4b5a7621be2f99cb6ba75476ea9299e2155b9d16](https://github.com/dOpensource/dsiprouter/commit/4b5a7621be2f99cb6ba75476ea9299e2155b9d16) > Date: Sun, 11 Mar 2018 21:27:56 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4b5a7621be2f99cb6ba75476ea9299e2155b9d16) [//]: # (START_SECTION 4278d5b249e8f0302866777e10d57ceff32945cc) ### Add files via upload > Commit: [4278d5b249e8f0302866777e10d57ceff32945cc](https://github.com/dOpensource/dsiprouter/commit/4278d5b249e8f0302866777e10d57ceff32945cc) > Date: Sun, 11 Mar 2018 21:26:05 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4278d5b249e8f0302866777e10d57ceff32945cc) [//]: # (START_SECTION 5d6cbfff711d6721af44702d9baee487078d77e3) ### Updated the README > Commit: [5d6cbfff711d6721af44702d9baee487078d77e3](https://github.com/dOpensource/dsiprouter/commit/5d6cbfff711d6721af44702d9baee487078d77e3) > Date: Mon, 12 Mar 2018 01:20:38 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 5d6cbfff711d6721af44702d9baee487078d77e3) [//]: # (START_SECTION a64b491d710f1258e6b1fd58772726a459ac8e91) ### Prevent the DBROOTPW from being prompted during an install on a fresh machine > Commit: [a64b491d710f1258e6b1fd58772726a459ac8e91](https://github.com/dOpensource/dsiprouter/commit/a64b491d710f1258e6b1fd58772726a459ac8e91) > Date: Mon, 12 Mar 2018 00:53:19 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION a64b491d710f1258e6b1fd58772726a459ac8e91) [//]: # (START_SECTION 14f87963c736094ad35c8e0908514670b2111774) ### Completed GUI support for PBX Registration > Commit: [14f87963c736094ad35c8e0908514670b2111774](https://github.com/dOpensource/dsiprouter/commit/14f87963c736094ad35c8e0908514670b2111774) > Date: Mon, 12 Mar 2018 00:29:14 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 14f87963c736094ad35c8e0908514670b2111774) [//]: # (START_SECTION a8f0df2a88064d31ed445a05e032c69ca490fc82) ### Fixed the Add PBX with subscriber support > Commit: [a8f0df2a88064d31ed445a05e032c69ca490fc82](https://github.com/dOpensource/dsiprouter/commit/a8f0df2a88064d31ed445a05e032c69ca490fc82) > Date: Sun, 11 Mar 2018 14:30:30 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION a8f0df2a88064d31ed445a05e032c69ca490fc82) [//]: # (START_SECTION bb9adc17826e2b4df40255f3c7efd366541e1795) ### added teleblock blacklisting feature > Commit: [bb9adc17826e2b4df40255f3c7efd366541e1795](https://github.com/dOpensource/dsiprouter/commit/bb9adc17826e2b4df40255f3c7efd366541e1795) > Date: Fri, 9 Mar 2018 22:03:46 -0500 > Author: Tyler Moore (tmoore@goflyball.com) > Committer: Tyler Moore (tmoore@goflyball.com) > Signed: --- [//]: # (END_SECTION bb9adc17826e2b4df40255f3c7efd366541e1795) [//]: # (START_SECTION 088ef02a18ffd0c57fbcc5565358905c624a3dda) ### Added GUI support for allowing a PBX/Endpoint to register > Commit: [088ef02a18ffd0c57fbcc5565358905c624a3dda](https://github.com/dOpensource/dsiprouter/commit/088ef02a18ffd0c57fbcc5565358905c624a3dda) > Date: Wed, 7 Mar 2018 05:41:00 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 088ef02a18ffd0c57fbcc5565358905c624a3dda) [//]: # (START_SECTION 0c689b9e7d08a64c320d4769ff6d312ec61f1b00) ### Completed Kamailio support to allow PBX's to register to dSIPRouter > Commit: [0c689b9e7d08a64c320d4769ff6d312ec61f1b00](https://github.com/dOpensource/dsiprouter/commit/0c689b9e7d08a64c320d4769ff6d312ec61f1b00) > Date: Mon, 5 Mar 2018 03:25:26 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 0c689b9e7d08a64c320d4769ff6d312ec61f1b00) [//]: # (START_SECTION 94fc56e69265f0d61d0cb7e896335d017b77747b) ### Added support to allow PBX's to register > Commit: [94fc56e69265f0d61d0cb7e896335d017b77747b](https://github.com/dOpensource/dsiprouter/commit/94fc56e69265f0d61d0cb7e896335d017b77747b) > Date: Sat, 3 Mar 2018 16:56:11 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 94fc56e69265f0d61d0cb7e896335d017b77747b) [//]: # (START_SECTION 0c5292d265bb33dd3be8b6d217fdfbdb0ad7bcd9) ### Added curl to the packages that needs to tbe downloaded. Also fixed issue with the dSIPRouter port not being added > Commit: [0c5292d265bb33dd3be8b6d217fdfbdb0ad7bcd9](https://github.com/dOpensource/dsiprouter/commit/0c5292d265bb33dd3be8b6d217fdfbdb0ad7bcd9) > Date: Fri, 2 Mar 2018 05:08:18 +0000 > Author: root (root@disrouter-kam5-dev2.localdomain) > Committer: root (root@disrouter-kam5-dev2.localdomain) > Signed: --- [//]: # (END_SECTION 0c5292d265bb33dd3be8b6d217fdfbdb0ad7bcd9) [//]: # (START_SECTION 35989c8bb8226c0d15bf02cd07ab23a5237d6eff) ### Fixed issues with install script > Commit: [35989c8bb8226c0d15bf02cd07ab23a5237d6eff](https://github.com/dOpensource/dsiprouter/commit/35989c8bb8226c0d15bf02cd07ab23a5237d6eff) > Date: Fri, 2 Mar 2018 04:40:16 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 35989c8bb8226c0d15bf02cd07ab23a5237d6eff) [//]: # (START_SECTION fb1385b87914ddc040630bbe0d10c5a40bdc8b99) ### Fixed and validated the debian stretch install > Commit: [fb1385b87914ddc040630bbe0d10c5a40bdc8b99](https://github.com/dOpensource/dsiprouter/commit/fb1385b87914ddc040630bbe0d10c5a40bdc8b99) > Date: Fri, 2 Mar 2018 01:43:22 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION fb1385b87914ddc040630bbe0d10c5a40bdc8b99) [//]: # (START_SECTION d9a3d2dc612cd1c5956ae98bb9393c4b58f94653) ### Refactoring the install script into more maintainable and testable units > Commit: [d9a3d2dc612cd1c5956ae98bb9393c4b58f94653](https://github.com/dOpensource/dsiprouter/commit/d9a3d2dc612cd1c5956ae98bb9393c4b58f94653) > Date: Sun, 25 Feb 2018 07:58:28 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION d9a3d2dc612cd1c5956ae98bb9393c4b58f94653) [//]: # (START_SECTION 9caa328dcaa35b751c48f2e2197ce92104cdf132) ### Fixed issues with the Stretch install > Commit: [9caa328dcaa35b751c48f2e2197ce92104cdf132](https://github.com/dOpensource/dsiprouter/commit/9caa328dcaa35b751c48f2e2197ce92104cdf132) > Date: Sat, 24 Feb 2018 22:06:10 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 9caa328dcaa35b751c48f2e2197ce92104cdf132) [//]: # (START_SECTION 3fb6f45c7a48a148cfc94afd4475e90a8d56f15f) ### Adding support for Debian Stretch release > Commit: [3fb6f45c7a48a148cfc94afd4475e90a8d56f15f](https://github.com/dOpensource/dsiprouter/commit/3fb6f45c7a48a148cfc94afd4475e90a8d56f15f) > Date: Sat, 24 Feb 2018 20:40:50 +0000 > Author: root (root@dsiprouter-kam5.localdomain) > Committer: root (root@dsiprouter-kam5.localdomain) > Signed: --- [//]: # (END_SECTION 3fb6f45c7a48a148cfc94afd4475e90a8d56f15f) [//]: # (START_SECTION 1bbb6616fbf5393a984bda57e2d7423d15e06d7f) ### Update README.md > Commit: [1bbb6616fbf5393a984bda57e2d7423d15e06d7f](https://github.com/dOpensource/dsiprouter/commit/1bbb6616fbf5393a984bda57e2d7423d15e06d7f) > Date: Sat, 24 Feb 2018 11:56:30 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1bbb6616fbf5393a984bda57e2d7423d15e06d7f) [//]: # (START_SECTION 8c00bda7dbc94eae694acb5e0204c8b12eec633a) ### Fixed an issue that prevented Kamailio 4.4 from being installed > Commit: [8c00bda7dbc94eae694acb5e0204c8b12eec633a](https://github.com/dOpensource/dsiprouter/commit/8c00bda7dbc94eae694acb5e0204c8b12eec633a) > Date: Tue, 19 Dec 2017 14:50:24 -0500 > Author: root (root@debian89) > Committer: root (root@debian89) > Signed: --- [//]: # (END_SECTION 8c00bda7dbc94eae694acb5e0204c8b12eec633a) [//]: # (START_SECTION c49f1656c78bfe96ea2c562f3bba3f80e294157f) ### Update README.md > Commit: [c49f1656c78bfe96ea2c562f3bba3f80e294157f](https://github.com/dOpensource/dsiprouter/commit/c49f1656c78bfe96ea2c562f3bba3f80e294157f) > Date: Mon, 18 Dec 2017 20:48:57 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION c49f1656c78bfe96ea2c562f3bba3f80e294157f) [//]: # (START_SECTION 9da44b990faf882f0988bb419456fa7a466e3d67) ### Removed debugging statements from bash scripts and made kamailio restart after the dSIPRouter install > Commit: [9da44b990faf882f0988bb419456fa7a466e3d67](https://github.com/dOpensource/dsiprouter/commit/9da44b990faf882f0988bb419456fa7a466e3d67) > Date: Tue, 19 Dec 2017 01:41:58 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 9da44b990faf882f0988bb419456fa7a466e3d67) [//]: # (START_SECTION 882cb7a3eac6de74cc913ce9fdc898c8fe0728b2) ### Added logic to handle different versios of Kamailio > Commit: [882cb7a3eac6de74cc913ce9fdc898c8fe0728b2](https://github.com/dOpensource/dsiprouter/commit/882cb7a3eac6de74cc913ce9fdc898c8fe0728b2) > Date: Tue, 19 Dec 2017 01:26:04 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 882cb7a3eac6de74cc913ce9fdc898c8fe0728b2) [//]: # (START_SECTION 2da11a5f987d00e235ec6b7d7ea5d072b6a36198) ### fixed the install the uninstall scripts > Commit: [2da11a5f987d00e235ec6b7d7ea5d072b6a36198](https://github.com/dOpensource/dsiprouter/commit/2da11a5f987d00e235ec6b7d7ea5d072b6a36198) > Date: Tue, 19 Dec 2017 00:28:13 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 2da11a5f987d00e235ec6b7d7ea5d072b6a36198) [//]: # (START_SECTION afb668cb39cf623379989209a3f6cf4584eae71b) ### Update README.md > Commit: [afb668cb39cf623379989209a3f6cf4584eae71b](https://github.com/dOpensource/dsiprouter/commit/afb668cb39cf623379989209a3f6cf4584eae71b) > Date: Mon, 18 Dec 2017 19:00:16 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION afb668cb39cf623379989209a3f6cf4584eae71b) [//]: # (START_SECTION 224b098a0dfc5adf8da010713ae55588d411c011) ### added support for installing kamailio on debian > Commit: [224b098a0dfc5adf8da010713ae55588d411c011](https://github.com/dOpensource/dsiprouter/commit/224b098a0dfc5adf8da010713ae55588d411c011) > Date: Mon, 18 Dec 2017 23:56:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 224b098a0dfc5adf8da010713ae55588d411c011) [//]: # (START_SECTION b7c3abf5004ccda474507dd39c0c52d579186584) ### Correct reference to REQ_PYTHON_MAJOR_VER > Commit: [b7c3abf5004ccda474507dd39c0c52d579186584](https://github.com/dOpensource/dsiprouter/commit/b7c3abf5004ccda474507dd39c0c52d579186584) > Date: Sun, 10 Dec 2017 09:55:02 -0500 > Author: hailthemelody (rainman@hailthemelody.com) > Committer: hailthemelody (rainman@hailthemelody.com) > Signed: - Was pointing to REQ_PYTHON_VER, which presumable was the previous name of the variable --- [//]: # (END_SECTION b7c3abf5004ccda474507dd39c0c52d579186584) [//]: # (START_SECTION 63496f31a39d0bbfc35460259d90e4c5053c1db5) ### Correct reference to variable > Commit: [63496f31a39d0bbfc35460259d90e4c5053c1db5](https://github.com/dOpensource/dsiprouter/commit/63496f31a39d0bbfc35460259d90e4c5053c1db5) > Date: Sun, 10 Dec 2017 08:44:47 -0500 > Author: hailthemelody (rainman@hailthemelody.com) > Committer: hailthemelody (rainman@hailthemelody.com) > Signed: - Was missing "$" and being displayed as text. Now resolves to variable --- [//]: # (END_SECTION 63496f31a39d0bbfc35460259d90e4c5053c1db5) [//]: # (START_SECTION 52189aced1a306c35cd2ad4af6db8826aa879837) ### update the version from 0.30 to 0.31 > Commit: [52189aced1a306c35cd2ad4af6db8826aa879837](https://github.com/dOpensource/dsiprouter/commit/52189aced1a306c35cd2ad4af6db8826aa879837) > Date: Mon, 4 Dec 2017 12:12:24 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 52189aced1a306c35cd2ad4af6db8826aa879837) [//]: # (START_SECTION 51e91ee29e272e8d6af2854b46ff54096b942108) ### Update README.md > Commit: [51e91ee29e272e8d6af2854b46ff54096b942108](https://github.com/dOpensource/dsiprouter/commit/51e91ee29e272e8d6af2854b46ff54096b942108) > Date: Mon, 4 Dec 2017 07:09:50 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 51e91ee29e272e8d6af2854b46ff54096b942108) [//]: # (START_SECTION 669cf794d179668c1176d0f7b3e9af0cc266187a) ### Update README.md > Commit: [669cf794d179668c1176d0f7b3e9af0cc266187a](https://github.com/dOpensource/dsiprouter/commit/669cf794d179668c1176d0f7b3e9af0cc266187a) > Date: Mon, 4 Dec 2017 07:07:36 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 669cf794d179668c1176d0f7b3e9af0cc266187a) [//]: # (START_SECTION 9c8444ae737cab7a94bd62edb0e06a70699cdad1) ### Fixed some minor bugs and formatting issues > Commit: [9c8444ae737cab7a94bd62edb0e06a70699cdad1](https://github.com/dOpensource/dsiprouter/commit/9c8444ae737cab7a94bd62edb0e06a70699cdad1) > Date: Mon, 4 Dec 2017 01:19:21 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 9c8444ae737cab7a94bd62edb0e06a70699cdad1) [//]: # (START_SECTION 03c315ebbbfa321d8494f2ee5d32ad09ed2eb4a5) ### Update README.md > Commit: [03c315ebbbfa321d8494f2ee5d32ad09ed2eb4a5](https://github.com/dOpensource/dsiprouter/commit/03c315ebbbfa321d8494f2ee5d32ad09ed2eb4a5) > Date: Sun, 3 Dec 2017 17:06:14 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 03c315ebbbfa321d8494f2ee5d32ad09ed2eb4a5) [//]: # (START_SECTION 7bd1de71a7e6a8a4e2104fe8a92255c957f2094e) ### Generate unique password during install > Commit: [7bd1de71a7e6a8a4e2104fe8a92255c957f2094e](https://github.com/dOpensource/dsiprouter/commit/7bd1de71a7e6a8a4e2104fe8a92255c957f2094e) > Date: Sun, 3 Dec 2017 22:03:42 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7bd1de71a7e6a8a4e2104fe8a92255c957f2094e) [//]: # (START_SECTION 82917923a05f944ff8e283dc3846e06fb4a97b7c) ### Added support for generating a unique password during the installation process > Commit: [82917923a05f944ff8e283dc3846e06fb4a97b7c](https://github.com/dOpensource/dsiprouter/commit/82917923a05f944ff8e283dc3846e06fb4a97b7c) > Date: Sun, 3 Dec 2017 21:59:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 82917923a05f944ff8e283dc3846e06fb4a97b7c) [//]: # (START_SECTION f1c1f30dd2b9a0720f3c2821c11a65f6908e50c0) ### restored the format of the file > Commit: [f1c1f30dd2b9a0720f3c2821c11a65f6908e50c0](https://github.com/dOpensource/dsiprouter/commit/f1c1f30dd2b9a0720f3c2821c11a65f6908e50c0) > Date: Sat, 2 Dec 2017 11:58:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f1c1f30dd2b9a0720f3c2821c11a65f6908e50c0) [//]: # (START_SECTION f6912be94e6e4cf913c639398cffac55c5c96fa9) ### restored the format of the file > Commit: [f6912be94e6e4cf913c639398cffac55c5c96fa9](https://github.com/dOpensource/dsiprouter/commit/f6912be94e6e4cf913c639398cffac55c5c96fa9) > Date: Sat, 2 Dec 2017 11:56:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f6912be94e6e4cf913c639398cffac55c5c96fa9) [//]: # (START_SECTION 00538e7cc48ade86b72f17a41c4e5dd28ad6051d) ### Fixed the reloadcmd file, but forgot to commit. Fixes #17 > Commit: [00538e7cc48ade86b72f17a41c4e5dd28ad6051d](https://github.com/dOpensource/dsiprouter/commit/00538e7cc48ade86b72f17a41c4e5dd28ad6051d) > Date: Sat, 2 Dec 2017 11:02:45 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 00538e7cc48ade86b72f17a41c4e5dd28ad6051d) [//]: # (START_SECTION 659dac168fc571efe6872ed61abfce4a5800fb2c) ### Fixed the container padding to remove the padding on the left and right. Fixes #12 > Commit: [659dac168fc571efe6872ed61abfce4a5800fb2c](https://github.com/dOpensource/dsiprouter/commit/659dac168fc571efe6872ed61abfce4a5800fb2c) > Date: Sat, 2 Dec 2017 10:28:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 659dac168fc571efe6872ed61abfce4a5800fb2c) [//]: # (START_SECTION f7e26d7590574085fe389c8f8a8abfd28cb20499) ### Enhanced the logic around reloading Kamailio from the GUI. Thanks to @khorsmann Fixes #17 > Commit: [f7e26d7590574085fe389c8f8a8abfd28cb20499](https://github.com/dOpensource/dsiprouter/commit/f7e26d7590574085fe389c8f8a8abfd28cb20499) > Date: Sat, 2 Dec 2017 09:43:40 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f7e26d7590574085fe389c8f8a8abfd28cb20499) [//]: # (START_SECTION 89a2de5b935125ab917af7f8107d7f93e80f7cb6) ### Fixed an issue with the Kamailio module path not being populated properly during install. Close #18 in release 0.31 > Commit: [89a2de5b935125ab917af7f8107d7f93e80f7cb6](https://github.com/dOpensource/dsiprouter/commit/89a2de5b935125ab917af7f8107d7f93e80f7cb6) > Date: Fri, 1 Dec 2017 11:36:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 89a2de5b935125ab917af7f8107d7f93e80f7cb6) [//]: # (START_SECTION cdbc3d9be43d180f90d02ba4dce6fb76c6d25774) ### Added logic that would distinguish between local dialing and external dialing through a carrier when registering endpoints through the SIPProxy. It's hardcoded so that extensions has to contain 5 or more digits. Otherwise, it will try to route the call to a carrier > Commit: [cdbc3d9be43d180f90d02ba4dce6fb76c6d25774](https://github.com/dOpensource/dsiprouter/commit/cdbc3d9be43d180f90d02ba4dce6fb76c6d25774) > Date: Sat, 25 Nov 2017 06:27:05 -0800 > Author: root (root@noc-lcb-spxy1.garlic.com) > Committer: root (root@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION cdbc3d9be43d180f90d02ba4dce6fb76c6d25774) [//]: # (START_SECTION e7a7d4b282560f435be995fe0a08f992d944bee8) ### Fixed issue with ACK's not propagating thru the Kamailio correctedly. Also, set the retranmission timeout to 10sec when trying to initial a call to an endpoint. > Commit: [e7a7d4b282560f435be995fe0a08f992d944bee8](https://github.com/dOpensource/dsiprouter/commit/e7a7d4b282560f435be995fe0a08f992d944bee8) > Date: Wed, 22 Nov 2017 21:48:45 -0800 > Author: root (root@noc-lcb-spxy1.garlic.com) > Committer: root (root@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION e7a7d4b282560f435be995fe0a08f992d944bee8) [//]: # (START_SECTION 7b958fda8aa5befdb950b226214cc89a982f696b) ### Fixed an issue with endpoints being able to receive calls once registered > Commit: [7b958fda8aa5befdb950b226214cc89a982f696b](https://github.com/dOpensource/dsiprouter/commit/7b958fda8aa5befdb950b226214cc89a982f696b) > Date: Tue, 21 Nov 2017 20:57:26 -0800 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION 7b958fda8aa5befdb950b226214cc89a982f696b) [//]: # (START_SECTION ac333d0828cb9e772799d33a99ee2931fa6800d1) ### close 23 > Commit: [ac333d0828cb9e772799d33a99ee2931fa6800d1](https://github.com/dOpensource/dsiprouter/commit/ac333d0828cb9e772799d33a99ee2931fa6800d1) > Date: Tue, 21 Nov 2017 09:22:21 -0800 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION ac333d0828cb9e772799d33a99ee2931fa6800d1) [//]: # (START_SECTION f8e6dac639b6437f73ee607da87f737911aa9c63) ### Fixed an issue with a quote not being specified correctly > Commit: [f8e6dac639b6437f73ee607da87f737911aa9c63](https://github.com/dOpensource/dsiprouter/commit/f8e6dac639b6437f73ee607da87f737911aa9c63) > Date: Tue, 21 Nov 2017 02:49:41 -0800 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION f8e6dac639b6437f73ee607da87f737911aa9c63) [//]: # (START_SECTION 5152e54cd53dda2261269fe0ecbd4e73b1d54e79) ### Will run apt-get update before installing > Commit: [5152e54cd53dda2261269fe0ecbd4e73b1d54e79](https://github.com/dOpensource/dsiprouter/commit/5152e54cd53dda2261269fe0ecbd4e73b1d54e79) > Date: Tue, 21 Nov 2017 02:45:56 -0800 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION 5152e54cd53dda2261269fe0ecbd4e73b1d54e79) [//]: # (START_SECTION 6a58a375b667a3447bf28527937ddd2cf8ec3a95) ### Added a parameter to the save function in the registrar module. Close #23 > Commit: [6a58a375b667a3447bf28527937ddd2cf8ec3a95](https://github.com/dOpensource/dsiprouter/commit/6a58a375b667a3447bf28527937ddd2cf8ec3a95) > Date: Tue, 21 Nov 2017 16:37:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6a58a375b667a3447bf28527937ddd2cf8ec3a95) [//]: # (START_SECTION f98711e799efc4ce3799b4ffde9e021a0f2ffded) ### Fixed a bug with the commands to enable dSIPRouter to access the FusionPBX DB > Commit: [f98711e799efc4ce3799b4ffde9e021a0f2ffded](https://github.com/dOpensource/dsiprouter/commit/f98711e799efc4ce3799b4ffde9e021a0f2ffded) > Date: Tue, 14 Nov 2017 23:30:37 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f98711e799efc4ce3799b4ffde9e021a0f2ffded) [//]: # (START_SECTION 0cac2e6e1a0642f65963b2aceb34a38dce332956) ### Update README.md > Commit: [0cac2e6e1a0642f65963b2aceb34a38dce332956](https://github.com/dOpensource/dsiprouter/commit/0cac2e6e1a0642f65963b2aceb34a38dce332956) > Date: Mon, 13 Nov 2017 15:02:31 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 0cac2e6e1a0642f65963b2aceb34a38dce332956) [//]: # (START_SECTION c6d6a174294ac9e6598f4eb7e44114419e083474) ### Updated the release version > Commit: [c6d6a174294ac9e6598f4eb7e44114419e083474](https://github.com/dOpensource/dsiprouter/commit/c6d6a174294ac9e6598f4eb7e44114419e083474) > Date: Mon, 13 Nov 2017 17:50:18 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION c6d6a174294ac9e6598f4eb7e44114419e083474) [//]: # (START_SECTION 81f0bcc195e06efeee1e1c1f38a5e857aebdbc27) ### Fixed the issue with overwriting the original Kamailio configuration files when installing the product multiple times. Closes #19 > Commit: [81f0bcc195e06efeee1e1c1f38a5e857aebdbc27](https://github.com/dOpensource/dsiprouter/commit/81f0bcc195e06efeee1e1c1f38a5e857aebdbc27) > Date: Mon, 13 Nov 2017 17:47:30 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 81f0bcc195e06efeee1e1c1f38a5e857aebdbc27) [//]: # (START_SECTION 6ab256bc23ca59ebca0e282732e9bd3e1e8eb41f) ### Commented out database mapping for the fusionpbx_db_mapping table > Commit: [6ab256bc23ca59ebca0e282732e9bd3e1e8eb41f](https://github.com/dOpensource/dsiprouter/commit/6ab256bc23ca59ebca0e282732e9bd3e1e8eb41f) > Date: Mon, 13 Nov 2017 16:23:29 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 6ab256bc23ca59ebca0e282732e9bd3e1e8eb41f) [//]: # (START_SECTION 30a0d5b5f6ed421bff78e92320c91841aa7fc5e1) ### Added a library to the install script and fixed an issue with the mysql script > Commit: [30a0d5b5f6ed421bff78e92320c91841aa7fc5e1](https://github.com/dOpensource/dsiprouter/commit/30a0d5b5f6ed421bff78e92320c91841aa7fc5e1) > Date: Mon, 13 Nov 2017 16:19:11 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 30a0d5b5f6ed421bff78e92320c91841aa7fc5e1) [//]: # (START_SECTION 1970e0617e9671f155b866f7b27c70d91975f153) ### Update README.md > Commit: [1970e0617e9671f155b866f7b27c70d91975f153](https://github.com/dOpensource/dsiprouter/commit/1970e0617e9671f155b866f7b27c70d91975f153) > Date: Mon, 13 Nov 2017 10:37:12 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1970e0617e9671f155b866f7b27c70d91975f153) [//]: # (START_SECTION 0fbfa1e96b069d605d5cb6fe91aa7e88baeb46dd) ### Fixed an issue with stopping the server > Commit: [0fbfa1e96b069d605d5cb6fe91aa7e88baeb46dd](https://github.com/dOpensource/dsiprouter/commit/0fbfa1e96b069d605d5cb6fe91aa7e88baeb46dd) > Date: Mon, 13 Nov 2017 15:27:52 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0fbfa1e96b069d605d5cb6fe91aa7e88baeb46dd) [//]: # (START_SECTION 9b981ea6e8cbc1c9bab4b23576bb58dd3026ea42) ### Update README.md > Commit: [9b981ea6e8cbc1c9bab4b23576bb58dd3026ea42](https://github.com/dOpensource/dsiprouter/commit/9b981ea6e8cbc1c9bab4b23576bb58dd3026ea42) > Date: Mon, 13 Nov 2017 09:40:24 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9b981ea6e8cbc1c9bab4b23576bb58dd3026ea42) [//]: # (START_SECTION 7b12db3d9acd7a1198dba333387b37aaf7db2ff8) ### Update README.md > Commit: [7b12db3d9acd7a1198dba333387b37aaf7db2ff8](https://github.com/dOpensource/dsiprouter/commit/7b12db3d9acd7a1198dba333387b37aaf7db2ff8) > Date: Mon, 13 Nov 2017 09:40:12 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7b12db3d9acd7a1198dba333387b37aaf7db2ff8) [//]: # (START_SECTION 16773e75599c05f867f44fa622636031f75bd8c8) ### Add files via upload > Commit: [16773e75599c05f867f44fa622636031f75bd8c8](https://github.com/dOpensource/dsiprouter/commit/16773e75599c05f867f44fa622636031f75bd8c8) > Date: Mon, 13 Nov 2017 09:34:41 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 16773e75599c05f867f44fa622636031f75bd8c8) [//]: # (START_SECTION 00dc8386150c5da6207cd6f59a048ae26e38e136) ### Update README.md > Commit: [00dc8386150c5da6207cd6f59a048ae26e38e136](https://github.com/dOpensource/dsiprouter/commit/00dc8386150c5da6207cd6f59a048ae26e38e136) > Date: Mon, 13 Nov 2017 09:24:26 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 00dc8386150c5da6207cd6f59a048ae26e38e136) [//]: # (START_SECTION 25479f2a5fffce7ff304d6b38c979581b32dc801) ### Update README.md > Commit: [25479f2a5fffce7ff304d6b38c979581b32dc801](https://github.com/dOpensource/dsiprouter/commit/25479f2a5fffce7ff304d6b38c979581b32dc801) > Date: Mon, 13 Nov 2017 09:21:45 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 25479f2a5fffce7ff304d6b38c979581b32dc801) [//]: # (START_SECTION fdd69aa8e1d79ef14fb0448b3ee702982e43f9da) ### Update README.md > Commit: [fdd69aa8e1d79ef14fb0448b3ee702982e43f9da](https://github.com/dOpensource/dsiprouter/commit/fdd69aa8e1d79ef14fb0448b3ee702982e43f9da) > Date: Mon, 13 Nov 2017 09:15:09 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION fdd69aa8e1d79ef14fb0448b3ee702982e43f9da) [//]: # (START_SECTION 1bc9419eb983a1bb2dd99645476b16670f5a565a) ### Update README.md > Commit: [1bc9419eb983a1bb2dd99645476b16670f5a565a](https://github.com/dOpensource/dsiprouter/commit/1bc9419eb983a1bb2dd99645476b16670f5a565a) > Date: Mon, 13 Nov 2017 09:00:39 -0500 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 1bc9419eb983a1bb2dd99645476b16670f5a565a) [//]: # (START_SECTION 4ff4d11309aa6d40595ed6ec89d800c39bfcdf9c) ### Fixed issues with the install script > Commit: [4ff4d11309aa6d40595ed6ec89d800c39bfcdf9c](https://github.com/dOpensource/dsiprouter/commit/4ff4d11309aa6d40595ed6ec89d800c39bfcdf9c) > Date: Mon, 13 Nov 2017 12:39:15 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4ff4d11309aa6d40595ed6ec89d800c39bfcdf9c) [//]: # (START_SECTION 015fce0deaf6b2edc5b4072d0fdddd65096f61c4) ### Chnaged to support FusionPBX Domain Support > Commit: [015fce0deaf6b2edc5b4072d0fdddd65096f61c4](https://github.com/dOpensource/dsiprouter/commit/015fce0deaf6b2edc5b4072d0fdddd65096f61c4) > Date: Sun, 12 Nov 2017 15:36:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 015fce0deaf6b2edc5b4072d0fdddd65096f61c4) [//]: # (START_SECTION 2918708fb475111254a86f6303a0e0fb23ac0c6c) ### Added logic to sync the Kamailio domain and domain_attrs tables with FusionPBX instances > Commit: [2918708fb475111254a86f6303a0e0fb23ac0c6c](https://github.com/dOpensource/dsiprouter/commit/2918708fb475111254a86f6303a0e0fb23ac0c6c) > Date: Sat, 11 Nov 2017 09:40:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2918708fb475111254a86f6303a0e0fb23ac0c6c) [//]: # (START_SECTION 763434d4868607ea78d5d1d233551f41490f6c4c) ### Added Add,Update and Delete support for FusionPBX Domain Support feature > Commit: [763434d4868607ea78d5d1d233551f41490f6c4c](https://github.com/dOpensource/dsiprouter/commit/763434d4868607ea78d5d1d233551f41490f6c4c) > Date: Sun, 5 Nov 2017 08:16:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 763434d4868607ea78d5d1d233551f41490f6c4c) [//]: # (START_SECTION 9161f79fc60c3d022be68c1c919168705af970f6) ### Add files via upload > Commit: [9161f79fc60c3d022be68c1c919168705af970f6](https://github.com/dOpensource/dsiprouter/commit/9161f79fc60c3d022be68c1c919168705af970f6) > Date: Sun, 22 Oct 2017 13:13:26 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9161f79fc60c3d022be68c1c919168705af970f6) [//]: # (START_SECTION bdac91314d94d450695afe886069df1def8cd1af) ### Added js to enable the FusionPBX toogle button and sytled the label for the toggle button > Commit: [bdac91314d94d450695afe886069df1def8cd1af](https://github.com/dOpensource/dsiprouter/commit/bdac91314d94d450695afe886069df1def8cd1af) > Date: Sun, 22 Oct 2017 17:10:25 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION bdac91314d94d450695afe886069df1def8cd1af) [//]: # (START_SECTION dd125a19e527939ff5c128856283e0bc4bc0def3) ### Initial Support for automatically syncing FusionPBX domains with Kamailio ' > Commit: [dd125a19e527939ff5c128856283e0bc4bc0def3](https://github.com/dOpensource/dsiprouter/commit/dd125a19e527939ff5c128856283e0bc4bc0def3) > Date: Thu, 12 Oct 2017 03:33:42 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION dd125a19e527939ff5c128856283e0bc4bc0def3) [//]: # (START_SECTION 0ae571409ca712c0e6032ff34a662fea009fde7c) ### Added some notes > Commit: [0ae571409ca712c0e6032ff34a662fea009fde7c](https://github.com/dOpensource/dsiprouter/commit/0ae571409ca712c0e6032ff34a662fea009fde7c) > Date: Wed, 11 Oct 2017 11:24:20 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0ae571409ca712c0e6032ff34a662fea009fde7c) [//]: # (START_SECTION d720a14a295d5c1f73db93e8b65b9fa459464018) ### Added an install script for configuring the CDR support within dSIPRouter > Commit: [d720a14a295d5c1f73db93e8b65b9fa459464018](https://github.com/dOpensource/dsiprouter/commit/d720a14a295d5c1f73db93e8b65b9fa459464018) > Date: Wed, 11 Oct 2017 11:16:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d720a14a295d5c1f73db93e8b65b9fa459464018) [//]: # (START_SECTION cb5b188dc0b07d264bbe59f0507a56b1a2a14ea4) ### update .gitignore fix #15 > Commit: [cb5b188dc0b07d264bbe59f0507a56b1a2a14ea4](https://github.com/dOpensource/dsiprouter/commit/cb5b188dc0b07d264bbe59f0507a56b1a2a14ea4) > Date: Wed, 11 Oct 2017 02:43:51 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION cb5b188dc0b07d264bbe59f0507a56b1a2a14ea4) [//]: # (START_SECTION e7427ec396688045c49a33df0bd997c6d0baf078) ### add info about configuring DSIProuter > Commit: [e7427ec396688045c49a33df0bd997c6d0baf078](https://github.com/dOpensource/dsiprouter/commit/e7427ec396688045c49a33df0bd997c6d0baf078) > Date: Mon, 9 Oct 2017 05:33:56 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION e7427ec396688045c49a33df0bd997c6d0baf078) [//]: # (START_SECTION 3f1075a79528d14ea34c921fab1a79791af034be) ### start server on port from settings fix #14 > Commit: [3f1075a79528d14ea34c921fab1a79791af034be](https://github.com/dOpensource/dsiprouter/commit/3f1075a79528d14ea34c921fab1a79791af034be) > Date: Mon, 9 Oct 2017 05:28:53 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION 3f1075a79528d14ea34c921fab1a79791af034be) [//]: # (START_SECTION 88bf113c19036b381fcc477166af1c2f98bf3e8d) ### set DSIP_PORT to variable > Commit: [88bf113c19036b381fcc477166af1c2f98bf3e8d](https://github.com/dOpensource/dsiprouter/commit/88bf113c19036b381fcc477166af1c2f98bf3e8d) > Date: Mon, 9 Oct 2017 05:14:06 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION 88bf113c19036b381fcc477166af1c2f98bf3e8d) [//]: # (START_SECTION 3d8be7ae7c28d86d733165b135781c8947e3330e) ### add PIP_CMD for pip3 on debian/ubuntu systems fix #11 > Commit: [3d8be7ae7c28d86d733165b135781c8947e3330e](https://github.com/dOpensource/dsiprouter/commit/3d8be7ae7c28d86d733165b135781c8947e3330e) > Date: Mon, 9 Oct 2017 05:10:03 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION 3d8be7ae7c28d86d733165b135781c8947e3330e) [//]: # (START_SECTION 61df3f49ca44afb570b4c7508238b7ebfebaab01) ### fix typo > Commit: [61df3f49ca44afb570b4c7508238b7ebfebaab01](https://github.com/dOpensource/dsiprouter/commit/61df3f49ca44afb570b4c7508238b7ebfebaab01) > Date: Mon, 9 Oct 2017 05:02:55 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION 61df3f49ca44afb570b4c7508238b7ebfebaab01) [//]: # (START_SECTION b95cb445d214687f20e728012f07606df5a6e9f3) ### fix markup and typos > Commit: [b95cb445d214687f20e728012f07606df5a6e9f3](https://github.com/dOpensource/dsiprouter/commit/b95cb445d214687f20e728012f07606df5a6e9f3) > Date: Mon, 9 Oct 2017 04:55:13 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION b95cb445d214687f20e728012f07606df5a6e9f3) [//]: # (START_SECTION 6f7ebede0ca8a72d6b6f7ccf0a9106bb862cac39) ### fix command for password change > Commit: [6f7ebede0ca8a72d6b6f7ccf0a9106bb862cac39](https://github.com/dOpensource/dsiprouter/commit/6f7ebede0ca8a72d6b6f7ccf0a9106bb862cac39) > Date: Mon, 9 Oct 2017 04:53:04 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION 6f7ebede0ca8a72d6b6f7ccf0a9106bb862cac39) [//]: # (START_SECTION feb0bebc498f8bf64ef9f2286c5c1cebb5127c4f) ### add info about License > Commit: [feb0bebc498f8bf64ef9f2286c5c1cebb5127c4f](https://github.com/dOpensource/dsiprouter/commit/feb0bebc498f8bf64ef9f2286c5c1cebb5127c4f) > Date: Mon, 9 Oct 2017 04:48:38 +0300 > Author: littleguga (fed777os@gmail.com) > Committer: littleguga (fed777os@gmail.com) > Signed: --- [//]: # (END_SECTION feb0bebc498f8bf64ef9f2286c5c1cebb5127c4f) [//]: # (START_SECTION 493840379aa8b915916a6ae6135ed8ccc3038bea) ### Initial commit for the fraud detection module > Commit: [493840379aa8b915916a6ae6135ed8ccc3038bea](https://github.com/dOpensource/dsiprouter/commit/493840379aa8b915916a6ae6135ed8ccc3038bea) > Date: Sun, 8 Oct 2017 06:03:37 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 493840379aa8b915916a6ae6135ed8ccc3038bea) [//]: # (START_SECTION 2fbe8142d19ed8cf0d6399f63214adcff09c1325) ### Add cdrs.sql > Commit: [2fbe8142d19ed8cf0d6399f63214adcff09c1325](https://github.com/dOpensource/dsiprouter/commit/2fbe8142d19ed8cf0d6399f63214adcff09c1325) > Date: Sat, 7 Oct 2017 19:32:48 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2fbe8142d19ed8cf0d6399f63214adcff09c1325) [//]: # (START_SECTION 592e3a4b0501cfa5a0dbe85693bd33cab18e5ce9) ### updated cdrs.sql with the new cdr sql file > Commit: [592e3a4b0501cfa5a0dbe85693bd33cab18e5ce9](https://github.com/dOpensource/dsiprouter/commit/592e3a4b0501cfa5a0dbe85693bd33cab18e5ce9) > Date: Sat, 7 Oct 2017 19:22:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 592e3a4b0501cfa5a0dbe85693bd33cab18e5ce9) [//]: # (START_SECTION 4dedc70f7dcecedc52e5f8dd2532ec9666308c96) ### Adding SQL for CDR's > Commit: [4dedc70f7dcecedc52e5f8dd2532ec9666308c96](https://github.com/dOpensource/dsiprouter/commit/4dedc70f7dcecedc52e5f8dd2532ec9666308c96) > Date: Thu, 5 Oct 2017 21:45:06 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 4dedc70f7dcecedc52e5f8dd2532ec9666308c96) [//]: # (START_SECTION 1f65b29dd8870d17100089e934c27e3774d136d1) ### Added support for domain routing (aka multidomain support) > Commit: [1f65b29dd8870d17100089e934c27e3774d136d1](https://github.com/dOpensource/dsiprouter/commit/1f65b29dd8870d17100089e934c27e3774d136d1) > Date: Fri, 29 Sep 2017 20:29:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 1f65b29dd8870d17100089e934c27e3774d136d1) [//]: # (START_SECTION 5cbd3ea1b5a3b584b4fea043c9ecfec6396d9ee8) ### Started to add support for Redhat 7.4 > Commit: [5cbd3ea1b5a3b584b4fea043c9ecfec6396d9ee8](https://github.com/dOpensource/dsiprouter/commit/5cbd3ea1b5a3b584b4fea043c9ecfec6396d9ee8) > Date: Wed, 27 Sep 2017 17:02:01 -0400 > Author: root (root@aio.kazoo.com) > Committer: root (root@aio.kazoo.com) > Signed: --- [//]: # (END_SECTION 5cbd3ea1b5a3b584b4fea043c9ecfec6396d9ee8) [//]: # (START_SECTION dbb626353928f9a130d707c057b038ff38ab9dca) ### Fixed an issue that might cause the wrong Python executable to be ran > Commit: [dbb626353928f9a130d707c057b038ff38ab9dca](https://github.com/dOpensource/dsiprouter/commit/dbb626353928f9a130d707c057b038ff38ab9dca) > Date: Fri, 15 Sep 2017 05:09:14 -0600 > Author: root (mack@dopensource.com) > Committer: root (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION dbb626353928f9a130d707c057b038ff38ab9dca) [//]: # (START_SECTION 64a24f404516f0c54222749fe7e924cce6706b40) ### Added support for CDR's to support call direction using a table column called calltype > Commit: [64a24f404516f0c54222749fe7e924cce6706b40](https://github.com/dOpensource/dsiprouter/commit/64a24f404516f0c54222749fe7e924cce6706b40) > Date: Thu, 14 Sep 2017 20:46:11 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 64a24f404516f0c54222749fe7e924cce6706b40) [//]: # (START_SECTION 0c40065b190b1eabb3ec95eb835a2f8decac81ff) ### Fixed it for Debian > Commit: [0c40065b190b1eabb3ec95eb835a2f8decac81ff](https://github.com/dOpensource/dsiprouter/commit/0c40065b190b1eabb3ec95eb835a2f8decac81ff) > Date: Mon, 11 Sep 2017 18:47:29 -0700 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION 0c40065b190b1eabb3ec95eb835a2f8decac81ff) [//]: # (START_SECTION e029468a13c3e8c0b6030a3276a179990d1de11e) ### Added a library that was need on Debian Jessie 8.8 > Commit: [e029468a13c3e8c0b6030a3276a179990d1de11e](https://github.com/dOpensource/dsiprouter/commit/e029468a13c3e8c0b6030a3276a179990d1de11e) > Date: Mon, 11 Sep 2017 14:12:50 -0700 > Author: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Committer: dopensource (dopensource@noc-lcb-spxy1.garlic.com) > Signed: --- [//]: # (END_SECTION e029468a13c3e8c0b6030a3276a179990d1de11e) [//]: # (START_SECTION 8c3eb214a98cad32e9798a2082170de6fd040870) ### Added logic to support stopping of both dsiprouter and rtpengine > Commit: [8c3eb214a98cad32e9798a2082170de6fd040870](https://github.com/dOpensource/dsiprouter/commit/8c3eb214a98cad32e9798a2082170de6fd040870) > Date: Sun, 10 Sep 2017 20:08:24 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 8c3eb214a98cad32e9798a2082170de6fd040870) [//]: # (START_SECTION 5a08d61c877932e39b997fff9e9980b2456dc348) ### Added logic to the stop command > Commit: [5a08d61c877932e39b997fff9e9980b2456dc348](https://github.com/dOpensource/dsiprouter/commit/5a08d61c877932e39b997fff9e9980b2456dc348) > Date: Sun, 10 Sep 2017 19:37:15 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 5a08d61c877932e39b997fff9e9980b2456dc348) [//]: # (START_SECTION 1a77d09fac5c722e294a1a8f9846758caa5a7c2a) ### Add logic to create a tmpfiles configuration for rtpengine > Commit: [1a77d09fac5c722e294a1a8f9846758caa5a7c2a](https://github.com/dOpensource/dsiprouter/commit/1a77d09fac5c722e294a1a8f9846758caa5a7c2a) > Date: Sun, 10 Sep 2017 19:28:28 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 1a77d09fac5c722e294a1a8f9846758caa5a7c2a) [//]: # (START_SECTION 918b7ba206cc5e0a97dd86e82695772c25ce8347) ### Fixed an issue with the script for installing the RTPEngine on Debian > Commit: [918b7ba206cc5e0a97dd86e82695772c25ce8347](https://github.com/dOpensource/dsiprouter/commit/918b7ba206cc5e0a97dd86e82695772c25ce8347) > Date: Sun, 10 Sep 2017 19:01:24 +0000 > Author: root (root@packer-debian-8-amd64.droplet.local) > Committer: root (root@packer-debian-8-amd64.droplet.local) > Signed: --- [//]: # (END_SECTION 918b7ba206cc5e0a97dd86e82695772c25ce8347) [//]: # (START_SECTION 7b433cd386abbb190cbde543708eaaf0dfd567bd) ### Updated the version > Commit: [7b433cd386abbb190cbde543708eaaf0dfd567bd](https://github.com/dOpensource/dsiprouter/commit/7b433cd386abbb190cbde543708eaaf0dfd567bd) > Date: Sun, 10 Sep 2017 18:46:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 7b433cd386abbb190cbde543708eaaf0dfd567bd) [//]: # (START_SECTION af7d0f4b31df1bf21bf91cba6946685235ab3ece) ### Added logic to handle NAT > Commit: [af7d0f4b31df1bf21bf91cba6946685235ab3ece](https://github.com/dOpensource/dsiprouter/commit/af7d0f4b31df1bf21bf91cba6946685235ab3ece) > Date: Sun, 10 Sep 2017 17:54:42 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION af7d0f4b31df1bf21bf91cba6946685235ab3ece) [//]: # (START_SECTION 55e4d2a715c262dbdd5df12944e966bef9ee3d72) ### Added support for NAT when the RTPEngine process is running > Commit: [55e4d2a715c262dbdd5df12944e966bef9ee3d72](https://github.com/dOpensource/dsiprouter/commit/55e4d2a715c262dbdd5df12944e966bef9ee3d72) > Date: Sun, 10 Sep 2017 14:02:02 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 55e4d2a715c262dbdd5df12944e966bef9ee3d72) [//]: # (START_SECTION 25a091ed28521fea47632909651a725fc7eca153) ### Updated the README.md > Commit: [25a091ed28521fea47632909651a725fc7eca153](https://github.com/dOpensource/dsiprouter/commit/25a091ed28521fea47632909651a725fc7eca153) > Date: Sun, 10 Sep 2017 13:20:08 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 25a091ed28521fea47632909651a725fc7eca153) [//]: # (START_SECTION f46610f88beaa8b937097461b5fb77d2967f7016) ### Changed the RTPEngine port from 7222 to 7722 > Commit: [f46610f88beaa8b937097461b5fb77d2967f7016](https://github.com/dOpensource/dsiprouter/commit/f46610f88beaa8b937097461b5fb77d2967f7016) > Date: Sun, 10 Sep 2017 00:16:04 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f46610f88beaa8b937097461b5fb77d2967f7016) [//]: # (START_SECTION 0f56674cf0f18f39e18918c2191417b69ed2ea82) ### Fixed the installer command line and tested it on CentOS - fixed #8 > Commit: [0f56674cf0f18f39e18918c2191417b69ed2ea82](https://github.com/dOpensource/dsiprouter/commit/0f56674cf0f18f39e18918c2191417b69ed2ea82) > Date: Sat, 9 Sep 2017 23:48:26 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0f56674cf0f18f39e18918c2191417b69ed2ea82) [//]: # (START_SECTION b5667380011d1f940fd03d2f22ffefd380fe816e) ### Fixed the installer command line and tested it on CentOS - Issue #8 > Commit: [b5667380011d1f940fd03d2f22ffefd380fe816e](https://github.com/dOpensource/dsiprouter/commit/b5667380011d1f940fd03d2f22ffefd380fe816e) > Date: Sat, 9 Sep 2017 23:45:58 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b5667380011d1f940fd03d2f22ffefd380fe816e) [//]: # (START_SECTION ec411e94da0c4dbcd4078483689d0f409d59d278) ### Finsihed up the command options > Commit: [ec411e94da0c4dbcd4078483689d0f409d59d278](https://github.com/dOpensource/dsiprouter/commit/ec411e94da0c4dbcd4078483689d0f409d59d278) > Date: Sat, 9 Sep 2017 22:12:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ec411e94da0c4dbcd4078483689d0f409d59d278) [//]: # (START_SECTION 8838b5495fa752aa388c09eb28be989f320269f0) ### Added logic to store the process ID when the dsiprouter process is started > Commit: [8838b5495fa752aa388c09eb28be989f320269f0](https://github.com/dOpensource/dsiprouter/commit/8838b5495fa752aa388c09eb28be989f320269f0) > Date: Sun, 27 Aug 2017 05:42:12 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 8838b5495fa752aa388c09eb28be989f320269f0) [//]: # (START_SECTION d0b603a0cd9ef29aaaeecd5094df91a29222a4b6) ### Added support for installing RTPEngine on Debian > Commit: [d0b603a0cd9ef29aaaeecd5094df91a29222a4b6](https://github.com/dOpensource/dsiprouter/commit/d0b603a0cd9ef29aaaeecd5094df91a29222a4b6) > Date: Tue, 22 Aug 2017 01:34:46 -0400 > Author: root (root@SR215) > Committer: root (root@SR215) > Signed: --- [//]: # (END_SECTION d0b603a0cd9ef29aaaeecd5094df91a29222a4b6) [//]: # (START_SECTION 18b6620ad991c1e7896cf605377be10fa6569387) ### Added support for installing RTPEngine > Commit: [18b6620ad991c1e7896cf605377be10fa6569387](https://github.com/dOpensource/dsiprouter/commit/18b6620ad991c1e7896cf605377be10fa6569387) > Date: Mon, 21 Aug 2017 10:42:46 -0400 > Author: root (root@SR215) > Committer: root (root@SR215) > Signed: --- [//]: # (END_SECTION 18b6620ad991c1e7896cf605377be10fa6569387) [//]: # (START_SECTION d92edfb0b0d787f072513b68b9aff16f2021a678) ### will install rtpengine on CentOS7 by default > Commit: [d92edfb0b0d787f072513b68b9aff16f2021a678](https://github.com/dOpensource/dsiprouter/commit/d92edfb0b0d787f072513b68b9aff16f2021a678) > Date: Mon, 21 Aug 2017 13:44:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION d92edfb0b0d787f072513b68b9aff16f2021a678) [//]: # (START_SECTION 91c0c1881b5186f479db3f1046e207291b077582) ### Fixed an issue with carriers not being assigned to the right address type of carrier > Commit: [91c0c1881b5186f479db3f1046e207291b077582](https://github.com/dOpensource/dsiprouter/commit/91c0c1881b5186f479db3f1046e207291b077582) > Date: Thu, 17 Aug 2017 17:08:32 -0400 > Author: root (root@SR215) > Committer: root (root@SR215) > Signed: --- [//]: # (END_SECTION 91c0c1881b5186f479db3f1046e207291b077582) [//]: # (START_SECTION 68abc449f309e8b27c18f7e076721736620a1f21) ### Update README.md > Commit: [68abc449f309e8b27c18f7e076721736620a1f21](https://github.com/dOpensource/dsiprouter/commit/68abc449f309e8b27c18f7e076721736620a1f21) > Date: Wed, 16 Aug 2017 22:57:07 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 68abc449f309e8b27c18f7e076721736620a1f21) [//]: # (START_SECTION cce6379b13070616fd186c01dee622366ec3c517) ### Added logic to install dSIPRouter on Debian Jesie > Commit: [cce6379b13070616fd186c01dee622366ec3c517](https://github.com/dOpensource/dsiprouter/commit/cce6379b13070616fd186c01dee622366ec3c517) > Date: Wed, 16 Aug 2017 22:50:31 -0400 > Author: root (root@SR215) > Committer: root (root@SR215) > Signed: --- [//]: # (END_SECTION cce6379b13070616fd186c01dee622366ec3c517) [//]: # (START_SECTION 69195deb45197ffd6a6a78a72df65df3108fff6a) ### Turned the Reload Kamailio button into an ajax query that updates a div called message > Commit: [69195deb45197ffd6a6a78a72df65df3108fff6a](https://github.com/dOpensource/dsiprouter/commit/69195deb45197ffd6a6a78a72df65df3108fff6a) > Date: Sun, 30 Jul 2017 13:55:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 69195deb45197ffd6a6a78a72df65df3108fff6a) [//]: # (START_SECTION 798f3bb9f5eb16724ad4eef99967155ecd00b602) ### Fixed issue #2 by adding a div that shows any error messages in the login form > Commit: [798f3bb9f5eb16724ad4eef99967155ecd00b602](https://github.com/dOpensource/dsiprouter/commit/798f3bb9f5eb16724ad4eef99967155ecd00b602) > Date: Sun, 30 Jul 2017 00:59:38 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 798f3bb9f5eb16724ad4eef99967155ecd00b602) [//]: # (START_SECTION 6921ff7af74065190741e1a718f8a0551825a095) ### Added support to deal with MySQL expiring db connections after a certain timeframe. > Commit: [6921ff7af74065190741e1a718f8a0551825a095](https://github.com/dOpensource/dsiprouter/commit/6921ff7af74065190741e1a718f8a0551825a095) > Date: Thu, 20 Jul 2017 12:08:27 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 6921ff7af74065190741e1a718f8a0551825a095) [//]: # (START_SECTION e1490bc7b17ae36c6f081b092014754ae3a8c115) ### Update README.md > Commit: [e1490bc7b17ae36c6f081b092014754ae3a8c115](https://github.com/dOpensource/dsiprouter/commit/e1490bc7b17ae36c6f081b092014754ae3a8c115) > Date: Mon, 17 Jul 2017 12:30:12 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e1490bc7b17ae36c6f081b092014754ae3a8c115) [//]: # (START_SECTION 3be4ee51309b570f6617403f24b1c8662f35c487) ### Update README.md > Commit: [3be4ee51309b570f6617403f24b1c8662f35c487](https://github.com/dOpensource/dsiprouter/commit/3be4ee51309b570f6617403f24b1c8662f35c487) > Date: Mon, 17 Jul 2017 12:28:06 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3be4ee51309b570f6617403f24b1c8662f35c487) [//]: # (START_SECTION 6546ded1458f724d33ffe527cfaf453d8979da0a) ### Update README.md > Commit: [6546ded1458f724d33ffe527cfaf453d8979da0a](https://github.com/dOpensource/dsiprouter/commit/6546ded1458f724d33ffe527cfaf453d8979da0a) > Date: Mon, 17 Jul 2017 12:26:18 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 6546ded1458f724d33ffe527cfaf453d8979da0a) [//]: # (START_SECTION 74dd6ca3e4d2f88db6d8205b2118a8e2f08475ed) ### Update README.md > Commit: [74dd6ca3e4d2f88db6d8205b2118a8e2f08475ed](https://github.com/dOpensource/dsiprouter/commit/74dd6ca3e4d2f88db6d8205b2118a8e2f08475ed) > Date: Mon, 17 Jul 2017 12:25:42 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 74dd6ca3e4d2f88db6d8205b2118a8e2f08475ed) [//]: # (START_SECTION b127757f8912661d9d9292db7567782e042d464b) ### Add files via upload > Commit: [b127757f8912661d9d9292db7567782e042d464b](https://github.com/dOpensource/dsiprouter/commit/b127757f8912661d9d9292db7567782e042d464b) > Date: Mon, 17 Jul 2017 12:14:06 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION b127757f8912661d9d9292db7567782e042d464b) [//]: # (START_SECTION 3994d71451bc8596310945e043cf31018212d815) ### Add files via upload > Commit: [3994d71451bc8596310945e043cf31018212d815](https://github.com/dOpensource/dsiprouter/commit/3994d71451bc8596310945e043cf31018212d815) > Date: Mon, 17 Jul 2017 12:12:21 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 3994d71451bc8596310945e043cf31018212d815) [//]: # (START_SECTION 23e91efb4050638a050125af743862915875bc11) ### Delete dsiprouter_outboundrouting > Commit: [23e91efb4050638a050125af743862915875bc11](https://github.com/dOpensource/dsiprouter/commit/23e91efb4050638a050125af743862915875bc11) > Date: Mon, 17 Jul 2017 12:10:47 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 23e91efb4050638a050125af743862915875bc11) [//]: # (START_SECTION e90589481e62211a0c57fff537c20db4226c3a47) ### Add files via upload > Commit: [e90589481e62211a0c57fff537c20db4226c3a47](https://github.com/dOpensource/dsiprouter/commit/e90589481e62211a0c57fff537c20db4226c3a47) > Date: Mon, 17 Jul 2017 12:09:31 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION e90589481e62211a0c57fff537c20db4226c3a47) [//]: # (START_SECTION 9571c5a872379c646a04d0b2701602d3cff93521) ### Update README.md > Commit: [9571c5a872379c646a04d0b2701602d3cff93521](https://github.com/dOpensource/dsiprouter/commit/9571c5a872379c646a04d0b2701602d3cff93521) > Date: Mon, 17 Jul 2017 12:08:48 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 9571c5a872379c646a04d0b2701602d3cff93521) [//]: # (START_SECTION 7c71eb8fb1af846c5acb56809d0064a3e1ae25f8) ### Add files via upload > Commit: [7c71eb8fb1af846c5acb56809d0064a3e1ae25f8](https://github.com/dOpensource/dsiprouter/commit/7c71eb8fb1af846c5acb56809d0064a3e1ae25f8) > Date: Mon, 17 Jul 2017 11:54:35 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 7c71eb8fb1af846c5acb56809d0064a3e1ae25f8) [//]: # (START_SECTION 2e195af1554cf51d9d57ad11b63862e9bc6c64e0) ### Adding a docs directory > Commit: [2e195af1554cf51d9d57ad11b63862e9bc6c64e0](https://github.com/dOpensource/dsiprouter/commit/2e195af1554cf51d9d57ad11b63862e9bc6c64e0) > Date: Mon, 17 Jul 2017 15:49:03 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 2e195af1554cf51d9d57ad11b63862e9bc6c64e0) [//]: # (START_SECTION 5d8e7ca80a359c04b1e5839266647245c8b05a0d) ### Fixed an issue with the MySQL DB closing a connection after 8 hours > Commit: [5d8e7ca80a359c04b1e5839266647245c8b05a0d](https://github.com/dOpensource/dsiprouter/commit/5d8e7ca80a359c04b1e5839266647245c8b05a0d) > Date: Mon, 17 Jul 2017 06:56:54 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 5d8e7ca80a359c04b1e5839266647245c8b05a0d) [//]: # (START_SECTION fbdd0b7881be637f8893f8f138805e56d06aa22d) ### added a intro screen > Commit: [fbdd0b7881be637f8893f8f138805e56d06aa22d](https://github.com/dOpensource/dsiprouter/commit/fbdd0b7881be637f8893f8f138805e56d06aa22d) > Date: Mon, 17 Jul 2017 04:21:21 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION fbdd0b7881be637f8893f8f138805e56d06aa22d) [//]: # (START_SECTION f852135775123a7d2f4d4338038f97bfda21efd0) ### Changed the navigation so that the left hand navigation is one level > Commit: [f852135775123a7d2f4d4338038f97bfda21efd0](https://github.com/dOpensource/dsiprouter/commit/f852135775123a7d2f4d4338038f97bfda21efd0) > Date: Mon, 17 Jul 2017 01:31:50 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION f852135775123a7d2f4d4338038f97bfda21efd0) [//]: # (START_SECTION 17555a94a9fae749331872c0cff2da8b1aca42d4) ### added execute permissions > Commit: [17555a94a9fae749331872c0cff2da8b1aca42d4](https://github.com/dOpensource/dsiprouter/commit/17555a94a9fae749331872c0cff2da8b1aca42d4) > Date: Sun, 16 Jul 2017 13:48:36 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 17555a94a9fae749331872c0cff2da8b1aca42d4) [//]: # (START_SECTION 0df719c6bd8d001311418c3b4bcc31d417d184ab) ### Made the kamailio configuration more generic > Commit: [0df719c6bd8d001311418c3b4bcc31d417d184ab](https://github.com/dOpensource/dsiprouter/commit/0df719c6bd8d001311418c3b4bcc31d417d184ab) > Date: Sun, 16 Jul 2017 03:06:39 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 0df719c6bd8d001311418c3b4bcc31d417d184ab) [//]: # (START_SECTION 67e9f09358bfd180d71c19d12377e72b8601a7bf) ### fixed an error with the symbolic link with the kamailio.cfg file > Commit: [67e9f09358bfd180d71c19d12377e72b8601a7bf](https://github.com/dOpensource/dsiprouter/commit/67e9f09358bfd180d71c19d12377e72b8601a7bf) > Date: Sat, 15 Jul 2017 23:20:35 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION 67e9f09358bfd180d71c19d12377e72b8601a7bf) [//]: # (START_SECTION 38f275621c9727894c80d37922023b1e82a62c13) ### Update README.md > Commit: [38f275621c9727894c80d37922023b1e82a62c13](https://github.com/dOpensource/dsiprouter/commit/38f275621c9727894c80d37922023b1e82a62c13) > Date: Sat, 15 Jul 2017 06:47:13 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 38f275621c9727894c80d37922023b1e82a62c13) [//]: # (START_SECTION 57f965d3c06817386be9519a1384f699d7ddf523) ### Update README.md > Commit: [57f965d3c06817386be9519a1384f699d7ddf523](https://github.com/dOpensource/dsiprouter/commit/57f965d3c06817386be9519a1384f699d7ddf523) > Date: Sat, 15 Jul 2017 06:46:28 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 57f965d3c06817386be9519a1384f699d7ddf523) [//]: # (START_SECTION 4b3798280cbf3b9735a54a982868e8cc91eb046f) ### Update README.md > Commit: [4b3798280cbf3b9735a54a982868e8cc91eb046f](https://github.com/dOpensource/dsiprouter/commit/4b3798280cbf3b9735a54a982868e8cc91eb046f) > Date: Sat, 15 Jul 2017 06:45:03 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 4b3798280cbf3b9735a54a982868e8cc91eb046f) [//]: # (START_SECTION a72121b9551921aa3dced32d943c6034ba318f82) ### Update README.md > Commit: [a72121b9551921aa3dced32d943c6034ba318f82](https://github.com/dOpensource/dsiprouter/commit/a72121b9551921aa3dced32d943c6034ba318f82) > Date: Sat, 15 Jul 2017 06:44:19 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION a72121b9551921aa3dced32d943c6034ba318f82) [//]: # (START_SECTION 955add0b09637e7ecdd62411272b2d0ef84d3aa3) ### Update README.md > Commit: [955add0b09637e7ecdd62411272b2d0ef84d3aa3](https://github.com/dOpensource/dsiprouter/commit/955add0b09637e7ecdd62411272b2d0ef84d3aa3) > Date: Sat, 15 Jul 2017 06:42:08 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: GitHub (noreply@github.com) > Signed: --- [//]: # (END_SECTION 955add0b09637e7ecdd62411272b2d0ef84d3aa3) [//]: # (START_SECTION ce6c5aac0db5476dc496c34388e4f9ce2c4b86e5) ### Initial commit as dsiprouter > Commit: [ce6c5aac0db5476dc496c34388e4f9ce2c4b86e5](https://github.com/dOpensource/dsiprouter/commit/ce6c5aac0db5476dc496c34388e4f9ce2c4b86e5) > Date: Sat, 15 Jul 2017 10:37:01 +0000 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION ce6c5aac0db5476dc496c34388e4f9ce2c4b86e5) [//]: # (START_SECTION b46b1e64f06f448bde78b98e3ae8228ce5f96067) ### Initial commit > Commit: [b46b1e64f06f448bde78b98e3ae8228ce5f96067](https://github.com/dOpensource/dsiprouter/commit/b46b1e64f06f448bde78b98e3ae8228ce5f96067) > Date: Sat, 15 Jul 2017 06:30:25 -0400 > Author: Mack Hendricks (mack@dopensource.com) > Committer: Mack Hendricks (mack@dopensource.com) > Signed: --- [//]: # (END_SECTION b46b1e64f06f448bde78b98e3ae8228ce5f96067) ================================================ FILE: CONTRIBUTING.md ================================================ # Contribution Guide This guide will provide you with the tools to start developing on the dSIPRouter platform and contributing back to the community. ## Getting Started First we will get our dev environment setup. We recommend you create a local or cloud hosted VM for your dev environment. 1. Clone the branch you would like to work on and create a feature branch for your changes. In this example we want to add support for **Ubuntu 20.04** to the **master** branch. ```bash git clone -b master https://github.com/dOpensource/dsiprouter.git /opt/dsiprouter cd /opt/dsiprouter git checkout -b feature-ubuntu-20.04 ``` 2. Install dSIPRouter with dev options. You may need different flags depending on where you deploy (servernat, etc..) ```bash ./dsiprouter.sh install -all -servernat -with_dev ``` This will run through the entire install process, then configure your git environment for the dsiprouter repo. For the rest of this walkthrough assume your starting location is in project root (default `/etc/dsiprouter`). 3. Make your changes.. Then prior to commit make sure you reset any defaults in `settings.py` or `kamailio.cfg`. You should **not** be commiting the generated versions from `/etc/dsiprouter`, this will include changes to the defaults. Instead you should check which changes you need to keep by runnning diff: ```bash diff /etc/dsiprouter/gui/settings.py /opt/dsiprouter/gui/settings.py ``` You should be able to pick out which changes were generated on install and which changes you need to keep. You can then merge the changes you need into the project source files. 4. Then commit, and push the changes to your feature branch. ```bash git add -A git commit git push ``` This will run the git hooks setup earlier and automatically do the following: - update python dependencies in requirements.txt - update the changelog doc - update the contributors doc - resolve git references in your commit message If your committing to a different remote (i.e. not origin), then you need to let our hooks know beforehand. This is useful if you forked dsiprouter, or you have a secondary upstream/downstream remote: ```bash git commit --remote=upstream git push upstream ``` 5. Create a Pull Request on Github (or Merge Request if on gitlab). See the [Github Docs](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) for more information. ## Core Architecture Principles - Installation should be less then 10 minutes - Someone with basic SIP knowledge should be able to configure it and place a test call within 10 minutes - The API structure should follow the Web UI ### File Structure ### Modules #### Structure Our module architecture has been loosely defined for a while, but we now want to define the structure and start moving all modules into this structure. Each module should have these components: | Component | Location | Purpose | | --------- | -------- | ------- | | module.js | gui/static/js/ | Contains the UI Javascript for a module | | module.css | gui/static/css/ | Contains the Cascading Style Sheets for a module | | module.py | gui/modules/module | Contains the Python scripts | For example, this is what the "***Domain***" module looks like: | Component | Location | Purpose | | --------- | -------- | ------- | | domain.js | gui/static/js/ | Contains the UI Javascript for a module | | domain.css | gui/static/css/ | Contains the Cascading Style Sheets for a module | | domain*.py | gui/modules/domain/ | Contains the Python scripts | | domain*.sql | gui/modules/domain/ | SQL Scripts for installing the database table structure fro the module | #### Packaging Modules not installed during dSIPRouter install should be packaged in a zipfile with an install script. The install script should place the components defined in the [Structure](#structure) section into their proper locations. #### Auto-discovery Modules should be automatically discoverable. This means that a new module should become automatically available from the UI without restarting the UI ### API Structure We are in the process of refactoring most of the application so that all of the GUI components leverages the API. Currently, the core API reside in gui/modules/api. #### Adding an API The following steps will guide you thru the process of adding a new API to dSIPRouter. We handle all of the security on your behalf. 1. Add a subdirectory in gui/modules/api/new_api 2. Copy sample_api.py to gui/modules/api/new_api/routes.py 3. Add the following line to the imports section of gui/dsiprouter.py ``` from modules.api.new_api.routes import new_api ``` 4. Add the following line to gui/dsiprouter to register the new API ``` app.register_blueprint(new_api) ``` 5. Restart dSIPRouter 6. Test the new API ``` export DSIP_TOKEN= export DSIP_HOST= curl --insecure -H "Authorization: Bearer $DSIP_TOKEN" -X GET https://$DSIP_HOST:5000/api/v1/new_api/new_entity ``` ### Usability - Web GUI - REST API - CLI Commands ### Development Environment ### dsiprouter.sh - Do not put platform specific commands in this file. Use the component/OS distribution/version.sh file to place those commands. For example, if we need to install the Letsencrypt OS package so that it can be used for Kamailio on debian, then you would place it in the kamailio/debian/9.sh and kamailio/debian/10.sh file ================================================ FILE: CONTRIBUTORS.md ================================================ ## Thank you to all contributors for your hard work ### Contributors - Asiel Lara - chelseatcarter - Dan - Dan Ryan - Dean Forester - demonspork - dependabot[bot] - dopensource - hailthemelody - James Peru Mmbono - jornsby - littleguga - Mack - Mack Hendicks - Mack hendricks - Mack Hendricks - matmurdock - Mat Murdock - Maurice Rogers - mhendricks - Micah Quinn - ncannon01 - Nicole - Omari S. King - reqlez - richard - Richard - Richard Bolaji - RichSosa28 - root - TheGolg - TuxPowered - Tyler Moore - VOICE1 ================================================ FILE: HA/consul/consul.fc ================================================ /usr/local/bin/consul -- gen_context(system_u:object_r:consul_exec_t,s0) /usr/lib/systemd/system/consul.service -- gen_context(system_u:object_r:consul_unit_file_t,s0) /etc/systemd/system/consul.service -- gen_context(system_u:object_r:consul_unit_file_t,s0) /var/lib/consul(/.*)? -- gen_context(system_u:object_r:consul_var_lib_t,s0) /var/run/consul.pid -- gen_context(system_u:object_r:consul_var_run_t,s0) /var/run/user/consul(/.*)? -- gen_context(system_u:object_r:consul_tmp_t,s0) /var/cache/consul(/.*)? -- gen_context(system_u:object_r:consul_cache_t,s0) /etc/consul.d(/.*)? -- gen_context(system_u:object_r:consul_etc_t,s0) ================================================ FILE: HA/consul/consul.hcl ================================================ datacenter = "${CLUSTER_NAME}" data_dir = "/opt/consul" node_name = "NODE_NAME" log_level = "INFO" bind_addr = "0.0.0.0" client_addr = "0.0.0.0" advertise_addr = "EXTERNAL_IP_ADDR" enable_syslog = true syslog_facility = "LOCAL3" encrypt = "${KEY_CIPHER_TEXT_B64}" retry_join = ${RETRY_JOIN} performance = { raft_multiplier = 1 } ================================================ FILE: HA/consul/consul.service ================================================ [Unit] Description="HashiCorp Consul Service Mesh" Documentation=https://www.consul.io/ Requires=network-online.target After=network-online.target ConditionFileNotEmpty=/etc/consul.d/consul.hcl [Service] User=consul Group=consul PIDFile=/var/run/consul/consul.pid ExecStartPre=[ -f "/var/run/consul/consul.pid" ] && /bin/rm -f /var/run/consul/consul.pid ExecStartPre=/usr/local/bin/consul validate /etc/consul.d ExecStart=/usr/local/bin/consul agent -pid-file /var/run/consul/consul.pid -enable-local-script-checks -config-dir=/etc/consul.d ExecReload=/usr/local/bin/consul reload KillMode=process Restart=on-failure LimitNOFILE=65536 [Install] WantedBy=multi-user.target ================================================ FILE: HA/consul/consul.te ================================================ policy_module(consul, 1.0.0) ######################################## # # Declarations # type consul_t; type consul_exec_t; init_daemon_domain(consul_t, consul_exec_t) attribute consul_domain; type consul_etc_t; files_config_file(consul_etc_t) type consul_cache_t; files_type(consul_cache_t) type consul_var_lib_t; files_type(consul_var_lib_t) type consul_var_run_t; files_pid_file(consul_var_run_t) type consul_tmp_t; files_tmp_file(consul_tmp_t) type consul_unit_file_t; systemd_unit_file(consul_unit_file_t) ####################################### # # consul local policy # optional_policy(` unconfined_domain(consul_t) ') ######################################## # # consul domain local policy # allow consul_t self:fifo_file rw_fifo_file_perms; allow consul_t self:unix_stream_socket create_stream_socket_perms; allow consul_t self:process signal_perms; allow consul_t self:tcp_socket create_socket_perms; allow consul_t self:udp_socket create_socket_perms; manage_dirs_pattern(consul_domain, consul_cache_t, consul_cache_t) manage_files_pattern(consul_domain, consul_cache_t, consul_cache_t) manage_lnk_files_pattern(consul_t, consul_cache_t, consul_cache_t) files_var_filetrans(consul_domain, consul_cache_t, { dir file }) manage_dirs_pattern(consul_domain, consul_log_t, consul_log_t) manage_files_pattern(consul_domain, consul_log_t, consul_log_t) manage_lnk_files_pattern(consul_t, consul_log_t, consul_log_t) logging_log_filetrans(consul_domain, consul_log_t, { dir file }) manage_dirs_pattern(consul_domain, consul_var_lib_t, consul_var_lib_t) manage_files_pattern(consul_domain, consul_var_lib_t, consul_var_lib_t) manage_lnk_files_pattern(consul_t, consul_var_lib_t, consul_var_lib_t) files_var_lib_filetrans(consul_domain, consul_var_lib_t, { dir file }) manage_dirs_pattern(consul_domain, consul_var_run_t, consul_var_run_t) manage_files_pattern(consul_domain, consul_var_run_t, consul_var_run_t) manage_lnk_files_pattern(consul_t, consul_var_run_t, consul_var_run_t) files_pid_filetrans(consul_domain, consul_var_run_t, { dir file }) manage_dirs_pattern(consul_t, consul_tmp_t, consul_tmp_t) manage_files_pattern(consul_t, consul_tmp_t, consul_tmp_t) manage_lnk_files_pattern(consul_t, consul_tmp_t,consul_tmp_t) files_tmp_filetrans(consul_t, consul_tmp_t, { file fifo_file dir }) # Stay in consul domain if consul is called by a script can_exec(consul_domain, consul_exec_t) kernel_read_system_state(consul_t) kernel_read_network_state(consul_t) corecmd_exec_bin(consul_domain) corecmd_exec_shell(consul_domain) ## Consul needs to bind to ports 8301 8302 8300 8400 8500 8600, all labeled unreserved_port_t corenet_tcp_bind_all_unreserved_ports(consul_domain) corenet_udp_bind_all_unreserved_ports(consul_domain) ## Including read random and read urandom since Consul features TLS encryption. Maybe we'll need those devices. dev_read_rand(consul_domain) dev_read_urand(consul_domain) dev_read_sysfs(consul_domain) domain_use_interactive_fds(consul_domain) fs_getattr_all_fs(consul_domain) fs_read_hugetlbfs_files(consul_domain) files_read_etc_files(consul_domain) files_read_usr_files(consul_domain) miscfiles_read_localization(consul_domain) sysnet_dns_name_resolve(consul_domain) ================================================ FILE: HA/consul/installConsulCluster.sh ================================================ #!/usr/bin/env bash # # Summary: consul service mesh # Supported OS: debian, ubuntu, linuxmint, redhat, centos, amazon linux # Notes: you must be able to ssh to every node in the cluster from where script is run # supported ssh authentication methods: password, pubkey # by default the consul agent type is set to client # atleast one consul agent must be a server or installation will hault # Usage: ./installConsulCluster.sh [-h|--help|-cloud] <[user1[:pass1]@]node1[:port1][?server]> <[user2[:pass2]@]node2[:port2][?server]> ... # # set project root, if in a git repo resolve top level dir PROJECT_ROOT=${PROJECT_ROOT:-$(dirname $(dirname $(dirname $(readlink -f "$0"))))} # import shared library functions . ${PROJECT_ROOT}/HA/shared_lib.sh # node configuration settings export CLUSTER_NAME="consulcluster" CLOUD_AUTO_JOIN=0 DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" PATH_UPDATE_FILE="/etc/profile.d/dsip_paths.sh" CONSUL_PRIV_KEY="${DSIP_SYSTEM_CONFIG_DIR}/consulkey" export KEY_CIPHER_TEXT_B64=$(dd if=/dev/urandom bs=32 count=1 2>/dev/null | base64) SERVICES_TO_TRACK=("dsiprouter" "kamailio" "haproxy" "asterisk" "freeswitch" "rtpengine" "rtpproxy" "mysql" "mariadb" "postgresql" "redis") SSH_DEFAULT_OPTS="-o StrictHostKeyChecking=no -o CheckHostIp=no -o ServerAliveInterval=5 -o ServerAliveCountMax=2" CONSUL_TCP_PORTS=(8300 8301 8302 8500 8501 8600) CONSUL_UDP_PORTS=(8301 8302 8600) CONSUL_URL="https://releases.hashicorp.com/consul/" printUsage() { pprint "$0 [-h|--help|-cloud] <[user1[:pass1]@]node1[:port1][?server]> <[user2[:pass2]@]node2[:port2][?server]> ..." } if ! isRoot; then printerr "Must be run with root privileges" && exit 1 fi if [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then printUsage && exit 1 fi # loop through args and evaluate any options ARGS=() while (( $# > 0 )); do ARG="$1" case $ARG in -cloud) # TODO: support cloud auto join # https://www.consul.io/docs/agent/cloud-auto-join.html printerr "Cloud auto join is not supported at this time" && exit 1 WITH_CLOUD_AUTO_JOIN=1 shift ;; *) # add to list of args ARGS+=( "$ARG" ) shift ;; esac done # make sure required args are fulfilled if (( ${#ARGS[@]} < 1 )); then printerr "No nodes provided to setup consul cluster" && printUsage && exit 1 elif ! echo "${ARGS[*]}" | grep -q '?server'; then printerr "At least 1 server node is required to setup a consul cluster" && printUsage && exit 1 fi # install local requirements for script setOSInfo case "$DISTRO" in debian|ubuntu|linuxmint) apt-get install -y sshpass gawk perl ;; centos|redhat|amazon) yum install -y epel-release yum install -y sshpass gawk perl ;; *) printerr "Your OS Distro is currently not supported" exit 1 ;; esac # prints number of nodes in cluster getClusterSize() { consul members 2>/dev/null | tail -n +2 | wc -l } isNodeInCluster() { local NODE_NAME="$1" if consul catalog nodes 2>/dev/null | tail -n +2 | awk '{print $1}' | grep -q "$NODE_NAME"; then return 0 else return 1 fi } # $1 == ipv4 persistent rules file # $2 == ipv6 persistent rules file setFirewallRules() { local IP4RESTORE_FILE="$1" local IP6RESTORE_FILE="$2" # use firewalld if installed if cmdExists "firewall-cmd"; then for PORT in ${CONSUL_TCP_PORTS[@]}; do firewall-cmd --zone=public --add-port=${PORT}/tcp --permanent done for PORT in ${CONSUL_UDP_PORTS[@]}; do firewall-cmd --zone=public --add-port=${PORT}/udp --permanent done firewall-cmd --reload else # set ipv4 firewall rules (on each node) for PORT in ${CONSUL_TCP_PORTS[@]}; do iptables -I INPUT 1 -p tcp --dport ${PORT} -j ACCEPT done for PORT in ${CONSUL_UDP_PORTS[@]}; do iptables -I INPUT 1 -p udp --dport ${PORT} -j ACCEPT done # set ipv6 firewall rules (on each node) for PORT in ${CONSUL_TCP_PORTS[@]}; do ip6tables -I INPUT 1 -p tcp --dport ${PORT} -j ACCEPT done for PORT in ${CONSUL_UDP_PORTS[@]}; do ip6tables -I INPUT 1 -p udp --dport ${PORT} -j ACCEPT done fi # Remove duplicates and save mkdir -p $(dirname ${IP4RESTORE_FILE}) iptables-save | awk '!x[$0]++' > ${IP4RESTORE_FILE} mkdir -p $(dirname ${IP6RESTORE_FILE}) ip6tables-save | awk '!x[$0]++' > ${IP6RESTORE_FILE} } createConsulTrackedService() { local NAME="$1" local SYSTEMCTL_CMD=$(type -p systemctl) (cat << EOF { "service": { "name": "${NAME}", "tags": ["${NAME}"], "check": { "id": "${NAME}", "service_id": "${NAME}", "args": ["${SYSTEMCTL_CMD}", "is-active", "--quiet", "${NAME}"], "interval": "10s" } } } EOF ) > /etc/consul.d/${NAME}.json } # loop through args and grab hosts export NUM_NODES=0 HOST_LIST=() for NODE in ${ARGS[@]}; do HOST_LIST+=( $(printf '%s' "$NODE" | cut -d '@' -f 2- | cut -d ':' -f -1) ) NUM_NODES=$((NUM_NODES+1)) done # set the auto join list for nodes export RETRY_JOIN="[$(join ',' $(printf '"%s" ' "${HOST_LIST[@]}"))]" # set config files as variables for transport CONSUL_SYSTEM_SERVICE=$(cat ${PROJECT_ROOT}/HA/consul/consul.service) CONSUL_SYSLOG_CONFIG=$(cat ${PROJECT_ROOT}/resources/syslog/consul.conf) CONSUL_LOGROTATE_CONFIG=$(cat ${PROJECT_ROOT}/resources/logrotate/consul) CONSUL_SELINUX_MODULE=$(cat ${PROJECT_ROOT}/HA/consul/consul.te) CONSUL_SELINUX_CONTEXT=$(cat ${PROJECT_ROOT}/HA/consul/consul.fc) # prevent quote expansion in client and server config # we will just scp it over because bash expands quote on assignment mkdir -p /tmp/consul envsubst < ${PROJECT_ROOT}/HA/consul/consul.hcl > /tmp/consul/consul.hcl envsubst < ${PROJECT_ROOT}/HA/consul/server.hcl > /tmp/consul/server.hcl # TODO: in an example config these dns settings were used, do we need them? # - https://github.com/sboily/asterisk-consul-module/blob/master/contribs/kamailio/config/kamailio/kamailio.cfg # # /etc/kamailio/kamailio.cfg # #dns_cache_init=no #use_dns_cache=no # # loop through args and run setup commands i=0 for NODE in ${ARGS[@]}; do USER=$(printf '%s' "$NODE" | cut -s -d '@' -f -1 | cut -d ':' -f -1) PASS=$(printf '%s' "$NODE" | cut -s -d '@' -f -1 | cut -s -d ':' -f 2-) HOST=$(printf '%s' "$NODE" | cut -d '@' -f 2- | cut -d ':' -f -1) PORT=$(printf '%s' "$NODE" | cut -d '@' -f 2- | cut -s -d ':' -f 2- | cut -d '?' -f -1) TYPE=$(printf '%s' "$NODE" | cut -s -d '?' -f 2-) # default user is root for ssh USER=${USER:-root} # default port is 22 for ssh PORT=${PORT:-22} # default type is client [[ "$TYPE" != "server" ]] && TYPE="client" # validate host connection if ! checkConn ${HOST} ${PORT}; then printerr "Could not establish connection to host [${HOST}] on port [${PORT}]" && exit 1 fi SSH_CMD="ssh" SCP_CMD="scp" if [ -z "$HOST" ]; then printerr "Node [${NODE}] does not contain a host" && printUsage && exit 1 else SSH_REMOTE_HOST="${HOST}" fi SSH_REMOTE_HOST="${USER}@${SSH_REMOTE_HOST}" if [ -n "$PASS" ]; then #SSH_CMD="sshpass -f <(printf '${PASS}\n') ssh" #SSH_CMD="sshpass -p '${PASS}' ssh" export SSHPASS="${PASS}" SSH_CMD="sshpass -e ssh" SCP_CMD="sshpass -e scp" fi SSH_OPTS="${SSH_DEFAULT_OPTS} -p ${PORT}" SCP_OPTS="-q ${SSH_DEFAULT_OPTS} -P ${PORT}" SSH_CMD="${SSH_CMD} ${SSH_REMOTE_HOST} ${SSH_OPTS}" SCP_CMD="${SCP_CMD} ${SCP_OPTS}" # validate unattended ssh connection if ! checkSsh ${SSH_CMD}; then printerr "Could not establish unattended ssh connection to [${SSH_REMOTE_HOST}] on port [${PORT}]" && exit 1 fi NODE_NAME="node$((i+1))" # remote server will be using bash as interpreter SSH_CMD="${SSH_CMD} bash" # DEBUG: printdbg "SSH_CMD: ${SSH_CMD}" printdbg "SCP_CMD: ${SCP_CMD}" # transfer some files before we connect ${SCP_CMD} -r /tmp/consul ${SSH_REMOTE_HOST}:/tmp/ rm -rf /tmp/consul # run commands through ssh (${SSH_CMD} <<- EOSSH set -x # re-declare functions and vars we pass to remote server # note that variables in function definitions (from calling environement) # lose scope unless local to function, they must be passed to remote $(typeset -f printdbg) $(typeset -f printerr) $(typeset -f cmdExists) $(typeset -f setOSInfo) $(typeset -f join) $(typeset -f pathCheck) $(typeset -f getClusterSize) $(typeset -f setFirewallRules) $(typeset -f detectServiceMan) $(typeset -f createConsulTrackedService) $(typeset -f isNodeInCluster) $(typeset -f ipv4Test) $(typeset -f ipv6Test) $(typeset -f getExternalIP) $(typeset -f getInternalIP) ESC_SEQ="\033[" ANSI_NONE="\${ESC_SEQ}39;49;00m" # Reset colors ANSI_RED="\${ESC_SEQ}1;31m" ANSI_GREEN="\${ESC_SEQ}1;32m" TYPE="$TYPE" # systemd is a hard requirement detectServiceMan if [[ "\$SERVICE_MANAGER" != "systemd" ]]; then printerr "Systemd is required and not installed on ${NODE_NAME}" && exit 1 fi setOSInfo printdbg 'installing requirements' case "\$DISTRO" in debian|ubuntu|linuxmint) # debian-based distro specific settings IP4RESTORE_FILE="/etc/iptables/rules.v4" IP6RESTORE_FILE="/etc/iptables/rules.v6" export DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget sed gawk dirmngr iptables-persistent netfilter-persistent ;; centos|redhat|amazon) # rhel-based distro specific settings IP4RESTORE_FILE="/etc/sysconfig/iptables" IP6RESTORE_FILE="/etc/sysconfig/ip6tables" yum install -y curl wget sed gawk # rhel/centos SELINUX # from: https://lists.fedoraproject.org/archives/list/selinux@lists.fedoraproject.org/thread/IOKQ26N53CT7WMZZWBW2RTSD2YT7TWNQ/ if sestatus | head -1 | grep -qi 'enabled'; then yum install -y policycoreutils-python setools-console selinux-policy-devel semanage permissive -a consul_t setsebool -P httpd_can_network_connect 1 mkdir -p /tmp/selinux printf '%s' "\$CONSUL_SELINUX_MODULE" > /tmp/selinux/consul.te printf '%s' "\$CONSUL_SELINUX_CONTEXT" > /tmp/selinux/consul.fc checkmodule -M -m /tmp/selinux/consul.te -o /tmp/selinux/consul.mod semodule_package -m /tmp/selinux/galera.mod -f /tmp/selinux/consul.fc -o /tmp/selinux/consul.pp semodule -i /tmp/selinux/consul.pp fi ;; *) printerr "OS Distro is currently not supported" && exit 1 ;; esac # set firewall rules setFirewallRules "\$IP4RESTORE_FILE" "\$IP6RESTORE_FILE" # fix PATH if needed # we are using the default install paths but these may change in the future mkdir -p \$(dirname ${PATH_UPDATE_FILE}) if [[ ! -e "$PATH_UPDATE_FILE" ]]; then (cat << 'EOF' #export PATH="/usr/local/bin\${PATH:+:\$PATH}" #export PATH="\${PATH:+\$PATH:}/usr/sbin" #export PATH="\${PATH:+\$PATH:}/sbin" EOF ) > ${PATH_UPDATE_FILE} fi # minimalistic approach avoids growing duplicates # enable (uncomment) and import only what we need PATH_UPDATED=0 # - consul if ! pathCheck /usr/local/bin; then sed -i -r 's|^#(export PATH="/usr/local/bin\\$\{PATH:\+:\\$PATH\}")\$|\1|' ${PATH_UPDATE_FILE} PATH_UPDATED=1 fi # import new path definition if it was updated (( \${PATH_UPDATED} == 1 )) && . \${PATH_UPDATE_FILE} # install consul and configure cluster settings if ! cmdExists "consul"; then printdbg "Installing consul" CONSUL_VER=\$(curl -s "$CONSUL_URL" 2>/dev/null | grep -oP ')' | head -1) ARCH=\$(uname -m) # normalize architecture to download correct ver (default to x86_64) case "\$ARCH" in x86_64) ARCH="amd64" ;; i386|i686) ARCH="386" ;; aarch64*|armv[8-9]*) ARCH="arm64" ;; arm|armv[0-7]*) ARCH="arm" ;; *) ARCH="amd64" ;; esac wget -q -O /tmp/consul.zip "${CONSUL_URL}\${CONSUL_VER}/consul_\${CONSUL_VER}_linux_\${ARCH}.zip" unzip -f /tmp/consul.zip consul -d /usr/local/bin/ && rm -f /tmp/consul.zip && chmod +x /usr/local/bin/consul if ! cmdExists "consul"; then printerr "Consul install failed on ${NODE_NAME}" && exit 1 fi else printdbg "consul is already installed" fi consul -autocomplete-install complete -C /usr/local/bin/consul consul useradd --system --user-group --home /etc/consul.d --shell /bin/false --comment "Consul Service Mesh" consul mkdir -p /opt/consul mkdir -p /etc/consul.d mkdir -p /var/run/consul chown -R consul:consul /opt/consul chown -R consul:consul /var/run/consul # create consul agent client configs sed "s|EXTERNAL_IP_ADDR|\$(getExternalIP)|; s|NODE_NAME|${NODE_NAME}|" /tmp/consul/consul.hcl > /etc/consul.d/consul.hcl # create consul agent server configs if [[ "$TYPE" == "server" ]]; then cp -f /tmp/consul/server.hcl /etc/consul.d/server.hcl fi # setup consul syslog configs printf '%s' "$CONSUL_SYSLOG_CONFIG" > /etc/rsyslog.d/consul.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # setup consul logrotate configs printf '%s' "$CONSUL_LOGROTATE_CONFIG" > /etc/logrotate.d/consul # create consul system service printf '%s' "$CONSUL_SYSTEM_SERVICE" > /etc/systemd/system/consul.service chmod 0644 /etc/systemd/system/consul.service # create consul agents for services SERVICES=\$(systemctl list-units | awk '{print $1}' | grep -oP "\$(join '|' ${SERVICES_TO_TRACK[@]})") for SERVICE in \${SERVICES[@]}; do createConsulTrackedService "\$SERVICE" done # set permissions for consul configs and cleanup tmp chmod -R 0740 /etc/consul.d chown -R consul:consul /etc/consul.d rm -rf /tmp/consul systemctl enable consul systemctl restart consul if systemctl is-active --quiet consul; then printdbg "Consul was successfully configured on [${NODE_NAME}]" else printerr "Consul configuration failed on [${NODE_NAME}]" && exit 1 fi # check if node connected to cluster sleep 5 if (( \$? == 0 )) && (( \$(getClusterSize) > 0 )) && isNodeInCluster "${NODE_NAME}"; then printdbg "Adding Node to cluster success" else printerr "Adding Node to cluster failed [${NODE_NAME}]" && exit 1 fi exit 0 EOSSH ) 2>&1 if (( $? != 0 )); then printerr "consul configuration failed on ${HOST}" && exit 1 fi i=$((i+1)) done unset CLUSTER_NAME KEY_CIPHER_TEXT_B64 NUM_NODES RETRY_JOIN exit 0 ================================================ FILE: HA/consul/server.hcl ================================================ server = true bootstrap_expect = ${NUM_NODES} ui = true ================================================ FILE: HA/mysql/installAAGaleraReplication.sh ================================================ #!/usr/bin/env bash # # Summary: mysql active active galera replication # # Supported OS: debian, centos, amzn # # Notes: uses mariadb # you must be able to ssh to every node in the cluster from where script is run # supported ssh authentication methods: password, pubkey # if quorum is lost between 2-node cluster you must reset the quorum, bootstrap the non-primary: # mysql -e "SET GLOBAL wsrep_provider_options='pc.bootstrap=YES';" # ref: ## # TODO: support active/passive galera replication # https://medium.com/mr-dops/mariadb-with-galera-cluster-8ded2e83721b # # set project root, if in a git repo resolve top level dir PROJECT_ROOT=${PROJECT_ROOT:-$(dirname $(dirname $(dirname $(readlink -f "$0"))))} # import shared library functions . ${PROJECT_ROOT}/HA/shared_lib.sh # node configuration settings CLUSTER_NAME="mysqlcluster" MYSQL_USER="root" MYSQL_PASS="$(createPass)" MYSQL_PORT="3306" BACKUPS_DIR="/var/backups" WITH_REMOTE_DB=0 MYSQL_BACKUP_DIR="${BACKUPS_DIR}/mysql" GALERA_REPL_PORT="4567" GALERA_INCR_PORT="4568" GALERA_SNAP_PORT="4444" SSH_KEY_FILE="" # galera library only available on mariadb ver >= 10.1 # at the time of writing default repo ver == 5.5 # they also do have patches for 5.5 and 10.0 if needed MYSQL_REQ_VER="10.1" DEBUG=0 # global variables used throughout script NODE_NAMES=() HOST_LIST=() CLUSTER_NODE_ADDRS=() declare -A CLOUD_DICT CLOUD_PLATFORM="" CLUSTER_RESOURCES=(cluster_vip cluster_srcaddr) SSH_CMD_LIST=() printUsage() { pprint "Usage: $0 [-h|--help|-debug|-remotedb] [-i ] <[sshuser1[:sshpass1]@]node1[:sshport1]> <[sshuser2[:sshpass2]@]node2[:sshport2]> ..." } # loop through args and evaluate any options NODES=() while (( $# > 0 )); do ARG="$1" case $ARG in -h|--help) printUsage exit 0 ;; -debug) DEBUG=1 shift ;; -remotedb) WITH_REMOTE_DB=1 shift ;; -i) shift SSH_KEY_FILE="$1" shift ;; *) # add to list of args NODES+=( "$ARG" ) shift ;; esac done if (( $DEBUG == 1 )); then set -x fi if (( ${#NODES[@]} < 2 )); then printerr "At least 2 nodes are required to setup replication" printUsage exit 1 fi # install local requirements for script if ! cmdExists 'ssh' || ! cmdExists 'sshpass' || ! cmdExists 'nmap' || ! cmdExists 'sed' || ! cmdExists 'awk'; then printdbg 'Installing local requirements for cluster install' if cmdExists 'apt-get'; then runas apt-get install -y openssh-client sshpass gawk elif cmdExists 'dnf'; then runas dnf install -y openssh-clients sshpass gawk elif cmdExists 'yum'; then runas yum install --enablerepo=epel -y openssh-clients sshpass gawk else printerr "Your local OS is not currently not supported" exit 1 fi fi # sanity check if (( $? != 0 )); then printerr 'Could not install requirements for cluster install' exit 1 fi # prints number of nodes in cluster getClusterSize() { local OPT="" local MYSQL_PARAMS=() while (( $# > 0 )); do OPT="$1" case $OPT in --user*) MYSQL_PARAMS+=( --user=$(printf '%s' "$1" | cut -d '=' -f 2-) ) shift ;; --pass*) MYSQL_PARAMS+=( --password=$(printf '%s' "$1" | cut -d '=' -f 2-) ) shift ;; --host*) MYSQL_PARAMS+=( --host=$(printf '%s' "$1" | cut -d '=' -f 2-) ) shift ;; --port*) MYSQL_PARAMS+=( --port=$(printf '%s' "$1" | cut -d '=' -f 2-) ) shift ;; *) # no valid args skip shift ;; esac done mysql -sN ${MYSQL_PARAMS[@]} \ -e "select VARIABLE_VALUE from information_schema.GLOBAL_STATUS where VARIABLE_NAME='wsrep_cluster_size'" \ || echo '0' } setFirewallRules() { firewall-cmd --zone=public --add-port=${MYSQL_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${GALERA_REPL_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${GALERA_REPL_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${GALERA_INCR_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${GALERA_SNAP_PORT}/tcp --permanent firewall-cmd --reload } # find the first private IP address (reverse order) on a physical interface # this is typically the secondary interface or secondary IP on the primary interface # if no RFC1918 address is found use the IP associated with the default route # NOTE: sourced here to allow easier declaration on remote node source <( cat < 1 )); then printerr 'nodes are deployed on different cloud platforms' printerr 'installation on differing cloud platforms is not supported' exit 1 fi # loop through args and pre-configure mysql server i=0 while (( $i < ${#NODES[@]} )); do # run commands through ssh (${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "$(typeset -f runas); runas bash" <<- EOSSH if (( $DEBUG == 1 )); then set -x fi # re-declare functions and vars we pass to remote server # note that variables in function definitions (from calling environement) # lose scope unless local to function, they must be passed to remote ESC_SEQ="\033[" ANSI_NONE="\${ESC_SEQ}39;49;00m" # Reset colors ANSI_RED="\${ESC_SEQ}1;31m" ANSI_GREEN="\${ESC_SEQ}1;32m" MYSQL_USER="$MYSQL_USER" MYSQL_PASS="$MYSQL_PASS" MYSQL_PORT="$MYSQL_PORT" GALERA_REPL_PORT="$GALERA_REPL_PORT" GALERA_INCR_PORT="$GALERA_INCR_PORT" GALERA_SNAP_PORT="$GALERA_SNAP_PORT" NODE_NAMES=( ${NODE_NAMES[@]} ) CLUSTER_NODE_ADDRS=( ${CLUSTER_NODE_ADDRS[@]} ) $(typeset -f printdbg) $(typeset -f printwarn) $(typeset -f printerr) $(typeset -f cmdExists) $(typeset -f getPkgVer) $(typeset -f mysqlSecureInstall) $(typeset -f setFirewallRules) # state is tracked here, if it exists we have completed this section already STATE_FILE="${MYSQL_BACKUP_DIR}/state/${HOST_LIST[$i]}" if [[ -f "\$STATE_FILE" ]]; then printwarn 'initial configuration already complete, skipping..' exit 0 fi # trap exit signals to remove state file in case we exit early cleanupHandler() { rm -f "\$STATE_FILE" trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM } trap 'cleanupHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM printdbg 'setting up cluster hostname resolution' # for each node remove the loopback hostname if present # this will cause issues when adding nodes to the cluster # ref: https://serverfault.com/questions/363095/why-does-my-hostname-appear-with-the-address-127-0-1-1-rather-than-127-0-0-1-in grep -v -E '^127\.0\.1\.1' /etc/hosts >/tmp/hosts && mv -f /tmp/hosts /etc/hosts # add section for the galera hostnames if ! grep -q 'MYSQL_CONFIG_START' /etc/hosts 2>/dev/null; then bash -c "( echo '' echo '#####MYSQL_CONFIG_START' )>>/etc/hosts" j=0 while (( \$j < \${#CLUSTER_NODE_ADDRS[@]} )); do bash -c "echo '\${CLUSTER_NODE_ADDRS[\$j]} \${NODE_NAMES[\$j]}' >>/etc/hosts" j=\$((j+1)) done bash -c "echo '#####MYSQL_CONFIG_END' >>/etc/hosts" else j=0; tmp=''; while (( \$j < \${#CLUSTER_NODE_ADDRS[@]} )); do tmp+="\${CLUSTER_NODE_ADDRS[\$j]} \${NODE_NAMES[\$j]}\\n" j=\$((j+1)) done perl -0777 -i -pe "s|(#+MYSQL_CONFIG_START).*?(#+MYSQL_CONFIG_END)|\\1\\n\${tmp}\\2|gms" /etc/hosts fi # backup dirs we will be using mkdir -p ${MYSQL_BACKUP_DIR}/{etc,var/lib,\${HOME},dumps,state} # will determine how we merge databases later on # 0 == no merge (fresh install no changes) # 1 == overwrite (existing databases cloned) # 2 == merge (merge primary databases with existing) echo 'MYSQL_MERGE_ACTION=0' >>\${STATE_FILE} printdbg 'installing requirements on remote node ${HOST_LIST[$i]}' if cmdExists 'apt-get'; then # debian specific settings echo 'MYSQL_SECTION="mysqld"' >>\${STATE_FILE} echo 'MYSQL_CLUSTER_CONFIG="/etc/mysql/mariadb.conf.d/cluster.cnf"' >>\${STATE_FILE} export DEBIAN_FRONTEND=noninteractive apt-get install -y curl perl sed gawk rsync dirmngr bc expect firewalld if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi # install or upgrade mysql if needed # debian has multiple packages for mariadb-server, easier to find version with dpkg MYSQL_VER=\$(getPkgVer 'mariadb-server(-[0-9.]+)?$') if cmdExists 'mysql'; then # check repo version and update if needed if (( \$(echo "\${MYSQL_VER:-0} < ${MYSQL_REQ_VER}" | bc -l) )); then echo 'MYSQL_MERGE_ACTION=1' >>\${STATE_FILE} # backup data in case upgrade fails systemctl stop mariadb mv -f /var/lib/mysql ${MYSQL_BACKUP_DIR}/var/lib/ cp -f /etc/my.cnf* ${MYSQL_BACKUP_DIR}/etc/ cp -rf /etc/my.cnf* ${MYSQL_BACKUP_DIR}/etc/ cp -rf /etc/mysql* ${MYSQL_BACKUP_DIR}/etc/ cp -f \${HOME}/.my.cnf* ${MYSQL_BACKUP_DIR}/\${HOME}/ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash MYSQL_MAJOR_VER=\$(getPkgVer -l 'mariadb-server' | cut -c -4) debconf-set-selections <<< "mariadb-server-\${MYSQL_MAJOR_VER} mysql-server/root_password password temp" debconf-set-selections <<< "mariadb-server-\${MYSQL_MAJOR_VER} mysql-server/root_password_again password temp" apt-get install -y mariadb-client mariadb-server if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi systemctl enable mariadb systemctl start mariadb # automate mysql_secure_install cmds mysqlSecureInstall "temp" "${MYSQL_PASS}" # allow auto login for root printf '%s\n%s\n%s\n' '[client]' 'user = root' 'password = ${MYSQL_PASS}' > ~/.my.cnf else echo 'MYSQL_MERGE_ACTION=2' >>\${STATE_FILE} # make sure root can login remotely mysql --user='root' --password='${MYSQL_PASS}' mysql \\ -e 'CREATE USER IF NOT EXISTS "root"@"%";' \\ -e 'SET PASSWORD FOR "root"@"%" = PASSWORD("${MYSQL_PASS}");' \\ -e 'GRANT ALL PRIVILEGES ON *.* TO "root"@"%" WITH GRANT OPTION;' \\ -e 'FLUSH PRIVILEGES;' fi else # check repo version and update if needed LATEST_VER=\$(getPkgVer -l 'mariadb-server') if (( \$(echo "\${LATEST_VER:-0} < ${MYSQL_REQ_VER}" | bc -l) )); then curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash MYSQL_MAJOR_VER=\$(getPkgVer -l 'mariadb-server' | cut -c -4) fi debconf-set-selections <<< "mariadb-server-\${MYSQL_MAJOR_VER} mysql-server/root_password password temp" debconf-set-selections <<< "mariadb-server-\${MYSQL_MAJOR_VER} mysql-server/root_password_again password temp" apt-get install -y mariadb-client mariadb-server if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi systemctl enable mariadb systemctl start mariadb # automate mysql_secure_install cmds mysqlSecureInstall "temp" "${MYSQL_PASS}" # allow auto login for root user printf '%s\n%s\n%s\n' '[client]' 'user = root' 'password = ${MYSQL_PASS}' > ~/.my.cnf fi # make sure mysql is listening for external connections prior to dumping databases if ! grep -qoP '^(?!#)bind-address[ \t]*=[ \t]*0\.0\.0\.0' /etc/mysql/mariadb.conf.d/50-server.cnf; then perl -i -pe 's%^(?!#)(bind-address[ \t]*=[ \t]*).*\$%\${1}0.0.0.0%m' /etc/mysql/mariadb.conf.d/50-server.cnf systemctl restart mariadb fi elif cmdExists 'yum' || cmdExists 'dnf'; then # centos specific settings echo 'MYSQL_SECTION="galera"' >>\${STATE_FILE} echo 'MYSQL_CLUSTER_CONFIG="/etc/my.cnf.d/cluster.cnf"' >>\${STATE_FILE} if cmdExists 'dnf'; then dnf install -y curl perl sed gawk rsync bc expect firewalld else yum install -y curl perl sed gawk rsync bc expect firewalld fi if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi # SELINUX integration if sestatus | head -1 | grep -qi 'enabled'; then yum install -y policycoreutils-python setools-console selinux-policy-devel if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi semanage permissive -a mysqld_t semanage port -a -t mysqld_port_t -p tcp 3306 semanage port -a -t mysqld_port_t -p tcp 4567 semanage port -a -t mysqld_port_t -p tcp 4568 semanage port -a -t mysqld_port_t -p tcp 4444 semanage port -a -t mysqld_port_t -p udp 4567 setsebool -P daemons_enable_cluster_mode 1 mkdir -p /tmp/selinux (cat <<'EOF' module galera 1.0; require { type mysqld_t; type rsync_exec_t; type anon_inodefs_t; type proc_net_t; type kerberos_port_t; class file { read execute execute_no_trans getattr open }; class tcp_socket { name_bind name_connect }; class process { setpgid siginh rlimitinh noatsecure }; type unconfined_t; type initrc_tmp_t; type init_t; class service enable; } #============= mysqld_t ============== allow mysqld_t initrc_tmp_t:file open; allow mysqld_t self:process setpgid; allow mysqld_t rsync_exec_t:file { read execute execute_no_trans getattr open }; allow mysqld_t anon_inodefs_t:file getattr; allow mysqld_t proc_net_t:file { read open }; allow mysqld_t kerberos_port_t:tcp_socket { name_bind name_connect }; #============= unconfined_t ============== allow unconfined_t init_t:service enable; EOF ) > /tmp/selinux/galera.te checkmodule -M -m /tmp/selinux/galera.te -o /tmp/selinux/galera.mod semodule_package -m /tmp/selinux/galera.mod -o /tmp/selinux/galera.pp semodule -i /tmp/selinux/galera.pp fi # install or upgrade mysql if needed MYSQL_VER=\$(getPkgVer 'mariadb-server') if cmdExists 'mysql'; then # check repo version and update if needed if (( \$(echo "\${MYSQL_VER:-0} < ${MYSQL_REQ_VER}" | bc -l) )); then echo 'MYSQL_MERGE_ACTION=1' >>\${STATE_FILE} # backup data in case upgrade fails systemctl stop mariadb mv -f /var/lib/mysql ${MYSQL_BACKUP_DIR}/var/lib/ cp -f /etc/my.cnf* ${MYSQL_BACKUP_DIR}/etc/ cp -rf /etc/my.cnf* ${MYSQL_BACKUP_DIR}/etc/ cp -rf /etc/mysql* ${MYSQL_BACKUP_DIR}/etc/ cp -f \${HOME}/.my.cnf* ${MYSQL_BACKUP_DIR}/\${HOME}/ curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash if cmdExists 'dnf'; then dnf install -y MariaDB-client MariaDB-server else yum install -y MariaDB-client MariaDB-server fi if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi # on CentOS mariadb fresh install doesn't have socket connection # we need this to allow unix socket as fallback (parsed last in configs) if ! grep -q '\[mysqld\]' /etc/my.cnf 2>/dev/null; then (cat <<'EOF' [mysqld] user = mysql pid-file = /var/lib/mysql/mysql.pid socket = /var/lib/mysql/mysql.sock port = 3306 basedir = /usr datadir = /var/lib/mysql bind-address = 127.0.0.1 tmpdir = /tmp log_error = /var/log/mysql/error.log expire_logs_days = 10 max_binlog_size = 100M plugin-load-add = auth_socket.so skip-external-locking [mysqld_safe] log-error = /var/log/mysql/error.log pid-file = /var/lib/mysql/mysql.pid EOF ) > /etc/my.cnf.d/server.cnf # make other configs lower priority mv -f /etc/my.cnf.d/server.cnf /etc/my.cnf.d/50-server.cnf mv -f /etc/my.cnf.d/mysql-clients.cnf /etc/my.cnf.d/50-mysql-clients.cnf fi systemctl enable mariadb systemctl start mariadb mysqladmin --user='root' password 'temp' # automate mysql_secure_install cmds mysqlSecureInstall "temp" "${MYSQL_PASS}" # allow auto login for root printf '%s\n%s\n%s\n' '[client]' 'user = root' 'password = ${MYSQL_PASS}' > ~/.my.cnf else echo 'MYSQL_MERGE_ACTION=2' >>\${STATE_FILE} # make sure root can login remotely mysql --user='root' --password='${MYSQL_PASS}' mysql \\ -e 'CREATE USER IF NOT EXISTS "root"@"%";' \\ -e 'SET PASSWORD FOR "root"@"%" = PASSWORD("${MYSQL_PASS}");' \\ -e 'GRANT ALL PRIVILEGES ON *.* TO "root"@"%" WITH GRANT OPTION;' \\ -e 'FLUSH PRIVILEGES;' fi else # check repo version and update if needed if (( \$(echo "\${MYSQL_VER:-0} < ${MYSQL_REQ_VER}" | bc -l) )); then curl -sS https://downloads.mariadb.com/MariaDB/mariadb_repo_setup | bash fi if cmdExists 'dnf'; then dnf install -y MariaDB-client MariaDB-server else yum install -y MariaDB-client MariaDB-server fi if (( \$? != 0 )); then printerr "failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi # on CentOS mariadb fresh install doesn't have socket connection # we need this to allow unix socket as fallback (parsed last in configs) if ! grep -q '\[mysqld\]' /etc/my.cnf 2>/dev/null; then (cat <<'EOF' [mysqld] user = mysql pid-file = /var/lib/mysql/mysql.pid socket = /var/lib/mysql/mysql.sock port = 3306 basedir = /usr datadir = /var/lib/mysql bind-address = 127.0.0.1 tmpdir = /tmp log_error = /var/log/mysql/error.log expire_logs_days = 10 max_binlog_size = 100M plugin-load-add = auth_socket.so skip-external-locking [mysqld_safe] log-error = /var/log/mysql/error.log pid-file = /var/lib/mysql/mysql.pid EOF ) > /etc/my.cnf.d/server.cnf # make other configs lower priority mv -f /etc/my.cnf.d/server.cnf /etc/my.cnf.d/50-server.cnf mv -f /etc/my.cnf.d/mysql-clients.cnf /etc/my.cnf.d/50-mysql-clients.cnf fi systemctl enable mariadb systemctl start mariadb mysqladmin --user='root' password 'temp' # automate mysql_secure_install cmds mysqlSecureInstall "temp" "${MYSQL_PASS}" # allow auto login for root user printf '%s\n%s\n%s\n' '[client]' 'user = root' 'password = ${MYSQL_PASS}' > ~/.my.cnf fi # make sure mysql is listening for external connections prior to dumping databases if ! grep -qoP '^(?!#)bind-address[ \t]*=[ \t]*0\.0\.0\.0' /etc/my.cnf.d/50-server.cnf; then perl -i -pe 's%^(?!#)(bind-address[ \t]*=[ \t]*).*\$%\${1}0.0.0.0%m' /etc/my.cnf.d/50-server.cnf systemctl restart mariadb fi else printerr "Your OS Distro is currently not supported" exit 1 fi # if mysql is not local to kamailio allow remote auth for kamailio user if (( ${WITH_REMOTE_DB} == 1 )); then mysql -sN -A -e "SELECT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') FROM mysql.user WHERE user<>'' AND host='localhost' AND user LIKE 'kamailio%';" \\ | mysql -sN -A \\ | sed 's/$/;/g' \\ | awk '!x[\$0]++' \\ | sed 's/localhost/%/g' \\ | mysql fi printdbg 'updating firewall rules' setFirewallRules # store the new root user password for next iteration to use echo "MYSQL_PASS='\$MYSQL_PASS'" >>\${STATE_FILE} # remove signal handler since we were successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM exit 0 EOSSH ) 2>&1 if (( $? != 0 )); then printerr "preparing node for cluster install failed on ${HOST_LIST[$i]}" exit 1 fi i=$((i+1)) done # loop through args and configure galera cluster i=0 while (( $i < ${#NODES[@]} )); do # run commands through ssh (${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "$(typeset -f runas); runas bash" <<- EOSSH if (( $DEBUG == 1 )); then set -x fi # re-declare functions and vars we pass to remote server # note that variables in function definitions (from calling environement) # lose scope unless local to function, they must be passed to remote ESC_SEQ="\033[" ANSI_NONE="\${ESC_SEQ}39;49;00m" # Reset colors ANSI_RED="\${ESC_SEQ}1;31m" ANSI_GREEN="\${ESC_SEQ}1;32m" MYSQL_USER="$MYSQL_USER" MYSQL_PASS="" MYSQL_PORT="$MYSQL_PORT" GALERA_REPL_PORT="$GALERA_REPL_PORT" GALERA_INCR_PORT="$GALERA_INCR_PORT" GALERA_SNAP_PORT="$GALERA_SNAP_PORT" $(typeset -f printdbg) $(typeset -f printerr) $(typeset -f cmdExists) $(typeset -f getClusterSize) $(typeset -f dumpMysqlDatabases) # get the stored state for this node STATE_FILE="${MYSQL_BACKUP_DIR}/state/${HOST_LIST[$i]}" . \${STATE_FILE} # export and merge databases from the primary node SQLCREDS=() case \$MYSQL_MERGE_ACTION in 0) printdbg 'no database export needed on ${HOST_LIST[$i]} using address ${CLUSTER_NODE_ADDRS[$i]}' ;; 1) printdbg 'exporting database overwrite on ${HOST_LIST[$i]} using address ${CLUSTER_NODE_ADDRS[$i]}' dumpMysqlDatabases --full ${MYSQL_USER}:${MYSQL_PASS}@localhost:${MYSQL_PORT} >${MYSQL_BACKUP_DIR}/dumps/primary.sql && dumpMysqlDatabases --grants ${MYSQL_USER}:${MYSQL_PASS}@localhost:${MYSQL_PORT} >>${MYSQL_BACKUP_DIR}/dumps/primary.sql || { printerr 'failed exporting databases' exit 1 } ;; 2) printdbg 'exporting merged databases on ${HOST_LIST[$i]} using address ${CLUSTER_NODE_ADDRS[$i]}' for IP in ${CLUSTER_NODE_ADDRS[@]}; do SQLCREDS+=(${MYSQL_USER}:${MYSQL_PASS}@\${IP}:${MYSQL_PORT}) done dumpMysqlDatabases --merge \${SQLCREDS[@]} >${MYSQL_BACKUP_DIR}/dumps/primary.sql && dumpMysqlDatabases --grants ${MYSQL_USER}:${MYSQL_PASS}@localhost:${MYSQL_PORT} >>${MYSQL_BACKUP_DIR}/dumps/primary.sql || { printerr 'failed exporting databases' exit 1 } ;; esac # merge databases if needed if (( $i == 0 )) && (( \$MYSQL_MERGE_ACTION != 0 )); then printdbg 'importing merged databases on primary node' mysql < ${MYSQL_BACKUP_DIR}/dumps/primary.sql else printdbg 'importing databases on ${HOST_LIST[$i]}' case \$MYSQL_MERGE_ACTION in 0) dumpMysqlDatabases --grants ${MYSQL_USER}:${MYSQL_PASS}@${CLUSTER_NODE_ADDRS[0]}:${MYSQL_PORT} >${MYSQL_BACKUP_DIR}/dumps/import.sql && mysql <${MYSQL_BACKUP_DIR}/dumps/import.sql || { printerr 'failed importing databases' exit 1 } ;; 1) dumpMysqlDatabases --full ${MYSQL_USER}:${MYSQL_PASS}@${CLUSTER_NODE_ADDRS[0]}:${MYSQL_PORT} >${MYSQL_BACKUP_DIR}/dumps/import.sql && dumpMysqlDatabases --grants ${MYSQL_USER}:${MYSQL_PASS}@${CLUSTER_NODE_ADDRS[0]}:${MYSQL_PORT} >>${MYSQL_BACKUP_DIR}/dumps/import.sql && mysql <${MYSQL_BACKUP_DIR}/dumps/import.sql || { printerr 'failed importing databases' exit 1 } ;; 2) dumpMysqlDatabases --full ${MYSQL_USER}:${MYSQL_PASS}@${CLUSTER_NODE_ADDRS[0]}:${MYSQL_PORT} >${MYSQL_BACKUP_DIR}/dumps/import.sql && dumpMysqlDatabases --grants ${MYSQL_USER}:${MYSQL_PASS}@localhost:${MYSQL_PORT} >>${MYSQL_BACKUP_DIR}/dumps/import.sql && mysql <${MYSQL_BACKUP_DIR}/dumps/import.sql || { printerr 'failed importing databases' exit 1 } ;; esac fi systemctl stop mariadb printdbg 'configuring galera cluster settings' ( cat << EOF [\${MYSQL_SECTION}] binlog_format=row default-storage-engine=innodb innodb_autoinc_lock_mode=2 bind-address=0.0.0.0 log_error=/var/log/mysql/error.log # Galera Provider Configuration wsrep_on=ON wsrep_provider=\$(find /usr/lib{32,64,}{/x86_64*,/i386*,}/galera/ -name 'libgalera_smm*.so' -print -quit 2>/dev/null) # Galera Cluster Configuration wsrep_cluster_name="${CLUSTER_NAME}" wsrep_cluster_address="gcomm://$(join ',' ${NODE_NAMES[@]})" # Galera Synchronization Configuration wsrep_sst_method=rsync # Galera Node Configuration wsrep_node_address="${NODE_NAMES[$i]}" wsrep_node_name="${NODE_NAMES[$i]}" EOF ) > \${MYSQL_CLUSTER_CONFIG} # fix debian.cnf to be the same on all nodes (used by debian-maintenance) if [[ -e "/etc/mysql/debian.cnf" ]]; then sed -i -r \\ -e "s|(host[\t ]*\=[\t ]*).*|host = localhost|g" \\ -e "s|(user[\t ]*\=[\t ]*).*|user = ${MYSQL_USER}|g" \\ -e "s|(password[\t ]*\=[\t ]*).*|password = ${MYSQL_PASS}|g" \\ /etc/mysql/debian.cnf fi # startup mysql server if (( $i == 0 )); then # bootstrap first db node perl -i -pe 's%(safe_to_bootstrap:)[ \t]*[0-9]%\1 1%' /var/lib/mysql/grastate.dat galera_new_cluster if (( \$? == 0 )) && (( \$(getClusterSize) == 1 )); then printdbg "Bootstrapping cluster success" else printerr "Bootstrapping cluster failed [${NODE_NAMES[$i]}]" exit 1 fi else # restart service to connect other db nodes systemctl restart mariadb if (( \$? == 0 )) && (( \$(getClusterSize) >= 2 )); then printdbg "Adding Node to cluster success" else printerr "Adding Node to cluster failed [${NODE_NAMES[$i]}]" exit 1 fi fi # remove the state file after successful configuration rm -f \${STATE_FILE} exit 0 EOSSH ) 2>&1 if (( $? != 0 )); then printerr "galera configuration failed on ${HOST_LIST[$i]}" exit 1 fi i=$((i+1)) done exit 0 ================================================ FILE: HA/mysql/installAAGroupReplication.sh ================================================ #!/usr/bin/env bash # # Summary: mysql active active group replication # Supported OS: debian # Author: DevOpSec # Date: Feb-2019 # Notes: uses mysql community db # # TODO: configure from remote server using ssh keys # TODO: add support for rhel-based distros ######################### warn user and exit ######################### { echo "" printerr "Mysql Group Replication install is under construction" echo "" printwarn "Use this script AT YOUR OWN RISK" echo "" echo "We suggest you use Mysql Galera Replication (install script included in repo) at this time" echo "If you must have Mysql Group Replication, READ THE ENTIRE script and FOLLOW THE COMMENTS" echo "" exit 1 } ###################################################################### # set project root, if in a git repo resolve top level dir PROJECT_ROOT=${PROJECT_ROOT:-$(dirname $(dirname $(dirname $(readlink -f "$0"))))} # import shared library functions . ${PROJECT_ROOT}/HA/shared_lib.sh # node configuration settings MYSQL_PORT="3306" REPL_PORT="6606" printUsage() { pprint "Usage: $0 [-remotedb|-h|--help] <[user1[:pass1]@]node1[:port1]> <[user2[:pass2]@]node2[:port2]> ..." } if ! isRoot; then printerr "Must be run with root privileges" && exit 1 fi if (( $# < 2 )) || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then printerr "At least 2 nodes are required to setup replication" && printUsage && exit 1 fi setOSInfo # install local requirements for script case "$DISTRO" in debian|ubuntu|linuxmint) apt-get install -y sshpass gawk ;; centos|redhat|amazon) yum install -y epel-release yum install -y sshpass gawk ;; *) printerr "Your OS Distro is currently not supported" exit 1 ;; esac ##### shared between ALL NODES (set per your own configs) # get using: uuidgen GROUP_ID="2ff15901-0fda-4ea8-9391-51fbb53ae28d" IP_LIST="10.10.2.40,10.10.2.41" SEED_LIST="10.10.2.40:${REPL_PORT},10.10.2.41:${REPL_PORT}" ######################################## ##### install requirements FOR EACH NODE MYSQL_APT_CONFIG_VER="0.8.10-1" MYSQL_APT_CONFIG_NAME="mysql-apt-config_${MYSQL_APT_CONFIG_VER}_all.deb" MYSQL_APT_CONFIG_URL="https://dev.mysql.com/get/${MYSQL_APT_CONFIG_NAME}" IP4RESTORE_FILE="/etc/iptables/rules.v4" IP6RESTORE_FILE="/etc/iptables/rules.v6" wget ${MYSQL_APT_CONFIG_URL} dpkg --force-depends -i ${MYSQL_APT_CONFIG_NAME} apt-get update -y apt-get install -y libaio1 apt-get install -y libmecab2 apt-get install -y mysql-common mysql-client mysql-server apt-get install -y uuid-runtime apt-get install -y iptables-persistent netfilter-persistent apt-get -f install -y ######################################## ##### set firewall rules FOR EACH NODE # use firewalld if installed if cmdExists "firewall-cmd"; then firewall-cmd --zone=public --add-port=${MYSQL_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${REPL_PORT}/tcp --permanent firewall-cmd --reload else # set ipv4 firewall rules for each node iptables -I INPUT 1 -p tcp --dport ${MYSQL_PORT} -j ACCEPT iptables -I INPUT 1 -p tcp --dport ${REPL_PORT} -j ACCEPT # set ipv6 firewall rules for each node ip6tables -I INPUT 1 -p tcp --dport ${MYSQL_PORT} -j ACCEPT ip6tables -I INPUT 1 -p tcp --dport ${REPL_PORT} -j ACCEPT fi # Remove duplicates and save mkdir -p $(dirname ${IP4RESTORE_FILE}) iptables-save | awk '!x[$0]++' > ${IP4RESTORE_FILE} mkdir -p $(dirname ${IP6RESTORE_FILE}) ip6tables-save | awk '!x[$0]++' > ${IP6RESTORE_FILE} ######################################## ##### set dynamic variables FOR EACH NODE (id increments per node) ID=1 INTERNAL_IP=$(hostname -I | awk '{print $1}') ######################################## ##### add cluster config FOR EACH NODE cat << EOF > /etc/mysql/conf.d/cluster.cnf [mysqld] bind-address=0.0.0.0 # General replication settings plugin-load = group_replication.so gtid_mode = ON enforce_gtid_consistency = ON master_info_repository = TABLE relay_log_info_repository = TABLE binlog_checksum = NONE log_slave_updates = ON log_bin = binlog binlog_format = ROW # prevent use of non-transactional storage engines disabled_storage_engines="MyISAM,BLACKHOLE,FEDERATED,ARCHIVE" transaction_write_set_extraction = XXHASH64 # InnoDB gap locks are problematic for multi-primary conflict detection, # but none are used with READ-COMMITTED so isolate those transaction-isolation = 'READ-COMMITTED' group_replication = FORCE_PLUS_PERMANENT group_replication_bootstrap_group = OFF group_replication_start_on_boot = ON # Shared replication group configuration group_replication_group_name = "${GROUP_ID}" group_replication_ip_whitelist = "${IP_LIST}" group_replication_group_seeds = "${SEED_LIST}" # Multi-primary mode, where any host can accept writes group_replication_single_primary_mode = OFF group_replication_enforce_update_everywhere_checks = ON # Host specific replication configuration server_id = ${ID} group_replication_local_address = "${INTERNAL_IP}:${REPL_PORT}" EOF # restart mysql with new config loaded MYSQL_SERVICES=$(systemctl list-unit-files | grep 'mysql' | awk '{print $1}') for SERVICE in $MYSQL_SERVICES; do systemctl restart ${SERVICE}; done ######################################## ##### set logging and privileges ON PRIMARY NODE mysql <<- 'EOF' SET SQL_LOG_BIN=0; ALTER USER IF EXISTS kamailio@'%' IDENTIFIED WITH 'mysql_native_password' BY 'kamailiorw'; FLUSH PRIVILEGES; CHANGE MASTER TO MASTER_USER='kamailio', MASTER_PASSWORD='kamailiorw' FOR CHANNEL 'group_replication_recovery'; # Parallel applier support -- Speedup distributed recovery time?? #SET @@GLOBAL.binlog_transaction_dependency_tracking=WRITESET; #SET @@GLOBAL.binlog_transaction_dependency_tracking=WRITESET_SESSION; SET @@GLOBAL.binlog_transaction_dependency_tracking=COMMIT_ORDER; #default SET SQL_LOG_BIN=1; EOF ######################################## ##### dump db ON PRIMARY NODE # TODO: change this to use functions from shared_lib mysqldump --single-transaction --opt --events --routines --triggers --all-databases --add-drop-database --flush-privileges > repl_dump.sql mysql -sN -A -e "SELECT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') FROM mysql.user WHERE user<>''" \ | mysql -sN -A \ | sed 's/$/;/g' \ | awk '!x[$0]++' >> repl_dump.sql ######################################## ##### import db dump to ALL NON-PRIMARY NODES mysql --init-command="RESET MASTER" < repl_dump.sql ######################################## ##### bootstrap the PRIMARY NODE mysql <<- 'EOF' SET GLOBAL group_replication_bootstrap_group=ON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_group=OFF; EOF ######################################## ### run replication on ALL NON-PRIMARY NODES mysql -e 'START GROUP_REPLICATION' #################################### # NOTE: if a node becomes out of sync and can not rejoin (irrecoverable GTID's out of sync) # here is the process to resync an out of sync server: # 1. dump one of the good databases # 2. import to down server (shown below) # 3. resync with the group (shown below) # #mysql --init-command="RESET MASTER" < repl_dump.sql #mysql -e 'START GROUP_REPLICATION' # check the status of group mysql -e 'SELECT * FROM performance_schema.replication_group_members' exit 0 ================================================ FILE: HA/pacemaker/AWS/ocf-floatip ================================================ #!/bin/sh # # # Manage Elastic IP with Pacemaker # # # Copyright 2016-2018 guessi # Copyright 2024 Tyler Moore # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # # # Prerequisites: # - preconfigured AWS CLI running environment (AccessKey, SecretAccessKey, etc.) or # (AWSRole) Setup up relevant AWS Policies to allow agent related functions to be executed. # - a reserved secondary private IP address for EC2 instances high availability # this secondary IP should be assigned to the primary NIC # - IAM user role with the following permissions: # * DescribeInstances # * AssociateAddress # * DescribeAddresses # * DisassociateAddress # - jq is required for parsing json # ####################################################################### # Initialization: : ${OCF_ROOT=${OCF_ROOT:-"/usr/lib/ocf"}} : ${OCF_FUNCTIONS_DIR=${OCF_ROOT}/resource.d/heartbeat} . ${OCF_FUNCTIONS_DIR}/.ocf-shellfuncs ####################################################################### # # Defaults # OCF_RESKEY_awscli_default="/usr/bin/aws" OCF_RESKEY_auth_type_default="key" OCF_RESKEY_profile_default="default" OCF_RESKEY_region_default="" OCF_RESKEY_api_delay_default="3" : ${OCF_RESKEY_awscli=${OCF_RESKEY_awscli_default}} : ${OCF_RESKEY_auth_type=${OCF_RESKEY_auth_type_default}} : ${OCF_RESKEY_profile=${OCF_RESKEY_profile_default}} : ${OCF_RESKEY_region=${OCF_RESKEY_region_default}} : ${OCF_RESKEY_api_delay=${OCF_RESKEY_api_delay_default}} meta_data() { cat < 1.0 Resource Agent for Amazon AWS Elastic IP Addresses. It manages AWS Elastic IP Addresses with awscli. Credentials needs to be setup by running "aws configure", or by using AWS Policies. See https://aws.amazon.com/cli/ for more information about awscli. Amazon AWS Elastic IP Address Resource Agent Path to the "aws" executable. aws cli Authentication type "key" for AccessKey and SecretAccessKey set via "aws configure", or "role" to use AWS Policies. Authentication type Valid AWS CLI profile name (see ~/.aws/config and 'aws configure') profile name Reserved elastic IP for ec2 instance. reserved elastic ip address Secondary private ip address for ec2 instance to bind elastic IP to. If not set, the resource will attempt to resolve it from the pacemaker configs. secondary private ip address Region for AWS resource (required for role-based authentication). Region A short delay between API calls, to avoid sending API too quick. a short delay between API calls XML } ####################################################################### awseip_usage() { echo "usage: $0 {start|stop|monitor|migrate_to|migrate_from|validate|validate-all|meta-data}" } awseip_start() { awseip_monitor && return $OCF_SUCCESS if [ -z "${PRIVATE_IP}" ]; then # TODO: make this the secondary / float ip, probably set in /etc/hosts PRIVATE_IP=$(getent hosts "$(crm_node -n)" 2>/dev/null | awk '{print $1}' | head -1) fi local ENI_MAC=$( ip -j address show | jq -re --arg ip $PRIVATE_IP '.[] | select(.addr_info[].local==$ip).address' ) if [ -z "$ENI_MAC" ]; then ocf_log info "NIC for private IP ($PRIVATE_IP) not found while starting aws_floatip" return $OCF_NOT_RUNNING fi local ENI_ID=$( curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \ "http://169.254.169.254/latest/meta-data/network/interfaces/macs/${ENI_MAC}/interface-id" ) $AWSCLI_CMD ec2 associate-address \ --allocation-id ${ALLOCATION_ID} \ --network-interface-id ${ENI_ID} \ --private-ip-address ${PRIVATE_IP} RET=$? # delay to avoid sending request too fast sleep ${OCF_RESKEY_api_delay} if [ $RET -ne 0 ]; then return $OCF_NOT_RUNNING fi ocf_log info "aws_floatip has been successfully brought up (${ELASTIC_IP})" return $OCF_SUCCESS } awseip_stop() { awseip_monitor || return $OCF_SUCCESS local ASSOCIATION_ID=$( $AWSCLI_CMD ec2 describe-addresses --allocation-id "$ALLOCATION_ID" --output json | jq -re '.Addresses[0].AssociationId' ) if [ -z "$ASSOCIATION_ID" ]; then ocf_log info "ASSOCIATION_ID not found while stopping aws_floatip" return $OCF_NOT_RUNNING fi $AWSCLI_CMD ec2 disassociate-address --association-id "$ASSOCIATION_ID" RET=$? # delay to avoid sending request too fast sleep ${OCF_RESKEY_api_delay} if [ $RET -ne 0 ]; then return $OCF_NOT_RUNNING fi ocf_log info "elastic_ip has been successfully brought down (${ELASTIC_IP})" return $OCF_SUCCESS } awseip_monitor() { $AWSCLI_CMD ec2 describe-instances --instance-id "$INSTANCE_ID" --output json | jq -re '.Reservations[].Instances[0].NetworkInterfaces[].PrivateIpAddresses[].Association.PublicIp | select(. == "3.19.8.113")' >/dev/null if [ $? -eq 0 ]; then return $OCF_SUCCESS fi return $OCF_NOT_RUNNING } awseip_validate() { check_binary "${OCF_RESKEY_awscli}" if [ "x${OCF_RESKEY_auth_type}" = "xkey" ] && [ -z "$OCF_RESKEY_profile" ]; then ocf_exit_reason "profile parameter not set" return $OCF_ERR_CONFIGURED fi if [ -z "${INSTANCE_ID}" ]; then ocf_exit_reason "instance_id not found. Is this a EC2 instance?" return $OCF_ERR_GENERIC fi return $OCF_SUCCESS } case $__OCF_ACTION in meta-data) meta_data exit $OCF_SUCCESS ;; usage|help) awseip_usage exit $OCF_SUCCESS ;; esac AWSCLI_CMD="${OCF_RESKEY_awscli}" if [ "x${OCF_RESKEY_auth_type}" = "xkey" ]; then AWSCLI_CMD="$AWSCLI_CMD --profile ${OCF_RESKEY_profile}" elif [ "x${OCF_RESKEY_auth_type}" = "xrole" ]; then if [ -z "${OCF_RESKEY_region}" ]; then ocf_exit_reason "region needs to be set when using role-based authentication" exit $OCF_ERR_CONFIGURED fi else ocf_exit_reason "Incorrect auth_type: ${OCF_RESKEY_auth_type}" exit $OCF_ERR_CONFIGURED fi if [ -n "${OCF_RESKEY_region}" ]; then AWSCLI_CMD="$AWSCLI_CMD --region ${OCF_RESKEY_region}" fi ELASTIC_IP="${OCF_RESKEY_elastic_ip}" PRIVATE_IP="${OCF_RESKEY_private_ip}" TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600") INSTANCE_ID=$(curl -s "http://169.254.169.254/latest/meta-data/instance-id" -H "X-aws-ec2-metadata-token: $TOKEN") ALLOCATION_ID=$($AWSCLI_CMD ec2 describe-addresses --public-ips "$ELASTIC_IP" --output json | jq -re '.Addresses[0].AllocationId') case $__OCF_ACTION in start) awseip_validate awseip_start ;; stop) awseip_stop ;; monitor) awseip_monitor ;; migrate_to) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} to ${OCF_RESKEY_CRM_meta_migrate_target}." awseip_stop ;; migrate_from) ocf_log info "Migrating ${OCF_RESOURCE_INSTANCE} from ${OCF_RESKEY_CRM_meta_migrate_source}." awseip_start ;; reload) ocf_log info "Reloading ${OCF_RESOURCE_INSTANCE} ..." ;; validate|validate-all) awseip_validate ;; *) awseip_usage exit $OCF_ERR_UNIMPLEMENTED ;; esac rc=$? ocf_log debug "${OCF_RESOURCE_INSTANCE} $__OCF_ACTION : $rc" exit $rc ================================================ FILE: HA/pacemaker/DO/assign-ip ================================================ #!/usr/bin/env bash # derived from http://do.co/ocf-floatip api_base='https://api.digitalocean.com/v2' usage() { echo "$0 " echo '' echo 'Your DigitialOcean API token must be in the "DO_TOKEN" environmental variable.' } main() { local floating_ip="$1" droplet_id=$(curl -s http://169.254.169.254/metadata/v1/id) resp=$( curl -s \ -H "Authorization: Bearer $DO_TOKEN" \ -H 'Content-type: application/json' \ -d '{"type": "assign", "droplet_id": '$droplet_id'}' \ "${api_base}/floating_ips/${floating_ip}/actions" ) msg=$(jq -e -r '.message' <<<"$resp") && { echo "$(jq -r '.id' <<<"$resp"): $msg" return 1 } echo "Moving IP address: $(jq -r '.action.status' <<<"$resp")" return 0 } if [[ -z "$DO_TOKEN" ]] || (( $# != 1 )); then usage exit 1 fi main "$@" exit $? ================================================ FILE: HA/pacemaker/DO/ocf-floatip ================================================ #!/bin/bash # derived from http://do.co/ocf-floatip param=$1 export DO_TOKEN=$OCF_RESKEY_do_token IP=$OCF_RESKEY_floating_ip has_floating_ip() { [ "$(curl -s http://169.254.169.254/metadata/v1/floating_ip/ipv4/active)" == "true" ] && return 0 || return 1 } meta_data() { cat < 0.1 floatip ocf resource agent for claiming a specified Floating IP via the DigitalOcean API Assign Floating IP via DigitalOcean API DigitalOcean API Token with Read/Write Permissions DigitalOcean API Token Floating IP to reassign Floating IP END } if [ "start" == "$param" ] ; then /usr/local/bin/assign-ip $IP exit 0 elif [ "stop" == "$param" ] ; then exit 0 elif [ "status" == "$param" ] ; then if has_floating_ip ; then echo "Has Floating IP" exit 0 else echo "Does Not Have Floating IP" exit 1 fi elif [ "monitor" == "$param" ] ; then if has_floating_ip ; then exit 0 else exit 7 fi elif [ "meta-data" == "$param" ] ; then meta_data exit 0 else echo "no such command $param" exit 1; fi ================================================ FILE: HA/pacemaker/installKamCluster.sh ================================================ #!/usr/bin/env bash # # Summary: corosync / pacemaker kamailio cluster config # # Notes: more than 2 nodes may require fencing settings # you must be able to ssh to every node in the cluster from where script is run # supported ssh authentication methods: password, pubkey # supported DB configurations: central, active/active # a secondary private IP address on a dedicated subnet is the preferred method # for communication between the pacemaker nodes ## # TODO: support active/passive galera replication # https://mariadb.com/kb/en/changing-a-replica-to-become-the-primary/ # better output to user when not in debug mode # ### log file locations: # /var/log/pcsd/pcsd.log # /var/log/cluster/corosync.log # /var/log/pcsd/pacemaker.log # /var/log/nodeutil.log #====================================================================================== # process and validate the CLI args #====================================================================================== # process args before doing anything else printUsage() { echo "Usage: $0 [OPTIONAL OPTIONS] " echo "OPTIONAL OPTIONS:" echo " -h|--help" echo " -debug" echo " -i " echo " --do-token=" echo " --aws-access-key=" echo " --aws-secret-key=" echo "REQUIRED OPTIONS (one of):" echo " -vip " echo " -net " echo "REQUIRED ARGUMENTS:" echo " <[sshuser1[:sshpass1]@]node1[:sshport1]> <[sshuser2[:sshpass2]@]node2[:sshport2]> ..." } # loop through args and evaluate any options IN_NODES=() while (( $# > 0 )); do ARG="$1" case $ARG in -h|--help) printUsage exit 0 ;; -debug) IN_DEBUG=1 shift ;; -vip) shift IN_KAM_VIP="$1" shift ;; -net) shift IN_CIDR_FULL="$1" IN_CIDR_NETMASK=$(cut -s -d '/' -f 2 <<<"$1") shift ;; -i) shift IN_SSH_KEY_FILE="$1" shift ;; --do-token=*) IN_DO_TOKEN=$(cut -s -d '=' -f 2 <<<"$1") shift ;; --aws-access-key=*) IN_AWS_ACCESS_KEY=$(cut -s -d '=' -f 2 <<<"$1") shift ;; --aws-secret-key=*) IN_AWS_SECRET_TOKEN=$(cut -s -d '=' -f 2 <<<"$1") shift ;; *) # add to list of args IN_NODES+=( "$ARG" ) shift ;; esac done # make sure required args are fulfilled if [[ -z "$IN_KAM_VIP" ]] && [[ -z "$IN_CIDR_NETMASK" ]]; then echo 'Kamailio virtual IP or CIDR network is required' echo "For usage information run: $0 --help" exit 1 fi if (( ${#IN_NODES[@]} < 2 )); then echo "At least 2 nodes are required to setup kam cluster" echo "For usage information run: $0 --help" exit 1 fi # start debugging if requested if (( ${IN_DEBUG:-0} == 1 )); then set -x fi #====================================================================================== # setup the environment we will use on remote nodes #====================================================================================== # export current environment (will be used later) INITIAL_ENV_FILE=$(mktemp) DIRTY_ENV_FILE=$(mktemp) CLEAN_ENV_FILE=$(mktemp) declare -p >"$INITIAL_ENV_FILE" # trap exit signals to remove environment files no matter how the script exits cleanupHandler() { printdbg 'cleaning up temp files onn all hosts prior to exit' # local files rm -f "$INITIAL_ENV_FILE" "$DIRTY_ENV_FILE" "$CLEAN_ENV_FILE" 2>/dev/null # remote files if (( ${#SSH_CMD_LIST[@]} > 0 )); then for i in $(seq 0 $((${#NODES[@]}-1))); do ${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "rm -f $CLEAN_ENV_FILE" done fi # reset signals trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM } trap 'cleanupHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # set project root, if in a git repo resolve top level dir PROJECT_ROOT=${PROJECT_ROOT:-$(dirname $(dirname $(dirname $(readlink -f "$0"))))} # import shared library functions source ${PROJECT_ROOT}/HA/shared_lib.sh # node configuration settings PACEMAKER_TCP_PORTS=(2224 3121 5403 21064) PACEMAKER_UDP_PORTS=(5404 5405) CLUSTER_NAME="kamcluster" CLUSTER_PASS="$(createPass)" RESOURCE_STARTUP_TIMEOUT=30 CLUSTER_OPTIONS=(transport udpu link bindnetaddr=0.0.0.0 broadcast=1) KAM_VIP="$IN_KAM_VIP" CIDR_NETMASK=${IN_CIDR_NETMASK:-32} DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" STATIC_NETWORKING_MODE=1 RETRY_CLUSTER_START=3 RETRY_SSH_CONNECT=3 # TODO: implement this PKG_MGR_TIMEOUT=300 # global variables used throughout script NODES=( ${IN_NODES[@]} ) DEBUG=${IN_DEBUG:-0} SSH_KEY_FILE="$IN_SSH_KEY_FILE" DO_TOKEN="$IN_DO_TOKEN" AWS_ACCESS_KEY="$IN_AWS_ACCESS_KEY" AWS_SECRET_TOKEN="$IN_AWS_SECRET_TOKEN" NODE_NAMES=() HOST_LIST=() USERHOST_LIST=() CLUSTER_NODE_ADDRS=() declare -A CLOUD_DICT CLOUD_PLATFORM="" CLUSTER_RESOURCES=() SSH_CMD_LIST=() RSYNC_CMD_LIST=() # install local requirements for script if ! cmdExists 'ssh' || ! cmdExists 'sshpass' || ! cmdExists 'nmap' || ! cmdExists 'sed' || ! cmdExists 'awk' || ! cmdExists 'comm'; then printdbg 'Installing local requirements for cluster install' if cmdExists 'apt-get'; then runas apt-get install -y openssh-client sshpass nmap sed gawk rsync coreutils elif cmdExists 'dnf'; then runas dnf install -y openssh-clients sshpass nmap sed gawk rsync coreutils elif cmdExists 'yum'; then runas yum install --enablerepo=epel -y openssh-clients sshpass nmap sed gawk rsync coreutils else printerr "Your local OS is not currently not supported" exit 1 fi fi # sanity check if (( $? != 0 )); then printerr 'Could not install requirements for cluster install' exit 1 fi # go back and find VIP if subnet given (nmap required) if [[ -z "$KAM_VIP" ]] && [[ -n "$IN_CIDR_FULL" ]]; then KAM_VIP=$(findAvailableIP "$IN_CIDR_FULL") if [[ -z "$KAM_VIP" ]]; then printerr "Could not available IP to use as the VIP in subnet '$CIDR_NETMASK'" exit 1 fi fi # find the first private IP address (reverse order) on a physical interface # this is typically the secondary interface or secondary IP on the primary interface # if no RFC1918 address is found use the IP associated with the default route # NOTE: sourced here to allow easier declaration on remote node source <( cat < 1 )); then printerr 'nodes are deployed on different cloud platforms' printerr 'installation on differing cloud platforms is not supported' exit 1 fi # make sure the credentials for the cloud provider was provided by the user if [[ "$CLOUD_PLATFORM" == "DO" ]] && [[ -z "$DO_TOKEN" ]]; then printerr '--do-token is required when deploying on digital ocean' exit 1 elif [[ "$CLOUD_PLATFORM" == "AWS" ]] && [[ -z "$AWS_ACCESS_KEY" || -z "$AWS_SECRET_TOKEN" ]]; then printerr '--aws-access-key and --aws-secret-key are required when deploying on amazon web services' exit 1 fi #====================================================================================== # stage1: install requirements and start configuring individual nodes #====================================================================================== printdbg 'configuring servers for cluster deployment' i=0 while (( $i < ${#NODES[@]} )); do # copy over any files needed for the install printdbg "Copying configuration files to ${HOST_LIST[$i]}" if [[ -n "$CLOUD_PLATFORM" ]]; then ${RSYNC_CMD_LIST[$i]} --rsh="ssh ${SSH_OPTS[*]} -o IPQoS=throughput" -a ${PROJECT_ROOT}/HA/pacemaker/${CLOUD_PLATFORM}/ ${USERHOST_LIST[$i]}:/tmp/cloud/ 2>&1 && ${RSYNC_CMD_LIST[$i]} --rsh="ssh ${SSH_OPTS[*]} -o IPQoS=throughput" -a ${PROJECT_ROOT}/HA/pacemaker/scripts/ ${USERHOST_LIST[$i]}:/tmp/scripts/ 2>&1 if (( $? != 0 )); then printerr "Copying configuration files to ${HOST_LIST[$i]} failed" exit 1 fi fi # the runtime environment we will use on the remote node needs cleaned and copied over declare -p >"$DIRTY_ENV_FILE" ( comm -3 <(sort "$INITIAL_ENV_FILE") <(sort "$DIRTY_ENV_FILE") declare -f ) >"$CLEAN_ENV_FILE" ${RSYNC_CMD_LIST[$i]} --rsh="ssh ${SSH_OPTS[*]} -o IPQoS=throughput" "$CLEAN_ENV_FILE" "${USERHOST_LIST[$i]}:${CLEAN_ENV_FILE}" 2>&1 if (( $? != 0 )); then printerr "Copying runtime environment file to ${HOST_LIST[$i]} failed" exit 1 fi # run commands through ssh ${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "bash /tmp/scripts/stage1.sh ${CLEAN_ENV_FILE}" 2>&1 if (( $? != 0 )); then printerr "server configuration failed on ${HOST_LIST[$i]}" exit 1 fi i=$((i+1)) done #====================================================================================== # stage2: configure pacemaker settings on each node #====================================================================================== printdbg 'initializing pacemaker cluster' i=0 while (( $i < ${#NODES[@]} )); do # run commands through ssh ${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "bash /tmp/scripts/stage2.sh ${CLEAN_ENV_FILE}" 2>&1 if (( $? != 0 )); then printerr "initializing pacemaker cluster failed on ${HOST_LIST[$i]}" exit 1 fi i=$((i+1)) done #====================================================================================== # stage3: configure the pacemaker cluster and resources #====================================================================================== # creates and configures the cluster # TODO: add support for updating virtual IP from various cloud providers printdbg 'configuring pacemaker cluster' i=0 while (( $i < ${#NODES[@]} )); do # run commands through ssh ${SSH_CMD_LIST[$i]} ${USERHOST_LIST[$i]} "bash /tmp/scripts/stage3.sh ${CLEAN_ENV_FILE}" 2>&1 if (( $? != 0 )); then printerr "pacemaker cluster configuration failed on ${HOST_LIST[$i]}" exit 1 fi i=$((i+1)) done printdbg 'Successfully configured pacemaker cluster' exit 0 ================================================ FILE: HA/pacemaker/nodeutil.sh ================================================ #!/usr/bin/env bash # # Summary: Utility functions for managing cluster nodes # Summary: Program should be run locally on desired node # TODO: panic() and kill() and startup() need ssh cmds w/ key setup # to execute on remote node, only work locally now LOG_FILE="/var/log/nodeutil.log" # defaults to current node NODE_NAME=$(sudo -u hacluster crm_node -n) standby() { sudo -u hacluster pcs cluster standby ${1:-$NODE_NAME} log_status "standby()" } unstandby() { sudo -u hacluster pcs cluster unstandby ${1:-$NODE_NAME} log_status "unstandby()" } stop() { sudo -u hacluster pcs cluster stop ${1:-$NODE_NAME} log_status "stop()" } start() { sudo -u hacluster pcs cluster start ${1:-$NODE_NAME} log_status "start()" } restart() { sudo -u hacluster pcs cluster stop ${1:-$NODE_NAME} sleep 2 sudo -u hacluster pcs cluster start ${1:-$NODE_NAME} log_status "restart()" } startup() { sudo systemctl start pacemaker sudo systemctl start corosync log_status "startup()" } panic() { echo c | sudo tee /proc/sysrq-trigger sudo systemctl stop pacemaker sudo systemctl stop corosync log_status "panic()" } kill() { sudo pkill -9 pacemaker sudo pkill -9 corosync sudo shutdown -h now log_status "kill()" } start_cluster() { sudo -u hacluster pcs cluster start --all log_status "start_cluster()" } restart_cluster() { sudo -u hacluster pcs cluster stop --all sleep 5 sudo -u hacluster pcs cluster start --all sleep 2 log_status "restart_cluster()" } stop_cluster() { sudo -u hacluster pcs cluster stop --all log_status "stop_cluster()" } # $1 == resource name # returns: 0 == success, else == failure find_resource() { ( sudo -u hacluster pcs status resources | grep "$1" | awk '{print $NF}' exit ${PIPESTATUS[0]} ) 2>/dev/null return $? } # $1 == name of command to log log_status() { printf '#===== log_status entry: %s =====#\n' "$(date)" >> ${LOG_FILE} printf 'current executing function: %s\n' "$1" >> ${LOG_FILE} { sudo -u hacluster pcs status printf '--------------------\n' sudo -u hacluster corosync-cfgtool -s } 2>&1 >> ${LOG_FILE} printf '#===== end of log entry =====#\n\n' >> ${LOG_FILE} } show_status() { sudo -u hacluster corosync-cfgtool -s sudo -u hacluster pcs status --full } ### arg parsing ### cmd="" # Default none HELP() { echo "Usage: " echo "nodeutil -h Display this help message." echo "nodeutil -t|--target Specify a specific node as target." echo "nodeutil -c|--cmd Specify from list of cmds to run." echo "Available options:" echo "standby, unstandby, stop, start, panic, start_cluster, restart_cluster, stop_cluster" } #Check the number of arguments. If none are passed, print help and exit options=$(getopt -o hc:t: -l cmd:,target: -n nodeutil -- "$@") if [ $? -ne 0 ]; then HELP exit 1 fi ### parse options ### eval set -- "$options" while true; do OPT="$1" case "$OPT" in -h) # Show help option menu shift HELP exit 1 ;; -t|--target) # Grab the target node name shift NODE_NAME="$1" shift ;; -c|--cmd) # Grab the cmd to execute shift cmd="$1" shift case $cmd in standby|unstandby|stop|start|restart|startup|panic|start_cluster|restart_cluster|stop_cluster|find_resource|show_status) $cmd "$@" ;; *) HELP && exit 1 ;; esac ;; --) shift break ;; esac done exit 0 ================================================ FILE: HA/pacemaker/scripts/stage1.sh ================================================ #!/bin/bash # import runtime environment if ! [[ -f "$1" ]] || ! source "$1"; then echo "Could not import runtime environment" exit 1 fi if (( ${DEBUG:-0} == 1 )); then set -x fi addDefRoute() { local IP="$1" local VIP_CIDR="${IP}/${CIDR_NETMASK:-32}" local ROUTE_INFO=$(ip route get 8.8.8.8 | head -1) local DEF_IF=$(printf '%s' "${ROUTE_INFO}" | grep -oP 'dev \K\w+') local VIP_ROUTE_INFO=$(printf '%s' "${ROUTE_INFO}" | sed -r "s|8.8.8.8|0.0.0.0/1|; s|dev [\w\d]+|dev ${DEF_IF}|; s|src [\w\d]+|src ${IP}|") runas ip address add $VIP_CIDR dev $DEF_IF runas ip route add $VIP_ROUTE_INFO } removeDefRoute() { local IP="$1" local ROUTE_INFO=$(ip route get 8.8.8.8 | head -1) local DEF_IF=$(printf '%s' "${ROUTE_INFO}" | grep -oP 'dev \K\w+') runas ip address del $VIP_CIDR dev $DEF_IF runas ip route del 0.0.0.0/1 } printdbg 'installing requirements' if cmdExists 'apt-get'; then runas apt-get -o DPkg::Lock::Timeout=$PKG_MGR_TIMEOUT install -y corosync pacemaker pcs firewalld jq perl dnsutils sed unzip elif cmdExists 'dnf'; then runas dnf install -y corosync pacemaker pcs firewalld jq perl bind-utils sed unzip elif cmdExists 'yum'; then runas yum install -y corosync pacemaker pcs firewalld jq perl bind-utils sed unzip else printerr "OS on remote node [${HOST_LIST[$i]}] is currently not supported" exit 1 fi if (( $? != 0 )); then printerr "Failed to install requirements on remote node ${HOST_LIST[$i]}" exit 1 fi printdbg 'configuring server for cluster deployment' printdbg 'updating firewall rules' for PORT in ${PACEMAKER_TCP_PORTS[@]}; do runas firewall-cmd --zone=public --add-port=${PORT}/tcp --permanent done for PORT in ${PACEMAKER_UDP_PORTS[@]}; do runas firewall-cmd --zone=public --add-port=${PORT}/udp --permanent done runas firewall-cmd --reload printdbg 'setting up cluster password' echo "${CLUSTER_PASS}" | runas passwd -q --stdin hacluster 2>/dev/null || echo "hacluster:${CLUSTER_PASS}" | runas chpasswd 2>/dev/null || { printerr "could not change hacluster user password"; exit 1; } printdbg 'setting up cluster hostname resolution' # for each node remove the loopback hostname if present # this will cause issues when adding nodes to the cluster # ref: https://serverfault.com/questions/363095/why-does-my-hostname-appear-with-the-address-127-0-1-1-rather-than-127-0-0-1-in grep -v -E '^127\.0\.1\.1' /etc/hosts >/tmp/hosts && runas mv -f /tmp/hosts /etc/hosts # add section for the pacemaker hostnames if ! grep -q 'PACEMAKER_CONFIG_START' /etc/hosts 2>/dev/null; then runas bash -c "( echo '' echo '#####PACEMAKER_CONFIG_START' )>>/etc/hosts" j=0 while (( $j < ${#CLUSTER_NODE_ADDRS[@]} )); do runas bash -c "echo '${CLUSTER_NODE_ADDRS[$j]} ${NODE_NAMES[$j]}' >>/etc/hosts" j=$((j+1)) done runas bash -c "echo '#####PACEMAKER_CONFIG_END' >>/etc/hosts" else j=0; tmp=''; while (( $j < ${#CLUSTER_NODE_ADDRS[@]} )); do tmp+="${CLUSTER_NODE_ADDRS[$j]} ${NODE_NAMES[$j]}\n" j=$((j+1)) done runas perl -0777 -i -pe "s|(#+PACEMAKER_CONFIG_START).*?(#+PACEMAKER_CONFIG_END)|\1\n${tmp}\2|gms" /etc/hosts fi printdbg 'configuring floating IP support on server' # enable binding to floating ip (on each node) runas bash -c "echo '1' >/proc/sys/net/ipv4/ip_nonlocal_bind" runas bash -c "echo 'net.ipv4.ip_nonlocal_bind = 1' >/etc/sysctl.d/99-non-local-bind.conf" # change kamcfg and rtpenginecfg to use floating ip (on each node) if [[ -e "${DSIP_SYSTEM_CONFIG_DIR}" ]]; then printdbg 'updating dsiprouter services' DSIP_INIT_PATH=$(systemctl show -P FragmentPath dsip-init) if [[ -n "$CLOUD_PLATFORM" ]]; then DSIP_VERSION=$(getConfigAttrib 'VERSION' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py) DSIP_MAJ_VER=$(perl -pe 's%([0-9]+)\..*%\1%' <<<"$DSIP_VERSION") DSIP_MIN_VER=$(perl -pe 's%[0-9]+\.([0-9]).*%\1%' <<<"$DSIP_VERSION") DSIP_PATCH_VER=$(perl -pe 's%[0-9]+\.[0-9]([0-9]).*%\1%' <<<"$DSIP_VERSION") # v0.72 and above have static networking supported if (( $DSIP_MAJ_VER > 0 )) || (( $DSIP_MAJ_VER == 0 && $DSIP_MIN_VER >= 7 )); then setConfigAttrib 'NETWORK_MODE' "$STATIC_NETWORKING_MODE" ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py else removeExecStartCmd 'dsiprouter updatertpconfig' ${DSIP_INIT_PATH} removeExecStartCmd 'dsiprouter updatekamconfig' ${DSIP_INIT_PATH} removeExecStartCmd 'dsiprouter updatedsipconfig' ${DSIP_INIT_PATH} fi setConfigAttrib 'INTERNAL_IP_ADDR' '${CLUSTER_NODE_ADDRS[$i]}' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py setConfigAttrib 'EXTERNAL_IP_ADDR' '$KAM_VIP' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py NEW_EXT_FQDN=$(dig +short -x $KAM_VIP) if [[ -n "$NEW_EXT_FQDN" ]]; then setConfigAttrib 'EXTERNAL_FQDN' '$NEW_EXT_FQDN' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py fi setConfigAttrib 'UAC_REG_ADDR' '$KAM_VIP' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py # update the settings in the various services if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then runas dsiprouter updatedsipconfig fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then runas dsiprouter updatekamconfig fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then runas dsiprouter updatertpconfig fi else # manually add default route to vip before updating settings addDefRoute "${KAM_VIP}" # TODO: enable kamailio to listen to both ip's (maybe??) if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then runas dsiprouter updatedsipconfig fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then runas dsiprouter updatekamconfig fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then runas dsiprouter updatertpconfig fi removeDefRoute "${KAM_VIP}" fi # systemd services will be managed by corosync/pacemaker instead of dsip-init if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then removeDependsOnService "dsiprouter.service" ${DSIP_INIT_PATH} runas systemctl stop dsiprouter runas systemctl disable dsiprouter fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then removeDependsOnService "kamailio.service" ${DSIP_INIT_PATH} runas systemctl stop kamailio runas systemctl disable kamailio fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then removeDependsOnService "rtpengine.service" ${DSIP_INIT_PATH} runas systemctl stop rtpengine runas systemctl disable rtpengine fi addDependsOnService "corosync.service" ${DSIP_INIT_PATH} addDependsOnService "pacemaker.service" ${DSIP_INIT_PATH} fi printdbg 'configuring systemd services for pacemaker cluster' runas systemctl enable pcsd runas systemctl enable corosync runas systemctl enable pacemaker runas systemctl start pcsd printdbg 'removing any previous corosync configurations' PCS_MAJMIN_VER=$(pcs --version | cut -d '.' -f -2 | tr -d '.') if (( $((10#$PCS_MAJMIN_VER)) >= 10 )); then runas pcs host deauth 2>/dev/null runas pcs cluster destroy --force 2>/dev/null else runas pcs pcsd clear-auth 2>/dev/null runas pcs cluster destroy --force 2>/dev/null fi exit 0 ================================================ FILE: HA/pacemaker/scripts/stage2.sh ================================================ #!/bin/bash # import runtime environment if ! [[ -f "$1" ]] || ! source "$1"; then echo "Could not import runtime environment" exit 1 fi if (( ${DEBUG:-0} == 1 )); then set -x fi # get the current region via the metadata api awsGetCurrentRegion() { RET=$(curl -s -o /dev/null -w '%{http_code}' http://169.254.169.254/latest/meta-data/ami-id 2>/dev/null) if (( $RET == 200 )); then curl -s -f http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null elif (( $RET == 401 )); then TOKEN=$(curl -s -X PUT --connect-timeout 2 -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token 2>/dev/null) curl -s -f --connect-timeout 2 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/placement/region 2>/dev/null else return 1 fi return 0 } PCS_MAJMIN_VER=$(pcs --version | cut -d '.' -f -2 | tr -d '.') printdbg 'authenticating hacluster user to pcsd' runas -u hacluster pcs client local-auth -u hacluster -p ${CLUSTER_PASS} if (( $((10#$PCS_MAJMIN_VER)) >= 10 )); then printdbg 'authenticating nodes to pcsd' runas pcs host auth -u hacluster -p ${CLUSTER_PASS} ${NODE_NAMES[@]} || { printerr "Cluster auth failed" exit 1 } if (( $i == ${#NODES[@]} - 1 )); then printdbg 'creating the cluster' runas pcs cluster setup --force --enable ${CLUSTER_NAME} ${NODE_NAMES[@]} ${CLUSTER_OPTIONS[@]} || { printerr "Cluster creation failed" exit 1 } fi else printdbg 'authenticating nodes to pcsd' runas pcs cluster auth --force -u hacluster -p ${CLUSTER_PASS} ${NODE_NAMES[@]} || { printerr "Cluster auth failed" exit 1 } if (( $i == ${#NODES[@]} - 1 )); then printdbg 'creating the cluster' runas pcs cluster setup --force --enable --name ${CLUSTER_NAME} ${NODE_NAMES[@]} ${CLUSTER_OPTIONS[@]} || { printerr "Cluster creation failed" exit 1 } fi fi # start cluster on the last node after all auth is completed if (( $i == ${#NODES[@]} - 1 )); then j=0 while (( $j < $RETRY_CLUSTER_START )); do runas pcs cluster start --all --request-timeout=15 --wait=15 && break j=$((j+1)) done # if we attempted all retries and finished the above loop we failed if (( $j == $RETRY_CLUSTER_START )); then printerr "Starting cluster failed" exit 1 fi fi # setup any cloud provider specific configurations case "$CLOUD_PLATFORM" in DO) runas cp -f /tmp/cloud/assign-ip /usr/local/bin/assign-ip runas chmod +x /usr/local/bin/assign-ip runas mkdir -p /usr/lib/ocf/resource.d/digitalocean runas cp -f /tmp/cloud/ocf-floatip /usr/lib/ocf/resource.d/digitalocean/floatip runas chmod +x /usr/lib/ocf/resource.d/digitalocean/floatip ;; AWS) if ! cmdExists 'aws'; then cd /tmp && curl 'https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip' -o awscli.zip && unzip -qo awscli.zip && rm -f awscli.zip && runas ./aws/install -b /usr/bin fi AWS_REGION=$(awsGetCurrentRegion) || { printerr "Could not determine current AWS region" exit 1 } runas aws configure set aws_access_key_id $AWS_ACCESS_KEY runas aws configure set aws_secret_access_key $AWS_SECRET_TOKEN runas aws configure set region $AWS_REGION runas mkdir -p /usr/lib/ocf/resource.d/aws runas cp -f /tmp/cloud/ocf-floatip /usr/lib/ocf/resource.d/aws/floatip runas chmod +x /usr/lib/ocf/resource.d/aws/floatip ;; esac exit 0 ================================================ FILE: HA/pacemaker/scripts/stage3.sh ================================================ #!/bin/bash # import runtime environment if ! [[ -f "$1" ]] || ! source "$1"; then echo "Could not import runtime environment" exit 1 fi if (( ${DEBUG:-0} == 1 )); then set -x fi # $1 == resource name # returns: 0 == success, else == failure # notes: prints node name where resource is found findResource() { local RESOURCE_FIND_TIMEOUT=5 local NODE timeout "$RESOURCE_FIND_TIMEOUT" bash </dev/null while true; do NODE=\$( runas -u hacluster pcs status resources | awk '\$2=="'$1'" && \$4=="Started" {print \$5}' ) if [[ -n "\$NODE" ]]; then echo "\$NODE" break fi sleep 1 done EOF return $? } # output: prints numbers of pacemaker resources that are down getNumResourcesDown() { runas -u hacluster pcs status resources | grep -v -F 'Resource Group' | awk '$4!="Started" {print $2}' | wc -l } # $1 == timeout # returns: 0 == resources up within timeout, else == resources not up within timeout # notes: block while waiting for resources to come online until timeout waitResources() { timeout "$1" bash <<'EOF' 2>/dev/null RESOURCES_DOWN=$(getNumResourcesDown) while (( $RESOURCES_DOWN > 0 )); do sleep 1 RESOURCES_DOWN=$(getNumResourcesDown) done EOF return $? } # notes: prints out detailed info about cluster showClusterStatus() { runas -u hacluster corosync-cfgtool -s runas -u hacluster pcs status --full } ## Pacemaker / Corosync cluster if (( $i == 0 )); then printdbg 'configuring pacemaker cluster' # disabling stonith/quorum for now because it has caused issues in the past (on first node) runas pcs property set stonith-enabled=false runas pcs property set no-quorum-policy=ignore printdbg 'Setting up the virtual ip address resource' # create resource for services and virtual ip and default route (on first node) case "$CLOUD_PLATFORM" in DO) runas pcs resource create cluster_vip ocf:digitalocean:floatip \ do_token=${DO_TOKEN} floating_ip=${KAM_VIP} \ op monitor interval=15s timeout=15s \ op start interval=0 timeout=30s \ meta resource-stickiness=100 \ --group vip_group ;; AWS) runas pcs resource create cluster_vip ocf:aws:floatip \ elastic_ip=${KAM_VIP} \ op monitor interval=15s timeout=15s \ op start interval=0 timeout=30s \ meta resource-stickiness=100 \ --group vip_group ;; *) runas pcs resource create cluster_vip ocf:heartbeat:IPaddr2 \ ip=${KAM_VIP} cidr_netmask=${CIDR_NETMASK} \ op monitor interval=15s timeout=15s \ op start interval=0 timeout=30s \ meta resource-stickiness=100 \ --group vip_group runas pcs resource create cluster_srcaddr ocf:heartbeat:IPsrcaddr \ ipaddress=${KAM_VIP} cidr_netmask=${CIDR_NETMASK} \ op monitor interval=15s timeout=15s \ op start interval=0 timeout=30s \ meta resource-stickiness=100 \ --group vip_group ;; esac printdbg 'Setting up resources for dsiprouter services' if grep -q 'rtpengine_service' 2>/dev/null <<<"${CLUSTER_RESOURCES[@]}"; then runas pcs resource create rtpengine_service systemd:rtpengine \ op monitor interval=30s timeout=15s \ op start interval=0 timeout=30s on-fail=restart \ op stop interval=0 timeout=30s \ meta resource-stickiness=100 \ --group dsip_group fi if grep -q 'kamailio_service' 2>/dev/null <<<"${CLUSTER_RESOURCES[@]}"; then runas pcs resource create kamailio_service systemd:kamailio \ op monitor interval=30s timeout=15s \ op start interval=0 timeout=30s on-fail=restart \ op stop interval=0 timeout=30s \ meta resource-stickiness=100 \ --group dsip_group fi if grep -q 'dsiprouter_service' 2>/dev/null <<<"${CLUSTER_RESOURCES[@]}"; then runas pcs resource create dsiprouter_service systemd:dsiprouter \ op monitor interval=60s timeout=15s \ op start interval=0 timeout=30s on-fail=restart \ op stop interval=0 timeout=30s \ meta resource-stickiness=100 \ --group dsip_group fi printdbg 'colocating resources on the same node' runas pcs constraint colocation set vip_group dsip_group \ sequential=true \ setoptions score=INFINITY runas pcs constraint order set vip_group dsip_group \ action=start sequential=true require-all=true \ setoptions symmetrical=false kind=Mandatory fi if (( $i == ${#NODES[@]} - 1 )); then printdbg 'testing cluster' PCS_MAJMIN_VER=$(pcs --version | cut -d '.' -f -2 | tr -d '.') RESOURCES=(${CLUSTER_RESOURCES[@]}) CURRENT_RESOURCE_LOCATIONS=() PREVIOUS_RESOURCE_LOCATIONS=() # wait on resources to come online waitResources ${RESOURCE_STARTUP_TIMEOUT} || { printerr "Cluster resources failed to start within ${RESOURCE_STARTUP_TIMEOUT} seconds" exit 1 } # grab operation data for tests # TODO: error checking for resources not yet started for RESOURCE in ${RESOURCES[@]}; do PREVIOUS_RESOURCE_LOCATIONS+=( $(findResource ${RESOURCE}) ) done printdbg "current resource locations: ${PREVIOUS_RESOURCE_LOCATIONS[@]}" printdbg "setting ${PREVIOUS_RESOURCE_LOCATIONS[0]} to standby" if (( $((10#$PCS_MAJMIN_VER)) >= 10 )); then runas -u hacluster pcs node standby ${PREVIOUS_RESOURCE_LOCATIONS[0]} else runas -u hacluster pcs cluster standby ${PREVIOUS_RESOURCE_LOCATIONS[0]} fi # wait for transfer to finish (to another node) waitResources ${RESOURCE_STARTUP_TIMEOUT} || { printerr "Cluster resources failed to start within ${RESOURCE_STARTUP_TIMEOUT} seconds" exit 1 } for RESOURCE in ${RESOURCES[@]}; do CURRENT_RESOURCE_LOCATIONS+=( $(findResource ${RESOURCE}) ) done printdbg "current resource locations: ${CURRENT_RESOURCE_LOCATIONS[@]}" printdbg "resetting ${PREVIOUS_RESOURCE_LOCATIONS[0]}" if (( $(pcs --version | cut -d '.' -f 2- | tr -d '.') >= 100 )); then runas -u hacluster pcs node unstandby ${PREVIOUS_RESOURCE_LOCATIONS[0]} else runas -u hacluster pcs cluster unstandby ${PREVIOUS_RESOURCE_LOCATIONS[0]} fi # wait for transfer to finish (to another node, could be original) waitResources ${RESOURCE_STARTUP_TIMEOUT} || { printerr "Cluster resources failed to start within ${RESOURCE_STARTUP_TIMEOUT} seconds" exit 1 } # run tests to make sure operations worked i=0 while (( $i < ${#RESOURCES[@]} )); do if [[ ${PREVIOUS_RESOURCE_LOCATIONS[$i]} != ${PREVIOUS_RESOURCE_LOCATIONS[$((i+1))]:-${PREVIOUS_RESOURCE_LOCATIONS[0]}} ]]; then printerr "Cluster resource colocation tests failed (before migration)" exit 1 fi if [[ ${CURRENT_RESOURCE_LOCATIONS[$i]} != ${CURRENT_RESOURCE_LOCATIONS[$((i+1))]:-${CURRENT_RESOURCE_LOCATIONS[0]}} ]]; then printerr "Cluster resource colocation tests failed (after migration)" exit 1 fi if [[ ${PREVIOUS_RESOURCE_LOCATIONS[$i]} == ${CURRENT_RESOURCE_LOCATIONS[$i]} ]]; then printerr "Cluster resource ${RESOURCE} failover tests failed" exit 1 fi i=$((i+1)) done printdbg 'Any non-critical resource errors are shown below:' runas -u hacluster pcs resource failcount show printdbg 'Clearing any non-critical resource errors' runas -u hacluster -n pcs resource cleanup # show status to user printdbg 'cluster info:' showClusterStatus fi exit 0 ================================================ FILE: HA/shared_lib.sh ================================================ #!/usr/bin/env bash # # NOTES: # contains utility functions and shared variables # should be sourced by an external script # # TODO: section library scripts into more manageable files (grouping related funcs) # we should also put them in a central location such as: /bashlibs # ############# # Constants # ############# # Ansi Colors ESC_SEQ="\033[" ANSI_NONE="${ESC_SEQ}39;49;00m" # Reset colors ANSI_RED="${ESC_SEQ}1;31m" ANSI_GREEN="${ESC_SEQ}1;32m" ANSI_YELLOW="${ESC_SEQ}1;33m" ANSI_CYAN="${ESC_SEQ}1;36m" # Shared Script Defaults DEFAULT_SSH_OPTS=(-o StrictHostKeyChecking=no -o CheckHostIp=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -x -t) ############################################## # Printing functions and String Manipulation # ############################################## printerr() { printf "%b%s%b\n" "${ANSI_RED}" "$*" "${ANSI_NONE}" } printwarn() { printf "%b%s%b\n" "${ANSI_YELLOW}" "$*" "${ANSI_NONE}" } printdbg() { printf "%b%s%b\n" "${ANSI_GREEN}" "$*" "${ANSI_NONE}" } pprint() { printf "%b%s%b\n" "${ANSI_CYAN}" "$*" "${ANSI_NONE}" } ###################################### # Traceback / Debug helper functions # ###################################### backtrace() { local DEPTN=${#FUNCNAME[@]} for ((i=1; i < ${DEPTN}; i++)); do local FUNC="${FUNCNAME[$i]}" local LINE="${BASH_LINENO[$((i-1))]}" local SRC="${BASH_SOURCE[$((i-1))]}" printf '%*s' $i '' # indent printerr "[ERROR]: ${FUNC}(), ${SRC}, line: ${LINE}" done } setErrorTracing() { set -o errtrace trap 'backtrace' ERR } # $1 == command to test # returns: 0 == true, 1 == false cmdExists() { if command -v "$1" > /dev/null 2>&1; then return 0 else return 1 fi } # $1 == directory to check for in PATH # returns: 0 == found, 1 == not found pathCheck() { case ":${PATH-}:" in *:"$1":*) return 0 ;; *) return 1 ;; esac } # $1 == delimeter to join args with # $* == strings to join # usage: STR=$(join ',' ${ARR[@]}) join() { local IFS="$1"; shift; echo "$*" } # $1 == delimeter to join args with # $@ == string(s) to split # usage: ARR=( $(split ',' '1,2,3,4') ) split() { local IFS="$1"; shift; read -r -a ARR <<< "$@"; echo "${ARR[@]}"; unset ARR; } # returns: 0 == root user, else not root user isRoot() { return $(id -u 2>/dev/null) } # sets DISTRO and DISTRO_VER variables in current shell setOSInfo() { DISTRO=$(cat /etc/os-release 2>/dev/null | grep '^ID=' | cut -d '=' -f 2 | cut -d '"' -f 2) if [[ "$DISTRO" == "linuxmint" ]]; then export DISTRO="linuxmint" export DISTRO_VER=$(grep -w "VERSION_ID" /etc/os-release | cut -d '"' -f 2) elif [[ "$DISTRO" == "ubuntu" ]]; then export DISTRO="ubuntu" export DISTRO_VER=$(grep -w "VERSION_ID" /etc/os-release | cut -d '"' -f 2) elif [[ "$DISTRO" == "debian" ]]; then export DISTRO="debian" export DISTRO_VER=$(grep -w "VERSION_ID" /etc/os-release | cut -d '"' -f 2) elif [[ "$DISTRO" == "amzn" ]]; then export DISTRO="amazon" export DISTRO_VER=$(grep -w "VERSION_ID" /etc/os-release | cut -d '"' -f 2) elif [[ "$DISTRO" == "centos" ]]; then export DISTRO="centos" export DISTRO_VER=$(grep -w "VERSION_ID" /etc/os-release | cut -d '"' -f 2) elif [[ -f /etc/redhat-release ]] && [[ "$(cat /etc/redhat-release | awk '{ print tolower($1) }')" == "red" ]]; then export DISTRO="redhat" export DISTRO_VER=$(cat /etc/redhat-release | sed 's/.* \(7\).[0-9] .*/\1/') else export DISTRO="" export DISTRO_VER="" fi } # usage: runas [options] # options: -u # notes: default runas user is root # returns: 255 == failed before running command, else == exit code of command runas() { local RUN_USER="root" if [[ "$1" == "-u" ]]; then shift RUN_USER="$1" shift fi if [[ $(id -u "$RUN_USER" 2>/dev/null) == $(id -u "$RUN_USER" 2>/dev/null) ]]; then "$@" elif command -v 'sudo' &>/dev/null; then sudo -u "$RUN_USER" "$@" elif command -v 'su' &>/dev/null; then su "$RUN_USER" -s /bin/bash -c "$*" else echo "$0: could not run command as user '$RUN_USER'" return 255 fi } # $1 == ip or hostname # $2 == port # returns: 0 == connection good, 1 == connection bad # note: timeout is set to 3 sec checkConn() { if cmdExists 'nc'; then nc -w 3 "$1" "$2" < /dev/null 2>&1 > /dev/null; return $? else timeout 3 bash -c "< /dev/tcp/$1/$2" 2> /dev/null; return $? fi } # $@ == ssh command to test # returns: 0 == ssh connected, 1 == ssh could not connect checkSsh() { local SSH_CMD="$@ -o ConnectTimeout=5 -q 'exit 0'" bash -c "${SSH_CMD}" 2>&1 > /dev/null; return $? } # $@ == ssh command to test # returns: 0 == user can escalate privileges, 1 == user could not escalate privileges checkSshRunas() { local SSH_CMD="$@ -o ConnectTimeout=5 -q '$(typeset -f runas); runas echo -n'" bash -c "trap 'exit 1' SIGINT; ${SSH_CMD}"; return $? } # Notes: prints generated password createPass() { tr -dc 'a-zA-Z0-9' /dev/null } # usage: getPkgVer [--opt] # opt == what version(s) to show (default --installed) # -i|--installed: get installed package version # -l|--latest: get latest version available # -a|--all: get all available versions # arg == the package to search for in repos # returns: 0 == success, else == failure # note: does not handle same versions from different repos # bug: if search is on a glob expr xargs will error out silently getPkgVer() { local OPT="" local ARG="" local KEY="" while (( $# > 0 )); do OPT="$1" case $OPT in -i|--installed) KEY="installed" shift ;; -l|--latest) KEY="latest" shift ;; -a|--all) KEY="all" shift ;; *) # only one arg is valid ARG="$OPT" shift ;; esac done case ${KEY:-installed} in installed) ( apt-cache policy "$ARG" \ | grep -oP 'Installed:\h*([0-9]+:)?\K([0-9]+\.)([0-9\.]+)' \ | sed 's/\./%/; s/\.//g; s/%/\./'; exit ${PIPESTATUS[0]}; ) 2>/dev/null && return $? || ( yum --cacheonly list installed "$ARG" 2>/dev/null \ | xargs -d '\r\n' -n 3 \ | grep -oP '\h*([0-9]+:)?\K([0-9]+\.)([0-9\.]+)' \ | sed 's/\./%/; s/\.//g; s/%/\./'; exit ${PIPESTATUS[0]}; ) 2>/dev/null && return $? || return 1 ;; latest) ( apt-cache policy "$ARG" \ | grep -oP 'Candidate:\h*([0-9]+:)?\K([0-9]+\.)([0-9\.]+)' \ | sed 's/\./%/; s/\.//g; s/%/\./'; exit ${PIPESTATUS[0]}; ) 2>/dev/null || ( yum --cacheonly --showduplicates list available "$ARG" 2>/dev/null \ | xargs -d '\r\n' -n 3 \ | grep -oP '\h*([0-9]+:)?\K([0-9]+\.)([0-9\.]+)' \ | sed 's/\./%/; s/\.//g; s/%/\./' \ | head -1; exit ${PIPESTATUS[0]}; ) 2>/dev/null && return $? || return 1 ;; all) ( apt-cache policy "$ARG" \ | grep -oP '((?/dev/null || ( yum --cacheonly --showduplicates list "$ARG" 2>/dev/null \ | xargs -d '\r\n' -n 3 \ | grep -oP '\h*([0-9]+:)?\K([0-9]+\.)([0-9\.]+)' \ | sed 's/\./%/; s/\.//g; s/%/\./' \ | uniq; exit ${PIPESTATUS[0]}; ) 2>/dev/null && return $? || return 1 ;; esac } # usage: dumpMysqlDatabases [options] [ <[user1[:pass]@]host1[:port]> <[user1[:pass]@]host2[:port]> ... ] # options: -a|--all # -f|--full # -m|--merge # -g|--grants # notes: redirect output sql as needed (in shell) dumpMysqlDatabases() { local KEY="full" #default local OPT NODE USER PASS HOST PORT NON_SYSTEM_DB local IDX=0 IDX_MAX=0 IDX_LAST=0 local USERS=() PASSES=() HOSTS=() PORTS=() while (( $# > 0 )); do OPT="$1" case $OPT in -a|--all) KEY="all" shift ;; -f|--full) KEY="full" shift ;; -m|--merge) KEY="merge" shift ;; -g|--grants) KEY="grants" shift ;; *) NODE="$1" USER=$(printf '%s' "$NODE" | cut -s -d '@' -f -1 | cut -d ':' -f -1) PASS=$(printf '%s' "$NODE" | cut -s -d '@' -f -1 | cut -s -d ':' -f 2-) HOST=$(printf '%s' "$NODE" | cut -d '@' -f 2- | cut -d ':' -f -1) PORT=$(printf '%s' "$NODE" | cut -d '@' -f 2- | cut -s -d ':' -f 2-) USERS+=("$USER") PASSES+=("$PASS") HOSTS+=("$HOST") PORTS+=("$PORT") IDX_MAX=$(( IDX_MAX + 1 )) shift ;; esac done IDX_LAST=$(( IDX_MAX - 1 )) # key is not handled case "$KEY" in all|full|merge|grants) ;; *) return 1;; esac # for all databases if [[ "$KEY" == "all" ]] || [[ "$KEY" == "full" ]]; then IDX=0 while (( $IDX < $IDX_MAX )); do mysqldump --single-transaction --opt --events --routines --triggers --all-databases --add-drop-database --flush-privileges --hex-blob \ --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" 2>/dev/null \ | sed -r -e 's|DEFINER=[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']@[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']||g' IDX=$(( IDX + 1 )) done fi # for merging non system databases if [[ "$KEY" == "merge" ]]; then # TODO: handle nodes other than 1st have other non-system DBs NON_SYSTEM_DB=$(mysql -sN --user="${USERS[0]}" --password="${PASSES[0]}" --port="${PORTS[0]}" --host="${HOSTS[0]}" \ -e "SELECT schema_name FROM information_schema.schemata WHERE schema_name NOT IN ('mysql','information_schema','performance_schema')" 2>/dev/null) while (( $IDX < $IDX_MAX )); do if (( $IDX == 0 )); then # recreate DB schema without triggers mysqldump --single-transaction --skip-opt --quick --skip-triggers --routines --create-options --disable-keys --set-charset --add-drop-database --no-data --skip-comments \ --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" --databases ${NON_SYSTEM_DB} 2>/dev/null \ | sed -r -e 's|DEFINER=[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']@[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']||g' fi # fill data from each node mysqldump --single-transaction --skip-opt --skip-triggers --no-create-db --no-create-info --insert-ignore --hex-blob --skip-comments \ --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" --databases ${NON_SYSTEM_DB} 2>/dev/null if (( $IDX == $IDX_LAST )); then # recreate DB triggers now that data is processed mysqldump --single-transaction --skip-opt --quick --triggers --no-create-db --no-create-info --no-data --skip-comments \ --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" --databases ${NON_SYSTEM_DB} 2>/dev/null \ | sed -r -e 's|DEFINER=[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']@[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']||g' fi IDX=$(( IDX + 1 )) done fi # for copying privileges if [[ "$KEY" == "all" ]] || [[ "$KEY" == "grants" ]]; then ( IDX=0 while (( $IDX < $IDX_MAX )); do mysql -sN -A --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" \ -e "SELECT DISTINCT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') FROM mysql.user WHERE user<>''" 2>/dev/null \ | mysql -sN -A --user="${USERS[$IDX]}" --password="${PASSES[$IDX]}" --port="${PORTS[$IDX]}" --host="${HOSTS[$IDX]}" 2>/dev/null \ | sed 's/$/;/g' \ | awk '!x[$0]++' IDX=$(( IDX + 1 )) done ) | sort -u echo 'FLUSH PRIVILEGES;' fi return 0 } detectServiceMan() { INIT_PROC=$(runas readlink -f $(runas readlink -f /proc/1/exe)) case "$INIT_PROC" in *systemd) SERVICE_MANAGER="systemd" ;; *upstart) SERVICE_MANAGER="upstart" ;; *runit-init) SERVICE_MANAGER="runit" ;; *openrc-init) SERVICE_MANAGER="openrc" ;; /sbin/init) INIT_PROC_INFO=$(/sbin/init --version 2>/dev/null | head -1) case "$INIT_PROC_INFO" in *systemd*) SERVICE_MANAGER="systemd" ;; *upstart*) SERVICE_MANAGER="upstart" ;; *runit-init*) SERVICE_MANAGER="runit" ;; *openrc-init*) SERVICE_MANAGER="openrc" ;; esac ;; *) SERVICE_MANAGER="sysv" ;; esac export SERVICE_MANAGER } # $1 == ip to test # returns: 0 == success, 1 == failure # notes: regex credit to ipv4Test() { local IP="$1" if [[ $IP =~ ^([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]]; then return 0 fi return 1 } # $1 == ip to test # returns: 0 == success, 1 == failure # notes: regex credit to ipv6Test() { local IP="$1" if [[ $IP =~ ^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$ ]]; then return 0 fi return 1 } # $1 == ip to test # returns: 0 == success, 1 == failure ipv4TestRFC1918() { local IP="$1" if [[ $IP =~ ^(10\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|172\.(1[6-9]|2[0-9]|3[01])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|192\.168\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))$ ]]; then return 0 fi return 1 } # notes: prints external ip, or empty string if not available # notes: below we have measurements for average time of each service # over 10 non-cached requests, in seconds, round trip # # | External Service | Mean RTT | IP Protocol | # |:---------------------------------:|:--------:|:-----------:| # | https://icanhazip.com | 0.38080 | IPV4 | # | https://ipecho.net/plain | 0.39810 | IPV4 | # | https://myexternalip.com/raw | 0.51850 | IPV4 | # | https://api.ipify.org | 0.64860 | IPV4 | # | https://bot.whatismyipaddress.com | 0.69640 | IPV4 | # | https://icanhazip.com | 0.40190 | IPV6 | # | https://bot.whatismyipaddress.com | 0.72490 | IPV6 | # | https://ifconfig.co | 0.80290 | IPV6 | # | https://ident.me | 0.97620 | IPV6 | # | https://api6.ipify.org | 1.08510 | IPV6 | # getExternalIP() { local IPV6_ENABLED=${IPV6_ENABLED:-0} local EXTERNAL_IP="" local URLS=() CURL_CMD="curl" if (( ${IPV6_ENABLED} == 1 )); then URLS=( "https://icanhazip.com" "https://bot.whatismyipaddress.com" "https://ifconfig.co" "https://ident.me" "https://api6.ipify.org" ) CURL_CMD="curl -6" IP_TEST="ipv6Test" else URLS=( "https://icanhazip.com" "https://ipecho.net/plain" "https://myexternalip.com/raw" "https://api.ipify.org" "https://bot.whatismyipaddress.com" ) CURL_CMD="curl -4" IP_TEST="ipv4Test" fi for URL in ${URLS[@]}; do EXTERNAL_IP=$(${CURL_CMD} -s --connect-timeout 2 $URL 2>/dev/null) ${IP_TEST} "$EXTERNAL_IP" && break done printf '%s' "$EXTERNAL_IP" } # prints internal ip address for the default route getInternalIP() { local IFACE=$(ip -4 route show default | awk '{print $5}' | head -1) ip -4 -o addr show $IFACE | awk '{split($4,a,"/"); print a[1];}' | head -1 } # $1 == cidr subnet # returns: 0 == success, 1 == failure # notes: prints first available ip in subnet # notes: assumes .1 is used as default gw in net findAvailableIP() { local NET_TAKEN_LIST=$(runas nmap -n -sP -T 5 "$1" -oG - | awk '/Up$/{print $2}') local NET_ADDR_LIST=$(runas nmap -n -sL "$1" | grep "Nmap scan report" | awk '{print $NF}' | tail -n +3 | sed '$ d') for IP in ${NET_ADDR_LIST[@]}; do for ip in ${NET_TAKEN_LIST[@]}; do if [[ "$IP" != "$ip" ]]; then printf '%s' "$IP" return 0 fi done done return 1 } # automate mysql_secure_installation # original: https://gist.github.com/kahidna/512b0d507ac90d1cbbf6b0230d38a502 # $1 == new root password # $2 == old root password mysqlSecureInstall() { local NEW_MYSQL_PASSWORD="$1" local CURRENT_MYSQL_PASSWORD="$2" expect </dev/null; then echo -n 'AWS' # -- digital ocean check -- elif curl -s -f --connect-timeout 2 http://169.254.169.254/metadata/v1/id &>/dev/null; then echo -n 'DO' # -- google compute engine check -- elif curl -s -f --connect-timeout 2 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/id &>/dev/null; then echo -n 'GCE' # -- microsoft azure check -- elif curl -s -f --connect-timeout 2 -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2018-10-01" &>/dev/null; then echo -n 'AZURE' # -- vultr cloud check -- elif curl -s -f --connect-timeout 2 http://169.254.169.254/v1/instanceid &>/dev/null; then echo -n 'VULTR' # -- oracle cloud environment check -- elif curl -s -f --connect-timeout 2 -H 'Authorization: Bearer Oracle' http://169.254.169.254/opc/v2/instance; then echo -n 'OCE' fi # -- bare metal or unsupported cloud platform -- } # $1 == attribute name # $2 == python config file # output: attribute value getConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" local VALUE=$(runas grep -oP '^(?!#)(?:'${NAME}')[ \t]*=[ \t]*\K(?:\w+\(.*\)[ \t\v]*$|[\w\d\.]+[ \t]*$|\{.*\}|\[.*\][ \t]*$|\(.*\)[ \t]*$|b?""".*"""[ \t]*$|'"b?'''.*'''"'[ \v]*$|b?".*"[ \t]*$|'"b?'.*'"')' ${CONFIG_FILE}) printf '%s' "${VALUE}" | perl -0777 -pe 's~^b?["'"'"']+(.*?)["'"'"']+$|(.*)~\1\2~g' } # $1 == attribute name # $2 == attribute value # $3 == python config file # $4 == -q (quote string) | -qb (quote byte string) setConfigAttrib() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" if (( $# >= 4 )); then if [[ "$4" == "-q" ]]; then VALUE="'${VALUE}'" elif [[ "$4" == "-qb" ]]; then VALUE="b'${VALUE}'" fi fi runas sed -i -r -e "s|$NAME[ \t]*=[ \t]*.*|$NAME = $VALUE|g" ${CONFIG_FILE} } # $1 == cmd as executed in systemd (by ExecStart=) # $2 == service file to add command to # notes: take precaution when adding long running functions as they will block startup in boot order # notes: adding init commands on an AMI instance must not be long running processes, otherwise they will fail addExcStartCmd() { local CMD=$(printf '%s' "$1" | sed -e 's|[\/&]|\\&|g') # escape string local SVC_FILE="$2" local TMP_FILE="/tmp/${SVC_FILE}" # sanity check, does the entry already exist? grep -q -oP "^ExecStart\=.*${CMD}.*" 2>/dev/null ${SVC_FILE} && return 0 tac ${SVC_FILE} | sed -r "0,\|^ExecStart\=.*|{s|^ExecStart\=.*|ExecStart=${CMD}\n&|}" | tac > ${TMP_FILE} runas mv -f ${TMP_FILE} ${SVC_FILE} runas systemctl daemon-reload } # $1 == string to match for removal (after ExecStart=) # $2 == service file to remove command from removeExecStartCmd() { local STR=$(printf '%s' "$1" | sed -e 's|[\/&]|\\&|g') # escape string local SVC_FILE="$2" runas sed -i -r "\|^ExecStart\=.*${STR}.*|d" ${SVC_FILE} runas systemctl daemon-reload } # $1 == service name (full name with target) to be dependent # $2 == service file to add dependency to # notes: only adds startup ordering dependency (service continues if dependency fails) # notes: the Before= section of init will link to an After= dependency on daemon-reload addDependsOnService() { local SERVICE="$1" local SVC_FILE="$2" # sanity check, does the entry already exist? grep -q -oP "^(Before\=|Wants\=).*${SERVICE}.*" 2>/dev/null ${SVC_FILE} && return 0 runas perl -i -e "\$service='$SERVICE';" -pe 's%^(Before\=|Wants\=)(.*)%length($2)==0 ? "${1}${service}" : "${1}${2} ${service}"%ge;' ${SVC_FILE} runas systemctl daemon-reload } # $1 == service name (full name with target) to remove dependency on # $2 == service file to remove dependency from removeDependsOnService() { local SERVICE="$1" local SVC_FILE="$2" runas perl -i -e "\$service='$SERVICE';" -pe 's%^((?:Before\=|Wants\=).*?)( ${service}|${service} |${service})(.*)%\1\3%g;' ${SVC_FILE} runas systemctl daemon-reload } # output: all physical network interfaces on this machine getPhysicalIfaces() { ( cd /sys/class/net && dirname */device; ) } ================================================ FILE: Jenkinsfile ================================================ pipeline { parameters { password (name: 'DIGITALOCEAN_TOKEN') } environment { TF_WORKSPACE = 'dev' //Sets the Terraform Workspace TF_IN_AUTOMATION = 'true' DIGITALOCEAN_TOKEN = "${params.DIGITALOCEAN_TOKEND}" } stages { stage('Terraform Init') { steps { sh "${env.TERRAFORM_HOME}/terraform init -input=false" } } stage('Terraform Plan') { steps { sh "${env.TERRAFORM_HOME}/terraform plan -out=tfplan -input=false -var-file='dev.tfvars'" } } stage('Terraform Apply') { steps { input 'Apply Plan' sh "${env.TERRAFORM_HOME}/terraform apply -input=false tfplan" } } } } ================================================ FILE: Jenkinsfile.common ================================================ gitlabBuilds(builds: ['build', 'test', 'artifacts']) { stage('build') { gitlabCommitStatus(name: 'build') { sh "./gradlew --no-daemon clean build" }} stage('test') { gitlabCommitStatus(name: 'test') { sh "./gradlew --no-daemon check" }} stage('artifacts') { gitlabCommitStatus(name: 'artifacts') { archiveArtifacts artifacts: '**/build/libs/*.jar', fingerprint: true, onlyIfSuccessful: true step([$class: 'JavadocArchiver', javadocDir: 'thrifty-compiler/build/docs/javadoc/', keepAll: false]) step([$class: 'JavadocArchiver', javadocDir: 'thrifty-schema/build/docs/javadoc/', keepAll: false]) step([$class: 'JavadocArchiver', javadocDir: 'thrifty-java-codegen/build/docs/javadoc/', keepAll: false]) step([$class: 'JavadocArchiver', javadocDir: 'thrifty-runtime/build/docs/javadoc/', keepAll: false]) }} } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # dSIPRouter Platform [

dSIPRouter Logo

](https://dsiprouter.org) ## What is dSIPRouter? dSIPRouter allows you to quickly turn [Kamailio](https://www.kamailio.org/) into an easy to use SIP Service Provider platform, which enables three basic use cases: - **SIP Trunking services:** Provide services to customers that have an on-premise PBX such as FreePBX, FusionPBX, Avaya, etc. We have support for IP and credential based authentication. - **Hosted PBX services:** Proxy SIP Endpoint requests to a multi-tenant PBX such as FusionPBX or single-tenant such as FreePBX. We have an integration with FusionPBX that make this really easy and scalable! - **Microsoft Teams Direct Routing (Core Subscription Required):** We can provide SBC functionality that allows dSIPRouter to interconnect your existing voice infrastructure or VoIP carrier to your Microsoft Teams environment. - **WebRTC Proxy (Core Subscription Required):** We can provide functionality that allows dSIPRouter to register WebRTC clients to PBX's that has extensions being exposed as just UDP and TCP. Hence, becoming a WebRTC Proxy. The dSIPRouter UI allows you to manage the platform. We also make it easy to intergrate dSIPRouter into your existing workflow by using our [API](https://www.postman.com/dopensource/workspace/dsiprouter/overview) **Follow us at [#dsiprouter](https://twitter.com/dsiprouter) on Twitter to get the latest updates on dSIPRouter** ### Project Web Site Check out our official website [dsiprouter.org](http://dsiprouter.org) ### Demo System Try out our demo system [demo.dsiprouter.net](https://demo.dsiprouter.net:5000/) Demo system GUI Credentials: - username: `admin` - password: `ZmIwMTdmY2I5NjE4` Demo system API Credentials: You can test out the API using the demo system. We have defined a [Postman](https://www.postman.com/dopensource/workspace/dsiprouter/overview) collection that will make the process easier. The API token is below: - bearer token: `9lyrny3HOtwgjR6JIMwRaMej9LijIS835zhVbD8ywHDzXT07Xm6vem1sgfvWkFz3` ### Documentation You can find our documentation online: [dSIPRouter Documentation](https://dsiprouter.readthedocs.io/en/latest) For a list of updates and changes refer to our [Changelog](CHANGELOG.md) ### Contributing See the [Contributing Guidelines](CONTRIBUTING.md) for more details A current list of contributors can be found [here](CONTRIBUTORS.md) ### Getting Started You can find the steps to install of support operating systems: - [Debian Based Systems](https://dsiprouter.readthedocs.io/en/latest/debian_install.html#debian-install) - [Redhat Based Systems](https://dsiprouter.readthedocs.io/en/latest/rhel_install.html#rhel-install) ### Support - Free Support: [dSIPRouter Question & Answer Forum](https://groups.google.com/forum/#!forum/dsiprouter) - Paid Support: [dSIPRouter Support](https://dsiprouter.org/#fh5co-support-section) ### Training Details on training can be found [here](https://dopensource.com/product/dsiprouter-admin-course/) ### License - Apache License 2.0, [read more here](LICENSE) ### Supported Features - Carrier Management - Manage carriers as a group - Endpoint Management - Manage endpoints as a group - Call Limiting per Endpoint Group - Call Detail Records generation per Endpoint Group - Notification System - Over Call Limit Notifications - Endpoint Failure Notifications - Call Detail Record Notifications - Enhanced DID Management - DID Failover to a Carrier/Endpoint Group or DID - DID Hard Forwarding to a Carrier/Endpoint Group or DID - Flowroute DID synchronization - Enhanced Route Management - FusionPBX Domain Routing Enhancements - Outbound / Inbound DID prefix routing - Least Cost Locality Outbound routing - Load balancing / sequential routing via groups - Integration with your own custom Kamailio routes - E911 Priority routing - Local Extension routing - Voicemail routing - Security - TLS Enabled by Default - Rate-limiting / DOS protection - Teleblock blacklist support - High Availablity (Subscription Required) - Mysql Active-Active replication - Pacemaker / Corosync Active-Passive floating IP - Consul DNS Load-balancing and DNS Failover - dSIPRouter cluster synchronization - Kamailio DMQ replication - Microsoft Teams Support (Subscription Required) - WebSockets Enabled by Default ================================================ FILE: cloud/build_image.sh ================================================ #!/usr/bin/env bash # # Summary: build dsiprouter as an VM/VPS Deployable Image # Usage: ./build_image.sh [--ver=''] [--dir=''] [--repo=''] [--opts=''] # # parse args if given while (( $# > 0 )); do ARG="$1" case "$ARG" in --ver=*) DSIP_VERSION=$(echo "$1" | cut -d '=' -f 2) shift ;; --dir=*) DSIP_DIR=$(echo "$1" | cut -d '=' -f 2) shift ;; --repo=*) DSIP_REPO=$(echo "$1" | cut -d '=' -f 2) shift ;; --opts=*) BUILD_OPTIONS=$(echo "$1" | cut -d '=' -f 2) shift ;; *) echo "[ERROR] argument $ARG is not valid" exit 1 ;; esac done # set defaults if needed, exports will be passed to dsiprouter.sh export DSIP_VERSION=${DSIP_VERSION:-"master"} DSIP_DIR=${DSIP_DIR:-"/opt/dsiprouter"} DSIP_REPO=${DSIP_REPO:-"https://github.com/dOpensource/dsiprouter.git"} BUILD_OPTIONS=${BUILD_OPTIONS:-"install -all"} export IMAGE_BUILD=1 function cmdExists() { if command -v "$1" > /dev/null 2>&1; then return 0 else return 1 fi } # wait for any other programs using package manager to complete if cmdExists "apt-get"; then while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1 done # make package manager quieter export DEBIAN_FRONTEND="noninteractive" export DEBIAN_PRIORITY="critical" apt-get update -qq -y >/dev/null apt-get install -qq -y git perl >/dev/null # TODO: move to installScriptRequirements() # make sure english UTF-8 locale is installed if ! locale -a 2>/dev/null | grep -q 'en_US.UTF-8'; then perl -i -pe 's%# (en_US\.UTF-8 UTF-8)%\1%' /etc/locale.gen locale-gen fi elif cmdExists 'dnf'; then while [[ -f /var/run/dnf.pid ]]; do sleep 1 done dnf makecache -y --quiet --errorlevel=0 >/dev/null dnf install -y --quiet --errorlevel=0 git >/dev/null elif cmdExists "yum"; then while [ -f /var/run/yum.pid ]; do sleep 1 done yum makecache -y -q -e 0 >/dev/null yum install -y -q -e 0 git >/dev/null fi # clone and install git clone --depth 1 -c advice.detachedHead=false ${DSIP_REPO} -b ${DSIP_VERSION} ${DSIP_DIR} || exit 1 ${DSIP_DIR}/dsiprouter.sh ${BUILD_OPTIONS} || exit 1 # cleanup environment for image ${DSIP_DIR}/cloud/pre-snapshot.sh || exit 1 exit 0 ================================================ FILE: cloud/build_instance.sh ================================================ #!/usr/bin/env bash # # Summary: build dsiprouter as an VM/VPS Instance # Usage: ./build_instance.sh [--ver=''] [--dir=''] [--repo=''] [--opts=''] # # parse args if given while (( $# > 0 )); do ARG="$1" case "$ARG" in --ver=*) DSIP_VERSION=$(echo "$1" | cut -d '=' -f 2) shift ;; --dir=*) DSIP_DIR=$(echo "$1" | cut -d '=' -f 2) shift ;; --repo=*) DSIP_REPO=$(echo "$1" | cut -d '=' -f 2) shift ;; --opts=*) BUILD_OPTIONS=$(echo "$1" | cut -d '=' -f 2) shift ;; *) echo "[ERROR] argument $ARG is not valid" exit 1 ;; esac done # set defaults if needed, exports will be passed to dsiprouter.sh export DSIP_VERSION=${DSIP_VERSION:-"master"} DSIP_DIR=${DSIP_DIR:-"/opt/dsiprouter"} DSIP_REPO=${DSIP_REPO:-"https://github.com/dOpensource/dsiprouter.git"} BUILD_OPTIONS=${BUILD_OPTIONS:-"install -all"} function cmdExists() { if command -v "$1" > /dev/null 2>&1; then return 0 else return 1 fi } # wait for any other programs using package manager to complete if cmdExists "apt-get"; then while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do sleep 1 done # make package manager quieter export DEBIAN_FRONTEND="noninteractive" export DEBIAN_PRIORITY="critical" apt-get update -qq -y >/dev/null apt-get install -qq -y git perl >/dev/null # TODO: move to installScriptRequirements() # make sure english UTF-8 locale is installed if ! locale -a 2>/dev/null | grep -q 'en_US.UTF-8'; then perl -i -pe 's%# (en_US\.UTF-8 UTF-8)%\1%' /etc/locale.gen locale-gen fi elif cmdExists 'dnf'; then while [[ -f /var/run/dnf.pid ]]; do sleep 1 done dnf makecache -y --quiet --errorlevel=0 >/dev/null dnf install -y --quiet --errorlevel=0 git >/dev/null elif cmdExists "yum"; then while [[ -f /var/run/yum.pid ]]; do sleep 1 done yum makecache -y -q -e 0 >/dev/null yum install -y -q -e 0 git >/dev/null fi # clone and install git clone --depth 1 -c advice.detachedHead=false ${DSIP_REPO} -b ${DSIP_VERSION} ${DSIP_DIR} || exit 1 ${DSIP_DIR}/dsiprouter.sh ${BUILD_OPTIONS} || exit 1 exit 0 ================================================ FILE: cloud/cloud-init/configs/AWS.cfg ================================================ datasource_list: [ Ec2, NoCloud, None ] disable_root: true ssh_pwauth: false ssh_deletekeys: true ssh_genkeytypes: [rsa, dsa, ecdsa, ed25519] allow_public_ssh_keys: true ssh_quiet_keygen: true manage_etc_hosts: true manage_resolv_conf: false preserve_hostname: false apt_preserve_sources_list: true ================================================ FILE: cloud/cloud-init/configs/AZURE.cfg ================================================ datasource_list: [ Azure, NoCloud, None ] disable_root: true ssh_pwauth: false ssh_deletekeys: true ssh_genkeytypes: [rsa, dsa, ecdsa, ed25519] allow_public_ssh_keys: true ssh_quiet_keygen: true manage_etc_hosts: true manage_resolv_conf: false preserve_hostname: false apt_preserve_sources_list: true ================================================ FILE: cloud/cloud-init/configs/DO.cfg ================================================ datasource_list: [ ConfigDrive, DigitalOcean, NoCloud, None ] disable_root: false ssh_pwauth: false ssh_deletekeys: true ssh_genkeytypes: [rsa, dsa, ecdsa, ed25519] allow_public_ssh_keys: true ssh_quiet_keygen: true manage_etc_hosts: true manage_resolv_conf: false preserve_hostname: false apt_preserve_sources_list: true ================================================ FILE: cloud/cloud-init/configs/GCE.cfg ================================================ datasource_list: datasource_list: [ GCE, NoCloud, None ] disable_root: true ssh_pwauth: false ssh_deletekeys: true ssh_genkeytypes: [rsa, dsa, ecdsa, ed25519] allow_public_ssh_keys: true ssh_quiet_keygen: true manage_etc_hosts: true manage_resolv_conf: false preserve_hostname: false apt_preserve_sources_list: true ================================================ FILE: cloud/cloud-init/configs/VULTR.cfg ================================================ datasource_list: [ Vultr, NoCloud, None ] disable_root: true ssh_pwauth: false ssh_deletekeys: true ssh_genkeytypes: [rsa, dsa, ecdsa, ed25519] allow_public_ssh_keys: true ssh_quiet_keygen: true manage_etc_hosts: true manage_resolv_conf: false preserve_hostname: false apt_preserve_sources_list: true ================================================ FILE: cloud/cloud-init/templates/hosts.almalinux.tmpl ================================================ ## template:jinja {# This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # # The following lines are desirable for IPv4 capable hosts 127.0.0.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost.localdomain localhost 127.0.0.1 localhost4.localdomain4 localhost4 # The following lines are desirable for IPv6 capable hosts ::1 {{fqdn}} {{hostname}} ::1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.amzn.tmpl ================================================ ## template:jinja {# This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # # The following lines are desirable for IPv4 capable hosts 127.0.0.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost.localdomain localhost 127.0.0.1 localhost4.localdomain4 localhost4 # The following lines are desirable for IPv6 capable hosts ::1 {{fqdn}} {{hostname}} ::1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.centos.tmpl ================================================ ## template:jinja {# This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # # The following lines are desirable for IPv4 capable hosts 127.0.0.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost.localdomain localhost 127.0.0.1 localhost4.localdomain4 localhost4 # The following lines are desirable for IPv6 capable hosts ::1 {{fqdn}} {{hostname}} ::1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.debian.tmpl ================================================ ## template:jinja {# This file (/etc/cloud/templates/hosts.debian.tmpl) is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # {# The value '{{hostname}}' will be replaced with the local-hostname -#} 127.0.1.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.rhel.tmpl ================================================ ## template:jinja {# This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # # The following lines are desirable for IPv4 capable hosts 127.0.0.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost.localdomain localhost 127.0.0.1 localhost4.localdomain4 localhost4 # The following lines are desirable for IPv6 capable hosts ::1 {{fqdn}} {{hostname}} ::1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.rocky.tmpl ================================================ ## template:jinja {# This file /etc/cloud/templates/hosts.redhat.tmpl is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.redhat.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # # The following lines are desirable for IPv4 capable hosts 127.0.0.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost.localdomain localhost 127.0.0.1 localhost4.localdomain4 localhost4 # The following lines are desirable for IPv6 capable hosts ::1 {{fqdn}} {{hostname}} ::1 localhost.localdomain localhost ::1 localhost6.localdomain6 localhost6 #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/cloud-init/templates/hosts.ubuntu.tmpl ================================================ ## template:jinja {# This file (/etc/cloud/templates/hosts.debian.tmpl) is only utilized if enabled in cloud-config. Specifically, in order to enable it you need to add the following to config: manage_etc_hosts: True -#} # Your system has configured 'manage_etc_hosts' as True. # As a result, if you wish for changes to this file to persist # then you will need to either # a.) make changes to the master file in /etc/cloud/templates/hosts.debian.tmpl # b.) change or remove the value of 'manage_etc_hosts' in # /etc/cloud/cloud.cfg or cloud-config from user-data # {# The value '{{hostname}}' will be replaced with the local-hostname -#} 127.0.1.1 {{fqdn}} {{hostname}} 127.0.0.1 localhost # The following lines are desirable for IPv6 capable hosts ::1 ip6-localhost ip6-loopback fe00::0 ip6-localnet ff00::0 ip6-mcastprefix ff02::1 ip6-allnodes ff02::2 ip6-allrouters ff02::3 ip6-allhosts #####DSIP_CONFIG_START #####DSIP_CONFIG_END #####PACEMAKER_CONFIG_START #####PACEMAKER_CONFIG_END ================================================ FILE: cloud/find_hosts_tmpl.sh ================================================ #!/usr/bin/env bash # # Summary: find the correct hosts template for the current environment # case "$(cloud-init query distro)" in alpine) echo '/etc/cloud/templates/hosts.alpine.tmpl' ;; arch) echo '/etc/cloud/templates/hosts.arch.tmpl' ;; debian|ubuntu) echo '/etc/cloud/templates/hosts.debian.tmpl' ;; freebsd|dragonfly) echo '/etc/cloud/templates/hosts.freebsd.tmpl' ;; gentoo|cos) echo '/etc/cloud/templates/hosts.gentoo.tmpl' ;; netbsd) echo '/etc/cloud/templates/hosts.netbsd.tmpl' ;; openbsd) echo '/etc/cloud/templates/hosts.openbsd.tmpl' ;; almalinux|amazon|centos|cloudlinux|eurolinux|fedora|mariner|miraclelinux|openmandriva|photon|rhel|rocky|virtuozzo) echo '/etc/cloud/templates/hosts.redhat.tmpl' ;; opensuse|opensuse-leap|opensuse-microos|opensuse-tumbleweed|sle_hpc|sle-micro|sles|suse) echo '/etc/cloud/templates/hosts.suse.tmpl' ;; openeuler) echo '/etc/cloud/templates/hosts.openeuler.tmpl' ;; OpenCloudOS|TencentOS) echo '/etc/cloud/templates/hosts.OpenCloudOS.tmpl' ;; *) echo '/etc/cloud/templates/hosts.tmpl' ;; esac ================================================ FILE: cloud/pre-snapshot.sh ================================================ #!/usr/bin/env bash # # Summary: clean up / harden system before creating an image # function cmdExists() { if command -v "$1" >/dev/null 2>&1; then return 0 else return 1 fi } function getDistroName() { grep '^ID=' /etc/os-release 2>/dev/null | cut -d '=' -f 2 | cut -d '"' -f 2 } function joinwith() { local START="$1" IFS="$2" END="$3" ARR=() shift;shift;shift for VAR in "$@"; do ARR+=("${START}${VAR}${END}") done echo "${ARR[*]}" } # removed from cleanup logic as this is run on virtual hardware # we shouldn't need to flush the disks, this saves us time function clearDiskCache() { dd if=/dev/zero of=/zerofile 2>/dev/null rm -f /zerofile sync } # make sure all security updates are installed # remove insecure services (FTP, Telnet, Rlogin/Rsh) # TEMP: remove known bad packages # TODO: in the future this will instead be handled by using pre-packaged binaries function runSecurityUpdates() { if cmdExists 'apt-get'; then # grub updates adhere to ucf not debconf # make sure ucf defaults to unattended upgrade unset UCF_FORCE_CONFFOLD export UCF_FORCE_CONFFNEW=YES ucf --purge /boot/grub/menu.lst apt-mark hold linux-image-* linux-headers-* apt-get update -y apt-get upgrade -y apt-mark unhold linux-image-* linux-headers-* apt-get remove -y --purge xinetd nis yp-tools tftpd atftpd tftpd-hpa telnetd rsh-server rsh-redone-server apt-get remove -y --purge libcap-dev apt-get autoremove -y --purge apt-get clean -y elif cmdExists 'dnf'; then dnf upgrade -y --exclude='kernel*' --exclude='linux-headers-*' dnf remove -y xinetd ypserv tftp-server telnet-server rsh-server dnf remove -y libcap-devel dnf autoremove -y dnf clean all elif cmdExists 'yum'; then yum upgrade -y --exclude='linux-image-*' --exclude='linux-headers-*' yum remove -y xinetd ypserv tftp-server telnet-server rsh-server yum remove -y libcap-devel yum autoremove -y yum clean all -y fi } function hardenSshdConfigs() { ( cat <<'EOF' # |== SSHD Server Settings ==| Port 22 Protocol 2 # |== Log Settings ==| SyslogFacility AUTH LogLevel INFO # |== Authentication Settings ==| # we only allow pubkey auth using ssh protocol v2 PermitRootLogin no StrictModes yes # enable protocol v2 auth PubkeyAuthentication yes # disable protocol v1 auth RSAAuthentication no ChallengeResponseAuthentication no PasswordAuthentication no KerberosAuthentication no GSSAPIAuthentication no PermitEmptyPasswords no # HostKeys for protocol v2 # see: man sshd_config for more details HostKey /etc/ssh/ssh_host_rsa_key HostKey /etc/ssh/ssh_host_dsa_key HostKey /etc/ssh/ssh_host_ecdsa_key HostKey /etc/ssh/ssh_host_ed25519_key # where to check for authorized keys AuthorizedKeysFile .ssh/authorized_keys # |== Security Settings ==| # Process is unprivileged until auth is complete UsePrivilegeSeparation yes # Make brute force attempts much harder # NOTE: if you have many identity keys (>5) each one causes an auth attempt and this may cause auth failure # clients with this issue need to specify the key explicitly for that host (on cmdline or in ~/.ssh/ssh_config) # ex) ssh -o IdentitiesOnly=yes -i ~/.ssh/.pem @ MaxAuthTries 5 LoginGraceTime 60 # Don't read the user's ~/.rhosts and ~/.shosts files IgnoreRhosts yes # Don't allow remote host auth protocol v1 RhostsRSAAuthentication no # Don't allow remote host auth protocol v2 HostbasedAuthentication no # PAM is needed for some 2-factor auth solutions UsePAM yes # Some exploits have been published using X11 offsets # so we disable it just in case X11Forwarding no # |== General sSettings ==| PrintMotd yes TCPKeepAlive yes ClientAliveInterval 240 # Allow client to pass locale environment variable AcceptEnv LANG LANGUAGE LC_* # Allow sftp over ssh Subsystem sftp /usr/lib/openssh/sftp-server EOF ) >/etc/ssh/sshd_config } function hardenKernelConfigs() { # source: https://www.cyberciti.biz/tips/linux-security.html ( cat <<'EOF' ###################################################################### # /etc/sysctl.conf - Configuration file for setting system variables # See /etc/sysctl.d/ for additional system variables. # See sysctl.conf (5) for information. ###################################################################### # Turn on execshield kernel.exec-shield=1 # ASLR enabled on boot kernel.randomize_va_space=1 # Enable IP spoofing protection net.ipv4.conf.all.rp_filter=1 # Disable IP source routing net.ipv4.conf.all.accept_source_route=0 # Ignoring broadcasts request net.ipv4.icmp_echo_ignore_broadcasts=1 net.ipv4.icmp_ignore_bogus_error_messages=1 # Make sure spoofed packets get logged net.ipv4.conf.all.log_martians = 1 EOF ) >/etc/sysctl.conf # ensure address space layout randomization (ASLR) is enabled echo '2' >/proc/sys/kernel/randomize_va_space } # sets up the filesystem as a golden image # we are running a portoin of the "cloud-init clean" logic to ensure we keep dsiprouter specific scripts # cloud-init versions < 23.1 remove machine-id instead of zeroing it out # see discussion here: https://bugs.launchpad.net/ubuntu/+source/cloud-init/+bug/1563951 # TODO: revisit this in the future, there is a bit more logic they do we might want to incorporate: # ref: https://github.com/canonical/cloud-init/blob/main/cloudinit/cmd/clean.py#L165 function cleanCloudInit() { find /var/lib/cloud -mindepth 1 -maxdepth 1 -type d ! -name 'scripts' -exec rm -rf {} + find /var/lib/cloud -mindepth 1 -maxdepth 1 ! -type d -exec rm -f {} + truncate -s 0 /etc/machine-id } function cleanUserAccounts() { # delete any accounts attempting to be root BAD_USERS=$(joinwith '' ';' 'd' $(awk -F ':' '($3 == "0") && !/root/ {print FNR}' /etc/passwd)) [[ ! -z "${BAD_USERS}" ]] && sed -i "/${BAD_USERS}/d" /etc/passwd # remove and lock the root user's password passwd -d root passwd -l root } # remove ssh keys, remove known hosts files function cleanKeys() { rm -f /etc/ssh/*key* /root/.ssh/{authorized_keys,known_hosts} /home/*/.ssh/{authorized_keys,known_hosts} 2>/dev/null touch /etc/ssh/revoked_keys chmod 600 /etc/ssh/revoked_keys } # let cloud-init write these on boot function cleanNetworkConfigs() { rm -f /etc/systemd/network/* rm -f /etc/netplan/* } function cleanSourceFiles() { find /usr/local/src -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + find /usr/local/src -mindepth 1 -maxdepth 1 ! -type d -exec rm -f {} + } function cleanRuntimeFiles() { ( shopt -s globstar rm -rf /opt/dsiprouter/**/__pycache__/ ) rm -rf /run/* rm -rf /tmp/* /var/tmp/* } # remove logs and any information from build process function cleanLogs() { history -c truncate -s 0 /root/.*history /home/*/.*history 2>/dev/null unset HISTFILE find /var/log -mtime -1 -type f -exec truncate -s 0 {} + rm -rf /var/log/*.gz /var/log/*.[0-9] /var/log/*-???????? truncate -s 0 /var/log/lastlog /var/log/wtmp } # main logic runSecurityUpdates hardenSshdConfigs hardenKernelConfigs cleanUserAccounts cleanKeys cleanNetworkConfigs cleanSourceFiles cleanLogs cleanCloudInit cleanRuntimeFiles exit 0 ================================================ FILE: dnsmasq/almalinux/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service yum install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # Configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v2.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq return 0 } function uninstall { # Stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # Uninstall packages yum remove -y dnsmasq return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/amzn/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service yum install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v3.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq # backup the original resolv.conf and dhclient scripts [[ ! -e "${BACKUPS_DIR}/etc/resolv.conf" ]] && { mkdir -p ${BACKUPS_DIR}/{etc/sysconfig/network-scripts/,usr/sbin/} cp -df /etc/resolv.conf ${BACKUPS_DIR}/etc/resolv.conf cp -dfr /etc/sysconfig/network-scripts/. ${BACKUPS_DIR}/etc/sysconfig/network-scripts/ cp -f /usr/sbin/dhclient-script ${BACKUPS_DIR}/usr/sbin/ } # make dnsmasq the DNS provider rm -f /etc/resolv.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolv.conf /etc/resolv.conf # make all the dhclient scripts use a different resolv.conf location sed -i 's%/etc/resolv\.conf%/var/lib/dhclient/resolv.conf%g' /usr/sbin/dhclient-script find /etc/sysconfig/network-scripts/ -type f -exec sed -i 's%/etc/resolv\.conf%/var/lib/dhclient/resolv.conf%g' {} + # make sure the dhclient resolv.conf is recreated in the new location dhclient -r && dhclient # tell dnsmasq to grab dynamic DNS servers from dhclient export DNSMASQ_RESOLV_FILE="/var/lib/dhclient/resolv.conf" envsubst <${DSIP_PROJECT_DIR}/dnsmasq/configs/dnsmasq_sh.conf >/etc/dnsmasq.conf return 0 } function uninstall { # stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # uninstall packages yum remove -y dnsmasq # restore dhclient scripts cp -dfr ${BACKUPS_DIR}/etc/sysconfig/network-scripts/. /etc/sysconfig/network-scripts/ cp -f ${BACKUPS_DIR}/usr/sbin/dhclient-script /usr/sbin/dhclient-script # restore original resolv.conf cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf # update resolv.conf if needed dhclient -r && dhclient # cleanup backup files rm -rf ${BACKUPS_DIR}/etc/sysconfig/network-scripts/ rm -f ${BACKUPS_DIR}/{etc/resolv.conf,usr/sbin/dhclient-script} return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/centos/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service if (( ${DISTRO_VER} >= 8 )); then dnf install -y dnsmasq else yum install -y dnsmasq fi if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # configure dnsmasq systemd service if (( ${DISTRO_VER} > 7 )); then cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v2.service /lib/systemd/system/dnsmasq.service else cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v3.service /lib/systemd/system/dnsmasq.service fi chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq # backup the original resolv.conf [[ ! -e "${BACKUPS_DIR}/etc/resolv.conf" ]] && { mkdir -p ${BACKUPS_DIR}/etc/ cp -df /etc/resolv.conf ${BACKUPS_DIR}/etc/resolv.conf } # make dnsmasq the DNS provider # centos uses a static resolv.conf by default, which dnsmasq will use for its upstream DNS servers [[ ! -e /etc/dnsmasq_resolv.conf ]] && { cp -df /etc/resolv.conf /etc/dnsmasq_resolv.conf } rm -f /etc/resolv.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolv.conf /etc/resolv.conf # tell NetworkManager we will manage the DNS servers mkdir -p /etc/NetworkManager/conf.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/networkmanager/dsiprouter.conf /etc/NetworkManager/conf.d/99-dsiprouter.conf # make sure the NetworkManager resolv.conf is recreated with the new configuration options systemctl restart NetworkManager # tell dnsmasq to grab dynamic DNS servers from dhclient export DNSMASQ_RESOLV_FILE="/etc/dnsmasq_resolv.conf" envsubst <${DSIP_PROJECT_DIR}/dnsmasq/configs/dnsmasq_sh.conf >/etc/dnsmasq.conf return 0 } function uninstall { # stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # uninstall packages if (( ${DISTRO_VER} >= 8 )); then dnf remove -y dnsmasq else yum remove -y dnsmasq fi # remove our NetworkManager configurations rm -f /etc/NetworkManager/conf.d/99-dsiprouter.conf # restore original resolv.conf cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf # update resolv.conf / restart NetworkManager with new configs systemctl restart NetworkManager # cleanup backup files rm -f ${BACKUPS_DIR}/etc/resolv.conf return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/configs/dnsmasq_sh.conf ================================================ # Configuration file for dnsmasq. # # Format is one option per line, legal options are the same # as the long options legal on the command line. See # "/usr/sbin/dnsmasq --help" or "man 8 dnsmasq" for details. # Listen on this specific port instead of the standard DNS port # (53). Setting this to zero completely disables DNS function, # leaving only DHCP and/or TFTP. port=53 # The following two options make you a better netizen, since they # tell dnsmasq to filter out queries which the public DNS cannot # answer, and which load the servers (especially the root servers) # unnecessarily. If you have a dial-on-demand link they also stop # these requests from bringing up the link unnecessarily. # Never forward plain names (without a dot or domain part) domain-needed # Never forward addresses in the non-routed address spaces. bogus-priv # Uncomment these to enable DNSSEC validation and caching: # (Requires dnsmasq to be built with DNSSEC option.) #conf-file=%%PREFIX%%/share/dnsmasq/trust-anchors.conf #dnssec # Replies which are not DNSSEC signed may be legitimate, because the domain # is unsigned, or may be forgeries. Setting this option tells dnsmasq to # check that an unsigned reply is OK, by finding a secure proof that a DS # record somewhere between the root and the domain does not exist. # The cost of setting this is that even queries in unsigned domains will need # one or more extra DNS queries to verify. #dnssec-check-unsigned # Uncomment this to filter useless windows-originated DNS requests # which can trigger dial-on-demand links needlessly. # Note that (amongst other things) this blocks all SRV requests, # so don't use it if you use eg Kerberos, SIP, XMMP or Google-talk. # This option only affects forwarding, SRV records originating for # dnsmasq (via srv-host= lines) are not suppressed by it. #filterwin2k # Change this line if you want dns to get its upstream servers from # somewhere other that /etc/resolv.conf resolv-file=${DNSMASQ_RESOLV_FILE} # By default, dnsmasq will send queries to any of the upstream # servers it knows about and tries to favour servers to are known # to be up. Uncommenting this forces dnsmasq to try each query # with each server strictly in the order they appear in # /etc/resolv.conf strict-order # If you don't want dnsmasq to read /etc/resolv.conf or any other # file, getting its servers from this file instead (see below), then # uncomment this. #no-resolv # If you don't want dnsmasq to poll /etc/resolv.conf or other resolv # files for changes and re-read them then uncomment this. #no-poll # Add other name servers here, with domain specs if they are for # non-public domains. #server=/localnet/192.168.0.1 # Example of routing PTR queries to nameservers: this will send all # address->name queries for 192.168.3/24 to nameserver 10.1.2.3 #server=/3.168.192.in-addr.arpa/10.1.2.3 # Add local-only domains here, queries in these domains are answered # from /etc/hosts or DHCP only. #local=/localnet/ # Add domains which you want to force to an IP address here. # The example below send any host in double-click.net to a local # web-server. #address=/double-click.net/127.0.0.1 # --address (and --server) work with IPv6 addresses too. #address=/www.thekelleys.org.uk/fe80::20d:60ff:fe36:f83 # Add the IPs of all queries to yahoo.com, google.com, and their # subdomains to the vpn and search ipsets: #ipset=/yahoo.com/google.com/vpn,search # Add the IPs of all queries to yahoo.com, google.com, and their # subdomains to netfilters sets, which is equivalent to # 'nft add element ip test vpn { ... }; nft add element ip test search { ... }' #nftset=/yahoo.com/google.com/ip#test#vpn,ip#test#search # Use netfilters sets for both IPv4 and IPv6: # This adds all addresses in *.yahoo.com to vpn4 and vpn6 for IPv4 and IPv6 addresses. #nftset=/yahoo.com/4#ip#test#vpn4 #nftset=/yahoo.com/6#ip#test#vpn6 # You can control how dnsmasq talks to a server: this forces # queries to 10.1.2.3 to be routed via eth1 # server=10.1.2.3@eth1 # and this sets the source (ie local) address used to talk to # 10.1.2.3 to 192.168.1.1 port 55 (there must be an interface with that # IP on the machine, obviously). # server=10.1.2.3@192.168.1.1#55 # If you want dnsmasq to change uid and gid to something other # than the default, edit the following lines. user=dnsmasq group=dnsmasq # Where dnsmasq will write it's process id. pid-file=/run/dnsmasq/dnsmasq.pid # If you want dnsmasq to listen for DHCP and DNS requests only on # specified interfaces (and the loopback) give the name of the # interface (eg eth0) here. # Repeat the line for more than one interface. interface=lo # Or you can specify which interface _not_ to listen on #except-interface= # Or which to listen on by address (remember to include 127.0.0.1 if # you use this.) #listen-address= # If you want dnsmasq to provide only DNS service on an interface, # configure it as shown above, and then use the following line to # disable DHCP and TFTP on it. #no-dhcp-interface= # On systems which support it, dnsmasq binds the wildcard address, # even when it is listening on only some interfaces. It then discards # requests that it shouldn't reply to. This has the advantage of # working even when interfaces come and go and change address. If you # want dnsmasq to really bind only the interfaces it is listening on, # uncomment this option. About the only time you may need this is when # running another nameserver on the same machine. bind-interfaces # If you don't want dnsmasq to read /etc/hosts, uncomment the # following line. #no-hosts # or if you want it to read another file, as well as /etc/hosts, use # this. #addn-hosts=/etc/banner_add_hosts # Set this (and domain: see below) if you want to have a domain # automatically added to simple names in a hosts-file. #expand-hosts # Set the domain for dnsmasq. this is optional, but if it is set, it # does the following things. # 1) Allows DHCP hosts to have fully qualified domain names, as long # as the domain part matches this setting. # 2) Sets the "domain" DHCP option thereby potentially setting the # domain of all systems configured by DHCP # 3) Provides the domain part for "expand-hosts" #domain=thekelleys.org.uk # Set a different domain for a particular subnet #domain=wireless.thekelleys.org.uk,192.168.2.0/24 # Same idea, but range rather then subnet #domain=reserved.thekelleys.org.uk,192.68.3.100,192.168.3.200 # Uncomment this to enable the integrated DHCP server, you need # to supply the range of addresses available for lease and optionally # a lease time. If you have more than one network, you will need to # repeat this for each network on which you want to supply DHCP # service. #dhcp-range=192.168.0.50,192.168.0.150,12h # This is an example of a DHCP range where the netmask is given. This # is needed for networks we reach the dnsmasq DHCP server via a relay # agent. If you don't know what a DHCP relay agent is, you probably # don't need to worry about this. #dhcp-range=192.168.0.50,192.168.0.150,255.255.255.0,12h # This is an example of a DHCP range which sets a tag, so that # some DHCP options may be set only for this network. #dhcp-range=set:red,192.168.0.50,192.168.0.150 # Use this DHCP range only when the tag "green" is set. #dhcp-range=tag:green,192.168.0.50,192.168.0.150,12h # Specify a subnet which can't be used for dynamic address allocation, # is available for hosts with matching --dhcp-host lines. Note that # dhcp-host declarations will be ignored unless there is a dhcp-range # of some type for the subnet in question. # In this case the netmask is implied (it comes from the network # configuration on the machine running dnsmasq) it is possible to give # an explicit netmask instead. #dhcp-range=192.168.0.0,static # Enable DHCPv6. Note that the prefix-length does not need to be specified # and defaults to 64 if missing/ #dhcp-range=1234::2, 1234::500, 64, 12h # Do Router Advertisements, BUT NOT DHCP for this subnet. #dhcp-range=1234::, ra-only # Do Router Advertisements, BUT NOT DHCP for this subnet, also try and # add names to the DNS for the IPv6 address of SLAAC-configured dual-stack # hosts. Use the DHCPv4 lease to derive the name, network segment and # MAC address and assume that the host will also have an # IPv6 address calculated using the SLAAC algorithm. #dhcp-range=1234::, ra-names # Do Router Advertisements, BUT NOT DHCP for this subnet. # Set the lifetime to 46 hours. (Note: minimum lifetime is 2 hours.) #dhcp-range=1234::, ra-only, 48h # Do DHCP and Router Advertisements for this subnet. Set the A bit in the RA # so that clients can use SLAAC addresses as well as DHCP ones. #dhcp-range=1234::2, 1234::500, slaac # Do Router Advertisements and stateless DHCP for this subnet. Clients will # not get addresses from DHCP, but they will get other configuration information. # They will use SLAAC for addresses. #dhcp-range=1234::, ra-stateless # Do stateless DHCP, SLAAC, and generate DNS names for SLAAC addresses # from DHCPv4 leases. #dhcp-range=1234::, ra-stateless, ra-names # Do router advertisements for all subnets where we're doing DHCPv6 # Unless overridden by ra-stateless, ra-names, et al, the router # advertisements will have the M and O bits set, so that the clients # get addresses and configuration from DHCPv6, and the A bit reset, so the # clients don't use SLAAC addresses. #enable-ra # Supply parameters for specified hosts using DHCP. There are lots # of valid alternatives, so we will give examples of each. Note that # IP addresses DO NOT have to be in the range given above, they just # need to be on the same network. The order of the parameters in these # do not matter, it's permissible to give name, address and MAC in any # order. # Always allocate the host with Ethernet address 11:22:33:44:55:66 # The IP address 192.168.0.60 #dhcp-host=11:22:33:44:55:66,192.168.0.60 # Always set the name of the host with hardware address # 11:22:33:44:55:66 to be "fred" #dhcp-host=11:22:33:44:55:66,fred # Always give the host with Ethernet address 11:22:33:44:55:66 # the name fred and IP address 192.168.0.60 and lease time 45 minutes #dhcp-host=11:22:33:44:55:66,fred,192.168.0.60,45m # Give a host with Ethernet address 11:22:33:44:55:66 or # 12:34:56:78:90:12 the IP address 192.168.0.60. Dnsmasq will assume # that these two Ethernet interfaces will never be in use at the same # time, and give the IP address to the second, even if it is already # in use by the first. Useful for laptops with wired and wireless # addresses. #dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.60 # Give the machine which says its name is "bert" IP address # 192.168.0.70 and an infinite lease #dhcp-host=bert,192.168.0.70,infinite # Always give the host with client identifier 01:02:02:04 # the IP address 192.168.0.60 #dhcp-host=id:01:02:02:04,192.168.0.60 # Always give the InfiniBand interface with hardware address # 80:00:00:48:fe:80:00:00:00:00:00:00:f4:52:14:03:00:28:05:81 the # ip address 192.168.0.61. The client id is derived from the prefix # ff:00:00:00:00:00:02:00:00:02:c9:00 and the last 8 pairs of # hex digits of the hardware address. #dhcp-host=id:ff:00:00:00:00:00:02:00:00:02:c9:00:f4:52:14:03:00:28:05:81,192.168.0.61 # Always give the host with client identifier "marjorie" # the IP address 192.168.0.60 #dhcp-host=id:marjorie,192.168.0.60 # Enable the address given for "judge" in /etc/hosts # to be given to a machine presenting the name "judge" when # it asks for a DHCP lease. #dhcp-host=judge # Never offer DHCP service to a machine whose Ethernet # address is 11:22:33:44:55:66 #dhcp-host=11:22:33:44:55:66,ignore # Ignore any client-id presented by the machine with Ethernet # address 11:22:33:44:55:66. This is useful to prevent a machine # being treated differently when running under different OS's or # between PXE boot and OS boot. #dhcp-host=11:22:33:44:55:66,id:* # Send extra options which are tagged as "red" to # the machine with Ethernet address 11:22:33:44:55:66 #dhcp-host=11:22:33:44:55:66,set:red # Send extra options which are tagged as "red" to # any machine with Ethernet address starting 11:22:33: #dhcp-host=11:22:33:*:*:*,set:red # Give a fixed IPv6 address and name to client with # DUID 00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2 # Note the MAC addresses CANNOT be used to identify DHCPv6 clients. # Note also that the [] around the IPv6 address are obligatory. #dhcp-host=id:00:01:00:01:16:d2:83:fc:92:d4:19:e2:d8:b2, fred, [1234::5] # Ignore any clients which are not specified in dhcp-host lines # or /etc/ethers. Equivalent to ISC "deny unknown-clients". # This relies on the special "known" tag which is set when # a host is matched. #dhcp-ignore=tag:!known # Send extra options which are tagged as "red" to any machine whose # DHCP vendorclass string includes the substring "Linux" #dhcp-vendorclass=set:red,Linux # Send extra options which are tagged as "red" to any machine one # of whose DHCP userclass strings includes the substring "accounts" #dhcp-userclass=set:red,accounts # Send extra options which are tagged as "red" to any machine whose # MAC address matches the pattern. #dhcp-mac=set:red,00:60:8C:*:*:* # If this line is uncommented, dnsmasq will read /etc/ethers and act # on the ethernet-address/IP pairs found there just as if they had # been given as --dhcp-host options. Useful if you keep # MAC-address/host mappings there for other purposes. #read-ethers # Send options to hosts which ask for a DHCP lease. # See RFC 2132 for details of available options. # Common options can be given to dnsmasq by name: # run "dnsmasq --help dhcp" to get a list. # Note that all the common settings, such as netmask and # broadcast address, DNS server and default route, are given # sane defaults by dnsmasq. You very likely will not need # any dhcp-options. If you use Windows clients and Samba, there # are some options which are recommended, they are detailed at the # end of this section. # Override the default route supplied by dnsmasq, which assumes the # router is the same machine as the one running dnsmasq. #dhcp-option=3,1.2.3.4 # Do the same thing, but using the option name #dhcp-option=option:router,1.2.3.4 # Override the default route supplied by dnsmasq and send no default # route at all. Note that this only works for the options sent by # default (1, 3, 6, 12, 28) the same line will send a zero-length option # for all other option numbers. #dhcp-option=3 # Set the NTP time server addresses to 192.168.0.4 and 10.10.0.5 #dhcp-option=option:ntp-server,192.168.0.4,10.10.0.5 # Send DHCPv6 option. Note [] around IPv6 addresses. #dhcp-option=option6:dns-server,[1234::77],[1234::88] # Send DHCPv6 option for namservers as the machine running # dnsmasq and another. #dhcp-option=option6:dns-server,[::],[1234::88] # Ask client to poll for option changes every six hours. (RFC4242) #dhcp-option=option6:information-refresh-time,6h # Set option 58 client renewal time (T1). Defaults to half of the # lease time if not specified. (RFC2132) #dhcp-option=option:T1,1m # Set option 59 rebinding time (T2). Defaults to 7/8 of the # lease time if not specified. (RFC2132) #dhcp-option=option:T2,2m # Set the NTP time server address to be the same machine as # is running dnsmasq #dhcp-option=42,0.0.0.0 # Set the NIS domain name to "welly" #dhcp-option=40,welly # Set the default time-to-live to 50 #dhcp-option=23,50 # Set the "all subnets are local" flag #dhcp-option=27,1 # Send the etherboot magic flag and then etherboot options (a string). #dhcp-option=128,e4:45:74:68:00:00 #dhcp-option=129,NIC=eepro100 # Specify an option which will only be sent to the "red" network # (see dhcp-range for the declaration of the "red" network) # Note that the tag: part must precede the option: part. #dhcp-option = tag:red, option:ntp-server, 192.168.1.1 # The following DHCP options set up dnsmasq in the same way as is specified # for the ISC dhcpcd in # http://www.samba.org/samba/ftp/docs/textdocs/DHCP-Server-Configuration.txt # adapted for a typical dnsmasq installation where the host running # dnsmasq is also the host running samba. # you may want to uncomment some or all of them if you use # Windows clients and Samba. #dhcp-option=19,0 # option ip-forwarding off #dhcp-option=44,0.0.0.0 # set netbios-over-TCP/IP nameserver(s) aka WINS server(s) #dhcp-option=45,0.0.0.0 # netbios datagram distribution server #dhcp-option=46,8 # netbios node type # Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave. #dhcp-option=252,"\n" # Send RFC-3397 DNS domain search DHCP option. WARNING: Your DHCP client # probably doesn't support this...... #dhcp-option=option:domain-search,eng.apple.com,marketing.apple.com # Send RFC-3442 classless static routes (note the netmask encoding) #dhcp-option=121,192.168.1.0/24,1.2.3.4,10.0.0.0/8,5.6.7.8 # Send vendor-class specific options encapsulated in DHCP option 43. # The meaning of the options is defined by the vendor-class so # options are sent only when the client supplied vendor class # matches the class given here. (A substring match is OK, so "MSFT" # matches "MSFT" and "MSFT 5.0"). This example sets the # mtftp address to 0.0.0.0 for PXEClients. #dhcp-option=vendor:PXEClient,1,0.0.0.0 # Send microsoft-specific option to tell windows to release the DHCP lease # when it shuts down. Note the "i" flag, to tell dnsmasq to send the # value as a four-byte integer - that's what microsoft wants. See # http://technet2.microsoft.com/WindowsServer/en/library/a70f1bb7-d2d4-49f0-96d6-4b7414ecfaae1033.mspx?mfr=true #dhcp-option=vendor:MSFT,2,1i # Send the Encapsulated-vendor-class ID needed by some configurations of # Etherboot to allow is to recognise the DHCP server. #dhcp-option=vendor:Etherboot,60,"Etherboot" # Send options to PXELinux. Note that we need to send the options even # though they don't appear in the parameter request list, so we need # to use dhcp-option-force here. # See http://syslinux.zytor.com/pxe.php#special for details. # Magic number - needed before anything else is recognised #dhcp-option-force=208,f1:00:74:7e # Configuration file name #dhcp-option-force=209,configs/common # Path prefix #dhcp-option-force=210,/tftpboot/pxelinux/files/ # Reboot time. (Note 'i' to send 32-bit value) #dhcp-option-force=211,30i # Set the boot filename for netboot/PXE. You will only need # this if you want to boot machines over the network and you will need # a TFTP server; either dnsmasq's built-in TFTP server or an # external one. (See below for how to enable the TFTP server.) #dhcp-boot=pxelinux.0 # The same as above, but use custom tftp-server instead machine running dnsmasq #dhcp-boot=pxelinux,server.name,192.168.1.100 # Boot for iPXE. The idea is to send two different # filenames, the first loads iPXE, and the second tells iPXE what to # load. The dhcp-match sets the ipxe tag for requests from iPXE. #dhcp-boot=undionly.kpxe #dhcp-match=set:ipxe,175 # iPXE sends a 175 option. #dhcp-boot=tag:ipxe,http://boot.ipxe.org/demo/boot.php # Encapsulated options for iPXE. All the options are # encapsulated within option 175 #dhcp-option=encap:175, 1, 5b # priority code #dhcp-option=encap:175, 176, 1b # no-proxydhcp #dhcp-option=encap:175, 177, string # bus-id #dhcp-option=encap:175, 189, 1b # BIOS drive code #dhcp-option=encap:175, 190, user # iSCSI username #dhcp-option=encap:175, 191, pass # iSCSI password # Test for the architecture of a netboot client. PXE clients are # supposed to send their architecture as option 93. (See RFC 4578) #dhcp-match=peecees, option:client-arch, 0 #x86-32 #dhcp-match=itanics, option:client-arch, 2 #IA64 #dhcp-match=hammers, option:client-arch, 6 #x86-64 #dhcp-match=mactels, option:client-arch, 7 #EFI x86-64 # Do real PXE, rather than just booting a single file, this is an # alternative to dhcp-boot. #pxe-prompt="What system shall I netboot?" # or with timeout before first available action is taken: #pxe-prompt="Press F8 for menu.", 60 # Available boot services. for PXE. #pxe-service=x86PC, "Boot from local disk" # Loads /pxelinux.0 from dnsmasq TFTP server. #pxe-service=x86PC, "Install Linux", pxelinux # Loads /pxelinux.0 from TFTP server at 1.2.3.4. # Beware this fails on old PXE ROMS. #pxe-service=x86PC, "Install Linux", pxelinux, 1.2.3.4 # Use bootserver on network, found my multicast or broadcast. #pxe-service=x86PC, "Install windows from RIS server", 1 # Use bootserver at a known IP address. #pxe-service=x86PC, "Install windows from RIS server", 1, 1.2.3.4 # If you have multicast-FTP available, # information for that can be passed in a similar way using options 1 # to 5. See page 19 of # http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf # Enable dnsmasq's built-in TFTP server #enable-tftp # Set the root directory for files available via FTP. #tftp-root=/var/ftpd # Do not abort if the tftp-root is unavailable #tftp-no-fail # Make the TFTP server more secure: with this set, only files owned by # the user dnsmasq is running as will be send over the net. #tftp-secure # This option stops dnsmasq from negotiating a larger blocksize for TFTP # transfers. It will slow things down, but may rescue some broken TFTP # clients. #tftp-no-blocksize # Set the boot file name only when the "red" tag is set. #dhcp-boot=tag:red,pxelinux.red-net # An example of dhcp-boot with an external TFTP server: the name and IP # address of the server are given after the filename. # Can fail with old PXE ROMS. Overridden by --pxe-service. #dhcp-boot=/var/ftpd/pxelinux.0,boothost,192.168.0.3 # If there are multiple external tftp servers having a same name # (using /etc/hosts) then that name can be specified as the # tftp_servername (the third option to dhcp-boot) and in that # case dnsmasq resolves this name and returns the resultant IP # addresses in round robin fashion. This facility can be used to # load balance the tftp load among a set of servers. #dhcp-boot=/var/ftpd/pxelinux.0,boothost,tftp_server_name # Set the limit on DHCP leases, the default is 150 #dhcp-lease-max=150 # The DHCP server needs somewhere on disk to keep its lease database. # This defaults to a sane location, but if you want to change it, use # the line below. #dhcp-leasefile=/var/lib/misc/dnsmasq.leases # Set the DHCP server to authoritative mode. In this mode it will barge in # and take over the lease for any client which broadcasts on the network, # whether it has a record of the lease or not. This avoids long timeouts # when a machine wakes up on a new network. DO NOT enable this if there's # the slightest chance that you might end up accidentally configuring a DHCP # server for your campus/company accidentally. The ISC server uses # the same option, and this URL provides more information: # http://www.isc.org/files/auth.html #dhcp-authoritative # Set the DHCP server to enable DHCPv4 Rapid Commit Option per RFC 4039. # In this mode it will respond to a DHCPDISCOVER message including a Rapid Commit # option with a DHCPACK including a Rapid Commit option and fully committed address # and configuration information. This must only be enabled if either the server is # the only server for the subnet, or multiple servers are present and they each # commit a binding for all clients. #dhcp-rapid-commit # Run an executable when a DHCP lease is created or destroyed. # The arguments sent to the script are "add" or "del", # then the MAC address, the IP address and finally the hostname # if there is one. #dhcp-script=/bin/echo # Set the cachesize here. #cache-size=150 # If you want to disable negative caching, uncomment this. #no-negcache # Normally responses which come from /etc/hosts and the DHCP lease # file have Time-To-Live set as zero, which conventionally means # do not cache further. If you are happy to trade lower load on the # server for potentially stale date, you can set a time-to-live (in # seconds) here. #local-ttl= # If you want dnsmasq to detect attempts by Verisign to send queries # to unregistered .com and .net hosts to its sitefinder service and # have dnsmasq instead return the correct NXDOMAIN response, uncomment # this line. You can add similar lines to do the same for other # registries which have implemented wildcard A records. #bogus-nxdomain=64.94.110.11 # If you want to fix up DNS results from upstream servers, use the # alias option. This only works for IPv4. # This alias makes a result of 1.2.3.4 appear as 5.6.7.8 #alias=1.2.3.4,5.6.7.8 # and this maps 1.2.3.x to 5.6.7.x #alias=1.2.3.0,5.6.7.0,255.255.255.0 # and this maps 192.168.0.10->192.168.0.40 to 10.0.0.10->10.0.0.40 #alias=192.168.0.10-192.168.0.40,10.0.0.0,255.255.255.0 # Change these lines if you want dnsmasq to serve MX records. # Return an MX record named "maildomain.com" with target # servermachine.com and preference 50 #mx-host=maildomain.com,servermachine.com,50 # Set the default target for MX records created using the localmx option. #mx-target=servermachine.com # Return an MX record pointing to the mx-target for all local # machines. #localmx # Return an MX record pointing to itself for all local machines. #selfmx # Change the following lines if you want dnsmasq to serve SRV # records. These are useful if you want to serve ldap requests for # Active Directory and other windows-originated DNS requests. # See RFC 2782. # You may add multiple srv-host lines. # The fields are ,,,, # If the domain part if missing from the name (so that is just has the # service and protocol sections) then the domain given by the domain= # config option is used. (Note that expand-hosts does not need to be # set for this to work.) # A SRV record sending LDAP for the example.com domain to # ldapserver.example.com port 389 #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389 # A SRV record sending LDAP for the example.com domain to # ldapserver.example.com port 389 (using domain=) #domain=example.com #srv-host=_ldap._tcp,ldapserver.example.com,389 # Two SRV records for LDAP, each with different priorities #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,1 #srv-host=_ldap._tcp.example.com,ldapserver.example.com,389,2 # A SRV record indicating that there is no LDAP server for the domain # example.com #srv-host=_ldap._tcp.example.com # The following line shows how to make dnsmasq serve an arbitrary PTR # record. This is useful for DNS-SD. (Note that the # domain-name expansion done for SRV records _does_not # occur for PTR records.) #ptr-record=_http._tcp.dns-sd-services,"New Employee Page._http._tcp.dns-sd-services" # Change the following lines to enable dnsmasq to serve TXT records. # These are used for things like SPF and zeroconf. (Note that the # domain-name expansion done for SRV records _does_not # occur for TXT records.) #Example SPF. #txt-record=example.com,"v=spf1 a -all" #Example zeroconf #txt-record=_http._tcp.example.com,name=value,paper=A4 # Provide an alias for a "local" DNS name. Note that this _only_ works # for targets which are names from DHCP or /etc/hosts. Give host # "bert" another name, bertrand #cname=bertand,bert # For debugging purposes, log each DNS query as it passes through # dnsmasq. #log-queries # Log lots of extra information about DHCP transactions. #log-dhcp # Include another lot of configuration options. #conf-file=/etc/dnsmasq.more.conf #conf-dir=/etc/dnsmasq.d # Include all the files in a directory except those ending in .bak #conf-dir=/etc/dnsmasq.d,.bak # Include all files in a directory which end in .conf #conf-dir=/etc/dnsmasq.d/,*.conf # If a DHCP client claims that its name is "wpad", ignore that. # This fixes a security hole. see CERT Vulnerability VU#598349 #dhcp-name-match=set:wpad-ignore,wpad #dhcp-ignore-names=tag:wpad-ignore ================================================ FILE: dnsmasq/configs/ifupdown/default.conf ================================================ # Configuration for networking init script being run during # the boot sequence # Set to 'no' to skip interfaces configuration on boot #CONFIGURE_INTERFACES=yes # Don't configure these interfaces. Shell wildcards supported/ #EXCLUDE_INTERFACES= # Set to 'yes' to enable additional verbosity #VERBOSE=no # Method to wait for the network to become online, # for services that depend on a working network: # - ifup: wait for ifup to have configured an interface. # - route: wait for a route to a given address to appear. # - ping/ping6: wait for a host to respond to ping packets. # - none: don't wait. #WAIT_ONLINE_METHOD=ifup # Which interface to wait for. # If none given, wait for all auto interfaces, or if there are none, # wait for at least one hotplug interface. #WAIT_ONLINE_IFACE= # Which address to wait for for route, ping and ping6 methods. # If none is given for route, it waits for a default gateway. #WAIT_ONLINE_ADDRESS= # Timeout in seconds for waiting for the network to come online. WAIT_ONLINE_TIMEOUT=30 ================================================ FILE: dnsmasq/configs/ifupdown/networking-pre.sh ================================================ #!/bin/sh mkdir -p /run/network/ rm -f /run/network/ifupdown.conf 2>/dev/null # check whether nmcli reports that network manager is managing the interface # ref: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/libnmc-base/nm-client-utils.c?ref_type=heads#L288 NM_IFACES=$(nmcli -t -m tabular device status | awk -F ':' '$3 !~ /unmanaged|[^ ]+ \(externally\)/ {print $1}') # check whether networkctl reports that systemd-networkd is manaing the interface # ref: https://github.com/systemd/systemd/blob/e603a438a7918a2fcc35d7683fd755d3837b7024/src/network/networkd-link.c#L2918 SN_IFACES=$(networkctl list --json=short | jq -r -e '.Interfaces[] | select(.AdministrativeState!="unmanaged").Name') # blacklist those managed interfaces from being managed by ifupdown EXCLUSIONS='' for IFACE in $NM_IFACES $SN_IFACES; do EXCLUSIONS="$EXCLUSIONS $IFACE ${IFACE}:*" done [ -n "$EXCLUSIONS" ] && echo "EXCLUDE_INTERFACES='$EXCLUSIONS'" >/run/network/ifupdown.conf exit 0 ================================================ FILE: dnsmasq/configs/ifupdown/override.conf ================================================ [Unit] After=systemd-networkd.service [Service] EnvironmentFile=-/run/network/ifupdown.conf ExecStartPre=/usr/lib/ifupdown/networking-pre #ExecStartPre=/bin/bash -c '[ ! -e /run/network/ifclean ] && { ifdown -f -a --read-environment -X lo; touch /run/network/restart-hotplug /run/network/ifclean; true; } || true' ================================================ FILE: dnsmasq/configs/networkmanager/dsiprouter.conf ================================================ # dSIPRouter does not utilize the NetworkManager dnsmasq plugin. # Instead DNSmasq is managed as a separate service that pulls DNS servers # from the upstream DNS provider (NetworkManager/dhclient/systemd-resolved). # Currently the only plugin utilized by dSIPRouter is the keyfile plugin. # The ifupdown plugin should not be loaded here as it is loaded via the # systemd service files with interfaces conditionally managed.. # Note: the "no-auto-default" setting tells network manager not to automatically # create profiles for connected ethernet devices, only allowing explicit profiles. # Ref: https://people.freedesktop.org/~lkundrak/nm-docs/NetworkManager.conf.html [main] dns=none no-auto-default=* plugins=keyfile [keyfile] unmanaged-devices=interface-name:docker* ================================================ FILE: dnsmasq/configs/networkmanager/wait-override.conf ================================================ [Service] Environment= Environment=NM_ONLINE_TIMEOUT=30 ================================================ FILE: dnsmasq/configs/resolv.conf ================================================ # DNS servers are being managed by dSIPRouter dynamically, DO NOT CHANGE THIS FILE # # The DNSmasq stub resolver will handle lookups in the following order: # 1. entries from /etc/hosts # 2. the system provided DNS servers (via DHCP) # # To change the upstream DNS servers DNSmasq forwards to update resolv-file in # /etc/dnsmasq.conf to your resolve file of choice. # For more information on configuring DNSmasq refer to dnsmasq(8) manpage. # nameserver 127.0.0.1 #####DSIP_CONFIG_START #####DSIP_CONFIG_END ================================================ FILE: dnsmasq/configs/resolvconf_def ================================================ REPORT_ABSENT_SYMLINK=no TRUNCATE_NAMESERVER_LIST_AFTER_LOOPBACK_ADDRESS=yes ================================================ FILE: dnsmasq/configs/resolvconf_upd ================================================ #!/bin/sh # # Script to update the resolver list for dnsmasq # # N.B. Resolvconf may run us even if dnsmasq is not (yet) running. # If dnsmasq is installed then we go ahead and update the resolver list # in case dnsmasq is started later. # # Assumption: On entry, PWD contains the resolv.conf-type files. # # This is a modified version of the file from the dnsmasq package. # set -e RUN_DIR="/run/dnsmasq" RSLVRLIST_FILE="${RUN_DIR}/resolv.conf" TMP_FILE="${RSLVRLIST_FILE}_new.$$" MY_NAME_FOR_RESOLVCONF="dnsmasq" RESOLVCONF_GEN_FILE="/run/resolvconf/resolv.conf" [ -x /usr/sbin/dnsmasq ] || exit 0 [ -x /lib/resolvconf/list-records ] || exit 1 PATH=/bin:/sbin report_err() { echo "$0: Error: $*" >&2 ; } # Stores arguments (minus duplicates) in RSLT, separated by spaces # Doesn't work properly if an argument itself contains whitespace uniquify() { RSLT="" while [ "$1" ] ; do for E in $RSLT ; do [ "$1" = "$E" ] && { shift ; continue 2 ; } done RSLT="${RSLT:+$RSLT }$1" shift done } filterdnsmasq() { while read ADDR; do for DNSMASQ_ADDR in $@; do [ "x$ADDR" = "x$DNSMASQ_ADDR" ] && continue 2 done echo "$ADDR" done } if [ ! -d "$RUN_DIR" ] && ! mkdir --parents --mode=0755 "$RUN_DIR" ; then report_err "Failed trying to create directory $RUN_DIR" exit 1 fi RSLVCNFFILES="$RESOLVCONF_GEN_FILE" for F in $(/lib/resolvconf/list-records) ; do case "$F" in "lo.$MY_NAME_FOR_RESOLVCONF") DNSMASQ_ADDRS="$(sed -n -e 's/^[[:space:]]*nameserver[[:space:]]\+//p' lo.$MY_NAME_FOR_RESOLVCONF)" ;; *) RSLVCNFFILES="${RSLVCNFFILES:+$RSLVCNFFILES }$F" ;; esac done NMSRVRS="" if [ "$RSLVCNFFILES" ] ; then uniquify $( sed -n -e 's/^[[:space:]]*nameserver[[:space:]]\+//p' $RSLVCNFFILES | filterdnsmasq $DNSMASQ_ADDRS ) NMSRVRS="$RSLT" fi # Dnsmasq uses the mtime of $RSLVRLIST_FILE, with a resolution of one second, # to detect changes in the file. This means that if a resolvconf update occurs # within one second of the previous one then dnsmasq may fail to notice the # more recent change. To work around this problem we sleep one second here # if necessary in order to ensure that the new mtime is different. if [ -f "$RSLVRLIST_FILE" ] && [ "$(ls -go --time-style='+%s' "$RSLVRLIST_FILE" | { read p h s t n ; echo "$t" ; })" = "$(date +%s)" ] ; then sleep 1 fi clean_up() { rm -f "$TMP_FILE" ; } trap clean_up EXIT : >| "$TMP_FILE" for N in $NMSRVRS ; do echo "nameserver $N" >> "$TMP_FILE" ; done mv -f "$TMP_FILE" "$RSLVRLIST_FILE" ================================================ FILE: dnsmasq/configs/systemdnetworkd/docker.network ================================================ [Match] Name=docker* [Link] Unmanaged=yes ================================================ FILE: dnsmasq/configs/systemdnetworkd/dsiprouter.network ================================================ [Match] Name=* [Network] DHCP=yes [DHCP] UseDNS=true ================================================ FILE: dnsmasq/configs/systemdnetworkd/networkd-pre.sh ================================================ #!/bin/sh mkdir -p /run/systemd/network/ rm -f /run/systemd/network/00-nm_managed-*.network 2>/dev/null # check whether nmcli reports that network manager is managing the interface # ref: https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/blob/main/src/libnmc-base/nm-client-utils.c?ref_type=heads#L288 # blacklist those managed interfaces from being managed by systemd-networkd for IFACE in $(nmcli -t -m tabular device status | awk -F ':' '$3 !~ /unmanaged|[^ ]+ \(externally\)/ {print $1}'); do cat <"/run/systemd/network/00-nm_managed-$IFACE.network" [Match] Name=$IFACE [Link] Unmanaged=yes EOF done exit 0 ================================================ FILE: dnsmasq/configs/systemdnetworkd/override.conf ================================================ [Unit] After=NetworkManager.service [Service] ExecStartPre=!/usr/lib/systemd/networkd-pre ================================================ FILE: dnsmasq/configs/systemdnetworkd/wait-override.conf ================================================ [Service] ExecStart=/lib/systemd/systemd-networkd-wait-online --timeout=30 ================================================ FILE: dnsmasq/configs/systemdresolved/dsiprouter.conf ================================================ [Resolve] MulticastDNS=no LLMNR=no Cache=no CacheFromLocalhost=no DNSStubListener=no ReadEtcHosts=no ================================================ FILE: dnsmasq/debian/12.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { if ! systemctl is-active -q systemd-resolved; then printerr 'systemd-resolved is required for this deployment' echo 'install systemd-resolved and then retry installation' return 1 fi apt-get install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing new dns stack' return 1 fi # configure init.d daemon cp -f ${DSIP_PROJECT_DIR}/dnsmasq/init.d/dnsmasq /etc/init.d/dnsmasq chmod 755 /etc/init.d/dnsmasq touch /usr/share/dnsmasq/installed-marker # configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v1.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq # backup the original resolv.conf [[ ! -e "${BACKUPS_DIR}/etc/resolv.conf" ]] && { mkdir -p ${BACKUPS_DIR}/etc/ cp -df /etc/resolv.conf ${BACKUPS_DIR}/etc/resolv.conf } # make dnsmasq the DNS provider rm -f /etc/resolv.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolv.conf /etc/resolv.conf # for some reason the defaults on systemd-networkd are not followed after changing the above # so we give the interfaces explicit rules to make sure DNS servers are resolved via DHCP on the ifaces # see systemd.network and systemd.networkd for more information mkdir -p /etc/systemd/network/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemd.network /etc/systemd/network/99-dsiprouter.network # restart systemd network services systemctl restart systemd-networkd if (( $? != 0 )); then printerr 'failed loading new systemd network configurations..' printwarn 'reverting network changes' cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf rm -f /etc/systemd/network/99-dsiprouter.network systemctl restart systemd-networkd return 1 fi # tell dnsmasq where to find upstream DNS servers # we only need the dhcp dynamic dns servers feature of systemd-resolved, everything else is turned off mkdir -p /etc/systemd/resolved.conf.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdresolved/dsiprouter.conf /etc/systemd/resolved.conf.d/dsiprouter.conf systemctl restart systemd-resolved # tell dnsmasq to grab dns servers from systemd-resolved export DNSMASQ_RESOLV_FILE="/run/systemd/resolve/resolv.conf" envsubst <${DSIP_PROJECT_DIR}/dnsmasq/configs/dnsmasq_sh.conf >/etc/dnsmasq.conf return 0 } function uninstall() { # stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # uninstall packages apt-get remove -y --purge dnsmasq # remove our systemd-resolved configurations rm -f /etc/systemd/resolved.conf.d/99-dsiprouter.conf # remove the systemd.network rules rm -f /etc/systemd/network/99-dsiprouter.network # restore original resolv.conf cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf # restart systemd.networkd with the original rules systemctl restart systemd-networkd # update resolv.conf / restart systemd-resolved with new configs systemctl restart systemd-resolved # cleanup backup files rm -f ${BACKUPS_DIR}/etc/resolv.conf return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/debian/install.sh ================================================ #!/usr/bin/env bash # # dSIPRouter DNS Resolution - How it Works # # Many of the cluster features require a multiple IP addresses to be associated with a local hostname. # To make these checks performant and not rely on external DNS, a local stub resolver supporting # multiple IPs per host is required (dnsmasq). This is equivalent to having multiple A / AAAA records # on an external DNS server. The difference here is that the entries are read locally from /etc/hosts. # A hostname is first checked locally, and before trying to resolve via the external DNS servers. # # By default DNS resolution via other applications is bypassed. DNSMasq is therefore the primary DNS # resolver for the entire system (even for glibc). # Upstream DNS resolvers (external, other stub resolvers, etc..) are attempted only after the DNSMasq # stub resolver checks local records from /etc/hosts. # # References: # dnsmasq(8) https://manpages.debian.org/stable/dnsmasq-base/dnsmasq.8.en.html # resolv.conf(5) https://man7.org/linux/man-pages/man5/resolv.conf.5.html # hosts(5) https://man7.org/linux/man-pages/man5/hosts.5.html # # dSIPRouter Network Configuration - How it Works # # To support a variety of deployment environments the network stack is strictly configured on install. # The goal is to make builds as reproducible as possible in any environment, without concern for the # OS provider's (downstream, VM image, etc..) chosen network stack. # Therefore, to customize your network configuration, make sure your network configurations operate on # the supported network applications outlined here. # # Here is a summary of how the installed network stack works on debian-based OS: # 1. ignore cloud-init network configurations # 2. try configuring the network via network-manager # 3. try configuring the network via systemd-networkd # 4. try configuring the network via ifupdown # # By default network-manager / systemd-networkd will try to assign IPs based on DHCP. # By default ifupdown is left unaltered. # # References: # cloud-init(1) https://manpages.debian.org/stable/cloud-init/cloud-init.1.en.html # systemd-networkd(8) https://man7.org/linux/man-pages/man8/systemd-networkd.service.8.html # networkd-dispatcher(8) https://manpages.debian.org/stable/networkd-dispatcher/networkd-dispatcher.8.en.html # interfaces(5) https://manpages.debian.org/stable/ifupdown/interfaces.5.en.html # # TODO: Currently this is implemented with systemd service drop-ins but this is very hacky and does not allow # fine grain control over timing and service status / network availability checks throughout the startup # ordering chain. # The preferred method we will implement in the future, will be using network-manager to load the rest of # the possible network management services. # I.E. instead of trying network-manager then waiting for it to timeout and trying systemd-networkd, the # network manager would try each plugin (plugins=keyfile,networkd,ifupdown) in order, until one is successful. # The network manager project currently only supports keyfile.ifupdown above. networkd support needs implemented. # Other plugins can be used as an example for the new implementation: # https://github.com/NetworkManager/NetworkManager/tree/main/src/core/settings/plugins # # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # backup the configuration files we will replace [[ ! -e "${BACKUPS_DIR}/network/" ]] && { mkdir -p ${BACKUPS_DIR}/network/ cp -df /etc/resolv.conf ${BACKUPS_DIR}/network/resolv.conf cp -df /etc/default/networking ${BACKUPS_DIR}/network/networking } # make sure the dns stack is installed (minimal images do not include these packages) # debian used resolvconf up to debian12 when they switch to systemd-resolved if (( $DISTRO_VER < 12 )); then apt-get purge -y systemd-resolved libnss-resolve apt-get install -y resolvconf ifupdown network-manager resolvconf -u else apt-get purge -y resolvconf apt-get install -y systemd-resolved libnss-resolve ifupdown network-manager # we only need the dhcp dynamic dns servers feature of systemd-resolved, everything else is turned off mkdir -p /etc/systemd/resolved.conf.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdresolved/dsiprouter.conf /etc/systemd/resolved.conf.d/99-dsiprouter.conf systemctl restart systemd-resolved fi # we give the interfaces explicit rules to make sure DNS servers are resolved via DHCP on the interfaces # docker interfaces are managed by docker services so we also make sure systemd-networkd does not manage those # see systemd.network and systemd.networkd for more information mkdir -p /etc/systemd/network/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdnetworkd/docker.network /etc/systemd/network/98-docker.network cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdnetworkd/dsiprouter.network /etc/systemd/network/99-dsiprouter.network # configure NetworkManager mkdir -p /etc/NetworkManager/conf.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/networkmanager/dsiprouter.conf /etc/NetworkManager/conf.d/99-dsiprouter.conf # systemd-networkd and networking service customizations mkdir -p /etc/systemd/system/systemd-networkd.service.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdnetworkd/override.conf /etc/systemd/system/systemd-networkd.service.d/00-dsiprouter.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdnetworkd/networkd-pre.sh /usr/lib/systemd/networkd-pre chmod +x /usr/lib/systemd/networkd-pre mkdir -p /etc/systemd/system/networking.service.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/ifupdown/override.conf /etc/systemd/system/networking.service.d/00-dsiprouter.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/ifupdown/networking-pre.sh /usr/lib/ifupdown/networking-pre chmod +x /usr/lib/ifupdown/networking-pre # adjusting the service timeouts "online" mkdir -p /etc/systemd/system/systemd-networkd-wait-online.service.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/systemdnetworkd/wait-override.conf /etc/systemd/system/systemd-networkd-wait-online.service.d/00-dsiprouter.conf mkdir -p /etc/systemd/system/NetworkManager-wait-online.service.d/ cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/networkmanager/wait-override.conf /etc/systemd/system/NetworkManager-wait-online.service.d/00-dsiprouter.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/ifupdown/default.conf /etc/default/networking systemctl daemon-reload systemctl enable NetworkManager systemctl enable systemd-networkd systemctl enable networking # restart network services. if we fail, revert and exit. # TODO: we can not ensure the network stack is properly reverted without tracking the original set of packages and reverting them as well systemctl restart NetworkManager && systemctl restart systemd-networkd && systemctl restart networking || { printerr 'failed loading updated network configurations..' printwarn 'reverting network changes and aborting dnsmasq install' cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf cp -df ${BACKUPS_DIR}/network/networking /etc/default/networking rm -f /etc/systemd/resolved.conf.d/99-dsiprouter.conf rm -f /etc/systemd/network/98-docker.network rm -f /etc/systemd/network/99-dsiprouter.network rm -f /etc/NetworkManager/conf.d/99-dsiprouter.conf rm -f /etc/systemd/system/systemd-networkd.service.d/00-dsiprouter.conf rm -f /etc/systemd/system/networking.service.d/00-dsiprouter.conf rm -f /etc/systemd/system/NetworkManager-wait-online.service.d/00-dsiprouter.conf rm -f /usr/lib/systemd/networkd-pre rm -f /usr/lib/ifupdown/networking-pre systemctl daemon-reload systemctl revert NetworkManager systemctl revert systemd-networkd systemctl revert networking systemctl restart NetworkManager || systemctl restart systemd-networkd || systemctl restart networking return 1 } # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service apt-get install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing new dns stack' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v1.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq # make dnsmasq the DNS provider rm -f /etc/resolv.conf cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolv.conf /etc/resolv.conf # update the dnsmasq settings if (( $DISTRO_VER < 12 )); then export DNSMASQ_RESOLV_FILE="/run/dnsmasq/resolv.conf" # setup resolvconf to work with dnsmasq cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolvconf_def /etc/default/resolvconf && rm -f /etc/resolvconf/update.d/dnsmasq && cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/resolvconf_upd /etc/resolvconf/update.d/dnsmasq && chmod +x /etc/resolvconf/update.d/dnsmasq && resolvconf -u || { printerr 'failed loading new resolvconf network configurations..' } else export DNSMASQ_RESOLV_FILE="/run/systemd/resolve/resolv.conf" fi # tell dnsmasq to grab dns servers found via dhcp envsubst <${DSIP_PROJECT_DIR}/dnsmasq/configs/dnsmasq_sh.conf >/etc/dnsmasq.conf return 0 } function uninstall() { # stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # uninstall packages apt-get remove -y --purge dnsmasq # remove network manager config rm -f /etc/NetworkManager/conf.d/99-dsiprouter.conf # remove our systemd-resolved configurations rm -f /etc/systemd/resolved.conf.d/99-dsiprouter.conf # remove the systemd.network rules rm -f /etc/systemd/network/99-dsiprouter.network # restore original resolv.conf cp -df ${BACKUPS_DIR}/etc/resolv.conf /etc/resolv.conf # restart related services if (( $DISTRO_VER < 12 )); then resolvconf -u else systemctl restart systemd-networkd systemctl restart systemd-resolved fi if systemctl is-active -q NetworkManager &>/dev/null; then systemctl restart NetworkManager fi # cleanup backup files rm -f ${BACKUPS_DIR}/etc/resolv.conf return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/init.d/dnsmasq ================================================ #!/bin/sh ### BEGIN INIT INFO # Provides: dnsmasq # Required-Start: $network $remote_fs $syslog # Required-Stop: $network $remote_fs $syslog # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Description: DHCP and DNS server ### END INIT INFO # Don't exit on error status set +e PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/sbin/dnsmasq NAME=dnsmasq DESC="DNS forwarder and DHCP server" INSTANCE="${2}" # Most configuration options in /etc/default/dnsmasq are deprecated # but still honoured. ENABLED=1 if [ -r /etc/default/${NAME}${INSTANCE:+.${INSTANCE}} ]; then . /etc/default/${NAME}${INSTANCE:+.${INSTANCE}} fi # Get the system locale, so that messages are in the correct language, and the # charset for IDN is correct if [ -r /etc/default/locale ]; then . /etc/default/locale export LANG fi # The following test ensures the dnsmasq service is not started, when the # package 'dnsmasq' is removed but not purged, even if the dnsmasq-base # package is still in place. test -e /usr/share/dnsmasq/installed-marker || exit 0 test -x ${DAEMON} || exit 0 # Provide skeleton LSB log functions for backports which don't have LSB functions. if [ -f /lib/lsb/init-functions ]; then . /lib/lsb/init-functions else log_warning_msg () { echo "${@}." } log_success_msg () { echo "${@}." } log_daemon_msg () { echo -n "${1}: ${2}" } log_end_msg () { if [ "${1}" -eq 0 ]; then echo "." elif [ "${1}" -eq 255 ]; then /bin/echo -e " (warning)." else /bin/echo -e " failed!" fi } fi # RESOLV_CONF: # If the resolvconf package is installed then use the resolv conf file # that it provides as the default. Otherwise use /etc/resolv.conf as # the default. # # If IGNORE_RESOLVCONF is set in /etc/default/dnsmasq or an explicit # filename is set there then this inhibits the use of the resolvconf-provided # information. # # Note that if the resolvconf package is installed it is not possible to # override it just by configuration in /etc/dnsmasq.conf, it is necessary # to set IGNORE_RESOLVCONF=yes in /etc/default/dnsmasq. if [ ! "${RESOLV_CONF}" ] && [ "${IGNORE_RESOLVCONF}" != "yes" ] && [ -x /sbin/resolvconf ] then RESOLV_CONF=/run/dnsmasq/resolv.conf fi for INTERFACE in ${DNSMASQ_INTERFACE}; do DNSMASQ_INTERFACES="${DNSMASQ_INTERFACES} -i ${INTERFACE}" done for INTERFACE in ${DNSMASQ_EXCEPT}; do DNSMASQ_INTERFACES="${DNSMASQ_INTERFACES} -I ${INTERFACE}" done if [ ! "${DNSMASQ_USER}" ]; then DNSMASQ_USER="dnsmasq" fi # This tells dnsmasq to ignore DNS requests that don't come from a local network. # It's automatically ignored if --interface --except-interface, --listen-address # or --auth-server exist in the configuration, so for most installations, it will # have no effect, but for otherwise-unconfigured installations, it stops dnsmasq # from being vulnerable to DNS-reflection attacks. DNSMASQ_OPTS="${DNSMASQ_OPTS} --local-service" # If the dns-root-data package is installed, then the trust anchors will be # available in ROOT_DS, in BIND zone-file format. Reformat as dnsmasq # --trust-anchor options. ROOT_DS="/usr/share/dns/root.ds" if [ -f ${ROOT_DS} ]; then DNSMASQ_OPTS="$DNSMASQ_OPTS `env LC_ALL=C sed -rne "s/^([.a-zA-Z0-9]+)([[:space:]]+[0-9]+)*([[:space:]]+IN)*[[:space:]]+DS[[:space:]]+/--trust-anchor=\1,/;s/[[:space:]]+/,/gp" $ROOT_DS | tr '\n' ' '`" fi start() { # Return # 0 if daemon has been started # 1 if daemon was already running # 2 if daemon could not be started # /run may be volatile, so we need to ensure that # /run/dnsmasq exists here as well as in postinst if [ ! -d /run/dnsmasq ]; then mkdir /run/dnsmasq || { [ -d /run/dnsmasq ] || return 2 ; } chown dnsmasq:nogroup /run/dnsmasq || return 2 fi [ -x /sbin/restorecon ] && /sbin/restorecon /run/dnsmasq start-stop-daemon --start --quiet --pidfile /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid --exec ${DAEMON} --test > /dev/null || return 1 start-stop-daemon --start --quiet --pidfile /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid --exec ${DAEMON} -- \ -x /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid \ ${MAILHOSTNAME:+ -m ${MAILHOSTNAME}} \ ${MAILTARGET:+ -t ${MAILTARGET}} \ ${DNSMASQ_USER:+ -u ${DNSMASQ_USER}} \ ${DNSMASQ_INTERFACES:+ ${DNSMASQ_INTERFACES}} \ ${DHCP_LEASE:+ -l ${DHCP_LEASE}} \ ${DOMAIN_SUFFIX:+ -s ${DOMAIN_SUFFIX}} \ ${RESOLV_CONF:+ -r ${RESOLV_CONF}} \ ${CACHESIZE:+ -c ${CACHESIZE}} \ ${CONFIG_DIR:+ -7 ${CONFIG_DIR}} \ ${DNSMASQ_OPTS:+ ${DNSMASQ_OPTS}} \ || return 2 } start_resolvconf() { # If interface "lo" is explicitly disabled in /etc/default/dnsmasq # Then dnsmasq won't be providing local DNS, so don't add it to # the resolvconf server set. for interface in ${DNSMASQ_EXCEPT}; do [ ${interface} = lo ] && return done # Also skip this if DNS functionality is disabled in /etc/dnsmasq.conf if grep -qs '^port=0' /etc/dnsmasq.conf; then return fi if [ -x /sbin/resolvconf ] ; then echo "nameserver 127.0.0.1" | /sbin/resolvconf -a lo.${NAME}${INSTANCE:+.${INSTANCE}} fi return 0 } stop() { # Return # 0 if daemon has been stopped # 1 if daemon was already stopped # 2 if daemon could not be stopped # other if a failure occurred start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid --name ${NAME} } stop_resolvconf() { if [ -x /sbin/resolvconf ] ; then /sbin/resolvconf -d lo.${NAME}${INSTANCE:+.${INSTANCE}} fi return 0 } status() { # Return # 0 if daemon is running # 1 if daemon is dead and pid file exists # 3 if daemon is not running # 4 if daemon status is unknown start-stop-daemon --start --quiet --pidfile /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid --exec ${DAEMON} --test > /dev/null case "${?}" in 0) [ -e "/run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid" ] && return 1 ; return 3 ;; 1) return 0 ;; *) return 4 ;; esac } case "${1}" in start) test "${ENABLED}" != "0" || exit 0 log_daemon_msg "Starting ${DESC}" "${NAME}${INSTANCE:+.${INSTANCE}}" start case "${?}" in 0) log_end_msg 0 start_resolvconf exit 0 ;; 1) log_success_msg "(already running)" exit 0 ;; *) log_end_msg 1 exit 1 ;; esac ;; stop) stop_resolvconf if [ "${ENABLED}" != "0" ]; then log_daemon_msg "Stopping ${DESC}" "${NAME}${INSTANCE:+.${INSTANCE}}" fi stop RETVAL="${?}" if [ "${ENABLED}" = "0" ]; then case "${RETVAL}" in 0) log_daemon_msg "Stopping ${DESC}" "${NAME}${INSTANCE:+.${INSTANCE}}"; log_end_msg 0 ;; esac exit 0 fi case "${RETVAL}" in 0) log_end_msg 0 ; exit 0 ;; 1) log_warning_msg "(not running)" ; exit 0 ;; *) log_end_msg 1; exit 1 ;; esac ;; checkconfig) ${DAEMON} --test ${CONFIG_DIR:+ -7 ${CONFIG_DIR}} ${DNSMASQ_OPTS:+ ${DNSMASQ_OPTS}} >/dev/null 2>&1 RETVAL="${?}" exit ${RETVAL} ;; restart|force-reload) test "${ENABLED}" != "0" || exit 1 ${DAEMON} --test ${CONFIG_DIR:+ -7 ${CONFIG_DIR}} ${DNSMASQ_OPTS:+ ${DNSMASQ_OPTS}} >/dev/null 2>&1 if [ ${?} -ne 0 ]; then NAME="configuration syntax check" RETVAL="2" else stop_resolvconf stop RETVAL="${?}" fi log_daemon_msg "Restarting ${DESC}" "${NAME}${INSTANCE:+.${INSTANCE}}" case "${RETVAL}" in 0|1) sleep 2 start case "${?}" in 0) log_end_msg 0 start_resolvconf exit 0 ;; *) log_end_msg 1 exit 1 ;; esac ;; *) log_end_msg 1 exit 1 ;; esac ;; status) log_daemon_msg "Checking ${DESC}" "${NAME}${INSTANCE:+.${INSTANCE}}" status case "${?}" in 0) log_success_msg "(running)" ; exit 0 ;; 1) log_success_msg "(dead, pid file exists)" ; exit 1 ;; 3) log_success_msg "(not running)" ; exit 3 ;; *) log_success_msg "(unknown)" ; exit 4 ;; esac ;; dump-stats) kill -s USR1 `cat /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid` ;; systemd-start-resolvconf) start_resolvconf ;; systemd-stop-resolvconf) stop_resolvconf ;; systemd-exec) # /run may be volatile, so we need to ensure that # /run/dnsmasq exists here as well as in postinst if [ ! -d /run/dnsmasq ]; then mkdir /run/dnsmasq || { [ -d /run/dnsmasq ] || return 2 ; } chown dnsmasq:nogroup /run/dnsmasq || return 2 fi exec ${DAEMON} -x /run/dnsmasq/${NAME}${INSTANCE:+.${INSTANCE}}.pid \ ${MAILHOSTNAME:+ -m ${MAILHOSTNAME}} \ ${MAILTARGET:+ -t ${MAILTARGET}} \ ${DNSMASQ_USER:+ -u ${DNSMASQ_USER}} \ ${DNSMASQ_INTERFACES:+ ${DNSMASQ_INTERFACES}} \ ${DHCP_LEASE:+ -l ${DHCP_LEASE}} \ ${DOMAIN_SUFFIX:+ -s ${DOMAIN_SUFFIX}} \ ${RESOLV_CONF:+ -r ${RESOLV_CONF}} \ ${CACHESIZE:+ -c ${CACHESIZE}} \ ${CONFIG_DIR:+ -7 ${CONFIG_DIR}} \ ${DNSMASQ_OPTS:+ ${DNSMASQ_OPTS}} ;; *) echo "Usage: /etc/init.d/${NAME} {start|stop|restart|force-reload|dump-stats|status}" >&2 exit 3 ;; esac exit 0 ================================================ FILE: dnsmasq/rhel/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service dnf install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # Configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v3.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq return 0 } function uninstall { # Stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # Uninstall packages dnf remove -y dnsmasq return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/rocky/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service yum install -y dnsmasq if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # Configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v2.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq return 0 } function uninstall { # Stop and disable services systemctl disable dnsmasq systemctl stop dnsmasq # Uninstall packages yum remove -y dnsmasq return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dnsmasq/systemd/dnsmasq-v1.service ================================================ [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Wants=nss-lookup.target Before=nss-lookup.target DefaultDependencies=no [Service] Type=forking PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' Environment='IGNORE_RESOLVCONF=yes' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test # We run dnsmasq via the /etc/init.d/dnsmasq script which acts as a # wrapper picking up extra configuration files and then execs dnsmasq # itself, when called with the "systemd-exec" function. ExecStart=/etc/init.d/dnsmasq systemd-exec # The systemd-*-resolvconf functions configure (and deconfigure) # resolvconf to work with the dnsmasq DNS server. They're called like # this to get correct error handling (ie don't start-resolvconf if the # dnsmasq daemon fails to start. ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf ExecStop=/etc/init.d/dnsmasq systemd-stop-resolvconf ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target ================================================ FILE: dnsmasq/systemd/dnsmasq-v2.service ================================================ [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target ================================================ FILE: dnsmasq/systemd/dnsmasq-v3.service ================================================ [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PermissionsStartOnly=true PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target ================================================ FILE: dnsmasq/ubuntu/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # remove previous dns stack apt-get remove -y libnss-resolve systemd-resolved if (( $? != 0 )); then printerr 'Failed removing old dns stack' return 1 fi # mask the service before running package manager to avoid faulty startup errors systemctl mask dnsmasq.service apt-get install -y dnsmasq resolvconf if (( $? != 0 )); then printerr 'Failed installing new dns stack' return 1 fi # make sure we unmask before configuring the service ourselves systemctl unmask dnsmasq.service # configure dnsmasq systemd service cp -f ${DSIP_PROJECT_DIR}/dnsmasq/systemd/dnsmasq-v1.service /lib/systemd/system/dnsmasq.service chmod 644 /lib/systemd/system/dnsmasq.service systemctl daemon-reload systemctl enable dnsmasq # tell network manager to use dnsmasq instead cp -f ${DSIP_PROJECT_DIR}/dnsmasq/configs/networkmanager/dsiprouter.conf /etc/NetworkManager/conf.d/99-dsiprouter.conf return 0 } function uninstall() { # remove network manager config rm -f /etc/NetworkManager/conf.d/99-dsiprouter.conf # swap old resolvers in as static file so DNS still works while uninstalling mv -f /run/dnsmasq/resolv.conf /etc/resolv.conf # uninstall new dns stack apt-get remove -y --purge dnsmasq resolvconf # reinstall old dns stack apt-get install -y libnss-resolve systemd-resolved return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: docker/dsiprouter/dockerfile ================================================ # Dockerfile FROM python:3.6-stretch MAINTAINER Mack Hendricks COPY ./ /opt/dsiprouter/ WORKDIR /opt/dsiprouter RUN pip install -r ./gui/requirements.txt ENV DBSERVER '' ENV DBPORT '3306' ENV DBUSER '' ENV DBPASS '' ENV DBNAME '' EXPOSE 5000:5000 CMD ["python", "./gui/dsiprouter.py", "runserver"] ================================================ FILE: docker/dsiprouter/wait-for-dsiprouter-mysql.sh ================================================ #!/bin/sh # wait-for-dsiprouter-mysql.sh set -e host="$1" shift cmd="$@" until python ./gui/dsiprouter.py runserver; do >&2 echo "dSIPRouter MySQL is unavailable - can't start - sleeping" sleep 1 done >&2 echo "dSIPRouter MySQL is up - started dSIPRouter" ================================================ FILE: docker/mysql/dockerfile ================================================ # Dockerfile FROM python:3.6-stretch MAINTAINER Mack Hendricks COPY ../../ /opt/dsiprouter/ WORKDIR /opt/dsiprouter RUN pip install -r ./gui/requirements.txt ENV DBSERVER '' ENV DBPORT '3306' ENV DBUSER '' ENV DBPASS '' ENV DBNAME '' EXPOSE 5000:5000 CMD ["python", "./gui/dsiprouter.py"] ================================================ FILE: docker-compose.yml ================================================ version: '3' services: dsiprouter: build: context: . dockerfile: docker/dsiprouter/dockerfile image: dopensource/dsiprouter volumes: - .:/opt/dsiprouter ports: - "5000:5000" environment: - ENV=PROD - DSIP_USER=admin - DSIP_PASS=admin - DSIP_DEBUG=True - KAM_DB_HOST=dsiprouter-mysql - KAM_DB_TYPE=mysql - KAM_DB_PORT=3306 - KAM_DB_NAME=kamailio - KAM_DB_USER=kamailio - KAM_DB_PASS=kamailiorw depends_on: - dsiprouter-mysql command: ["./docker/dsiprouter/wait-for-dsiprouter-mysql.sh","dsiprouter-mysql","python","dsiprouter.py","runserver"] dsiprouter-mysql: image: mysql:5.7 volumes: - /tmp/dbdata:/var/lib/mysql - ./testing/sql/v0.60+ent:/docker-entrypoint-initdb.d/ ports: - "3306:3306" environment: - MYSQL_ALLOW_EMPTY_PASSWORD=yes - MYSQL_USER=kamailio - MYSQL_PASSWORD=kamailiorw - MYSQL_DATABASE=kamailio healthcheck: test: mysql --user=root -e "select * from kamailio.dr_gateways" timeout: 45s retries: 5 ================================================ FILE: docs/Makefile ================================================ # Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = /opt/dsiprouter/venv/bin/python -m sphinx SOURCEDIR = source BUILDDIR = build .PHONY: help Makefile html pdf # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)" # Make all available formats all: html pdf # Make building pdf with rinoh easier to run pdf: @$(SPHINXBUILD) -b rinoh $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/pdf" # Make building html html: $(SPHINXBUILD) -b html $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)/html" # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ $(SPHINXOPTS) "$(SOURCEDIR)" "$(BUILDDIR)" $(O) ================================================ FILE: docs/requirements.in ================================================ docutils<0.17,>=0.12 myst-parser recommonmark requests sphinx sphinxcontrib-httpdomain sphinx-rtd-theme piccolo_theme UltraDict ================================================ FILE: docs/requirements.txt ================================================ # # This file is autogenerated by pip-compile with Python 3.8 # by the following command: # # pip-compile --strip-extras docs/requirements.in # alabaster==0.7.13 # via sphinx babel==2.13.1 # via sphinx certifi==2023.7.22 # via requests charset-normalizer==3.3.2 # via requests commonmark==0.9.1 # via recommonmark docutils==0.16 # via # -r requirements.in # myst-parser # recommonmark # sphinx # sphinx-rtd-theme idna==3.4 # via requests imagesize==1.4.1 # via sphinx importlib-metadata==6.8.0 # via sphinx jinja2==3.1.3 # via # myst-parser # sphinx markdown-it-py==2.2.0 # via # mdit-py-plugins # myst-parser markupsafe==2.1.3 # via jinja2 mdit-py-plugins==0.3.5 # via myst-parser mdurl==0.1.2 # via markdown-it-py myst-parser==1.0.0 # via -r requirements.in packaging==23.2 # via sphinx piccolo-theme==0.19.0 # via -r requirements.in pygments==2.16.1 # via sphinx pytz==2023.3.post1 # via babel pyyaml==6.0.1 # via myst-parser recommonmark==0.7.1 # via -r requirements.in requests==2.31.0 # via # -r requirements.in # sphinx six==1.16.0 # via sphinxcontrib-httpdomain snowballstemmer==2.2.0 # via sphinx sphinx==5.3.0 # via # -r requirements.in # myst-parser # piccolo-theme # recommonmark # sphinx-rtd-theme # sphinxcontrib-httpdomain # sphinxcontrib-jquery sphinx-rtd-theme==1.3.0 # via -r requirements.in sphinxcontrib-applehelp==1.0.4 # via sphinx sphinxcontrib-devhelp==1.0.2 # via sphinx sphinxcontrib-htmlhelp==2.0.1 # via sphinx sphinxcontrib-httpdomain==1.8.1 # via -r requirements.in sphinxcontrib-jquery==4.1 # via sphinx-rtd-theme sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.5 # via sphinx ultradict==0.0.6 # via -r requirements.in urllib3==2.0.7 # via requests zipp==3.17.0 # via importlib-metadata ================================================ FILE: docs/source/_static/placeholder ================================================ ================================================ FILE: docs/source/_templates/placeholder ================================================ ================================================ FILE: docs/source/conf.py ================================================ # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os, sys sys.path.insert(0, os.path.abspath('../../gui')) sys.path.insert(0, '/etc/dsiprouter/gui') import settings sys.setrecursionlimit(1500) # -- Project information ----------------------------------------------------- project = 'dSIPRouter' copyright = 'dOpenSource' author = 'dOpenSource' # The short X.Y version version = str(settings.VERSION) # The full version, including alpha/beta/rc tags release = str(settings.VERSION) # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', 'sphinxcontrib.httpdomain', 'sphinxcontrib.autohttp.flask', 'sphinxcontrib.autohttp.flaskqref', # 'rinoh.frontend.sphinx', 'myst_parser' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: source_suffix = { '.rst': 'restructuredtext', '.md': 'markdown' } # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'piccolo_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { "source_url": f'{settings.GIT_REPO_URL.rsplit(".", 1)[0]}/', } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'dsiprouterdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. 'preamble': '', # Latex figure (float) alignment 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'dSIPRouter.tex', 'dSIPRouter Documentation', 'DevOpSec', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'dsiprouter', 'dSIPRouter Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'dSIPRouter', 'dSIPRouter Documentation', author, 'dSIPRouter', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} ================================================ FILE: docs/source/dev/database.rst ================================================ database ======== .. automodule:: database :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/dsiprouter.rst ================================================ dsiprouter ========== .. automodule:: dsiprouter :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/index.rst ================================================ Developer Hub ============= Python Modules -------------- The dSIPRouter project at its core is a WSGI Flask application that serves HTTP content. This section makes the core modules easier to explore for developers. .. toctree:: :titlesonly: database dsiprouter modules settings shared sysloginit util .. TODO: changelog uses github-flavored markdown comments and is not parsed correctly by myst-parser Contibuting Guidelines ---------------------- .. toctree:: external_links/CONTRIBUTING.md Credits ------- .. toctree:: :titlesonly: external_links/CONTRIBUTORS.md ================================================ FILE: docs/source/dev/modules.rst ================================================ ======= modules ======= modules.api.api_routes ====================== .. automodule:: modules.api.api_routes :members: :undoc-members: :private-members: :special-members: modules.api.cron_functions ========================== .. automodule:: modules.api.cron_functions :members: :undoc-members: :private-members: :special-members: modules.cdr.cron_functions ========================== .. automodule:: modules.cdr.cron_functions :members: :undoc-members: :private-members: :special-members: modules.domain.domain_routes ============================ .. automodule:: modules.domain.domain_routes :members: :undoc-members: :private-members: :special-members: modules.flowroute ================= .. automodule:: modules.flowroute :members: :undoc-members: :private-members: :special-members: modules.frauddetection.fraud ============================ .. automodule:: modules.frauddetection.fraud :members: :undoc-members: :private-members: :special-members: modules.fusionpbx.fusionpbx_sync_functions ========================================== .. automodule:: modules.fusionpbx.fusionpbx_sync_functions :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/settings.rst ================================================ settings ======== .. automodule:: settings :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/shared.rst ================================================ shared ====== .. automodule:: shared :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/sysloginit.rst ================================================ sysloginit ========== .. automodule:: sysloginit :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/dev/util.rst ================================================ ==== util ==== util.conversions ================ .. automodule:: util.conversions :members: :undoc-members: :private-members: :special-members: util.cron ========= .. automodule:: util.cron :members: :undoc-members: :private-members: :special-members: util.file_handling ================== .. automodule:: util.file_handling :members: :undoc-members: :private-members: :special-members: util.ipc ======== .. automodule:: util.ipc :members: :undoc-members: :private-members: :special-members: util.kamtls =========== .. automodule:: util.kamtls :members: :undoc-members: :private-members: :special-members: util.letsencrypt ================ .. automodule:: util.letsencrypt :members: :undoc-members: :private-members: :special-members: util.networking =============== .. automodule:: util.networking :members: :undoc-members: :private-members: :special-members: util.notifications ================== .. automodule:: util.notifications :members: :undoc-members: :private-members: :special-members: util.parse_json =============== .. automodule:: util.parse_json :members: :undoc-members: :private-members: :special-members: util.persistence ================ .. automodule:: util.persistence :members: :undoc-members: :private-members: :special-members: util.pyasync ============ .. automodule:: util.pyasync :members: :undoc-members: :private-members: :special-members: util.security ============= .. automodule:: util.security :members: :undoc-members: :private-members: :special-members: util.time_funcs =============== .. automodule:: util.time_funcs :members: :undoc-members: :private-members: :special-members: ================================================ FILE: docs/source/index.rst ================================================ dSIPRouter Docs =============== There are 3 main sections of documentation: 1. User Documentation 2. Developer Documentation 3. Routes Documentation (API/GUI routes) Use the table of contents on your left to navigate between sections. Indices and tables ------------------ * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. toctree:: :caption: Table of Contents :name: mastertoc :titlesonly: :glob: :hidden: User Documentation Developer Documentation Routes Documentation ================================================ FILE: docs/source/routes/details.rst ================================================ Route Details ============= .. autoflask:: dsiprouter:app :undoc-static: :include-empty-docstring: ================================================ FILE: docs/source/routes/index.rst ================================================ GUI/API Routes ============== The GUI and API make several routes accessible for usage. A summary and description of each route are provided below. Summary Table ------------- .. toctree:: :maxdepth: 2 summary.rst Detailed Descriptions --------------------- .. toctree:: :maxdepth: 2 details.rst ================================================ FILE: docs/source/routes/summary.rst ================================================ Route Summary ============= .. raw:: html .. raw:: html .. qrefflask:: dsiprouter:app :undoc-static: :autoquickref: ================================================ FILE: docs/source/user/api.rst ================================================ dSIPRouter API Intro ==================== The complete API is defined as a public Postman Workspace, which can be found `here `_ The steps to obtain the API Token key and examples of using the API via curl are below, but we highly recommend using Postman for testing the API. Getting Your Token ------------------ Your token was provided to you after you installed dSIPRouter. You can reset your token if you didn't write it down, by executing the following command .. code-block:: bash DSIP_HOSTNAME= DSIP_TOKEN= dsiprouter setcredentials -ac $DSIP_TOKEN Executing Kamailio stats API ---------------------------- .. code-block:: bash curl -k -H "Authorization: Bearer $DSIP_TOKEN" -X GET https://$DSIP_HOSTNAME:5000/api/v1/kamailio/stats Executing Lease Point API ------------------------- Create a new endpoint lease .. code-block:: bash curl -k -H "Authorization: Bearer $DSIP_TOKEN" -H "Content-Type: application/json" -X GET "https://$DSIP_HOSTNAME:5000/api/v1/endpoint/lease?ttl=15&email=mack@dsiprouter.org" Revoking and replacing with your own lease ID .. code-block:: bash curl -k -H "Authorization: Bearer $DSIP_TOKEN" -H "Content-Type: application/json" -X PUT "https://$DSIP_HOSTNAME:5000/api/v1/endpoint/lease/1/revoke" Further Reading +++++++++++++++ All available routes are documented in the :doc:`routes documentation <../routes/index>`. ================================================ FILE: docs/source/user/carrier_groups.rst ================================================ .. _carrier_groups: Carrier Groups ^^^^^^^^^^^^^^ The Carrier Group section of dSIPRouter allows you to define which carriers will be used to provide Internet service (aka ISP) for your VOIP (Voice Over IP) services. Carrier groups support IP Authentication and Username/Password authentication. Below is an example of a carrier groups list. .. image:: images/carrier_groups.png :align: center Adding a Carrier ^^^^^^^^^^^^^^^^ - Log into dSIPRouter using proper username and password. - Click "Add" to create a Carrier Group. A carrier group can contain 1 or more SIP endpoints provided by the carrier. A SIP Endpoint represents a device that makes or receives calls via your Gateway. This could be a physical IP phone, a softphone app such as Skype, on a PC or smartphone, an Analog Telephone Adapter (ATA) such as for fax machines, or even a PBX system. - Select Username/Password Auth, fill in the username, password of your registration server and the registration server name. Then click ADD. .. image:: images/add_carrier_group.png :align: center NOTE: Click IP authenication to use only the IP address of your PBX/endpoint. .. image:: images/IP_authenication.png :align: center For example: .. image:: images/username_password.png :align: center After you have added the new group, the screen will return back to the List of Carriers Group page. Select the pencil in the blue box to the right to allow editing the Config and Endpoints. .. image:: images/carrier_editing.png :align: center Select the Config tab. The Config tab allows you to edit/change the Carrier group name. Then click Update. .. image:: images/config_pic.png :align: center To add an Endpoint, click the Endpoint tab. .. image:: images/add_endpoint.png :align: center Click ADD, enter the Friendly name (optional), the IP address of the endpoint/device, # of characters to strip from RURI, the character to prefix to a RURI then click ADD again. For example, if a PBX sends a number over as 914443332222 but the carrier wants the number to be sent as 14443332222 then the # of characters to strip should be defined as 1, which would strip off the 9. Some carriers request added digits (aka Prefixes) in front of the phone number. .. image:: images/add_new_carrier_details.png :align: center Edit and click ADD again to add addtional endpoints. Click the gray X in that box to save the window and close. You should now see your added carrier with endpoints in the Carrier Group List. .. image:: images/carrier_group_list.png :align: center Be sure to click the Reload Kamailio button to apply changes. .. image:: images/reload_button.png :align: center ================================================ FILE: docs/source/user/command_line_options.rst ================================================ Command Line Options ==================== Execute "./dsiprouter.sh" followed by one of the listed commands. **NOTE** Once installed the command will be available globally as *dsiprouter* with tab-completion. =================================== ====================================================================== Command What does it do? =================================== ====================================================================== install Installs dSIPRouter and related services uninstall Uninstall dSIPRouter and related services clusterinstall Install dSIPRouter (via SSH) on a cluster of nodes upgrade Upgrade dSIPRouter platform (requires license) start Starts dSIPRouter stop Stops dSIPRouter restart Restarts dSIPRouter chown Update file permissions for dSIPRouter and related services configurekam Reconfigures the Kamailio configurations based on dSIPRouter settings configuredsip Reconfigures the dSIPRouter configurations, updating any dynamic settings configurertp Reconfigures the RTPEngine configurations based on dSIPRouter settings renewsslcert Renew configured letsencrypt SSL certificate configuresslcert Reconfigures SSL certificate used by Kamailio and dSIPRouter installmodules Install / uninstall dDSIProuter modules resetpassword Generate new random dSIPRouter admin account password setcredentials Set various credentials manually version Show dSIPRouter version help List all of the options =================================== ====================================================================== Refer to :ref:`installing_dsiprouter` to get the complete one line version of the command. To start dSIPRouter: .. code-block:: bash dsiprouter start To stop dSIPRouter: .. code-block:: bash dsiprouter stop To restart dSIPRouter: .. code-block:: bash dsiprouter restart To uninstall dSIPRouter: .. code-block:: bash dsiprouter uninstall -all ================================================ FILE: docs/source/user/conf.py ================================================ # -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os, sys #sys.path.insert(0, os.path.abspath('../../gui')) #sys.path.insert(0, '/etc/dsiprouter/gui') #import settings sys.setrecursionlimit(1500) # -- Project information ----------------------------------------------------- project = 'dSIPRouter' copyright = 'dOpenSource' author = 'dOpenSource' # The short X.Y version #version = settings.VERSION # The full version, including alpha/beta/rc tags #release = settings.VERSION # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.githubpages', # 'sphinxcontrib.httpdomain', # 'sphinxcontrib.autohttp.flask', # 'sphinxcontrib.autohttp.flaskqref', # 'rinoh.frontend.sphinx', 'myst_parser' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: source_suffix = { '.rst': 'restructuredtext', '.md': 'markdown' } # The master toctree document. master_doc = 'index' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = 'en' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { "collapse_navigation" : False } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = 'dsiprouterdoc' # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. 'preamble': '', # Latex figure (float) alignment 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'dSIPRouter.tex', 'dSIPRouter Documentation', 'DevOpSec', 'manual'), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'dsiprouter', 'dSIPRouter Documentation', [author], 1) ] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'dSIPRouter', 'dSIPRouter Documentation', author, 'dSIPRouter', 'One line description of project.', 'Miscellaneous'), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ['search.html'] # -- Extension configuration ------------------------------------------------- # -- Options for intersphinx extension --------------------------------------- # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping = {'https://docs.python.org/': None} ================================================ FILE: docs/source/user/configuring.rst ================================================ dSIPRouter GUI Intro ==================== .. toctree:: :maxdepth: 3 carrier_groups.rst pbxs_and_endpoints.rst domains.rst inbound_did_mapping.rst global_outbound_routes.rst ================================================ FILE: docs/source/user/debian_install.rst ================================================ .. _debian_install: Installing on a Debian-based Distro =================================== For a specific version of dSIPRouter add "-b " to the end of the `git clone` command. Make sure to **set the hostmane to a fully qualified domain name (FQDN)** that has DNS records pointed at the server (like sbc.yourdomain.com) prior to installation. The average install time is between 9-12 minutes depending on the resources on your vm/server and the options your specify. Set the Hostname ---------------- .. code-block:: bash hostnamectl set-hostname Install (Don't Proxy audio (RTP) traffic) ----------------------------------------- .. code-block:: bash apt-get update -y apt-get install -y git cd /opt git clone https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install One Line Version: .. code-block:: bash apt-get update -y && apt-get install -y git && cd /opt && git clone https://github.com/dOpensource/dsiprouter.git && cd dsiprouter && ./dsiprouter.sh install Install (Proxy audio (RTP) traffic) ----------------------------------- If you need to proxy RTP traffic then use -all install option. The command to install dSIPRouter and the RTPEngine would be: .. code-block:: bash apt-get update -y apt-get install -y git cd /opt git clone https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install -all One Line Version: .. code-block:: bash apt-get update -y && apt-get install -y git && cd /opt && git clone https://github.com/dOpensource/dsiprouter.git && cd dsiprouter && ./dsiprouter.sh install -all The install script will automatically determine if the server is behind NAT. Once the install is complete, dSIPRouter will automatically start MySQL, Kamailio and the UI. ================================================ FILE: docs/source/user/domains.rst ================================================ Adding a Domain ^^^^^^^^^^^^^^^ To add a domain click on Domains then click the green add button. .. image:: images/add_a_domain.png :align: center Fill in the domain name. (Note: You can create 1 or more domains by separating them with commas). - Select Realtime DB or Local Subscriber table (for multiple domains) - Select Pass Thru to PBX (single domain). Note: Details can be found in Realtime DB if you want to ensure that the Kamailio configuration file is setup to point to the Asterisk Realtime database configuration. Details on how to populate the table can be found in the Local Suscriber table if you want to use the built in subscriber table that's part of Kamailio. Use the pass thru to register info to the FreePBX server so that you don’t have to change how authentication is done. .. image:: images/add_new_domain522.png :align: center - For the List of backend PBX ID's you should use the ID assigned to each PBX that you want to be part of that domain. Such as naming the ID number thats assigned to media-02.voipmuch.com for example in :doc:`PBX(s) and Enpoints `. .. image:: images/add_new_domain_dev522.png :align: center - Click ADD You will then be returned back to the List of domains page and you should see your new domain added. You can delete this domain by clicking the red trash can to the right of the page. .. image:: images/list_of_domains1.png :align: center Be sure to click the Reload Kamailio button to apply changes. .. image:: images/reload_button.png :align: center ================================================ FILE: docs/source/user/global_outbound_routes.rst ================================================ .. _global_outbound_routes: Global Outbound Routes ^^^^^^^^^^^^^^^^^^^^^^^^ 1) Go to the Dashboard screen. .. image:: images//dSIP_dashboard.png :align: center 2) Click on Global Outbound Routes. 3) Click on the green Add button. .. image:: images//dSIP_Global_Out_Add.png :align: center 4) a) Enter in the Outbound Route information. b) Click on the green Add button. .. image:: images//dSIP_Global_Out_Add_Outbound_Route.png :align: center 5) Click on the blue Reload Kamailio button in order for the changes to be updated. ================================================ FILE: docs/source/user/images/DID_test.csv ================================================ 13134860409 13134860410 13134860411 ================================================ FILE: docs/source/user/inbound_did_mapping.rst ================================================ Inbound DID Mapping ====================== To Import a DID from a CSV file: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1) Click on Inbound DID Mapping. .. image:: images//dSIP_IN_DID_Map.png :align: center 2) Click on the green Import DID button underneath List on Inbound Mappings. .. image:: images//dSIP_IN_Import_DID.png :align: center 3) Click the Browse button and select the file that contains the DID numbers that you wish to use. 4) Click the green Add button. Click `CSV Example `_ to view a sample of the .CSV file 5) Click on the Reload Kamailio button in order for the changes to be updated. To Manually import a DID: ^^^^^^^^^^^^^^^^^^^^^^^^^ 1) Click on Inbound DID Mapping 2) Click on the green ADD button. - Enter the name of the Inbound mapping - Enter the DID number in the DID field. - Select the Endpoint Group from the drop-down list Note: Each endpoint will contain at least two entries. One that leverages load balancing weights and another that randomly selects an endpoint. The one denoted with a LB is the one that uses the load balancing algorithm. If FusionPBX Domain Support is enabled you will see an additional entry for routing to the external interface of the FusionPBX server. - Click the green Add button. .. image:: images//dSIP_IN_DID_Map.png :align: center 3) Click on the Reload Kamailio button in order for the changes to be updated. ================================================ FILE: docs/source/user/index.rst ================================================ The dSIPRouter Project ====================== .. raw:: html

Intro to dSIPRouter

dSIPRouter allows you to quickly turn `Kamailio `_ into an easy to use SIP Service Provider platform, which enables the following two basic use cases: - **SIP Trunking services:** Provide services to customers that have an on-premise PBX such as FreePBX, FusionPBX, Avaya, etc. We have support for IP and credential based authentication. - **Hosted PBX services:** Proxy SIP Endpoint requests to a multi-tenant PBX such as FusionPBX or single-tenant such as FreePBX. We have an integration with FusionPBX that make is really easy and scalable! - **Microsoft Teams Direct Routing:** We can provide SBC functionality that allows dSIPRouter to interconnect your existing voice infrastructure or VoIP carrier to your Microsoft Teams environment. .. raw:: html

Demo System

You can checkout our demo system by clicking the link below and enter the listed username and password: http://demo.dsiprouter.net:5000 username: admin password: ZmIwMTdmY2I5NjE4 API Token: 91RpJidL1f2eEDSyxUDnn83B7jipuDyl .. raw:: html

Credits

I'd like to say thank you to Nicole D., John O. and Courtney G. for their time in fulfilling this document. I'd also like to give a hardy thank you to dOpensource for their monetary support in funding this document. .. raw:: html

Support

Free support is available via our `group `_ and `Slack `_ Paid support is available `here `_ Installing dSIPRouter --------------------- .. toctree:: :maxdepth: 2 installing.rst Command Line Options -------------------- .. toctree:: :maxdepth: 2 command_line_options.rst Configuring dSIPRouter ---------------------- .. toctree:: :maxdepth: 2 configuring.rst Implementing Use Cases ---------------------- .. toctree:: :maxdepth: 2 use-cases.rst REST API -------- .. toctree:: :maxdepth: 2 api.rst Supported Configurations ------------------------ .. toctree:: :maxdepth: 2 supported_configurations.rst Troubleshooting --------------- .. toctree:: :maxdepth: 2 troubleshooting.rst Upgrading dSIPRouter -------------------- .. toctree:: :maxdepth: 2 upgrading.rst Extra Resources --------------- .. toctree:: :maxdepth: 2 resources.rst ================================================ FILE: docs/source/user/installing.rst ================================================ .. _installing_dsiprouter: Installing dSIPRouter ===================== The following video shows you the install process: .. raw:: html
We maintain installation documentation for the following operating systems. Please open a pull request if you want to add and maintain addtional documentation: - :ref:`debian_install` - :ref:`rhel_install` Install times vary by depending on OS and system hardware. On debian/centos, expect a short install time, typically around 12 minutes. On amazon linux, expect long compilation times, typically around 45 minutes. dSIPRouter should be installed on a clean install of the OS. To upgrade your dSIPRouter platform, see instead :ref:`upgrading` Prerequisites: -------------- - Must run this as the root user (you can use sudo) - git needs to be installed - Hostname needs to be set to a FQDN (for certbot to get LetsEncrypt certificate) - The installer will handle all other dependencies Install Options ---------------- - Proxy SIP Traffic Only (Don't Proxy audio (RTP) traffic) - Proxy SIP Traffic, Audio and it configures the system to work properly when the PBX's and dSIPRouter are behind a NAT. OS Support ---------- =================================== ================ OS / Distro Current Support =================================== ================ Debian 12 (bookworm) STABLE Debian 11 (bullseye) STABLE Debian 10 (buster) STABLE Debian 9 (stretch) DEPRECATED CentOS 9 (stream) STABLE CentOS 8 (stream) STABLE CentOS 7 DEPRECATED RedHat Linux 9 STABLE RedHat Linux 8 ALPHA Alma Linux 9 STABLE Alma Linux 8 BETA Rocky Linux 9 STABLE Rocky Linux 8 BETA Amazon Linux 2 STABLE Ubuntu 24.04 (noble) STABLE Ubuntu 22.04 (jammy) ALPHA Ubuntu 20.04 (focal) DEPRECATED =================================== ================ Amazon AMI's ------------ We now provide Amazon AMI's (pre-built images) which allows you to get up and going even faster. You can find a list of the images `here `_. The images are a nominal fee, which goes toward supporting the project. ================================================ FILE: docs/source/user/pbxs_and_endpoints.rst ================================================ PBX(s) and Endpoints ====================== Allows you to define a PBX or Endpoint that will send or receive calls from dSIPRouter. The PBX or Endpoint can use IP authentication or a username/password can be defined. To add an Endpoint Group: ^^^^^^^^^^^^^^^^^^^^^^^^^ 1) Click on Endpoints Groups. 2) Click on the green Add button. .. image:: images//dSIP_PBX_Add.png :align: center 3) Configure the Endpoint Group The Endpoint Tab is where you specify the endpoints that will be signaling with dSIPRouter. The weight field allows you to define how much SIP traffic is distributed to a particular endpoint. If you don't specify a weight for an endpoint the system will automatically generate a weight. If you are using FusionPBX Domain Auth then Register and INVITE requests will be distributed to the endpoints based upon the weights. You will also have the option to route Inbound calls to the endpoints based on the weights by selecting the name of the Endpoint Group with an LB concatenated to the name. For example, if the name of the Endpoint Group is **PBXCluster** then you would select **PBXCluster LB** from the Inbound Mapping Endpoint Group drop down. b) Click the green Add button. .. image:: images//dSIP_PBX_ADD_New_PBX.png :align: center 4) Click on the Reload Kamailio button in order for the changes to be updated. ================================================ FILE: docs/source/user/resources.rst ================================================ Extra Resources =============== Uploading CSVs -------------- `CSV Example `_ Proxy FusionPBX UI ------------------ Add the following stanza before "location /images/" stanza to proxy the FusionPBX UI thru dSIPRouter. Once the following text is added to /opt/dsiprouter/gui/modules/fusionpbx/dsiprouter.nginx.tpl you will be able to access the FusionPBX GUI via: https://dSIPRouter_IP/ or https://dSIPRouter_IP:: location / { proxy_pass https://fusionpbx; proxy_redirect off; proxy_next_upstream error timeout http_404 http_403 http_500 http_502 http_503 http_504 non_idempotent; } ================================================ FILE: docs/source/user/rhel_install.rst ================================================ .. _rhel_install: Installing on a RHEL-based Distro ================================= For a specific version of dSIPRouter add "-b " to the end of the `git clone` command. Make sure to **set the hostmane to a fully qualified domain name (FQDN)** that has DNS records pointed at the server (like sbc.yourdomain.com) prior to installation. The average install time is between 9-12 minutes depending on the resources on your vm/server and the options your specify. Install (Don't Proxy audio (RTP) traffic) ----------------------------------------- .. code-block:: bash yum install -y git cd /opt git clone https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install One Line Version: .. code-block:: bash yum install -y git && cd /opt && git clone https://github.com/dOpensource/dsiprouter.git && cd dsiprouter && ./dsiprouter.sh install -kam -dsip Once the install is complete, dSIPRouter will automatically start MySQL, Kamailio and the UI. Install (Proxy audio (RTP) traffic) ----------------------------------- If you need to proxy RTP traffic then use -all install option. The command to install dSIPRouter and the RTPEngine would be: .. code-block:: bash yum install -y git cd /opt git clone https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install -all One Line Version: .. code-block:: bash yum install -y git && cd /opt && git clone https://github.com/dOpensource/dsiprouter.git && cd dsiprouter && ./dsiprouter.sh install -all The install script will automatically determine if the server is behind NAT. Once the install is complete, dSIPRouter will automatically start MySQL, Kamailio and the UI. ================================================ FILE: docs/source/user/supported_configurations.rst ================================================ .. _supported_configurations Supported Configurations ======================== Pass Thru to PBX Authentication Supported Configurations -------------------------------------------------------- ================ ================= =========== ================= ================ ========================================================= PBX Distribution PBX Version Driver Type Registration Test Ext to Ext Test Notes ================ ================= =========== ================= ================ ========================================================= FreePBX Asterisk 13.22.0 chan_sip Pass Pass see :ref:`enabling-the-path-header-for-asterisk-chan_sip` FreePBX Asterisk 13.22.0 chan_pjsip Pass Not Tested suppport_path needs to be enabled FusionPBX FreeSWITCH 1.6 Sofia Pass Pass ================ ================= =========== ================= ================ ========================================================= .. _enabling-the-path-header-for-asterisk-chan_sip: Enabling the Path Header for Asterisk chan_sip ---------------------------------------------- 1. Login into the FreePBX Admin GUI 2. Click Settings -> Asterisk SIP Settings 3. Click Chan SIP Settings 4. Find the "Other SIP Settings" field 5. Add the following field and click "Add Field" supportpath = yes 6. Click Submit 7. Click the red "Apply" settings button at the very top of the page ================================================ FILE: docs/source/user/troubleshooting.rst ================================================ Troubleshooting =============== Here you can troubleshoot logs for dSIPRouter, Kamailio and rtpengine: All of our services are using syslog. For more information on `syslog `_ click here. Default log facilities: ============ ========== Log Facility Service ============ ========== local0 kamailio local1 rtpengine local2 dsiprouter ============ ========== Kamailio Logging ---------------- 1. How to turn logging on Edit /etc/rsyslog.d/kamailio.conf and ensure the line beginning with local0 is not commented out: .. code-block:: bash vi /etc/rsyslog.d/kamailio.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 2. How to turn logging off Edit /etc/rsyslog.d/kamailio.conf and ensure the line beginning with local0 is commented out: .. code-block:: bash vi /etc/rsyslog.d/kamailio.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 3. Location of the log files The default location is found here: /var/log/kamailio.log 4. How to configure it Edit /etc/kamailio/kamailio.conf and change the variable ‘debug’ to the syslog logging verbosity of your choice. .. code-block:: bash vi /etc/kamailio/kamailio.conf 5. For more information see the documentation below: https://www.kamailio.org/wiki/tutorials/3.2.x/syslog RTPEngine Logging ----------------- 1. How to turn logging on Edit /etc/rsyslog.d/rtpengine.conf and ensure the line beginning with local1 is not commented out: .. code-block:: bash vi /etc/rsyslog.d/rtpengine.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 2. How to turn logging off Edit /etc/rsyslog.d/rtpengine.conf and ensure the line beginning with local1 is commented out: .. code-block:: bash vi/etc/rsyslog.d/rtpengine.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 3. Location of the log files The default location is found here: /var/log/rtpengine.log 4. How to configure it Edit /etc/rtpengine/rtpengine.conf and change the variable ‘debug’ to the syslog logging verbosity of your choice. .. code-block:: bash vi /etc/rtpengine/rtpengine.conf **5. For more information see the documentation below:** https://github.com/sipwise/rtpengine dSIPRouter Logging ------------------ 1. How to turn logging on Edit /etc/rsyslog.d/dsiprouter.conf and ensure the line beginning with local2 is not commented out: .. code-block:: bash vi /etc/rsyslog.d/dsiprouter.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 2. How to turn logging off Edit /etc/rsyslog.d/dsiprouter.conf and ensure the line beginning with local2 is commented out: .. code-block:: bash vi /etc/rsyslog.d/dsiprouter.conf Then restart syslog: .. code-block:: bash systemctl restart rsyslog 3. Location of the log files The default location is found here: /var/log/dsiprouter.log 4. How to configure it Edit /etc/dsiprouter/gui/settings.py and change the variable ‘DSIP_LOG_LEVEL’ to the syslog logging verbosity of your choice. .. code-block:: bash vi /etc/dsiprouter/gui/settings.py **5. For more infornation see the documentation below:** https://success.trendmicro.com/solution/TP000086250-What-are-Syslog-Facilities-and-Levels ================================================ FILE: docs/source/user/upgrade_0.50_to_0.51.rst ================================================ In this section we will show you how to upgrade from 0.50 to 0.51. Before starting the upgrade process you will need to backup your kamailio database using the following command: .. code-block:: bash cd /opt/ mysqldump kamailio > kamailio-bk.sql After you've backed up your database you can now uninstall dsiprouter v0.50 by running the following commands: .. code-block:: bash cd /opt/dsiprouter ./dsiprouter.sh uninstall Once the uninstall is complete you will need to either move or delete the /dsiprouter directory using the following command. .. code-block:: bash mv /dsiprouter /usr/local/src (moving directory) Alternatively: .. code-block:: bash rm -r /dsiprouter (removing directory) Installing dsiprouter v0.51 .. code-block:: bash cd /opt/ apt-get update apt-get install -y git curl cd /opt git clone -b v0.51 https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install **Note: please take note of the credentials given after the script has completed.** After the install is completed you can now restore your kamailio database using the following command: .. code-block:: bash cd /opt/ mysql kamailio < kamailio-bk.sql After the kamailio database is restored you need to restart dsiprouter using the following commands: .. code-block:: bash cd /opt/disprouter/ ./dsiprouter.sh restart After the install is complete and the dsiprouter service has been restarted, the login screen should now reflect v0.51 and you should be able to login with the dsiprouter credentials provided after the install completed. .. image:: images/dsip_v51.png :align: center ================================================ FILE: docs/source/user/upgrade_0.522_to_0.523.rst ================================================ In this section we will show you how to upgrade from 0.522 to 0.523. Before starting the upgrade process you will need to backup your kamailio database using the following command: .. code-block:: bash cd /opt/ mysqldump kamailio > kamailio-bk.sql After you've backed up your database you can now uninstall dsiprouter v0.50 by running the following commands: .. code-block:: bash cd /opt/dsiprouter ./dsiprouter.sh uninstall Once the uninstall is complete you will need to either move or delete the /dsiprouter directory using the following command. .. code-block:: bash mv /dsiprouter /usr/local/src (moving directory) Alternatively: .. code-block:: bash rm -r /dsiprouter (removing directory) Installing dsiprouter v0.523 .. code-block:: bash cd /opt/ apt-get update apt-get install -y git curl cd /opt git clone -b v0.523 https://github.com/dOpensource/dsiprouter.git cd dsiprouter ./dsiprouter.sh install **Note: please take note of the credentials given after the script has completed.** After the install is completed you can now restore your kamailio database using the following command: .. code-block:: bash cd /opt/ mysql kamailio < kamailio-bk.sql mysql kamailio -e "alter table dsip_multidomain_mapping add column domain_list_hash varchar(255) after domain_list;" Now please restart dsiprouter using the following commands: .. code-block:: bash cd /opt/disprouter/ ./dsiprouter.sh restart After the install is complete and the dsiprouter service has been restarted, the login screen should now reflect v0.51 and you should be able to login with the dsiprouter credentials provided after the install completed. .. image:: images/dsip_v51.png :align: center ================================================ FILE: docs/source/user/upgrade_0.621_to_0.63.rst ================================================ In this section we will show you how to upgrade from 0.621 to 0.63. This is the first release to contain our new upgrade approach. The following steps will upgrade your Kamailio configuration from 0.621 to 0.63. .. code-block:: bash cd /opt/dsiprouter git stash git checkout v0.63 dsiprouter upgrade -rel 0.63 You should now be able to login to dSIPRouter and see that the new release has been applied. ================================================ FILE: docs/source/user/upgrading.rst ================================================ .. _upgrading: Upgrading dSIPRouter ==================== Auto Upgrade Feature -------------------- The dSIPRouter auto upgrade feature allows you to upgrade dSIPRouter from the User Interface (UI) and the command line (CLI). If you are upgrading from 0.70, 0.72, or 0.721 you can boostrap to the latest release to get the auto-upgrade feature. Upgrading to 0.73 doesn't require a dSIPRouter Core Subscription license because the auto-upgrade framework was not yet feature complete. However, all subsequent releases of dSIPRouter require a Core Subscription License to use the auto-upgrade feature. A core license can be purchased from the `dSIPRouter Marketplace `_. .. image:: images/upgrade_up_to_date.png :align: center Note that during upgrades, the hashed passwords have to be reset (such as dsiprouter admin password). You can find the new passwords displayed; either on the CLI after completion, or by viewing the upgrade log in the UI: .. image:: images/upgrading-log-01.png :align: center .. image:: images/upgrading-log-02.png :align: center Upgrade to 0.78 -------------------- Upgrading from version 0.77 to 0.78 can be done using the auto upgrade feature via the UI or CLI. To upgrade using the UI click the "Upgrade Now" button: .. image:: images/upgrade_available.png :align: center Or by using the CLI: .. code-block:: bash dsiprouter upgrade -rel 0.78 Upgrade to 0.77 -------------------- Upgrading from version 0.76 to 0.77 can be done using the auto upgrade feature via the UI or CLI. To upgrade using the UI click the "Upgrade Now" button: .. image:: images/upgrade_available.png :align: center Or by using the CLI: .. code-block:: bash dsiprouter upgrade -rel 0.77 Upgrade to 0.76 -------------------- Upgrading from version 0.75 to 0.76 can be done using the auto upgrade feature via the UI or CLI. To upgrade using the UI click the "Upgrade Now" button: .. image:: images/upgrade_available.png :align: center Or by using the CLI: .. code-block:: bash dsiprouter upgrade -rel 0.76 Upgrade to 0.75 --------------- Upgrading to 0.75 can be done from a version greater than or equal to 0.72. Upgrading from version 0.73 or 0.74 can be done using the UI: .. image:: images/upgrade_available.png :align: center Or by using the CLI: .. code-block:: bash dsiprouter upgrade To upgrade from 0.72 or 0.721 use the command below to bootstrap the upgrade: .. code-block:: bash curl -s https://raw.githubusercontent.com/dOpensource/dsiprouter/v0.75/resources/upgrade/v0.75/scripts/bootstrap.sh | bash Upgrade 0.73 to 0.74 -------------------- Upgrading from version 0.73 to 0.74 can be done using the auto upgrade feature via the UI or CLI. Upgrade 0.72x to 0.73 --------------------- Upgrading to 0.73 can be done from 0.72 or 0.721 by doing the following 1. SSH to your dSIPRouter Instance 2. Run the following command .. code-block:: bash curl -s https://raw.githubusercontent.com/dOpensource/dsiprouter/v0.73/resources/upgrade/v0.73/scripts/bootstrap.sh | bash 3. Login to the dSIPRouter UI to validate that the upgrade was successful. **Note**, if you are upgrading from a debian 9 system you must first upgrade OS versions to a supported version. See the `debian upgrade `_ documentation for more information. Note, if the upgrade fails you can purchase a dSIPRouter Core Subscription from the `dSIPRouter Marketplace `_. This will provide you with support hours so that we can help with the upgrade. Upgrade 0.70 to 0.721 --------------------- You can upgrade from 0.70 by doing the following 1. SSH to your dSIPRouter Instance 2. Run the following command .. code-block:: bash curl -s https://raw.githubusercontent.com/dOpensource/dsiprouter/v0.721/resources/upgrade/v0.721/scripts/bootstrap.sh | bash 3. Login to the dSIPRouter UI to validate that the upgrade was successful. Note, if the upgrade fails you can purchase a dSIPRouter Core Subscription which can be purchased from the `dSIPRouter Marketplace `_. This will provide you with support hours so that we can help with the upgrade. Upgrade 0.70 to 0.72 -------------------- This upgrade path is deprecated. Upgrade to the **0.721** release instead. Upgrade 0.644 to 0.70 --------------------- There is no automated upgrade available from 0.644 to 0.70. Support is available via a dSIPRouter Core Subscription which can be purchased from the `dSIPRouter Marketplace `_.This will provide you with support hours so that we can help with the upgrade. Upgrade 0.621 to 0.63 --------------------- .. include:: upgrade_0.621_to_0.63.rst Upgrade 0.522 to 0.523 ---------------------- .. include:: upgrade_0.522_to_0.523.rst Upgrade 0.50 to 0.51 -------------------- .. include:: upgrade_0.50_to_0.51.rst ================================================ FILE: docs/source/user/use-cases.rst ================================================ Common Use Cases ================ This section contains a list of the common use cases that are implemented using dSIPRouter SIP Trunking Using IP Authentication ------------------------------------ dSIPRouter enables an organization to start supporting SIP Trunking within minutes. Here are the steps to set it up using IP Authentication: 1. Login to dSIPRouter 2. Validate that your carrier is defined and specified in the Global Outbound Routes. If not, please follow the steps in :ref:`carrier_groups`_ and/or :ref:`global_outbound_routes`_ documentation. 3. Click on PBX's and Endpoints 4. Click "Add" 5. Select **IP Authentication** and fill in the fields specified below: - Friendly Name - IP Address of the PBX or Endpoint Device .. image:: images/sip_trunking_ip_auth.png :align: center 6. Click "Add" 7. Click "Reload" to make the change active. SIP Trunking Using Username/Password Authentication --------------------------------------------------- Here are the steps to set it up using Username/Password Authentication: 1. Login to dSIPRouter 2. Valiate that your carrier is defined and specified in the Global Outbound Routes. If not, please follow the steps in ``_ and/or ``_ documentation. 3. Click on PBX's and Endpoints 4. Click "Add" 5. Select **Username/Password Authentication** and fill in the fields specified below: - Friendly Name - Click the "Username/Password Auth" radio button - Enter a username - Enter a domain. Note, you can make up the domain name. If you don't specify one then the default domain will be used, which is sip.dsiprouter.org by default. - Enter a password .. image:: images/sip_trunking_credentials_auth.png :align: center 6. Click "Add" 7. Click "Reload" to make the change active. Using PJSIP Trunking - FreePBX Example --------------------------------------- The following screenshot(s) shows how to configure a PJSIP trunk within FreePBX for Username/Password Authentication. The first screenshot shows the General tab of the "pjsip settings" page: .. image:: images/sip_trunking_freepbx_pjsip_1.png :align: center The following fields needs to be entered ================== =============================================== Field Value ================== =============================================== Username Username from dSIPRouter PBX Setup Secret Password from dSIPRouter PBX Setup Authentication Outbound Registration Send SIP Server Domain name defined in the dSIPRouter PBX Setup SIP Server SIP port, which is 5060 in dSIPRouter ================== =============================================== .. image:: images/sip_trunking_freepbx_pjsip_2.png :align: center The following fields needs to be entered ================== ============================================================= Field Value ================== ============================================================= Outbound Proxy IP address of dSIPRouter - must include the "\;lr" at the end From Domain The name of the domain defined in the dSIPRouter PBX Setup ================== ============================================================= Using chanSIP Trunking - FreePBX Example ----------------------------------------- The following screenshot(s) shows how to configure a chanSIP trunk within FreePBX for Username/Password Authentication. 1. Log into FreePBX server 2. Click Connectivity→Trunks 3. Select Add SIP (chan_sip) Trunk 4. Under General tab enter The following fields needs to be entered ================== ===================================================================== Field Value ================== ===================================================================== Trunk Name Labeled in dsiprouter Outbound Caller ID Phone# that you want to appear during a outbound call (if applicable) ================== ===================================================================== .. image:: images/sipchan_general.png :align: center 5. Next you will enter the configurations under the SIP Settings. Here you will enter the SIP settings for outgoing calls by selecting the **Outbound** tab. You will need the following information: The following fields needs to be entered ================== ======================================= Field Value ================== ======================================= Host Username Secret Type peer Context from-trunk ================== ======================================= **The domain name has to be included and correct.** .. image:: images/chansip_outgoing.png :align: center NOTE:** Type underneath the in the Peer Details box if it does not appear. 6. Next you will enter the configurations for incoming by selecting the **Incoming** tab in the SIP Settings. Here you will enter the SIP settings for inbound calls. You will need: User Context: This is most often the account name or number your provider expects. In this example we named it "inbound". The following User Details needs to be entered: ================== ======================================= Field Value ================== ======================================= Host Insecure port,invite Type peer Context from-trunk ================== ======================================= .. image:: images/chansip_incoming.png :align: center In the **Register String** enter: :@. In this example it would be sipchantest@sip.dsiprouter.org:HFmx9u9N@demo.dsiprouter.org. **The domain name has to be included and correct.** .. image:: images/register_string.png :align: center 7. Click Submit 8. Be sure to click the **Apply Config** button after submitting to confirm. .. image:: images/apply_config_button.png :align: center You will now be able to see the new chanSIP added in the truck. .. image:: images/add_trunk.png :align: center 9. Next you will need to setup an outbound route. Select Connectivity→ Outbound Routes. Click the “+” sign to add a outbound route. In this tab you will need to enter: ================================= ====================================== Field Value ================================= ====================================== Route Name Type desired name Route CID Number you want to appear on caller ID Trunk Sequence for Matched Routes Trunk name (select from drop down box) ================================= ====================================== .. image:: images/outbound_routes_chansip.png :align: center 10. Click the Dial Patterns tab to set the dial patterns. If you are familiar with dial patterns, you can enter the dial patterns manually or you can click the Dial Patterans Wizard to auto create dial patterns if you like. You can choose 7, 10 or 11 digit patterns. Click Generate Routes. .. image:: images/chansip_dial_wizard.png :align: center Dial pattern is set to your preference. Prefixes are optional, not required. .. image:: images/chansip_dial_pattern.png :align: center 11. Click Submit and Apply Config button. Assuming you already have an extention created in your FreePBX, you can validate incoming/outgoing calls by configuring a softphone or a hard phone. Below is an example of the information you would enter if you use a softphone: In this example we are using Zoiper. Once you’ve downloaded Zoiper application on your PC or smart device you would enter the following to configure the soft phone: ================== ============================================== Field Value ================== ============================================== Username @ secret Hostname (should autofill) ================== ============================================== **Note** Skip Authenication and Outbound Proxy .. image:: images/chansip_zoiper.png :align: center You should now be able to make a inbound and outbound call successfully! Using SIP Trunking - FusionPBX IP Authenication ----------------------------------------------- The following screenshot(s) shows how to configure a SIP trunk within FusionPBX for IP Authenication. 1. Log into your FusionPBX. 2. Click Accounts --> Gateways-->Click the + sign to add a gateway/SIP Trunk. The only fields you will need to fill here are: - Gateway= Name of the SIP Trunk - Proxy= IP address of the SIP trunk - Register= Change to False because you are using IP authenication .. image:: images/sip_trunking_fusionpbx.png :align: center .. image:: images/sip_trunking_fusionpbx_2.png :align: center 3. Click Save 4. Click DialPlan-->Outboung Routes-->Click the + sign to add a outbound route. Here you will enter in the following fields: - Gateway= Name of the SIP Trunk - Alternate gateways (if applicable) - DialPlan Expression= 11d (standard setup in FusionPBX). To change the dialplan expression click on the dropdown box where it says "Shortcut to create the outbound dialplan entries for this Gateway." - Description= (if desired) 5. Click Save .. image:: images/outbound-routes_fusionpbx.png :align: center .. image:: images/outbound-routes_fusionpbx_2.png :align: center **NOTE** To make these changes global for ALL domains for this SIP Trunk: reopen outbound routes and change the Domain to Global and the Context to ${domain_name} as shown below. .. image:: images/fusionpbx_global_dialplan.png :align: center Using SIP Trunking - FusionPBX Username/Password Authenication -------------------------------------------------------------- The following screenshot(s) shows how to configure a SIP trunk within FusionPBX for Username/Password Authenication with IP Authenication off. 1. Log into your FusionPBX. 2. Click Accounts --> Gateways-->Click the + sign to add a gateway/SIP Trunk. The following fields you will need to fill here are: - Gateway= Name of the SIP Trunk - Username= specified by dSIPRouter provider - Password= specified by dSIPRouter provider - From Domain= Specified or set by default - Proxy= IP address of the SIP trunk - Register= set to True because you are using Username/Password authenication. .. image:: images/sip_trunking_fusionpbx_3.png :align: center .. image:: images/sip_trunking_fusionpbx_4.png :align: center 3. Click Save. 4. Click DialPlan-->Outboung Routes-->Click the + sign to add a outbound route. Here you will enter in the following fields: - Gateway= Name of the SIP Trunk - Alternate gateways (if applicable) - DialPlan Expression= 11d (standard setup in FusionPBX). To change the dialplan expression click on the dropdown box where it says "Shortcut to create the outbound dialplan entries for this Gateway." - Description= (if desired) .. image:: images/outbound-routes_fusionpbx.png :align: center .. image:: images/outbound-routes_fusionpbx_2.png :align: center 5. Click Save FusionPBX Hosting ----------------- Here we will demostrate how to setup dSIPRouter to enable hosting FusionPBX. We have built-in support for FusionPBX that allows domains to be dynamically pulled from FusionPBX. 1. Login to dSIPRouter 2. Click PBX(s) and EndPoints 3. Click ADD; enter the following fields - Friendly Name (opional) - IP address - IP Auth - Click to enable FusionPBX Domain Support - FusionPBX Database IP or Hostname 4. Click ADD .. image:: images/fusionpbx_hosting.png :align: center 5. Click Reload Kamailio. (when changes are made reload button will change to orange) .. image:: images/reload_button.png :align: center 6. Access your FusionPBX database via ssh. 7. Run the command as illustrated in the "Edit your PBX Detail" window as root on the FusionPBX server. Replace (not including the brackets) with the IP address of the dSIPRouter server you're adding. Command line will look simulair to the following picture. **NOTE** After you have entered the first two lines of commands you will not see a form of reply. If command is entered correctly it will return back to your root line. If the command line is incorrect you will receive a "command not found" error message. Recheck the command line and IP address. .. image:: images/fusionpbx_domain_support.png :align: center After the command is run you should now be able to see the domains of that PBX in dSIPRouter. .. image:: images/list_of_domain.png :align: center You can test PBX Hosting is valid by configuring a softphone or a hard phone. Below is an example using a softphone: Now that domains have been synced in dSIPRouter you are able to register a softphone. In this example we are using Zoiper. Once you've downloaded Zopier appliaction on your PC or smart device you would add: - username (extension@domainname) - password (password of that extension) - outbound proxy (IP address of the dSIPRouter) .. image:: images/zoiper_screenshot.png :align: center Provisioning and Registering a Polycom VVX Phone ------------------------------------------------ Now that domains have been synced in dSIPRouter you are able to register a endpoint/hard-phone. In this example we are using a Polycom VVX410 desk phone. 1. Log into your FusionPBX box a) Update the "outboundProxy.address" of the template with the IP address or hostname of the dSIPRouter in the provisioning editor. .. image:: images/outbound_proxy.png :align: center 2. Assign the phone to a template. .. image:: images/assign_template.png :align: center 3. Configuring the Provisioning Server section of the phone. Enter the appropriate information into the fields. a) Server Type (dSIPRouter uses HTTP/s by default) b) Server Address (dSIPRouter Address*) c) Server Username (device provisioning server name) d) Server Password 4. Click Save .. image:: images/provisioning_server.png :align: center 5. Reboot the phone * You will need to set http_domain_filter to false in FusionPBX. You can change this value by navagating to "Default Settings", search for http_domain_filter, and set the value to false. As shown below: .. image:: images/device_provisioning_http_domain_filter.png FreePBX Hosting - Pass Thru Authentication ------------------------------------------ Here we will demostrate how to setup dSIPRouter to enable hosting FreePBX using Pass Thru Authentication. FreePBX is designed to be a single tenant system or in other words, it was built to handle one SIP Domain. So, we use dSIPRouter to define a SIP Domain and we pass thru Registration info to the FreePBX server so that you don't have to change how authentication is done. However, this will only work for one FreePBX server. If you have a cluster of FreePBX servers then use "Local Subscriber Table" authentication. The value of having dSIPRouter in front of FreePBX is to provide you with flexibility. After setting this up you will have the ability upgrade or migrate users from one FreePBX instance to another without having to take an outage. The following video shows how to configure this. The steps to implement this is below the video. .. raw:: html
Steps to Implement ++++++++++++++++++ 1. Click PBX and Endpoints 2. Click Add .. image:: images/freepbx-pt-add-pbx.png :align: center 3. Reload Kamailio 4. Click Domains 5. Click Add .. image:: images/freepbx-pt-add-domain.png :align: center 6. Reload Kamailio 7. Register a phone via dSIPRouter - notice that we used the hostname of dSIPRouter as the Outbound Proxy. This forces the registration thru the proxy. .. image:: images/freepbx-pt-setup-softphone.png :align: center Microsoft Teams Direct Routing (SUBSCRIPTION REQUIRED) ------------------------------------------------------ dSIPRouter can act as an intermediary Session Border Controller between Microsoft Teams Direct Routing and your SIP provider or SIP servers. An instance of dSIPRouter can either be a single tenant configuration (like sbc.example.com) or multi-tenant under a single wildcard subdomain (like *.sbc.example.com where * is the tenant's name). .. image:: images/direct-routing-sbcs.png :align: center Steps to Implement ++++++++++++++++++ 1. `Buy a license `_ and follow the license installation instructions that are emailed to you. 2. Add any carriers you need for inbound and outbound routing, define appropriate routes. 3. Authorize your SBC's domain with Microsoft 365 by adding a TXT record starting with ms= per `Microsoft's documentation `_. Note: For multi-tenant use, authorizing the root subdomain or domain (if you use *.sbc.example.com, you would authorize sbc.example.com) should avoid the need to authorize each subdomain below this (like clientname.example.com) 4. Create a global admin user with proper Teams licensing associated with the domain (or for multi-tenant both the root subdomain (eg: sbc.example.com) and client's domain (eg: client.sbc.example.com)) 5. Add the Teams session border controller in `Teams Admin Center `_. Ensure the SIP port is correct (usually 5061) and the SBC is enabled! 6. `Install PowerShell `_ type pwsh then: .. code-block:: powershell Install-Module -Name MicrosoftTeams Import-Module MicrosoftTeams $userCredential = Get-Credential Connect-MicrosoftTeams -Credential $userCredential *Login Note*: If your using multi-factor authentication (MFA/2FA), log in by typing Connect-MicrosoftTeams *Debian 10 Note*: If you run into `this OpenSSL issue `_ , here is `a workaround `_! **Replace sbc.example.com, user@example.com and +13137175555** with your SBC's FQDN, the user's email address and their phone number (with + then country code, use +1 if you are in the North American Numbering Plan) .. code-block:: powershell Set-CsOnlinePstnUsage -Identity Global -Usage @{Add="US and Canada"} Set-CsOnlineVoiceRoute -Identity "LocalRoute" -NumberPattern ".*" -OnlinePstnGatewayList sbc.example.com New-CsOnlineVoiceRoutingPolicy "US Only" -OnlinePstnUsages "US and Canada" # This is suppose to stop MSTeams from using the Microsoft Dialing Plan and using the routing policies that was defined above Set-CsTenantHybridConfiguration -UseOnPremDialPlan $False # Apply and the US Only Voice Routing Policy to the user Grant-CsOnlineVoiceRoutingPolicy -Identity “user@example.com“ -PolicyName "US Only" # If it doesn’t return a value of US Only, then wait 15 minutes and try it again. It sometime takes a while for the policy to be ready. Get-CsOnlineUser “user@example.com" | select OnlineVoiceRoutingPolicy # Define a outgoing phone number (aka DID) and set Enterprise Voice and Voicemail Set-CsUser -Identity "user@example.com" -OnPremLineURI tel:+13137175555 -EnterpriseVoiceEnabled $true -HostedVoiceMail $true *Note*: Log out by typing ``Disconnect-MicrosoftTeams`` Credits to Mack at dSIPRouter for the SkypeForBusiness script and `this blog post `_ for helping me update these commands for the new MicrosoftTeams PowerShell module. Add a single Teams User +++++++++++++++++++++++ If you have an existing dSIPRouter SBC configured in Teams and have added a DID as an inbound route already, then run the commands below in PowerShell to add an additional user. **Replace user@example.com and +13137175555** with your SBC's FQDN, the user's email address and their phone number (with + then country code, use +1 if you are in the North American Numbering Plan) .. code-block:: powershell # Get Credentials, if using MFA/2FA just run Connect-MicrosoftTeams $userCredential = Get-Credential Connect-MicrosoftTeams -Credential $userCredential # Apply and the US Only Voice Routing Policy to the user Grant-CsOnlineVoiceRoutingPolicy -Identity “user@example.com“ -PolicyName "US Only" # Define a outgoing phone number (aka DID) and set Enterprise Voice and Voicemail Set-CsUser -Identity "user@example.com" -OnPremLineURI tel:+13137175555 -EnterpriseVoiceEnabled $true -HostedVoiceMail $true *Note*: Log out by typing ``Disconnect-MicrosoftTeams`` Configure STIR/SHAKEN (SUBSCRIPTION REQUIRED) --------------------------------------------- dSIPRouter enables an organization to start signing calls by enabling the STIR/SHAKEN module. This module will sign outbound calls and validate that inbound calls are signed. It also have the ability to add a prefix to the callerid if calls have an attestion of an A, B or C. You can also specify a callerid prefix if callers aren't validated. Lastly, you have the option to block invalidated callers. 1. Login to dSIPRouter 2. Purchase a license from the `dSIPRouter Marketplace `_ 3. Click System Settings -> License Manager 4. Add the license to the system 5. If testing, connect to your dSIPRouter instance using ssh, run the command below and enter the requested information to create a self-signed certificate .. code-block:: bash /opt/dsiprouter/resources/stir_shaken/generate_self_signed_cert.sh If not testing, obtain a valid STIR/SHAKEN certificate and place them in the /etc/dsiprouter/certs/stirshaken/ directory. For the purpose of these instructions, please name the certificate sp-cert.pem and name the key sp-key.pem 6. Check that the certificate can be accessed via https. Open a web browser and enter the following into the URL. This will be used by other VoIP servers to validate the signature of the the call. .. code-block:: bash https://:5000/stirshaken_certs/sp-cert.pem 7. Click System Settings -> STIR/SHAKEN 8. Slide the Disabled toggle to Enabled 9. Enter the Certificate URL from Step 6 10. Enter the Key Path, which by default will be .. code-block:: bash /etc/dsiprouter/certs/stirshaken/sp-key.pem 11. Click Save The STIR/SHAKEN page should look like this: .. image:: images/stir_shaken_settings.png :align: center ================================================ FILE: dsiprouter/almalinux/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter dnf install -y dnf-utils && dnf --setopt=group_package_types=mandatory,default,optional groupinstall -y "Development Tools" && dnf install -y firewalld sudo logrotate rsyslog perl \ python3.11 python3.11-pip python3.11-libs python3.11-devel python3.11-PyMySQL \ libev-devel util-linux postgresql-devel mariadb-devel openldap-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf systemctl enable nginx systemctl restart nginx # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion } function uninstall { dnf remove -y python36u\* dnf remove -y ius-release # Remove the repos rm -f /etc/dnf.repos.d/ius* rm -f /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY dnf clean all # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/almalinux/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # Install dependencies for dSIPRouter dnf install -y firewalld logrotate rsyslog perl curl python3 python3-devel libpq-devel \ libev-devel openldap-devel && dnf install -y --enablerepo=crb mariadb-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/amzn/2.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local NPROC=$(nproc) # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter yum install -y yum-utils && yum groupinstall --setopt=group_package_types=mandatory,default -y 'Development Tools' && yum install -y firewalld logrotate rsyslog perl libev-devel util-linux postgresql-devel \ bzip2-devel libffi-devel zlib-devel curl openldap-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi ## compile and install openssl v1.1.1 (workaround for amazon linux repo conflicts) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile openssl' return 1 } fi # python 3.8 or higher is required # if not installed already, install it now if [[ "$(python3 -V 2>/dev/null | cut -d ' ' -f 2)" != "3.9.18" ]]; then # installation / compilation never completed, start it now if [[ ! -d "${SRC_DIR}/Python-3.9.18" ]]; then ( cd ${SRC_DIR} && curl -s -o Python-3.9.18.tgz https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz && tar -xf Python-3.9.18.tgz && rm -f Python-3.9.18.tgz ) fi ( cd ${SRC_DIR} && cd Python-3.9.18/ && ./configure --enable-optimizations CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install required python version' return 1 } python3 -m pip install -U pip setuptools || { printerr 'Failed to update pip and setuptools' return 1 } fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld if (( $? != 0 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v1.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall() { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/centos/7.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { local NPROC # Install dependencies for dSIPRouter yum install -y yum-utils && yum groupinstall -y "Development Tools" && yum install -y firewalld logrotate rsyslog perl libev-devel util-linux postgresql-devel \ bzip2-devel libffi-devel zlib-devel curl openldap-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi NPROC=$(nproc) # python 3.8 or higher is required # if not installed already, install it now if [[ "$(python3 -V 2>/dev/null | cut -d ' ' -f 2)" != "3.9.18" ]]; then # installation / compilation never completed, start it now if [[ ! -d "${SRC_DIR}/Python-3.9.18" ]]; then ( cd ${SRC_DIR} && curl -s -o Python-3.9.18.tgz https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz && tar -xf Python-3.9.18.tgz && rm -f Python-3.9.18.tgz ) fi ( cd ${SRC_DIR} && cd Python-3.9.18/ && ./configure --enable-optimizations CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install required python version' return 1 } python3 -m pip install -U pip setuptools || { printerr 'Failed to update pip and setuptools' return 1 } fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v1.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/centos/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { local NPROC # Install dependencies for dSIPRouter dnf install -y yum-utils && dnf groupinstall -y "Development Tools" && dnf install -y firewalld logrotate rsyslog perl libev-devel util-linux postgresql-devel \ bzip2-devel libffi-devel zlib-devel curl openldap-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi NPROC=$(nproc) # python 3.8 or higher is required # if not installed already, install it now if [[ "$(python3 -V 2>/dev/null | cut -d ' ' -f 2)" != "3.9.18" ]]; then # installation / compilation never completed, start it now if [[ ! -d "${SRC_DIR}/Python-3.9.18" ]]; then ( cd ${SRC_DIR} && curl -s -o Python-3.9.18.tgz https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz && tar -xf Python-3.9.18.tgz && rm -f Python-3.9.18.tgz ) fi ( cd ${SRC_DIR} && cd Python-3.9.18/ && ./configure --enable-optimizations CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install required python version' return 1 } python3 -m pip install -U pip setuptools || { printerr 'Failed to update pip and setuptools' return 1 } fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/centos/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # Install dependencies for dSIPRouter dnf install -y firewalld logrotate rsyslog perl curl python3 python3-devel libpq-devel \ openldap-devel && dnf install -y --enablerepo=crb mariadb-devel libev-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/debian/10.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local NPROC=$(nproc) # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y build-essential curl pkg-config zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev \ libsqlite3-dev libreadline-dev libffi-dev libbz2-dev libpq-dev logrotate rsyslog perl sngrep libev-dev \ uuid-runtime tar && apt-get install -t bullseye -y firewalld python3 python3-venv python3-pip python3-dev libmariadb-dev && # Install libraries needed to install the python-ldap package apt-get install -y libsasl2-dev libldap2-dev libssl-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { rm -rf ${PYTHON_VENV} # TODO: this is dangerous without dependency management (i.e. our own DEB) # apt-get remove -y build-essential curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld # apt-get remove -y --allow-unauthenticated libmariadbclient-dev # apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime #apt-get remove -y build-essential curl python3 python3-pip python-dev libmariadbclient-dev libmariadb-client-lgpl-dev python-mysqldb libpq-dev firewalld # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/debian/11.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y build-essential curl python3 python3-pip python3-dev python3-venv \ libpq-dev firewalld sudo logrotate rsyslog perl sngrep libev-dev uuid-runtime \ libmariadb-dev pkg-config && # Install libraries needed to install the python-ldap package apt-get install -y libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { rm -rf ${PYTHON_VENV} # TODO: this is dangerous without dependency management (i.e. our own DEB) # apt-get remove -y build-essential curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld # apt-get remove -y --allow-unauthenticated libmariadbclient-dev # apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/debian/12.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y build-essential pkg-config python3-pip \ python3-dev libpq-dev python3-venv libev-dev libffi-dev libmariadb-dev \ curl python3 firewalld sudo logrotate rsyslog perl sngrep uuid-runtime && # Install libraries needed to install the python-ldap package apt-get install -y libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { rm -rf ${PYTHON_VENV} # TODO: this is dangerous without dependency management (i.e. our own DEB) # apt-get remove -y build-essential curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld # apt-get remove -y --allow-unauthenticated libmariadbclient-dev # apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/debian/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y build-essential curl python3 python3-pip python-dev python3-openssl \ python3-venv libpq-dev firewalld sudo apt-get install -y --allow-unauthenticated libmariadbclient-dev && apt-get install -y logrotate rsyslog perl sngrep libev-dev uuid-runtime libpq-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { # Uninstall dependencies for dSIPRouter cat ${DSIP_PROJECT_DIR}/gui/requirements.txt | xargs -n 1 $PYTHON_CMD -m pip uninstall --yes if (( $? == 1 )); then printerr "dSIPRouter uninstall failed or the libraries are already uninstalled" exit 1 else printdbg "DSIPRouter uninstall was successful" exit 0 fi apt-get remove -y build-essential curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld apt-get remove -y --allow-unauthenticated libmariadbclient-dev apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime #apt-get remove -y build-essential curl python3 python3-pip python-dev libmariadbclient-dev libmariadb-client-lgpl-dev python-mysqldb libpq-dev firewalld # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/dsip-net-cfg.py ================================================ #!/usr/bin/env python3 # # Handle any network configurations that need setup prior to the dSIPRouter services starting # Usage: ./dsip-net-cfg.py [cloud platform] [network mode] # # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import requests, subprocess, json import settings # get variables either from CLI args or settings file args = sys.argv[1:] try: cloud_platform = args[0] except IndexError: cloud_platform = settings.CLOUD_PLATFORM try: network_mode = args[1] network_mode = int(network_mode) except IndexError: network_mode = settings.NETWORK_MODE #=============================================================================================== # Use Case 1 #=============================================================================================== # set floating IP as default route if available # TODO: support more cloud providers #=============================================================================================== if cloud_platform == 'DO' and network_mode == 0: # NOTE: we are using ipv4 def hasFloatingIp(): resp = requests.get( 'http://169.254.169.254/metadata/v1/floating_ip/ipv4/active' ).text if resp == 'true': return True return False def getAnchorGateway(): return requests.get( 'http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/gateway' ).text def getAnchorIpAddr(): return requests.get( 'http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address' ).text def ipAddrToIface(ip_addr): result = subprocess.run( ['ip', '-4', '-json', 'address', 'show'], capture_output=True, text=True ) result_json = json.loads(result.stdout.strip()) for iface_info in result_json: for addr_info in iface_info['addr_info']: if addr_info['local'] == ip_addr: return iface_info['ifname'] raise Exception(f'No matching Iface for IP {ip_addr}') def getDefRoutes(): result = subprocess.run( ['ip', '-4', 'route', 'list', 'scope', 'global'], capture_output=True, text=True ) routes = result.stdout.strip().split('\n') def_routes = [] for route in routes: route_args = route.split(' ') if route_args[0] in ('default', '0.0.0.0/0'): def_routes.append(route_args) return def_routes def isIpDefRoute(check_ip): result = subprocess.run( ['ip', '-4', 'route', 'list', 'scope', 'global'], capture_output=True, text=True ) route = result.stdout.strip().split('\n')[0] found = False for route_arg in route.split(' '): if found and route_arg == check_ip: return True if route_arg == 'via': found = True return False if hasFloatingIp(): anchor_gw = getAnchorGateway() anchor_ip = getAnchorIpAddr() if not isIpDefRoute(anchor_gw): anchor_iface = ipAddrToIface(anchor_ip) def_routes = getDefRoutes() for route_args in def_routes: subprocess.run( ['ip', 'route', 'delete', *route_args], stdout=subprocess.DEVNULL ) subprocess.run( ['ip', 'route', 'add', 'default', 'via', anchor_gw, 'dev', anchor_iface], stdout=subprocess.DEVNULL ) #=============================================================================================== # Default Case #=============================================================================================== # do nothing #=============================================================================================== exit(0) ================================================ FILE: dsiprouter/dsip_completion.sh ================================================ #!/usr/bin/env bash ##################################### # dsiprouter command completion ##################################### _dsiprouter() { COMPREPLY=( ) local cur="${COMP_WORDS[$COMP_CWORD]}" local prev="${COMP_WORDS[$((COMP_CWORD-1))]}" local cmd="${COMP_WORDS[1]}" # available commands for dsiprouter declare -a cmds=( install uninstall clusterinstall upgrade start stop restart chown configurekam configuredsip configurertp renewsslcert configuresslcert installmodules resetpassword setcredentials licensemanager backup restore help -h --help version -v --version ) # available long options (with value) for each cmd declare -A llopts=( [install]='--database= --dsip-clusterid= --database-admin= --dsip-clustersync= --dsip-privkey= --with_lcr= --with_dev= --dmz= --network-mode=' [uninstall]='' [clusterinstall]='' [upgrade]='--dsip-clusterid= --release= --repo-url=' [start]='' [stop]='' [restart]='' [chown]='' [configurekam]='' [configuredsip]='' [configurertp]='' [renewsslcert]='' [configuresslcert]='' [installmodules]='' [resetpassword]='' [setcredentials]='--dsip-creds= --api-creds= --kam-creds= --mail-creds= --ipc-creds= --db-admin-creds= --session-creds=' [licensemanager]='' [backup]='' [restore]='' [help]='' [-h]='' [--help]='' [version]='' [-v]='' [--version]='' ) # available long options (without value) for each cmd declare -A lopts=( [install]='--all --kamailio --dsiprouter --rtpengine --dnsmasq' [uninstall]='--all --kamailio --dsiprouter --rtpengine' [clusterinstall]='--' [upgrade]='' [start]='--all --kamailio --dsiprouter --rtpengine' [stop]='--all --kamailio --dsiprouter --rtpengine' [restart]='--all --kamailio --dsiprouter --rtpengine' [chown]='' [configurekam]='' [configuredsip]='' [configurertp]='' [renewsslcert]='' [configuresslcert]='--force' [installmodules]='' [resetpassword]='--all --dsip-creds --api-creds --kam-creds --ipc-creds --force-instance-id' [setcredentials]='' [licensemanager]='' [backup]='' [restore]='' [help]='' [-h]='' [--help]='' [version]='' [-v]='' [--version]='' ) # available short options (with or without value) for each cmd declare -A sopts=( [install]='-debug -all -kam -dsip -rtp -dns -db -dsipcid -dbadmin -dsipcsync -dsipkey -with_lcr -with_dev -dmz -netm -homer' [uninstall]='-debug -all -kam -dsip -rtp' [clusterinstall]='-debug -i' [upgrade]='-debug -dsipcid -rel -url' [start]='-debug -all -kam -dsip -rtp' [stop]='-debug -all -kam -dsip -rtp' [restart]='-debug -all -kam -dsip -rtp' [chown]='-debug -certs -dnsmasq -nginx -kamailio -dsiprouter -rtpengine' [configurekam]='-debug' [configuredsip]='-debug' [configurertp]='-debug' [renewsslcert]='-debug' [configuresslcert]='-debug -f' [installmodules]='-debug' [resetpassword]='-debug -q -all -dc -ac -kc -ic -fid' [setcredentials]='-debug --dc -ac -kc -mc -ic -dac -sc' [licensemanager]='-debug -retrieve -list -activate -import -deactivate -clear -check' [backup]='-debug -f' [restore]='-debug -f' [help]='' [-h]='' [--help]='' [version]='' [-v]='' [--version]='' ) # determine command being completed and generate possible values if [[ $({ for x in ${cmds[*]}; do [[ "$x" == "$cmd" ]] && echo "yes"; done; }) == "yes" ]]; then # special use cases if [[ "${cmd}" == "clusterinstall" && "${prev}" == "--" ]]; then cmd="install" fi # normal opt matching case "$cur" in --*) COMPREPLY=( $(compgen -W "${lopts[$cmd]}" -- ${cur}) $(compgen -o nospace -W "${llopts[$cmd]}" -- ${cur}) ) ;; -*) COMPREPLY=( $(compgen -W "${sopts[$cmd]}" -- ${cur}) ) ;; esac else COMPREPLY=( $(compgen -W "${cmds[*]}" -- ${cur}) ) fi return 0 } complete -F _dsiprouter dsiprouter ================================================ FILE: dsiprouter/dsip_lib.sh ================================================ # NOTES: # contains utility functions and shared variables # should be sourced by an external script # exporting upon import removes need to import again in sub-processes ###################### # Imported Constants # ###################### # Ansi Colors export ESC_SEQ="\033[" export ANSI_NONE="${ESC_SEQ}39;49;00m" # Reset colors export ANSI_RED="${ESC_SEQ}1;31m" export ANSI_GREEN="${ESC_SEQ}1;32m" export ANSI_YELLOW="${ESC_SEQ}1;33m" export ANSI_CYAN="${ESC_SEQ}1;36m" export ANSI_WHITE="${ESC_SEQ}1;37m" # public IP's us for testing / DNS lookups in scripts export GOOGLE_DNS_IPV4="8.8.8.8" export GOOGLE_DNS_IPV6="2001:4860:4860::8888" # Constants for imported functions export DSIP_INIT_FILE=${DSIP_INIT_FILE:-"/lib/systemd/system/dsip-init.service"} export DSIP_SYSTEM_CONFIG_DIR=${DSIP_SYSTEM_CONFIG_DIR:-"/etc/dsiprouter"} export DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-$(dirname $(dirname $(readlink -f "$BASH_SOURCE")))} # reuse credential settings from python files (exported for later usage) export SALT_LEN=${SALT_LEN:-$(grep -m 1 -oP 'SALT_LEN[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export DK_LEN_DEFAULT=${DK_LEN_DEFAULT:-$(grep -m 1 -oP 'DK_LEN_DEFAULT[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export CREDS_MAX_LEN=${CREDS_MAX_LEN:-$(grep -m 1 -oP 'CREDS_MAX_LEN[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export HASH_ITERATIONS=${HASH_ITERATIONS:-$(grep -m 1 -oP 'HASH_ITERATIONS[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export HASHED_CREDS_ENCODED_MAX_LEN=${HASHED_CREDS_ENCODED_MAX_LEN:-$(grep -m 1 -oP 'HASHED_CREDS_ENCODED_MAX_LEN[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export AESCTR_CREDS_ENCODED_MAX_LEN=${AESCTR_CREDS_ENCODED_MAX_LEN:-$(grep -m 1 -oP 'AESCTR_CREDS_ENCODED_MAX_LEN[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export AES_CTR_NONCE_SIZE=${AES_CTR_NONCE_SIZE:-$(grep -m 1 -oP 'NONCE_SIZE[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} export AES_CTR_KEY_SIZE=${AES_CTR_KEY_SIZE:-$(grep -m 1 -oP 'KEY_SIZE[ \t]+=[ \t]+\K[0-9]+' ${DSIP_PROJECT_DIR}/gui/util/security.py)} # Flag denoting that these functions have been imported (verifiable in sub-processes) export DSIP_LIB_IMPORTED=1 ############################################## # Printing functions and String Manipulation # ############################################## # checks if stdin is null and sets STDIN_FIRST_BYTE to first character of stdin function isStdinNull() { local c read -r -d '' c } function printbold() { if [[ "$1" == "-n" ]]; then shift; printf "%b%s%b" "${ANSI_WHITE}" "$*" "${ANSI_NONE}" else printf "%b%s%b\n" "${ANSI_WHITE}" "$*" "${ANSI_NONE}" fi } export -f printbold function printerr() { if [[ "$1" == "-n" ]]; then shift; printf "%b%s%b" "${ANSI_RED}" "$*" "${ANSI_NONE}" else printf "%b%s%b\n" "${ANSI_RED}" "$*" "${ANSI_NONE}" fi } export -f printerr function printwarn() { if [[ "$1" == "-n" ]]; then shift; printf "%b%s%b" "${ANSI_YELLOW}" "$*" "${ANSI_NONE}" else printf "%b%s%b\n" "${ANSI_YELLOW}" "$*" "${ANSI_NONE}" fi } export -f printwarn function printdbg() { if [[ "$1" == "-n" ]]; then shift; printf "%b%s%b" "${ANSI_GREEN}" "$*" "${ANSI_NONE}" else printf "%b%s%b\n" "${ANSI_GREEN}" "$*" "${ANSI_NONE}" fi } export -f printdbg function pprint() { if [[ "$1" == "-n" ]]; then shift; printf "%b%s%b" "${ANSI_CYAN}" "$*" "${ANSI_NONE}" else printf "%b%s%b\n" "${ANSI_CYAN}" "$*" "${ANSI_NONE}" fi } export -f pprint function tolower() { [[ -p /dev/stdin ]] && ( read -r -d '' INPUT [[ -z "$INPUT" ]] && exit 1 tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' '[abcdefghijklmnopqrstuvwxyz]' <<<"$INPUT" exit 0 ) || { printf '%s' "$1" | tr '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' '[abcdefghijklmnopqrstuvwxyz]' } } export -f tolower function toupper() { [[ -p /dev/stdin ]] && ( read -r -d '' INPUT [[ -z "$INPUT" ]] && exit 1 tr '[abcdefghijklmnopqrstuvwxyz]' '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' <<<"$INPUT" exit 0 ) || { printf '%s' "$1" | tr '[abcdefghijklmnopqrstuvwxyz]' '[ABCDEFGHIJKLMNOPQRSTUVWXYZ]' } } export -f toupper function hextoint() { [[ -p /dev/stdin ]] && ( read -r -d '' INPUT [[ -z "$INPUT" ]] && exit 1 printf '%d' "0x$INPUT" 2>/dev/null exit 0 ) || { printf '%d' "0x$1" 2>/dev/null } } export -f hextoint ###################################### # Traceback / Debug helper functions # ###################################### function backtrace() { local DEPTN=${#FUNCNAME[@]} for ((i=1; i < ${DEPTN}; i++)); do local FUNC="${FUNCNAME[$i]}" local LINE="${BASH_LINENO[$((i-1))]}" local SRC="${BASH_SOURCE[$((i-1))]}" printf '%*s' $i '' # indent printerr "[ERROR]: ${FUNC}(), ${SRC}, line: ${LINE}" done } export -f backtrace function setErrorTracing() { set -o errtrace trap 'backtrace' ERR } export -f setErrorTracing ####################################### # Reusable / Shared Utility functions # ####################################### # TODO: we need to change the config getter/setter functions to use options parsing: # - when the value to set variable to is the empty string our functions error out # - ordering of filename and other options can be easily mistaken, which can set wrong values in config # - input validation would also be much easier if we switched added option parsing # $1 == attribute name # $2 == attribute value # $3 == python config file # $4 == -q (quote string) | -qb (quote byte string) function setConfigAttrib() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" if (( $# >= 4 )); then if [[ "$4" == "-q" ]]; then VALUE="'${VALUE}'" elif [[ "$4" == "-qb" ]]; then VALUE="b'${VALUE}'" fi fi sed -i -r -e "s|^$NAME[ \t]*=[ \t]*.*|$NAME = $VALUE|g" ${CONFIG_FILE} } export -f setConfigAttrib # $1 == credentials to encrypt # usage: # encryptCreds [options] # echo 'plaintext credentials' | encryptCreds [options] # options: # -pk <private key string> # -kf <private key file> # notes: # one of "-pk" or "-kf" options must be given # returns: # 0 == successfully encrypted credentials # 1 == failed encrypting credentials # outputs: # <nonce>:<ciphertext credentials> function encryptCreds() { local TMP PT_CREDS NONCE CT_HEX PK_HEX while (( $# > 0 )); do case "$1" in -pk) shift PK_HEX=$(xxd -p -l $AES_CTR_KEY_SIZE -c $AES_CTR_KEY_SIZE <<<"$1") shift ;; -kf) shift PK_HEX=$(xxd -p -l $AES_CTR_KEY_SIZE -c $AES_CTR_KEY_SIZE <"$1") shift ;; *) PT_CREDS="$1" shift ;; esac done [[ -z "$PK_HEX" ]] && return 1 # if credentials were piped to stdin use that instead of positional args if [[ -p /dev/stdin ]]; then read -r -d '' TMP [[ -n "$TMP" ]] && PT_CREDS="$TMP" fi # openssl version - depends on openssl, xxd, and sed NONCE=$(openssl rand -hex $AES_CTR_NONCE_SIZE) && CT_HEX=$( set -o pipefail; openssl enc -aes-256-ctr -e \ -iv "$NONCE" \ -K "$PK_HEX" \ < <(echo -n "$PT_CREDS") \ 2>/dev/null | xxd -p -c $AESCTR_CREDS_ENCODED_MAX_LEN ) (( $? != 0 )) && return 1 echo -n "${NONCE}${CT_HEX}" return 0 } export -f encryptCreds # $1 == attribute name # $2 == attribute value # $3 == python config file function encryptConfigAttrib() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" local CT_CREDS local PK_FILE=$(getConfigAttrib 'DSIP_PRIV_KEY' "$CONFIG_FILE") # openssl version - depends on openssl, xxd, and sed CT_CREDS=$(encryptCreds -kf "$PK_FILE" "$VALUE") setConfigAttrib "$NAME" "$CT_CREDS" "$CONFIG_FILE" -qb # python version - depends on dsiprouter's python3 venv and pycryptodome # ${PYTHON_CMD} <<EOPY #import os, sys #os.chdir('${DSIP_PROJECT_DIR}/gui') #sys.path.insert(0, '${DSIP_SYSTEM_CONFIG_DIR}/gui') #import settings #from shared import updateConfig #from util.security import AES_CTR #updateConfig(settings, {'$NAME': AES_CTR.encrypt('$VALUE')}) #EOPY } export -f encryptConfigAttrib # $1 == attribute name # $2 == python config file # output: attribute value function getConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" local VALUE=$(grep -oP '^(?!#)(?:'${NAME}')[ \t]*=[ \t]*\K(?:\w+\(.*\)[ \t\v]*$|[\w\d\.]+[ \t]*$|\{.*\}|\[.*\][ \t]*$|\(.*\)[ \t]*$|b?""".*"""[ \t]*$|'"b?'''.*'''"'[ \v]*$|b?".*"[ \t]*$|'"b?'.*'"')' ${CONFIG_FILE}) printf '%s' "${VALUE}" | perl -0777 -pe 's~^b?["'"'"']+(.*?)["'"'"']+$|(.*)~\1\2~g' } export -f getConfigAttrib # $1 == attribute name # $2 == python config file # output: attribute value decrypted # notes: if value is not encrypted the value is output instead function decryptConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" # this is the file not the raw key local PK_FILE=$(getConfigAttrib 'DSIP_PRIV_KEY' "$CONFIG_FILE") local NONCE NONCE_OFFSET local VALUE=$(grep -oP '^(?!#)(?:'${NAME}')[ \t]*=[ \t]*\K(?:\w+\(.*\)[ \t\v]*$|[\w\d\.]+[ \t]*$|\{.*\}|\[.*\][ \t]*$|\(.*\)[ \t]*$|b?""".*"""[ \t]*$|'"b?'''.*'''"'[ \v]*$|b?".*"[ \t]*$|'"b?'.*'"')' ${CONFIG_FILE}) # if value is not a byte literal it isn't encrypted if ! printf '%s' "${VALUE}" | grep -q -oP '(b""".*"""|'"b'''.*'''"'|b".*"|'"b'.*')"; then printf '%s' "${VALUE}" | perl -0777 -pe 's~^b?["'"'"']+(.*?)["'"'"']+$|(.*)~\1\2~g' else VALUE=$(perl -pe 's%b"""(.*)"""|'"b'''(.*)'''"'|b"(.*)"|'"b'(.*)'"'%\1\2\3\4%' <<<"$VALUE") # openssl version - depends on openssl and xxd NONCE_OFFSET=$((AES_CTR_NONCE_SIZE * 2)) NONCE=${VALUE:0:$NONCE_OFFSET} openssl enc -aes-256-ctr -d \ -iv "$NONCE" \ -K "$(xxd -p -l $AES_CTR_KEY_SIZE -c $AES_CTR_KEY_SIZE <"${PK_FILE}")" \ < <(echo -n "${VALUE:$NONCE_OFFSET}" | xxd -r -p -c $AESCTR_CREDS_ENCODED_MAX_LEN) \ 2>/dev/null # python version - depends on dsiprouter's python3 venv and pycryptodome # ${PYTHON_CMD} <<EOPY #import os, sys #os.chdir('${DSIP_PROJECT_DIR}/gui') #sys.path.insert(0, '${DSIP_SYSTEM_CONFIG_DIR}/gui') #import settings #from util.security import AES_CTR #print(AES_CTR.decrypt(settings.${NAME}), end='') #EOPY fi } export -f decryptConfigAttrib # $1 == attribute name # $2 == kamailio config file function enableKamailioConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" sed -i -r -e "s~#+(!(define|trydef|redefine)[[:space:]]? $NAME)~#\1~g" ${CONFIG_FILE} } export -f enableKamailioConfigAttrib # $1 == attribute name # $2 == kamailio config file function disableKamailioConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" sed -i -r -e "s~#+(!(define|trydef|redefine)[[:space:]]? $NAME)~##\1~g" ${CONFIG_FILE} } export -f disableKamailioConfigAttrib # $1 == name of defined url to change # $2 == value to change url to # $3 == kamailio config file # notes: will skip any cluster url attributes function setKamailioConfigDburl() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" perl -e "\$dburl='${VALUE}';" \ -0777 -i -pe 's~(#!(define|trydef|redefine)\s+?'"${NAME}"'\s+)['"'"'"](?!cluster\:).*['"'"'"]~\1"${dburl}"~g' ${CONFIG_FILE} } export -f setKamailioConfigDburl # $1 == name of subst/substdef/substdefs to change # $2 == value to change subst/substdef/substdefs to # $3 == kamailio config file function setKamailioConfigSubst() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" perl -e "\$name='$NAME'; \$value='$VALUE';" \ -i -pe 's~(#!subst(?:def|defs)?.*!${name}!).*(!.*)~\1${value}\2~g' ${CONFIG_FILE} } export -f setKamailioConfigSubst # $1 == name of global variable to change # $2 == value to change variable to # $3 == kamailio config file function setKamailioConfigGlobal() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" local REPLACE_TOKEN='__ABCDEFGHIJKLMNOPQRSTUVWXYZ__' perl -pi -e "s~^(${NAME}\s?=\s?)(?:(\"|')(.*?)(\"|')|\d+)(\sdesc\s(?:\"|').*?(?:\"|'))?~\1\2${REPLACE_TOKEN}\4\5~g" ${CONFIG_FILE} sed -i -e "s%${REPLACE_TOKEN}%${VALUE}%g" ${CONFIG_FILE} } export -f setKamailioConfigGlobal # $1 == attribute name # $2 == rtpengine config file function enableRtpengineConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" sed -i -r -e "s~^#+(${NAME}[ \t]*=[ \t]*.*)~\1~g" ${CONFIG_FILE} } export -f enableRtpengineConfigAttrib # $1 == attribute name # $2 == rtpengine config file function disableRtpengineConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" sed -i -r -e "s~^#*(${NAME}[ \t]*=[ \t]*.*)~#\1~g" ${CONFIG_FILE} } export -f disableRtpengineConfigAttrib # $1 == attribute name # $2 == rtpengine config file function getRtpengineConfigAttrib() { local NAME="$1" local CONFIG_FILE="$2" grep -oP '^(?!#)('${NAME}'[ \t]*=[ \t]*\K.*)' ${CONFIG_FILE} } export -f getRtpengineConfigAttrib # $1 == attribute name # $2 == value of attribute # $3 == rtpengine config file function setRtpengineConfigAttrib() { local NAME="$1" local VALUE="$2" local CONFIG_FILE="$3" perl -e "\$name='$NAME'; \$value='$VALUE';" \ -i -pe 's%^(?!#)(${name}[ \t]*=[ \t]*.*)%${name} = ${value}%g' ${CONFIG_FILE} } export -f setRtpengineConfigAttrib # output: Linux Distro name function getDistroName() { grep '^ID=' /etc/os-release 2>/dev/null | cut -d '=' -f 2 | cut -d '"' -f 2 } export -f getDistroName # output: Linux Distro version function getDistroVer() { grep '^VERSION_ID=' /etc/os-release 2>/dev/null | cut -d '=' -f 2 | cut -d '"' -f 2 } export -f getDistroVer # $1 == command to test # returns: 0 == true, 1 == false function cmdExists() { if command -v "$1" > /dev/null 2>&1; then return 0 else return 1 fi } export -f cmdExists # $1 == directory to check for in PATH # returns: 0 == found, 1 == not found function pathCheck() { case ":${PATH-}:" in *:"$1":*) return 0 ;; *) return 1 ;; esac } export -f pathCheck # returns: 0 == success, otherwise failure # notes: try to access the AWS metadata URL to determine if this is an AMI instance function isInstanceAMI() { local RET TOKEN # handle IMDS version selection automatically # ref: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html RET=$(curl -s -o /dev/null --connect-timeout 2 -w '%{http_code}' http://169.254.169.254/latest/meta-data/ami-id) if (( $RET == 200 )); then return 0 elif (( $RET == 401 )); then TOKEN=$(curl -s -X PUT --connect-timeout 2 -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token) curl -s -f --connect-timeout 2 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/ami-id return $? else return 1 fi } export -f isInstanceAMI # returns: 0 == success, otherwise failure # notes: try to access the DO metadata URL to determine if this is an Digital Ocean instance function isInstanceDO() { curl -s -f --connect-timeout 2 http://169.254.169.254/metadata/v1/id &>/dev/null return $? } export -f isInstanceDO # returns: 0 == success, otherwise failure # notes: try to access the GCE metadata URL to determine if this is an Google instance function isInstanceGCE() { curl -s -f --connect-timeout 2 -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/id &>/dev/null return $? } export -f isInstanceGCE # returns: 0 == success, otherwise failure # notes: try to access the MS Azure metadata URL to determine if this is an Azure instance function isInstanceAZURE() { curl -s -f --connect-timeout 2 -H "Metadata: true" "http://169.254.169.254/metadata/instance?api-version=2018-10-01" &>/dev/null return $? } export -f isInstanceAZURE # returns: 0 == success, otherwise failure # notes: try to access the DO metadata URL to determine if this is an VULTR instance function isInstanceVULTR() { curl -s -f --connect-timeout 2 http://169.254.169.254/v1/instanceid &>/dev/null return $? } export -f isInstanceDO # output: instance ID || blank string # notes: we try checking for exported instance variable to avoid querying again function getInstanceID() { local RET TOKEN if (( ${AWS_ENABLED:-0} == 1)); then RET=$(curl -s -o /dev/null -w '%{http_code}' http://169.254.169.254/latest/meta-data/ami-id 2>/dev/null) if (( $RET == 200 )); then curl -s -f http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null elif (( $RET == 401 )); then TOKEN=$(curl -s -X PUT --connect-timeout 2 -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token 2>/dev/null) curl -s -f --connect-timeout 2 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null else return 1 fi elif (( ${DO_ENABLED:-0} == 1 )); then curl -s -f http://169.254.169.254/metadata/v1/id 2>/dev/null elif (( ${GCE_ENABLED:-0} == 1 )); then curl -s -f -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/id 2>/dev/null elif (( ${AZURE_ENABLED:-0} == 1 )); then curl -s -f -H "Metadata: true" "http://169.254.169.254/metadata/instance/compute/vmId?api-version=2018-10-01" 2>/dev/null elif (( ${VULTR_ENABLED:-0} == 1 )); then curl -s -f http://169.254.169.254/v1/instanceid 2>/dev/null else if isInstanceAMI; then RET=$(curl -s -o /dev/null -w '%{http_code}' http://169.254.169.254/latest/meta-data/ami-id 2>/dev/null) if (( $RET == 200 )); then curl -s -f http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null elif (( $RET == 401 )); then TOKEN=$(curl -s -X PUT --connect-timeout 2 -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token 2>/dev/null) curl -s -f --connect-timeout 2 -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/instance-id 2>/dev/null else return 1 fi elif isInstanceDO; then curl -s -f http://169.254.169.254/metadata/v1/id 2>/dev/null elif isInstanceGCE; then curl -s -f -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/id 2>/dev/null elif isInstanceAZURE; then curl -s -f -H "Metadata: true" "http://169.254.169.254/metadata/instance/compute/vmId?api-version=2018-10-01" 2>/dev/null elif isInstanceVULTR; then curl -s -f http://169.254.169.254/v1/instanceid 2>/dev/null fi fi } export -f getInstanceID # summary: append a crontab entry # usage: cronAppend [options] <crontab entry> # options: -u <crontab user> function cronAppend() { local ENTRY USER_ARGS=() case "$1" in -u) shift USER_ARGS=(-u "$1") shift ENTRY="$1" ;; *) ENTRY="$1" ;; esac crontab ${USER_ARGS[@]} -l 2>/dev/null | { cat; echo "$ENTRY"; } | crontab ${USER_ARGS[@]} - } export -f cronAppend # summary: remove crontab entries matching search # usage: cronRemove [options] <search string> # options: -u <crontab user> function cronRemove() { local SEARCH USER_ARGS=() case "$1" in -u) shift USER_ARGS=(-u "$1") shift SEARCH="$1" ;; *) SEARCH="$1" ;; esac crontab ${USER_ARGS[@]} -l 2>/dev/null | grep -v -F -w "$SEARCH" | crontab ${USER_ARGS[@]} - } export -f cronRemove # $1 == ip to test # returns: 0 == success, 1 == failure # notes: regex credit to <https://helloacm.com> function ipv4Test() { local IP="$1" if [[ $IP =~ ^([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])$ ]]; then return 0 fi return 1 } export -f ipv4Test # $1 == ip to test # returns: 0 == success, 1 == failure # notes: regex credit to <https://helloacm.com> function ipv6Test() { local IP="$1" if [[ $IP =~ ^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$ ]]; then return 0 fi return 1 } export -f ipv6Test # $1 == ip to test # returns: 0 == success, 1 == failure function ipv4TestRFC1918() { local IP="$1" if [[ $IP =~ ^(10\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|172\.(1[6-9]|2[0-9]|3[01])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])|192\.168\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.([0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))$ ]]; then return 0 fi return 1 } export -f ipv4TestRFC1918 # output: all physical network interfaces on this machine function getPhysicalIfaces() { ( cd /sys/class/net && dirname */device; ) } export -f getPhysicalIfaces # $1 == [-4|-6] to force specific IP version # output: the internal IP for this system # notes: prints internal ip, or empty string if not available # notes: tries ipv4 first then ipv6 # returns: 0 on success, 1 if the interface was not found # TODO: currently we only check for the internal IP associated with the default interface/default route # this will fail if the internal IP is not assigned to the default interface/default route # not sure what networking scenarios that would be useful for, the community should provide us feedback on this function getInternalIP() { local INTERNAL_IP="" case "$1" in -4) local IPV4_ENABLED=1 local IPV6_ENABLED=0 ;; -6) local IPV4_ENABLED=0 local IPV6_ENABLED=1 ;; *) local IPV4_ENABLED=1 local IPV6_ENABLED=${IPV6_ENABLED:-0} ;; esac if (( ${IPV6_ENABLED} == 1 )); then INTERFACE=$(ip -br -6 a| grep UP | head -1 | awk {'print $1'}) else INTERFACE=$(ip -4 route show default | head -1 | awk '{print $5}') fi # Get the ip address without depending on DNS if (( ${IPV4_ENABLED} == 1 )); then # Marked for removal because it depends on DNS #INTERNAL_IP=$(ip -4 route get $GOOGLE_DNS_IPV4 2>/dev/null | head -1 | grep -oP 'src \K([^\s]+)') INTERNAL_IP=$(ip addr show $INTERFACE | grep 'inet ' | awk '{print $2}' | cut -f1 -d'/' | head -1) fi if (( ${IPV6_ENABLED} == 1 )) && [[ -z "$INTERNAL_IP" ]]; then # Marked for removal because it depends on DNS #INTERNAL_IP=$(ip -6 route get $GOOGLE_DNS_IPV6 2>/dev/null | head -1 | grep -oP 'src \K([^\s]+)') INTERNAL_IP=$(ip addr show $INTERFACE | grep 'inet6 ' | awk '{print $2}' | cut -f1 -d'/' | head -1) fi printf '%s' "$INTERNAL_IP" } export -f getInternalIP # $1 == [-4|-6] to force specific IP version # $2 == network interface # output: the IP for the given interface # notes: prints ip, or empty string if not available # notes: tries ipv4 first then ipv6 function getIP() { local IP="" case "$1" in -4) local IPV4_ENABLED=1 local IPV6_ENABLED=0 ;; -6) local IPV4_ENABLED=0 local IPV6_ENABLED=1 ;; *) local IPV4_ENABLED=1 local IPV6_ENABLED=${IPV6_ENABLED:-0} ;; esac # Use the provided interface or get the first interface - other then lo if ! [ -z $2 ]; then INTERFACE=$2 else if (( ${IPV6_ENABLED} == 1 )); then INTERFACE=$(ip -br -6 a| grep UP | head -1 | awk {'print $1'}) else INTERFACE=$(ip -4 route show default | head -1 | awk '{print $5}') fi fi # Get the ip address without depending on DNS if (( ${IPV4_ENABLED} == 1 )); then # Marked for removal because it depends on DNS #INTERNAL_IP=$(ip -4 route get $GOOGLE_DNS_IPV4 2>/dev/null | head -1 | grep -oP 'src \K([^\s]+)') IP=$(ip addr show $INTERFACE | grep 'inet ' | awk '{print $2}' | cut -f1 -d'/' | head -1) fi if (( ${IPV6_ENABLED} == 1 )) && [[ -z "$INTERNAL_IP" ]]; then # Marked for removal because it depends on DNS #INTERNAL_IP=$(ip -6 route get $GOOGLE_DNS_IPV6 2>/dev/null | head -1 | grep -oP 'src \K([^\s]+)') IP=$(ip addr show $INTERFACE | grep 'inet6 ' | awk '{print $2}' | cut -f1 -d'/' | head -1) fi printf '%s' "$IP" } export -f getIP # TODO: run requests in parallel and grab first good one (start ipv4 first) # this is what we are already doing in gui/util/networking.py # this would be much faster bcuz DNS exceptions take a while to handle # GNU parallel should not be used bcuz package support is not very good # this should instead use a pure bash version of GNU parallel, refs: # https://stackoverflow.com/questions/10909685/run-parallel-multiple-commands-at-once-in-the-same-terminal # https://www.cyberciti.biz/faq/how-to-run-command-or-code-in-parallel-in-bash-shell-under-linux-or-unix/ # https://unix.stackexchange.com/questions/305039/pausing-a-bash-script-until-previous-commands-are-finished # https://unix.stackexchange.com/questions/497614/bash-execute-background-process-whilst-reading-output # https://stackoverflow.com/questions/3338030/multiple-bash-traps-for-the-same-signal # $1 == [-4|-6] to force specific IP version # output: the external IP for this system # notes: prints external ip, or empty string if not available # notes: below we have measurements for average time of each service # over 10 non-cached requests, in seconds, round trip # # | External Service | Mean RTT | IP Protocol | # |:---------------------------------:|:--------:|:-----------:| # | https://icanhazip.com | 0.38080 | IPV4 | # | https://ipecho.net/plain | 0.39810 | IPV4 | # | https://myexternalip.com/raw | 0.51850 | IPV4 | # | https://api.ipify.org | 0.64860 | IPV4 | # | https://bot.whatismyipaddress.com | 0.69640 | IPV4 | # | https://icanhazip.com | 0.40190 | IPV6 | # | https://bot.whatismyipaddress.com | 0.72490 | IPV6 | # | https://ifconfig.co | 0.80290 | IPV6 | # | https://ident.me | 0.97620 | IPV6 | # | https://api6.ipify.org | 1.08510 | IPV6 | # function getExternalIP() { local EXTERNAL_IP="" TIMEOUT=5 local IPV4_URLS=( "https://icanhazip.com" "https://ipecho.net/plain" "https://myexternalip.com/raw" "https://api.ipify.org" "https://bot.whatismyipaddress.com" ) local IPV6_URLS=( "https://icanhazip.com" "https://bot.whatismyipaddress.com" "https://ifconfig.co" "https://ident.me" "https://api6.ipify.org" ) case "$1" in -4) local IPV4_ENABLED=1 local IPV6_ENABLED=0 ;; -6) local IPV4_ENABLED=0 local IPV6_ENABLED=1 ;; *) local IPV4_ENABLED=1 local IPV6_ENABLED=${IPV6_ENABLED:-0} ;; esac if (( ${IPV4_ENABLED} == 1 )); then for URL in ${IPV4_URLS[@]}; do EXTERNAL_IP=$(curl -4 -s --connect-timeout $TIMEOUT $URL 2>/dev/null) ipv4Test "$EXTERNAL_IP" && { printf '%s' "$EXTERNAL_IP"; return 0; } done fi if (( ${IPV6_ENABLED} == 1 )) && [[ -z "$EXTERNAL_IP" ]]; then for URL in ${IPV6_URLS[@]}; do EXTERNAL_IP=$(curl -6 -s --connect-timeout $TIMEOUT $URL 2>/dev/null) ipv6Test "$EXTERNAL_IP" && { printf '%s' "$EXTERNAL_IP"; return 0; } done fi return 1 } export -f getExternalIP # output: the internal FQDN for this system # notes: prints internal FQDN, or empty string if not available function getInternalFQDN() { printf '%s' "$(hostname -f 2>/dev/null || hostname 2>/dev/null)" } export -f getInternalFQDN # output: the external FQDN for this system # notes: prints external FQDN, or empty string if not available # notes: will use EXTERNAL_IP if available or look it up dynamically # notes: tries ipv4 first then ipv6 function getExternalFQDN() { local EXTERNAL_FQDN=$(dig @${GOOGLE_DNS_IPV4} +short -x ${EXTERNAL_IP:-$(getExternalIP -4)} 2>/dev/null | head -1 | sed 's/\.$//') if (( ${IPV6_ENABLED:-0} == 1 )) && [[ -z "$EXTERNAL_FQDN" ]]; then EXTERNAL_FQDN=$(dig @${GOOGLE_DNS_IPV6} +short -x ${EXTERNAL_IP6:-$(getExternalIP -6)} 2>/dev/null | head -1 | sed 's/\.$//') fi printf '%s' "$EXTERNAL_FQDN" } export -f getExternalFQDN # $1 == [-4|-6] to force specific IP version # $2 == interface # output: the internal IP CIDR for this system # notes: prints internal CIDR address, or empty string if not available # notes: tries ipv4 first then ipv6 function getInternalCIDR() { local PREFIX_LEN="" DEF_IFACE="" INTERNAL_IP="" #local IP=$(ip -4 route get $GOOGLE_DNS_IPV4 2>/dev/null | head -1 | grep -oP 'src \K([^\s]+)') case "$1" in -4) local IPV4_ENABLED=1 local IPV6_ENABLED=0 ;; -6) local IPV4_ENABLED=0 local IPV6_ENABLED=1 ;; *) local IPV4_ENABLED=1 local IPV6_ENABLED=${IPV6_ENABLED:-0} ;; esac if ! [ -z $2 ]; then INTERFACE=$2 fi if (( ${IPV4_ENABLED} == 1 )); then INTERNAL_IP=$(getIP -4 "$INTERFACE") if [[ -n "$INTERNAL_IP" ]]; then if [[ -n "$INTERFACE" ]]; then DEF_IFACE=$INTERFACE else DEF_IFACE=$(ip -4 route list scope global 2>/dev/null | perl -e 'while (<>) { if (s%^(?:0\.0\.0\.0|default).*dev (\w+).*$%\1%) { print; exit; } }') fi PREFIX_LEN=$(ip -4 route list | grep "$INTERNAL_IP" | perl -e 'while (<>) { if (s%^(?!0\.0\.0\.0|default).*/(\d+) .*src [\w/.]*.*$%\1%) { print; exit; } }') fi fi if (( ${IPV6_ENABLED} == 1 )) && [[ -z "$INTERNAL_IP" ]]; then INTERNAL_IP=$(getInternalIP -6) if [[ -n "$INTERNAL_IP" ]]; then DEF_IFACE=$(ip -6 route list scope global 2>/dev/null | perl -e 'while (<>) { if (s%^(?:::/0|default).*dev (\w+).*$%\1%) { print; exit; } }') PREFIX_LEN=$(ip -6 route list 2>/dev/null | grep "dev $DEF_IFACE" | perl -e 'while (<>) { if (s%^(?!::/0|default).*/(\d+) .*via [\w:/.]*.*$%\1%) { print; exit; } }') fi fi # make sure output is empty if error occurred if [[ -n "$INTERNAL_IP" && -n "$PREFIX_LEN" ]]; then printf '%s/%s' "$INTERNAL_IP" "$PREFIX_LEN" fi } export -f getInternalCIDR # $1 == host to check # returns: 0 == host is local, 1 == host is remote function isHostLocal() { local LOCAL_MATCH=$( joinwith '' '|' '' \ localhost \ $(hostname 2>/dev/null) \ $(hostname -f 2>/dev/null) \ $(ip -json address show | jq -r '.[].addr_info[].local') ) if [[ "$1" =~ $LOCAL_MATCH ]]; then return 0 fi return 1 } export -f isHostLocal # $1 == cmd as executed in systemd (by ExecStart=) # notes: take precaution when adding long running functions as they will block startup in boot order # notes: adding init commands on an AMI instance must not be long running processes, otherwise they will fail function addInitCmd() { local CMD=$(printf '%s' "$1" | sed -e 's|[\/&]|\\&|g') # escape string local TMP_FILE="${DSIP_INIT_FILE}.tmp" # sanity check, does the entry already exist? grep -q -oP "^ExecStart\=.*${CMD}.*" 2>/dev/null ${DSIP_INIT_FILE} && return 0 tac ${DSIP_INIT_FILE} | sed -r "0,\|^ExecStart\=.*|{s|^ExecStart\=.*|ExecStart=${CMD}\n&|}" | tac > ${TMP_FILE} mv -f ${TMP_FILE} ${DSIP_INIT_FILE} systemctl daemon-reload } export -f addInitCmd # $1 == string to match for removal (after ExecStart=) function removeInitCmd() { local STR=$(printf '%s' "$1" | sed -e 's|[\/&]|\\&|g') # escape string sed -i -r "\|^ExecStart\=.*${STR}.*|d" ${DSIP_INIT_FILE} systemctl daemon-reload } export -f removeInitCmd # $1 == service name (full name with target) to add dependency on dsip-init service # notes: only adds startup ordering dependency (service continues if init fails) # notes: the Before= section of init will link to an After= dependency on daemon-reload function addDependsOnInit() { local SERVICE="$1" # sanity check, does the entry already exist? grep -q -oP "^(Before\=|Wants\=).*${SERVICE}.*" 2>/dev/null ${DSIP_INIT_FILE} && return 0 perl -i -e "\$service='$SERVICE';" -pe 's%^(Before\=|Wants\=)(.*)%length($2)==0 ? "${1}${service}" : "${1}${2} ${service}"%ge;' ${DSIP_INIT_FILE} systemctl daemon-reload } export -f addDependsOnInit # $1 == service name (full name with target) to remove dependency on dsip-init service function removeDependsOnInit() { local SERVICE="$1" perl -i -e "\$service='$SERVICE';" -pe 's%^((?:Before\=|Wants\=).*?)( ${service}|${service} |${service})(.*)%\1\3%g;' ${DSIP_INIT_FILE} systemctl daemon-reload } export -f removeDependsOnInit # $1 == ip or hostname # $2 == port (optional) # returns: 0 == connection good, 1 == connection bad # NOTE: if port is not given a ping test will be used instead function checkConn() { local TIMEOUT=3 IP_ADDR="" PING_V6_SELECTOR="" if (( $# == 2 )); then timeout $TIMEOUT bash -c "< /dev/tcp/$1/$2" &>/dev/null; return $? else # NOTE: older versions of ping don't automatically detect IP address version IP_ADDR=$(getent hosts "$1" 2>/dev/null | awk '{ print $1 ; exit }') if ipv6Test "$IP_ADDR"; then PING_V6_SELECTOR="-6" fi ping $PING_V6_SELECTOR -q -W $TIMEOUT -c 3 "$1" &>/dev/null; return $? fi } export -f checkConn # $@ == ssh command to test # returns: 0 == ssh connected, 1 == ssh could not connect function checkSSH() { $@ -o ConnectTimeout=5 'exit 0' &>/dev/null return $? } export -f checkSSH # bake in the connection details for kamailio user/database # standardizes our usage and avoids various pitfalls with the client APIs # usage: withKamDB <mysql cmd> [mysql options/args] function withKamDB() { local CONN_OPTS=() local CMD="$1" shift [[ -n "$KAM_DB_HOST" ]] && CONN_OPTS+=( "--host=${KAM_DB_HOST}" ) [[ -n "$KAM_DB_PORT" ]] && CONN_OPTS+=( "--port=${KAM_DB_PORT}" ) [[ -n "$KAM_DB_USER" ]] && CONN_OPTS+=( "--user=${KAM_DB_USER}" ) [[ -n "$KAM_DB_PASS" ]] && CONN_OPTS+=( "--password=${KAM_DB_PASS}" ) if [[ "$CMD" == "mysql" ]]; then [[ -n "$KAM_DB_NAME" ]] && CONN_OPTS+=( "--database=${KAM_DB_NAME}" ) fi if [[ -p /dev/stdin ]]; then ${CMD} "${CONN_OPTS[@]}" "$@" </dev/stdin else ${CMD} "${CONN_OPTS[@]}" "$@" fi return $? } export -f withKamDB # bake in the connection details for root user/database # standardizes our usage and avoids various pitfalls with the client APIs # usage: withRootDBConn [options] <mysql cmd> [mysql options/args] # options: --db=<mysql db name> function withRootDBConn() { local TMP CMD local CONN_OPTS=() case "$1" in --db=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--database=${TMP}" ) shift CMD="$1" shift ;; *) CMD="$1" shift if [[ "$CMD" == "mysql" ]]; then [[ -n "$ROOT_DB_NAME" ]] && CONN_OPTS+=( "--database=${ROOT_DB_NAME}" ) fi ;; esac [[ -n "$ROOT_DB_HOST" ]] && CONN_OPTS+=( "--host=${ROOT_DB_HOST}" ) [[ -n "$ROOT_DB_PORT" ]] && CONN_OPTS+=( "--port=${ROOT_DB_PORT}" ) [[ -n "$ROOT_DB_USER" ]] && CONN_OPTS+=( "--user=${ROOT_DB_USER}" ) [[ -n "$ROOT_DB_PASS" ]] && CONN_OPTS+=( "--password=${ROOT_DB_PASS}" ) if [[ -p /dev/stdin ]]; then ${CMD} "${CONN_OPTS[@]}" "$@" </dev/stdin else ${CMD} "${CONN_OPTS[@]}" "$@" fi return $? } export -f withRootDBConn # allow passing in connection details to bake into the command # standardizes our usage and avoids various pitfalls with the client APIs # usage: withGivenDB [options] <mysql cmd> [mysql options/args] # options: --user=<mysql user> # --pass=<mysql password> # --host=<mysql host> # --port=<mysql port> # --db=<mysql db name> function withGivenDB() { local TMP local ARGS=() local CONN_OPTS=() while (( $# > 0 )); do case "$1" in --user=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--user=${TMP}" ) shift ;; --pass=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--password=${TMP}" ) shift ;; --host=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--host=${TMP}" ) shift ;; --port=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--port=${TMP}" ) shift ;; --db=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--database=${TMP}" ) shift ;; *) ARGS+=( "$1" ) shift ;; esac done if [[ -p /dev/stdin ]]; then ${ARGS[0]} "${CONN_OPTS[@]}" "${ARGS[@]:1}" </dev/stdin else ${ARGS[0]} "${CONN_OPTS[@]}" "${ARGS[@]:1}" fi return $? } export -f withGivenDB # usage: checkDB <database> # returns: 0 if DB exists, 1 otherwise function checkDB() { local CHECK=$( withRootDBConn mysql -sN \ -e "SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME='$1';" \ 2>/dev/null ) if [[ -n "$CHECK" ]]; then return 0 fi return 1 } export -f checkDB # usage: checkDBUserExists <user>@<host> # returns: 0 if user exists, 1 on DB connection failure, 2 if user does not exist function checkDBUserExists() { local USER=$(cut -d '@' -f 1 <<<"$1") local HOST=$(cut -d '@' -f 2 <<<"$1") return $( withRootDBConn mysql -sN -A \ -e "SELECT IF(EXISTS(SELECT 1 FROM mysql.user WHERE User='${USER}' AND Host='${HOST}'),0,2);" \ 2>/dev/null ) } export -f checkDBUserExists # usage: dumpDB <databases> # output: dumped database as sql (redirect as needed) # returns: 0 on success, non zero otherwise function dumpDB() { withRootDBConn mysqldump --single-transaction --opt --routines --triggers --hex-blob --databases "$@" \ 2>/dev/null | sed -r \ -e 's|DEFINER=[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']@[`"'"'"'][a-zA-Z0-9_%]*[`"'"'"']||g' \ -e 's|ENGINE=MyISAM|ENGINE=InnoDB|g' \ 2>/dev/null return ${PIPESTATUS[0]} } export -f dumpDB # usage: dumpDBUser <user>@<host>|<user> # output: dumped database user as sql (redirect as needed) # returns: 0 on success, non zero otherwise function dumpDBUser() { USER=$(printf '%s' "$1" | cut -s -d '@' -f 1) if [[ -n "$USER" ]]; then shift USER="$1" fi (withRootDBConn mysql -sN -A \ -e "SELECT DISTINCT CONCAT('SHOW GRANTS FOR ''',user,'''@''',host,''';') FROM mysql.user WHERE user='${USER}'" 2>/dev/null \ | withRootDBConn mysql -sN -A 2>/dev/null \ | sed 's/$/;/g' \ | awk '!x[$0]++' && printf '%s\n' 'FLUSH PRIVILEGES;'; exit ${PIPESTATUS[0]}; ) 2>/dev/null return $? } export -f dumpDBUser # usage: # sqlAsTransaction [options] <database> <sql statements> # echo 'sql statements' | sqlAsTransaction [options] <database> # options: # --user=<mysql user> # --pass=<mysql password> # --host=<mysql host> # --port=<mysql port> # --db=<mysql database name> # returns: # 0 == statements executed successfully # 1 == error occurred running statements function sqlAsTransaction() { local TMP local DB_SET=0 local MYSQL_USER='' MYSQL_PASS='' MYSQL_HOST='' MYSQL_PORT='' local OPTS=() SQL_STATEMENTS=() while (( $# > 0 )); do case "$1" in --user=*|--pass=*|--host=*|--port=*) OPTS+=( "$1" ) shift ;; --db=*) OPTS+=( "$1" ) shift DB_SET=1 ;; *) # all positional args are part of SQL query SQL_STATEMENTS+=( "$1" ) shift ;; esac done # creating the procedure will fail if we don't select a database # TODO: do we need this now that we switched to prepared statements? (( $DB_SET == 0 )) && OPTS+=( "--db=mysql" ) # if query was piped to stdin use that instead of positional args if [[ -p /dev/stdin ]]; then read -r -d '' TMP [[ -n "$TMP" ]] && SQL_STATEMENTS=( "$TMP" ) fi cat <<EOF | withGivenDB "${OPTS[@]}" mysql -s START TRANSACTION; ${SQL_STATEMENTS[@]} SET @end_transaction = (SELECT IF(@@error_count > 0, "ROLLBACK;", "COMMIT;")); PREPARE stmt FROM @end_transaction; EXECUTE stmt; EOF return $? } export -f sqlAsTransaction # usage: parseDBConnURI <field> <connection uri> # field: -user # -pass # -host # -port # -name # output: the selected field of the connection uri # returns: # 0 == field set # 1 == field not set # 255 == error parsing function parseDBConnURI() { local GROUP case "$1" in -user) shift GROUP=0 ;; -pass) shift GROUP=1 ;; -host) shift GROUP=2 ;; -port) shift GROUP=3 ;; -name) shift GROUP=4 ;; *) # not valid field return 1 ;; esac perl -e ' @matches = ($ARGV[0] =~ m"^[\t\r\n\v\f]*(?:([^:]+)?(?::([^@]+)?)?@)?([^:]+)(?::([^/]+)?)?(?:/([^\t\r\n\v\f]+))?[\t\r\n\v\f]*$"); if ($matches[$ARGV[1]] ne "") { print $matches[$ARGV[1]]; exit 0; } else { exit 1; } ' "$1" "$GROUP" return $? } export -f parseDBConnURI # usage: urandomChars [options] [args] # options: -f <filter> == characters to allow # args: $1 == number of characters to get # output: string of random printable characters function urandomChars() { local LEN=32 FILTER="a-zA-Z0-9" while (( $# > 0 )); do # last arg is length if (( $# == 1 )); then LEN="$1" shift break fi case "$1" in # user defined filter -f) shift FILTER="$1" shift ;; # not valid option skip *) shift ;; esac done tr -dc "$FILTER" </dev/urandom | dd if=/dev/stdin of=/dev/stdout bs=1 count="$LEN" 2>/dev/null } export -f urandomChars # $1 == prefix for each arg # $2 == delimiter between args # $3 == suffix for each arg # $@ == args to join function joinwith() { local START="$1" IFS="$2" END="$3" ARR=() shift;shift;shift for VAR in "$@"; do ARR+=("${START}${VAR}${END}") done echo "${ARR[*]}" } export -f joinwith # $1 == rpc command # $@ == rpc args # output: output returned from kamailio # returns: curl return code (ref: man 1 curl) # note: curl will timeout after 3 seconds function sendKamCmd() { local CMD="$1" PARAMS="" KAM_API_URL='http://127.0.0.1:5060/api/kamailio' shift local ARGS=("$@") if [[ "$CMD" == "cfg.seti" ]]; then local LAST_ARG="${ARGS[$#-1]}" unset "ARGS[$#-1]" PARAMS='['$(joinwith '"' ',' '"' "${ARGS[@]}")",${LAST_ARG}"']' else PARAMS='['$(joinwith '"' ',' '"' "$@")']' fi curl -s -m 3 -X GET -d '{"method": "'"${CMD}"'", "jsonrpc": "2.0", "id": 1, "params": '"${PARAMS}"'}' ${KAM_API_URL} } export -f sendKamCmd # TODO: improve performance of openssl native version and swap it out function hashCreds() { local CREDS SALT DK_LEN # we use system python3 if dsiprouter python venv does not yet exist if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then local PYTHON_CMD="$PYTHON_CMD" else local PYTHON_CMD="python3" fi # grab credentials from stdin if provided if [[ -p /dev/stdin ]]; then CREDS=$(</dev/stdin) fi while (( $# > 0 )); do # last arg is credentials if (( $# == 1 )) && [[ -z "$CREDS" ]]; then CREDS="$1" shift break fi case "$1" in # user defined salt -s) shift SALT="$1" shift ;; # user defined derived key length -l) shift DK_LEN="$1" shift ;; # not valid option skip *) shift ;; esac done # defaults if not set by args SALT=${SALT:-$(urandomChars -f 'a-fA-F0-9' $SALT_LEN)} DK_LEN=${DK_LEN:-$DK_LEN_DEFAULT} # python native version # no external dependencies other than vanilla python3 ${PYTHON_CMD} <<EOPYTHON import hashlib,binascii creds='$CREDS'.encode('utf-8') salt='$SALT'.encode('utf-8') hash=hashlib.pbkdf2_hmac('sha512', creds, salt, iterations=$HASH_ITERATIONS, dklen=$DK_LEN) + salt print(binascii.hexlify(hash).decode('utf-8')) EOPYTHON # bash native version # currently too slow for production usage #${DSIP_PROJECT_DIR}/dsiprouter/pbkdf2.sh 'sha512' "$CREDS" "$SALT" "$HASH_ITERATIONS" 4 } export -f hashCreds # args: # $1 == version 1 to compare from # $2 == compare operation # $3 == version 2 to compare against # returns: # 0 == version comparison is true # 1 == version comparison is false # 255 == comparison failed function versionCompare() { ( set -e trap 'exit 255' ERR if [[ ! "$1$2" =~ [\.0-9]+ ]]; then echo "${FUNCNAME}(): invalid version" exit 255 fi local IFS=. local IDX LEN local VER1=( $1 ) VER2=( $3 ) if (( ${#VER1[@]} >= ${#VER2[@]} )); then LEN=${#VER1[@]} else LEN=${#VER2[@]} fi for (( IDX=0; IDX<$LEN; IDX++)); do VER1[$IDX]=${VER1[$IDX]:-0} VER2[$IDX]=${VER2[$IDX]:-0} done case "$2" in lt) [[ "${VER1[@]}" == "${VER2[@]}" ]] && exit 1 for (( IDX=0; IDX<$LEN; IDX++)); do (( ${VER1[$IDX]} > ${VER2[$IDX]} )) && exit 1 done exit 0 ;; lteq) for (( IDX=0; IDX<$LEN; IDX++)); do (( ${VER1[$IDX]} <= ${VER2[$IDX]} )) || exit 1 done exit 0 ;; eq) for (( IDX=0; IDX<$LEN; IDX++)); do (( ${VER1[$IDX]} == ${VER2[$IDX]} )) || exit 1 done exit 0 ;; gt) [[ "${VER1[@]}" == "${VER2[@]}" ]] && exit 1 for (( IDX=0; IDX<$LEN; IDX++)); do (( ${VER1[$IDX]} < ${VER2[$IDX]} )) && exit 1 done exit 0 ;; gteq) for (( IDX=0; IDX<$LEN; IDX++)); do (( ${VER1[$IDX]} >= ${VER2[$IDX]} )) || exit 1 done exit 0 ;; *) echo "${FUNCNAME}(): invalid comparator" exit 255 ;; esac ) } export -f versionCompare # $1 == repo path function getGitTagFromShallowRepo() { ( cd "$1" 2>/dev/null && git config --get remote.origin.fetch | cut -d ':' -f 2- | rev | cut -d '/' -f 1 | rev ) } export -f getGitTagFromShallowRepo ================================================ FILE: dsiprouter/pbkdf2.sh ================================================ #!/usr/bin/env bash # Inspired By: https://gist.github.com/grondilu/abe50de34f6c838dbc9388fe797ea4e4 # Credit to: https://github.com/stayradiated/pbkdf2-sha512 # Copyright (c) 2014, JP Richardson Copyright (c) 2010-2011 Intalio Pte, All Rights Reserved # Original Author: Lucien Grondin, 2022 declare hash_name="$1" key_str="$2" salt_str="$3" declare -ai key salt u t block1 dk declare -i hLen="$(openssl dgst "-$hash_name" -binary <<<"foo" |wc -c)" declare -i iterations=$4 dkLen=${5:-hLen} declare -i i j k l=$(( (dkLen+hLen-1)/hLen )) for ((i=0; i<${#key_str}; i++)); do printf -v "key[$i]" "%d" "'${key_str:i:1}" done for ((i=0; i<${#salt_str}; i++)); do printf -v "salt[$i]" "%d" "'${salt_str:i:1}" done block1=(${salt[@]} 0 0 0 0) step() { printf '%02x' "$@" | xxd -p -r | openssl dgst -"$hash_name" -hmac "$key_str" -binary | xxd -p -c 1 | sed 's/^/0x/' } for ((i=1;i<=l;i++)); do for k in {0..3}; do block1[${#salt[@]}+$k]=$((i >> (8*(3-k)) & 0xff)) done u=($(step "${block1[@]}")) t=(${u[@]}) for ((j=1; j<iterations; j++)); do u=($(step "${u[@]}")) for ((k=0; k<hLen; k++)); do t[k]=$((t[k]^u[k])) done done dk+=(${t[@]}) done printf "%02x" "${dk[@]:0:dkLen}"; echo ''; ================================================ FILE: dsiprouter/rhel/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter yum remove -y rs-epel-release* && yum install -y yum-utils && yum --setopt=group_package_types=mandatory,default,optional groupinstall -y "Development Tools" && yum install -y firewalld python36 python36-libs python36-devel python36-pip MySQL-python sudo \ python36-virtualenv logrotate rsyslog perl libev-devel util-linux postgresql-devel mariadb-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf systemctl enable nginx systemctl restart nginx # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { # Uninstall dependencies for dSIPRouter yum remove -y python36u\* yum remove -y ius-release # Remove the repos rm -f /etc/yum.repos.d/ius* rm -f /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY yum clean all # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/rhel/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # Install dependencies for dSIPRouter { dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-$(uname -m)-rpms || dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-rhui-rpms } && dnf install -y firewalld logrotate rsyslog perl curl python3 python3-devel libpq-devel \ libev-devel openldap-devel mariadb-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/rocky/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter dnf remove -y rs-epel-release* && dnf install -y dnf-utils && dnf --setopt=group_package_types=mandatory,default,optional groupinstall -y "Development Tools" && dnf install -y firewalld sudo logrotate rsyslog perl \ python3.11 python3.11-pip python3.11-libs python3.11-devel python3.11-PyMySQL \ libev-devel util-linux postgresql-devel mariadb-devel openldap-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf systemctl enable nginx systemctl restart nginx # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { dnf remove -y python36u\* dnf remove -y ius-release # Remove the repos rm -f /etc/dnf.repos.d/ius* rm -f /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY dnf clean all # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/rocky/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # Install dependencies for dSIPRouter dnf install -y firewalld logrotate rsyslog perl curl python3 python3-devel libpq-devel \ libev-devel openldap-devel && dnf install -y --enablerepo=crb mariadb-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # give dsiprouter permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # Enable and start firewalld systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter # add hook to bash_completion in the standard debian location echo '. /usr/share/bash-completion/bash_completion' > /etc/bash_completion return 0 } function uninstall { rm -rf ${PYTHON_VENV} # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/sudoers.d/99-dsiprouter ================================================ Cmnd_Alias DSIP_UPGRADE_CMDS = /usr/bin/dsiprouter upgrade, /usr/bin/dsiprouter upgrade * Cmnd_Alias DSIP_RESTART_CMDS = /usr/bin/dsiprouter restart, /usr/bin/dsiprouter restart * Cmnd_Alias DSIP_BACKUP_CMDS = /usr/bin/dsiprouter backup, /usr/bin/dsiprouter backup * Cmnd_Alias DSIP_RESTORE_CMDS = /usr/bin/dsiprouter restore, /usr/bin/dsiprouter restore * dsiprouter ALL=(ALL) NOPASSWD:SETENV: DSIP_UPGRADE_CMDS dsiprouter ALL=(ALL) NOPASSWD: DSIP_RESTART_CMDS dsiprouter ALL=(ALL) NOPASSWD: DSIP_BACKUP_CMDS dsiprouter ALL=(ALL) NOPASSWD: DSIP_RESTORE_CMDS ================================================ FILE: dsiprouter/systemd/dsiprouter-v1.service ================================================ # Utilzing Type=simple while main process is still single threaded # Main process is managed via signals from systemd (stop/reload data/etc..) # When stopping the service a SIGTERM is sent immediately, to the main process # If, after 3s dsiprouter is still running, sends SIGKILL to all process # # The following are updated dynamically on install and should not be changed here: # Environment=DSIP_PROJECT_DIR # Environment=DSIP_RUN_DIR # Environment=DSIP_SYSTEM_CONFIG_DIR [Unit] Description=dSIPRouter Service DefaultDependencies=no Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=rsyslog.service mariadb.service nginx.service kamailio.service Wants=nginx.service mariadb.service kamailio.service StartLimitInterval=30 StartLimitBurst=3 [Service] Type=simple PermissionsStartOnly=true User=dsiprouter Group=dsiprouter Environment='DSIP_PROJECT_DIR=/opt/dsiprouter' Environment='DSIP_RUN_DIR=/run/dsiprouter' Environment='DSIP_SYSTEM_CONFIG_DIR=/etc/dsiprouter' # PIDFile requires an absolute path PIDFile=/run/dsiprouter/dsiprouter.pid # ExecStart* requires an absolute path for the program ExecStartPre=/usr/bin/dsiprouter chown -dsiprouter ExecStart=/usr/bin/dsiprouter exec TimeoutStopSec=3 KillMode=mixed Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: dsiprouter/systemd/dsiprouter-v2.service ================================================ # Utilzing Type=simple while main process is still single threaded # Main process is managed via signals from systemd (stop/reload data/etc..) # When stopping the service a SIGTERM is sent immediately, to the main process # If, after 3s dsiprouter is still running, sends SIGKILL to all process # # The following are updated dynamically on install and should not be changed here: # Environment=DSIP_PROJECT_DIR # Environment=DSIP_RUN_DIR # Environment=DSIP_SYSTEM_CONFIG_DIR [Unit] Description=dSIPRouter Service DefaultDependencies=no Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=rsyslog.service mariadb.service nginx.service kamailio.service Wants=nginx.service mariadb.service kamailio.service StartLimitIntervalSec=30 StartLimitBurst=3 [Service] Type=simple User=dsiprouter Group=dsiprouter Environment='DSIP_PROJECT_DIR=/opt/dsiprouter' Environment='DSIP_RUN_DIR=/run/dsiprouter' Environment='DSIP_SYSTEM_CONFIG_DIR=/etc/dsiprouter' EnvironmentFile=-/etc/defaults/dsiprouter.conf EnvironmentFile=-/etc/defaults/dsiprouter.d/*.conf # PIDFile requires an absolute path PIDFile=/run/dsiprouter/dsiprouter.pid # ExecStart* requires an absolute path for the program ExecStartPre=!-/usr/bin/dsiprouter chown -dsiprouter ExecStart=/usr/bin/dsiprouter exec TimeoutStopSec=3 KillMode=mixed Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: dsiprouter/ubuntu/20.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter apt-get install -y build-essential curl python3 python3-pip python-dev python3-openssl python3-mysqldb \ python3-venv libpq-dev firewalld sudo && apt-get install -y --allow-unauthenticated libmariadbclient-dev && apt-get install -y logrotate rsyslog perl sngrep libev-dev uuid-runtime libpq-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { # Uninstall dependencies for dSIPRouter cat ${DSIP_PROJECT_DIR}/gui/requirements.txt | xargs -n 1 $PYTHON_CMD -m pip uninstall --yes if (( $? == 1 )); then printerr "dSIPRouter uninstall failed or the libraries are already uninstalled" exit 1 else printdbg "DSIPRouter uninstall was successful" exit 0 fi apt-get remove -y build-essential curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld apt-get remove -y --allow-unauthenticated libmariadbclient-dev apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/ubuntu/22.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install dependencies for dSIPRouter apt-get install -y build-essential curl python3 python3-pip python-dev python3-openssl python3-mysqldb \ python3-venv libpq-dev firewalld sudo && apt-get install -y --allow-unauthenticated libmariadbclient-dev && apt-get install -y logrotate rsyslog perl sngrep libev-dev uuid-runtime libpq-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { apt-get remove -y curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld apt-get remove -y --allow-unauthenticated libmariadbclient-dev apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter/ubuntu/24.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create dsiprouter user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dsiprouter &>/dev/null; groupdel dsiprouter &>/dev/null useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y build-essential pkg-config python3-pip \ python3-dev libpq-dev python3-venv libev-dev libffi-dev default-libmysqlclient-dev \ curl python3 firewalld sudo logrotate rsyslog perl sngrep uuid-runtime && # Install libraries needed to install the python-ldap package apt-get install -y libsasl2-dev python-dev-is-python3 libldap2-dev libssl-dev if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall for DSIP_PORT firewall-cmd --zone=public --add-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # TODO: figure out why compiling ultradict with the other deps hangs python3 -m venv --upgrade-deps ${PYTHON_VENV} && ${PYTHON_CMD} -m pip install UltraDict && ${PYTHON_CMD} -m pip install -r ${DSIP_PROJECT_DIR}/gui/requirements.txt if (( $? == 1 )); then printerr "Failed installing required python libraries" return 1 fi # setup dsiprouter nginx configs perl -e "\$dsip_port='${DSIP_PORT}'; \$dsip_unix_sock='${DSIP_UNIX_SOCK}'; \$dsip_ssl_cert='${DSIP_SSL_CERT}'; \$dsip_ssl_key='${DSIP_SSL_KEY}';" \ -pe 's%DSIP_UNIX_SOCK%${dsip_unix_sock}%g; s%DSIP_PORT%${dsip_port}%g; s%DSIP_SSL_CERT%${dsip_ssl_cert}%g; s%DSIP_SSL_KEY%${dsip_ssl_key}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/dsiprouter.conf >/etc/nginx/sites-available/dsiprouter.conf ln -sf /etc/nginx/sites-available/dsiprouter.conf /etc/nginx/sites-enabled/dsiprouter.conf # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup dSIPRouter Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/dsiprouter.conf /etc/rsyslog.d/dsiprouter.conf touch /var/log/dsiprouter.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/dsiprouter /etc/logrotate.d/dsiprouter # Install dSIPRouter as a service perl -p \ -e "s|'DSIP_RUN_DIR\=.*'|'DSIP_RUN_DIR=$DSIP_RUN_DIR'|;" \ -e "s|'DSIP_PROJECT_DIR\=.*'|'DSIP_PROJECT_DIR=$DSIP_PROJECT_DIR'|;" \ -e "s|'DSIP_SYSTEM_CONFIG_DIR\=.*'|'DSIP_SYSTEM_CONFIG_DIR=$DSIP_SYSTEM_CONFIG_DIR'|;" \ ${DSIP_PROJECT_DIR}/dsiprouter/systemd/dsiprouter-v2.service > /lib/systemd/system/dsiprouter.service chmod 644 /lib/systemd/system/dsiprouter.service systemctl daemon-reload systemctl enable dsiprouter return 0 } function uninstall() { apt-get remove -y curl python3 python3-pip python-dev python3-openssl libpq-dev firewalld apt-get remove -y --allow-unauthenticated libmariadbclient-dev apt-get remove -y logrotate rsyslog perl sngrep libev-dev uuid-runtime # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl stop dsiprouter.service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: dsiprouter.sh ================================================ #!/usr/bin/env bash # #=============== dSIPRouter Management Script ==============# # # install, configure, and manage dsiprouter # #========================== NOTES ==========================# # # Supported OS: # - Debian 12 (bookworm) - STABLE # - Debian 11 (bullseye) - STABLE # - Debian 10 (buster) - STABLE # - Debian 9 (stretch) - DEPRECATED # - CentOS 9 (stream) - STABLE # - CentOS 8 (stream) - STABLE # - CentOS 7 - DEPRECATED # - RedHat Linux 9 - STABLE # - RedHat Linux 8 - ALPHA # - Alma Linux 9 - STABLE # - Alma Linux 8 - BETA # - Rocky Linux 9 - STABLE # - Rocky Linux 8 - BETA # - Amazon Linux 2 - STABLE # - Ubuntu 24.04 (noble) - STABLE # - Ubuntu 22.04 (jammy) - ALPHA # - Ubuntu 20.04 (focal) - DEPRECATED # # Conventions: # - In general exported variables & functions are used in externally called scripts / programs # # TODO: # - allow user to move carriers freely between carrier groups # - allow a carrier to be in more than one carrier group # - add ncurses selection menu for enabling / disabling modules # - naming convention for system vs dsip config files is very confusing (make more explicit) # - cleanup dependency installs/checks, many of these could be condensed # - allow overwriting caller id per gwgroup / gw (setup in gui & kamcfg) # - update tests with new mysql command wrapper functions # - update HA scripts with new mysql command wrapper functions # - add documentation generation to supported CLI commands # - move python install into it's own script to allow fine grain control of version/compilation if needed # #============== Detailed Debugging Information =============# # - splits stdout, stderr, and trace streams into 3 files # - output files are timestamped throughout process (cpu intensive) # - useful for tracking down bugs, especially when a lot of output is produced # - the gawk version seems to be more efficient but mawk is supported as well # #mkdir -p /tmp/debug && rm -f /tmp/debug/*.log 2>/dev/null # # - gawk version (alias awk='gawk') #exec > >(awk '{ print strftime("[%Y-%m-%d_%H:%M:%S] "), $0; fflush(); }' | tee -ia /tmp/debug/stdout.log) #exec 2> >(awk '{ print strftime("[%Y-%m-%d_%H:%M:%S] "), $0; fflush(); }' | tee -ia /tmp/debug/stderr.log 1>&2) #exec 19> >(awk '{ print strftime("[%Y-%m-%d_%H:%M:%S] "), $0; fflush(); }' > /tmp/debug/trace.log) # - mawk version (alias awk='mawk') #exec > >(awk -v time=$(date +"[%Y-%m-%d_%H:%M:%S] ") '{ print time, $0; fflush(); }' | tee -ia /tmp/debug/stdout.log) #exec 2> >(awk -v time=$(date +"[%Y-%m-%d_%H:%M:%S] ") '{ print time, $0; fflush(); }' | tee -ia /tmp/debug/stderr.log 1>&2) #exec 19> >(awk -v time=$(date +"[%Y-%m-%d_%H:%M:%S] ") '{ print time, $0; fflush(); }' > /tmp/debug/trace.log) # #BASH_XTRACEFD="19" #set -x #===========================================================# # set project dir (where src files are located) export DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-$(dirname $(readlink -f "$0"))} # Import dsip_lib utility / shared functions . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh # settings used by script that are user configurable function setStaticScriptSettings() { # to be clear, we define constants or variables with defaults here # generally these configuration settings effect how this script or the platform operate # do not change these settings without knowing exactly how it effects normal operation FLT_CARRIER=8 FLT_PBX=9 FLT_MSTEAMS=17 FLT_OUTBOUND=8000 FLT_INBOUND=9000 FLT_LCR_MIN=10000 FLT_FWD_MIN=20000 WITH_LCR=1 export DEBUG=${DEBUG:-0} export TEAMS_ENABLED=1 DSIP_MIN_PYTHON_VER='3.8' export PYTHON_VENV="${DSIP_PROJECT_DIR}/venv" export PYTHON_CMD="${PYTHON_VENV}/bin/python" export PROJECT_KAMAILIO_CONFIG_DIR="${DSIP_PROJECT_DIR}/kamailio/configs" export PROJECT_DSIP_DEFAULTS_DIR="${DSIP_PROJECT_DIR}/kamailio/defaults" export DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" DSIP_PRIV_KEY="${DSIP_SYSTEM_CONFIG_DIR}/privkey" export DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/kamailio.cfg" export DSIP_KAMAILIO_TLS_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/tls.cfg" export DSIP_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py" export DSIP_RUN_DIR="/run/dsiprouter" export DSIP_LIB_DIR="/var/lib/dsiprouter" export DSIP_CERTS_DIR="${DSIP_SYSTEM_CONFIG_DIR}/certs" DSIP_DOCS_DIR="${DSIP_PROJECT_DIR}/docs/build/html" export SYSTEM_KAMAILIO_CONFIG_DIR="/etc/kamailio" export SYSTEM_KAMAILIO_CONFIG_FILE="${SYSTEM_KAMAILIO_CONFIG_DIR}/kamailio.cfg" # will be symlinked export SYSTEM_KAMAILIO_TLS_CONFIG_FILE="${SYSTEM_KAMAILIO_CONFIG_DIR}/tls.cfg" # will be symlinked export SYSTEM_RTPENGINE_CONFIG_DIR="/etc/rtpengine" export SYSTEM_RTPENGINE_CONFIG_FILE="${SYSTEM_RTPENGINE_CONFIG_DIR}/rtpengine.conf" export PATH_UPDATE_FILE="/etc/profile.d/dsip_paths.sh" # updates paths required GIT_UPDATE_FILE="/etc/profile.d/dsip_git.sh" # extends git command DSIP_SUDOERS_FILE="/etc/sudoers.d/99-dsiprouter" export SRC_DIR="/usr/local/src" export BACKUPS_DIR="/var/backups/dsiprouter" IMAGE_BUILD=${IMAGE_BUILD:-0} # TODO: marked for review in v0.78 # we should move these settings to the OS specific install scripts # OS vendors may change these locations in the future if [[ "$DISTRO" == "ubuntu" ]] && (( ${DISTRO_MAJOR_VER} >= 24 )); then APT_OFFICIAL_SOURCES="/etc/apt/sources.d/ubuntu.sources" APT_OFFICIAL_SOURCES_BAK="${BACKUPS_DIR}/original-sources.sources" else APT_OFFICIAL_SOURCES="/etc/apt/sources.list" APT_OFFICIAL_SOURCES_BAK="${BACKUPS_DIR}/original-sources.list" fi APT_OFFICIAL_PREFS="/etc/apt/preferences" APT_OFFICIAL_PREFS_BAK="${BACKUPS_DIR}/original-sources.pref" APT_DSIP_CONFIG="/etc/apt/apt.conf.d/99dsiprouter" YUM_OFFICIAL_REPOS="/etc/yum.repos.d/official-releases.repo" # Force the installation of an Kamailio version by uncommenting # can also be set as an environment variable #KAM_VERSION=5.8.3 # Force the installation of an RTPEngine version by uncommenting # can also be set as an environment variable #RTPENGINE_VER="mr11.5.1.11" # Network configuration values export DSIP_UNIX_SOCK='/run/dsiprouter/dsiprouter.sock' export DSIP_PORT=5000 export RTP_PORT_MIN=10000 export RTP_PORT_MAX=20000 export KAM_SIP_PORT=5060 export KAM_SIPS_PORT=5061 export KAM_DMQ_PORT=5090 export KAM_WSS_PORT=4443 export DSIP_PROTO='https' export DSIP_API_PROTO='https' export DSIP_SSL_KEY="${DSIP_CERTS_DIR}/dsiprouter-key.pem" export DSIP_SSL_CERT="${DSIP_CERTS_DIR}/dsiprouter-cert.pem" export DSIP_SSL_CA="${DSIP_CERTS_DIR}/ca-list.pem" # make sure we run package installs unattended if cmdExists 'apt-get'; then export DEBIAN_FRONTEND="noninteractive" export DEBIAN_PRIORITY="critical" fi # make perl CPAN installs non interactive export PERL_MM_USE_DEFAULT=1 } # NOTE: will set NETWORK_MODE in current scope function preProcessNetworkMode() { local ARG="$1" OPT="" # TEMP: parse these options ahead of time until we can move arg parsing ahead of this logic # [note to self] this will require preempting undefined functions and/or some porting to bash-native versions of parsing logic if [[ "$ARG" == "install" ]]; then shift for OPT in "$@"; do case $OPT in -dmz|--dmz=*) NETWORK_MODE=2 if echo "$1" | grep -q '=' 2>/dev/null; then TMP=$(echo "$1" | cut -d '=' -f 2) shift else shift TMP="$1" shift fi PUBLIC_IFACE=$(echo "$TMP" | cut -d ',' -f 1) PRIVATE_IFACE=$(echo "$TMP" | cut -d ',' -f 2) ;; -netm|--network-mode=*) if echo "$1" | grep -q '=' 2>/dev/null; then NETWORK_MODE=$(echo "$1" | cut -d '=' -f 2) shift else shift NETWORK_MODE="$1" shift fi ;; esac done fi # network settings determined by mode NETWORK_MODE=${NETWORK_MODE:-$(getConfigAttrib 'NETWORK_MODE' ${DSIP_CONFIG_FILE})} NETWORK_MODE=${NETWORK_MODE:-0} if [[ "$ARG" == "install" && ! -f "$DSIP_INIT_FILE" ]]; then # special use case # on install before dsip-init is installed the network changes must be manually applied # this must happen prior to setting the script variables used throughout the install portion # WARNING: this may change the default route setCloudPlatform ${DSIP_PROJECT_DIR}/dsiprouter/dsip-net-cfg.py ${CLOUD_PLATFORM} ${NETWORK_MODE} fi } # settings used by script that are generated by the script # NOTE: variable NETWORK_MODE should be set before running this function setDynamicScriptSettings() { # TODO: ipv6 intentionally disabled here export IPV6_ENABLED=0 # grab the network settings dynamically if (( $NETWORK_MODE == 0 )); then export INTERNAL_IP_ADDR=$(getInternalIP -4) export INTERNAL_IP_NET=$(getInternalCIDR -4) export INTERNAL_IP6_ADDR=$(getInternalIP -6) export INTERNAL_IP_NET6=$(getInternalCIDR -6) # if external ip address is not found then this box is on an internal subnet EXTERNAL_IP_ADDR=$(getExternalIP -4) export EXTERNAL_IP_ADDR=${EXTERNAL_IP_ADDR:-$INTERNAL_IP_ADDR} EXTERNAL_IP6_ADDR=$(getExternalIP -6) export EXTERNAL_IP6_ADDR=${EXTERNAL_IP6_ADDR:-$INTERNAL_IP6_ADDR} # determine whether ipv6 is enabled # /proc/net/if_inet6 tells us if the kernel has ipv6 enabled # if [[ -f /proc/net/if_inet6 ]] && [[ -n "$INTERNAL_IP6_ADDR" ]]; then # # sanity check, is the ipv6 address routable? # # if not we can not use this address (interface is not configured properly) # if ! checkConn "$INTERNAL_IP6_ADDR"; then # printerr "IPV6 enabled but address [$INTERNAL_IP6_ADDR] is not routable" # exit 1 # fi # export IPV6_ENABLED=1 # else # export IPV6_ENABLED=0 # fi # the address we put in the contact when registering to carriers via uac module # by default it is set to the external IP of this server export UAC_REG_ADDR="$EXTERNAL_IP_ADDR" export INTERNAL_FQDN=$(getInternalFQDN) export EXTERNAL_FQDN=$(getExternalFQDN) if [[ -z "$EXTERNAL_FQDN" ]] || ! checkConn "$EXTERNAL_FQDN"; then # if external fqdn is not routable set it to the internal fqdn instead export EXTERNAL_FQDN="$INTERNAL_FQDN" fi # set the external fqdn to the internal fqdn if the hostname contain vultrusercontent # Kamailio doesn't like hostname names with dots and LetsEncrypt can't create certs for that domain grep vultrusercontent <<< "$EXTERNAL_FQDN" >/dev/null if (( $? == 0 ));then export EXTERNAL_FQDN="$INTERNAL_FQDN" fi # network settings pulled from env variables or from config file elif (( $NETWORK_MODE == 1 )); then export INTERNAL_IP_ADDR=${INTERNAL_IP_ADDR:-$(getConfigAttrib 'INTERNAL_IP_ADDR' ${DSIP_CONFIG_FILE})} export INTERNAL_IP_NET=${INTERNAL_IP_NET:-$(getConfigAttrib 'INTERNAL_IP_NET' ${DSIP_CONFIG_FILE})} export INTERNAL_IP6_ADDR=${INTERNAL_IP6_ADDR:-$(getConfigAttrib 'INTERNAL_IP6_ADDR' ${DSIP_CONFIG_FILE})} export INTERNAL_IP_NET6=${INTERNAL_IP_NET6:-$(getConfigAttrib 'INTERNAL_IP_NET6' ${DSIP_CONFIG_FILE})} export EXTERNAL_IP_ADDR=${EXTERNAL_IP_ADDR:-$(getConfigAttrib 'EXTERNAL_IP_ADDR' ${DSIP_CONFIG_FILE})} export EXTERNAL_IP6_ADDR=${EXTERNAL_IP6_ADDR:-$(getConfigAttrib 'EXTERNAL_IP6_ADDR' ${DSIP_CONFIG_FILE})} # if [[ -n "$IPV6_ENABLED" ]]; then # export IPV6_ENABLED # else # [[ "$(getConfigAttrib 'IPV6_ENABLED' ${DSIP_CONFIG_FILE})" == "True" ]] && # export IPV6_ENABLED=1 || # export IPV6_ENABLED=0 # fi export INTERNAL_FQDN=${INTERNAL_FQDN:-$(getConfigAttrib 'INTERNAL_FQDN' ${DSIP_CONFIG_FILE})} export EXTERNAL_FQDN=${EXTERNAL_FQDN:-$(getConfigAttrib 'EXTERNAL_FQDN' ${DSIP_CONFIG_FILE})} export UAC_REG_ADDR=${UAC_REG_ADDR:-$(getConfigAttrib 'UAC_REG_ADDR' ${DSIP_CONFIG_FILE})} # network settings resolved dynamically except IP/subnets (they are resolved by interfaces from CLI args or from the config) elif (( $NETWORK_MODE == 2 )); then PUBLIC_IFACE=${PUBLIC_IFACE:-$(getConfigAttrib 'PUBLIC_IFACE' ${DSIP_CONFIG_FILE})} PRIVATE_IFACE=${PRIVATE_IFACE:-$(getConfigAttrib 'PRIVATE_IFACE' ${DSIP_CONFIG_FILE})} export INTERNAL_IP_ADDR=$(getIP -4 "$PRIVATE_IFACE") export INTERNAL_IP_NET=$(getInternalCIDR -4 "$PRIVATE_IFACE") export INTERNAL_IP6_ADDR=$(getIP -6 "$PRIVATE_IFACE") export INTERNAL_IP_NET6=$(getInternalCIDR -6 "$PRIVATE_IFACE") EXTERNAL_IP_ADDR=$(getIP -4 "$PUBLIC_IFACE") export EXTERNAL_IP_ADDR=${EXTERNAL_IP_ADDR:-$INTERNAL_IP_ADDR} EXTERNAL_IP6_ADDR=$(getIP -6 "$PUBLIC_IFACE") export EXTERNAL_IP6_ADDR=${EXTERNAL_IP6_ADDR:-$INTERNAL_IP6_ADDR} # if [[ -f /proc/net/if_inet6 ]] && [[ -n "$INTERNAL_IP6_ADDR" ]]; then # # sanity check, is the ipv6 address routable? # # if not we can not use this address (interface is not configured properly) # if ! checkConn "$INTERNAL_IP6_ADDR"; then # printerr "IPV6 enabled but address [$INTERNAL_IP6_ADDR] is not routable" # exit 1 # fi # export IPV6_ENABLED=1 # else # export IPV6_ENABLED=0 # fi # the address we put in the contact when registering to carriers via uac module # by default it is set to the external IP of this server export UAC_REG_ADDR="$EXTERNAL_IP_ADDR" export INTERNAL_FQDN=$(getInternalFQDN) export EXTERNAL_FQDN=$(getExternalFQDN) if [[ -z "$EXTERNAL_FQDN" ]] || ! checkConn "$EXTERNAL_FQDN"; then # if external fqdn is not routable set it to the internal fqdn instead export EXTERNAL_FQDN="$INTERNAL_FQDN" fi else printerr 'Network Mode is invalid, can not proceed any further' exit 1 fi # if the public ip address is not the same as the internal address then enable serverside NAT if [[ "$EXTERNAL_IP_ADDR" != "$INTERNAL_IP_ADDR" ]]; then export SIGNAL_SERVERNAT=1 else export SIGNAL_SERVERNAT=0 fi # same as above but for ipv6, note that NAT is rarely used on ipv6 networks if (( ${IPV6_ENABLED} == 1 )) && [[ "$EXTERNAL_IP6_ADDR" != "$INTERNAL_IP6_ADDR" ]]; then export SIGNAL_SERVERNAT6=1 else export SIGNAL_SERVERNAT6=0 fi # grab root db settings from env or settings file export ROOT_DB_USER=${ROOT_DB_USER:-$(getConfigAttrib 'ROOT_DB_USER' ${DSIP_CONFIG_FILE})} export ROOT_DB_PASS=${ROOT_DB_PASS:-$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE})} export ROOT_DB_HOST=${ROOT_DB_HOST:-$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE})} export ROOT_DB_PORT=${ROOT_DB_PORT:-$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE})} export ROOT_DB_NAME=${ROOT_DB_NAME:-$(getConfigAttrib 'ROOT_DB_NAME' ${DSIP_CONFIG_FILE})} # grab kam db settings from env or settings file export KAM_DB_HOST=${KAM_DB_HOST:-$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE})} export KAM_DB_TYPE=${KAM_DB_TYPE:-$(getConfigAttrib 'KAM_DB_TYPE' ${DSIP_CONFIG_FILE})} export KAM_DB_PORT=${KAM_DB_PORT:-$(getConfigAttrib 'KAM_DB_PORT' ${DSIP_CONFIG_FILE})} export KAM_DB_NAME=${KAM_DB_NAME:-$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE})} export KAM_DB_USER=${KAM_DB_USER:-$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE})} export KAM_DB_PASS=${KAM_DB_PASS:-$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE} 2>/dev/null)} # set the email used to obtain LetsEncrypt Certificates export DSIP_SSL_EMAIL="admin@${EXTERNAL_FQDN}" export DSIP_ID=$(getConfigAttrib 'DSIP_ID' ${DSIP_CONFIG_FILE}) if [[ "$DSIP_ID" == "None" || -z "$DSIP_ID" ]]; then export DSIP_ID=$(cat /etc/machine-id | hashCreds) fi export HOMER_ID=$(getConfigAttrib 'HOMER_ID' ${DSIP_CONFIG_FILE}) if [[ "$HOMER_ID" == "None" ]] || [[ -z "$HOMER_ID" ]]; then export HOMER_ID=$(cat /etc/machine-id | hashCreds -l 4 | dd if=/dev/stdin of=/dev/stdout bs=1 count=8 2>/dev/null | hextoint) fi # find the repo where we are getting upgrades from # note that remote is assumed to be "origin" # note that the VCS is assumed to be git GIT_REPO_URL=$(getConfigAttrib 'GIT_REPO_URL' ${DSIP_CONFIG_FILE}) GIT_RELEASE_URL=$(getConfigAttrib 'GIT_RELEASE_URL' ${DSIP_CONFIG_FILE}) export CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"${BACKUPS_DIR}/$(date '+%s')"} } # Check if we are on a VPS Cloud Instance function setCloudPlatform() { # if we already set the cloud platform return immediately if [[ ! -z "${CLOUD_PLATFORM+x}" ]]; then return fi # 0 == not enabled, 1 == enabled export AWS_ENABLED=0 export DO_ENABLED=0 export GCE_ENABLED=0 export AZURE_ENABLED=0 export VULTR_ENABLED=0 # -- amazon web service check -- if isInstanceAMI; then export AWS_ENABLED=1 CLOUD_PLATFORM='AWS' # -- digital ocean check -- elif isInstanceDO; then export DO_ENABLED=1 CLOUD_PLATFORM='DO' # -- google compute engine check -- elif isInstanceGCE; then export GCE_ENABLED=1 CLOUD_PLATFORM='GCE' # -- microsoft azure check -- elif isInstanceAZURE; then export AZURE_ENABLED=1 CLOUD_PLATFORM='AZURE' # -- vultr cloud check -- elif isInstanceVULTR; then export VULTR_ENABLED=1 CLOUD_PLATFORM='VULTR' # -- bare metal or unsupported cloud platform -- else CLOUD_PLATFORM='' fi } function displayLogo() { echo "CiAgICAgXyAgX19fX18gX19fX18gX19fX18gIF9fX19fICAgICAgICAgICAgIF8gCiAgICB8IHwv IF9fX198XyAgIF98ICBfXyBcfCAgX18gXCAgICAgICAgICAgfCB8ICAgICAgICAgICAKICBfX3wg fCAoX19fICAgfCB8IHwgfF9fKSB8IHxfXykgfF9fXyAgXyAgIF98IHxfIF9fXyBfIF9fIAogLyBf YCB8XF9fXyBcICB8IHwgfCAgX19fL3wgIF8gIC8vIF8gXHwgfCB8IHwgX18vIF8gXCAnX198Cnwg KF98IHxfX19fKSB8X3wgfF98IHwgICAgfCB8IFwgXCAoXykgfCB8X3wgfCB8fCAgX18vIHwgICAK IFxfXyxffF9fX19fL3xfX19fX3xffCAgICB8X3wgIFxfXF9fXy8gXF9fLF98XF9fXF9fX3xffCAg IAoKQnVpbHQgaW4gRGV0cm9pdCwgVVNBIC0gUG93ZXJlZCBieSBLYW1haWxpbyAgICAgICAgICAg ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgClN1cHBv cnQgY2FuIGJlIHB1cmNoYXNlZCBmcm9tIGh0dHBzOi8vZHNpcHJvdXRlci5vcmcvIAoKVGhhbmtz IHRvIG91ciBzcG9uc29yOiBkT3BlblNvdXJjZSAoaHR0cHM6Ly9kb3BlbnNvdXJjZS5jb20pCg==" \ | base64 -d \ | { echo -e "\e[1;49;36m"; cat; echo -e "\e[39;49;00m"; } } # check if running as root function validateRootPriv() { if (( $(id -u 2>/dev/null) != 0 )); then printerr "$0 must be run as root user" exit 1 fi } # Validate OS and export OS specific config variables function validateOSInfo() { export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") case "$DISTRO" in debian) case "$DISTRO_VER" in 12|11|10) KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 9) printerr "Your Operating System Version is DEPRECATED. To ask for support open an issue https://github.com/dOpensource/dsiprouter/" KAM_VERSION=${KAM_VERSION:-"5.5.7"} RTPENGINE_VER=${RTPENGINE_VER:-"mr9.5.5.1"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; centos) case "$DISTRO_VER" in 9) KAM_VERSION=${KAM_VERSION:-"6.0.2"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 7) printwarn "Your Operating System Version is DEPRECATED. To ask for support open an issue https://github.com/dOpensource/dsiprouter/" KAM_VERSION=${KAM_VERSION:-"5.7.6"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; amzn) case "$DISTRO_VER" in 2) KAM_VERSION=${KAM_VERSION:-"5.7.6"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; ubuntu) case "$DISTRO_VER" in 24.04) KAM_VERSION=${KAM_VERSION:-"5.8.4"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 22.04) printwarn "Your operating System Version is in ALPHA support. Some features may not work yet. Use at your own risk." KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 20.04) printwarn "Your Operating System Version is DEPRECATED. To ask for support open an issue https://github.com/dOpensource/dsiprouter/" KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr9.5.5.1"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; rhel) case "$DISTRO_MAJOR_VER" in 9) KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) printwarn "Your operating System Version is in ALPHA support. Some features may not work yet. Use at your own risk." KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr9.5.5.1"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; almalinux) case "$DISTRO_MAJOR_VER" in 9) KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) printwarn "Your operating System Version is in BETA support. Some features may have bugs. Use at your own risk." KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; rocky) case "$DISTRO_MAJOR_VER" in 9) KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) printwarn "Your operating System Version is in BETA support. Some features may have bugs. Use at your own risk." KAM_VERSION=${KAM_VERSION:-"5.8.3"} RTPENGINE_VER=${RTPENGINE_VER:-"mr11.5.1.11"} ;; *) printerr "Your Operating System Version is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac ;; *) printerr "Your Operating System is not supported yet. Please open an issue at https://github.com/dOpensource/dsiprouter/" exit 1 ;; esac # export it for external scripts export KAM_VERSION export RTPENGINE_VER } # run prior to any cmd being processed function initialChecks() { validateRootPriv validateOSInfo setStaticScriptSettings setupScriptRequiredFiles configureSystemRepos installScriptRequirements preProcessNetworkMode "$@" setDynamicScriptSettings } # exported because its used throughout child scripts as well function reconfigureMysqlSystemdService() { local KAM_DB_HOST="${SET_KAM_DB_HOST:-$KAM_DB_HOST}" KAM_DB_HOST=${KAM_DB_HOST:-$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE})} if isHostLocal "$KAM_DB_HOST"; then # in this case mysql DBMS is running locally on this server rm -f /etc/systemd/system/mariadb.service 2>/dev/null else # in this case mysql DBMS is running on a remote server cp -f ${DSIP_PROJECT_DIR}/mysql/systemd/dummy.service /etc/systemd/system/mariadb.service chmod 644 /etc/systemd/system/mariadb.service fi systemctl daemon-reload systemctl enable mariadb } export -f reconfigureMysqlSystemdService # note: exports variable MEDIA_SERVERNAT function reconfigureRtpengineSystemdService() { local RTPENGINE_URI=${RTPENGINE_URI:-$(getConfigAttrib 'RTPENGINE_URI' ${DSIP_CONFIG_FILE})} local RTPENGINE_HOST=$(cut -s -d ':' -f 2 <<<"$RTPENGINE_URI") if isHostLocal "$RTPENGINE_HOST"; then # in this case rtpengine is running locally on this server export MEDIA_SERVERNAT=1 rm -f /etc/systemd/system/rtpengine.service 2>/dev/null else # in this case rtpengine is running on a remote server export MEDIA_SERVERNAT=0 cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/dummy.service /etc/systemd/system/rtpengine.service chmod 644 /etc/systemd/system/rtpengine.service fi systemctl daemon-reload systemctl enable rtpengine } function generateDsiprouterConfig() { mkdir -p ${BACKUPS_DIR}/gui/ cp -f ${DSIP_SYSTEM_CONFIG_DIR}/gui/*.py ${BACKUPS_DIR}/gui/ 2>/dev/null rm -f ${DSIP_SYSTEM_CONFIG_DIR}/gui/*.py 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/gui/settings.py ${DSIP_CONFIG_FILE} } function updateDsiprouterConfig() { local NETWORK_MODE=${NETWORK_MODE:-$(getConfigAttrib 'NETWORK_MODE' ${DSIP_CONFIG_FILE})} local LOAD_SETTINGS_FROM=${LOAD_SETTINGS_FROM:-$(getConfigAttrib 'LOAD_SETTINGS_FROM' ${DSIP_CONFIG_FILE})} # the following variables are always updated setConfigAttrib 'KAM_KAMCMD_PATH' "$(type -p kamcmd)" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'KAM_CFG_PATH' "$SYSTEM_KAMAILIO_CONFIG_FILE" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'KAM_TLSCFG_PATH' "$SYSTEM_KAMAILIO_TLS_CONFIG_FILE" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'RTP_CFG_PATH' "$SYSTEM_RTPENGINE_CONFIG_FILE" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'FLT_CARRIER' "$FLT_CARRIER" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_PBX' "$FLT_PBX" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_MSTEAMS' "$FLT_MSTEAMS" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_OUTBOUND' "$FLT_OUTBOUND" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_INBOUND' "$FLT_INBOUND" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_LCR_MIN' "$FLT_LCR_MIN" ${DSIP_CONFIG_FILE} setConfigAttrib 'FLT_FWD_MIN' "$FLT_FWD_MIN" ${DSIP_CONFIG_FILE} setConfigAttrib 'DSIP_PROJECT_DIR' "$DSIP_PROJECT_DIR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'DSIP_DOCS_DIR' "$DSIP_DOCS_DIR" ${DSIP_CONFIG_FILE} -q # the following variables are only updated when set [[ -n "$DSIP_ID" ]] && setConfigAttrib 'DSIP_ID' "$DSIP_ID" ${DSIP_CONFIG_FILE} -qb [[ -n "$DSIP_CLUSTER_ID" ]] && setConfigAttrib 'DSIP_CLUSTER_ID' "$DSIP_CLUSTER_ID" ${DSIP_CONFIG_FILE} if [[ -n "$DSIP_CLUSTER_SYNC" ]]; then if (( $DSIP_CLUSTER_SYNC == 1 )); then setConfigAttrib 'DSIP_CLUSTER_SYNC' 'True' ${DSIP_CONFIG_FILE} else setConfigAttrib 'DSIP_CLUSTER_SYNC' 'False' ${DSIP_CONFIG_FILE} fi fi [[ -n "$DSIP_PROTO" ]] && setConfigAttrib 'DSIP_PROTO' "$DSIP_PROTO" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_PORT" ]] && setConfigAttrib 'DSIP_PORT' "$DSIP_PORT" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_API_PROTO" ]] && setConfigAttrib 'DSIP_API_PROTO' "$DSIP_API_PROTO" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_API_PORT" ]] && setConfigAttrib 'DSIP_API_PORT' "$DSIP_API_PORT" ${DSIP_CONFIG_FILE} [[ -n "$DSIP_PRIV_KEY" ]] && setConfigAttrib 'DSIP_PRIV_KEY' "$DSIP_PRIV_KEY" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_PID_FILE" ]] && setConfigAttrib 'DSIP_PID_FILE' "$DSIP_PID_FILE" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_UNIX_SOCK" ]] && setConfigAttrib 'DSIP_UNIX_SOCK' "$DSIP_UNIX_SOCK" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_IPC_SOCK" ]] && setConfigAttrib 'DSIP_IPC_SOCK' "$DSIP_IPC_SOCK" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_LOG_LEVEL" ]] && setConfigAttrib 'DSIP_LOG_LEVEL' "$DSIP_LOG_LEVEL" ${DSIP_CONFIG_FILE} [[ -n "$DSIP_LOG_FACILITY" ]] && setConfigAttrib 'DSIP_LOG_FACILITY' "$DSIP_LOG_FACILITY" ${DSIP_CONFIG_FILE} [[ -n "$DSIP_SSL_KEY" ]] && setConfigAttrib 'DSIP_SSL_KEY' "$DSIP_SSL_KEY" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_SSL_CERT" ]] && setConfigAttrib 'DSIP_SSL_CERT' "$DSIP_SSL_CERT" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_SSL_CA" ]] && setConfigAttrib 'DSIP_SSL_CA' "$DSIP_SSL_CA" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_SSL_EMAIL" ]] && setConfigAttrib 'DSIP_SSL_EMAIL' "$DSIP_SSL_EMAIL" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_CERTS_DIR" ]] && setConfigAttrib 'DSIP_CERTS_DIR' "$DSIP_CERTS_DIR" ${DSIP_CONFIG_FILE} -q [[ -n "$VERSION" ]] && setConfigAttrib 'VERSION' "$VERSION" ${DSIP_CONFIG_FILE} -q [[ -n "$ROLE" ]] && setConfigAttrib 'ROLE' "$ROLE" ${DSIP_CONFIG_FILE} -q [[ -n "$GUI_INACTIVE_TIMEOUT" ]] && setConfigAttrib 'GUI_INACTIVE_TIMEOUT' "$GUI_INACTIVE_TIMEOUT" ${DSIP_CONFIG_FILE} [[ -n "$KAM_DB_DRIVER" ]] && setConfigAttrib 'KAM_DB_DRIVER' "$KAM_DB_DRIVER" ${DSIP_CONFIG_FILE} -q [[ -n "$KAM_DB_TYPE" ]] && setConfigAttrib 'KAM_DB_TYPE' "$KAM_DB_TYPE" ${DSIP_CONFIG_FILE} -q [[ -n "$DEFAULT_AUTH_DOMAIN" ]] && setConfigAttrib 'DEFAULT_AUTH_DOMAIN' "$DEFAULT_AUTH_DOMAIN" ${DSIP_CONFIG_FILE} -q [[ -n "$TELEBLOCK_GW_ENABLED" ]] && setConfigAttrib 'TELEBLOCK_GW_ENABLED' "$TELEBLOCK_GW_ENABLED" ${DSIP_CONFIG_FILE} [[ -n "$TELEBLOCK_GW_IP" ]] && setConfigAttrib 'TELEBLOCK_GW_IP' "$TELEBLOCK_GW_IP" ${DSIP_CONFIG_FILE} -q [[ -n "$TELEBLOCK_GW_PORT" ]] && setConfigAttrib 'TELEBLOCK_GW_PORT' "$TELEBLOCK_GW_PORT" ${DSIP_CONFIG_FILE} -q [[ -n "$TELEBLOCK_MEDIA_IP" ]] && setConfigAttrib 'TELEBLOCK_MEDIA_IP' "$TELEBLOCK_MEDIA_IP" ${DSIP_CONFIG_FILE} -q [[ -n "$TELEBLOCK_MEDIA_PORT" ]] && setConfigAttrib 'TELEBLOCK_MEDIA_PORT' "$TELEBLOCK_MEDIA_PORT" ${DSIP_CONFIG_FILE} -q [[ -n "$FLOWROUTE_ACCESS_KEY" ]] && setConfigAttrib 'FLOWROUTE_ACCESS_KEY' "$FLOWROUTE_ACCESS_KEY" ${DSIP_CONFIG_FILE} -q [[ -n "$FLOWROUTE_SECRET_KEY" ]] && setConfigAttrib 'FLOWROUTE_SECRET_KEY' "$FLOWROUTE_SECRET_KEY" ${DSIP_CONFIG_FILE} -q [[ -n "$FLOWROUTE_API_ROOT_URL" ]] && setConfigAttrib 'FLOWROUTE_API_ROOT_URL' "$FLOWROUTE_API_ROOT_URL" ${DSIP_CONFIG_FILE} -q [[ -n "$HOMER_ID" ]] && setConfigAttrib 'HOMER_ID' "$HOMER_ID" ${DSIP_CONFIG_FILE} [[ -n "$HOMER_HEP_HOST" ]] && setConfigAttrib 'HOMER_HEP_HOST' "$HOMER_HEP_HOST" ${DSIP_CONFIG_FILE} -q [[ -n "$HOMER_HEP_PORT" ]] && setConfigAttrib 'HOMER_HEP_PORT' "$HOMER_HEP_PORT" ${DSIP_CONFIG_FILE} [[ -n "$UPLOAD_FOLDER" ]] && setConfigAttrib 'UPLOAD_FOLDER' "$UPLOAD_FOLDER" ${DSIP_CONFIG_FILE} -q [[ -n "$MAIL_SERVER" ]] && setConfigAttrib 'MAIL_SERVER' "$MAIL_SERVER" ${DSIP_CONFIG_FILE} -q [[ -n "$MAIL_PORT" ]] && setConfigAttrib 'MAIL_PORT' "$MAIL_PORT" ${DSIP_CONFIG_FILE} if [[ -n "$MAIL_USE_TLS" ]]; then if (( $MAIL_USE_TLS == 0 )); then setConfigAttrib 'MAIL_USE_TLS' "False" ${DSIP_CONFIG_FILE} else setConfigAttrib 'MAIL_USE_TLS' "True" ${DSIP_CONFIG_FILE} fi fi if [[ -n "$MAIL_ASCII_ATTACHMENTS" ]]; then if (( $MAIL_ASCII_ATTACHMENTS == 1 )); then setConfigAttrib 'MAIL_ASCII_ATTACHMENTS' "True" ${DSIP_CONFIG_FILE} else setConfigAttrib 'MAIL_ASCII_ATTACHMENTS' "False" ${DSIP_CONFIG_FILE} fi fi [[ -n "$MAIL_USERNAME" ]] && setConfigAttrib 'MAIL_DEFAULT_SENDER' "dSIPRouter $EXTERNAL_FQDN <$MAIL_USERNAME>" ${DSIP_CONFIG_FILE} -q [[ -n "$MAIL_DEFAULT_SUBJECT" ]] && setConfigAttrib 'MAIL_DEFAULT_SUBJECT' "$MAIL_DEFAULT_SUBJECT" ${DSIP_CONFIG_FILE} -q [[ -n "$RTPENGINE_URI" ]] && setConfigAttrib 'RTPENGINE_URI' "$RTPENGINE_URI" ${DSIP_CONFIG_FILE} -q [[ -n "$CLOUD_PLATFORM" ]] && setConfigAttrib 'CLOUD_PLATFORM' "$CLOUD_PLATFORM" ${DSIP_CONFIG_FILE} -q [[ -n "$BACKUPS_DIR" ]] && setConfigAttrib 'BACKUP_FOLDER' "$BACKUPS_DIR" ${DSIP_CONFIG_FILE} -q [[ -n "$DID_PREFIX_ALLOWED_CHARS" ]] && setConfigAttrib 'DID_PREFIX_ALLOWED_CHARS' "$DID_PREFIX_ALLOWED_CHARS" ${DSIP_CONFIG_FILE} [[ -n "$LOAD_SETTINGS_FROM" ]] && setConfigAttrib 'LOAD_SETTINGS_FROM' "$LOAD_SETTINGS_FROM" ${DSIP_CONFIG_FILE} -q [[ -n "$DSIP_LICENSE_STORE" ]] && setConfigAttrib 'DSIP_LICENSE_STORE' "$DSIP_LICENSE_STORE" ${DSIP_CONFIG_FILE} # update settings based on values set by setDynamicScriptSettings() setConfigAttrib 'NETWORK_MODE' "$NETWORK_MODE" ${DSIP_CONFIG_FILE} if (( $IPV6_ENABLED == 1 )); then setConfigAttrib 'IPV6_ENABLED' "True" ${DSIP_CONFIG_FILE} else setConfigAttrib 'IPV6_ENABLED' "False" ${DSIP_CONFIG_FILE} fi setConfigAttrib 'INTERNAL_IP_ADDR' "$INTERNAL_IP_ADDR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'INTERNAL_IP_NET' "$INTERNAL_IP_NET" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'INTERNAL_IP6_ADDR' "$INTERNAL_IP6_ADDR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'INTERNAL_IP6_NET' "$INTERNAL_IP6_NET" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'INTERNAL_FQDN' "$INTERNAL_FQDN" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'EXTERNAL_IP_ADDR' "$EXTERNAL_IP_ADDR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'EXTERNAL_IP6_ADDR' "$EXTERNAL_IP6_ADDR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'EXTERNAL_FQDN' "$EXTERNAL_FQDN" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'PUBLIC_IFACE' "$PUBLIC_IFACE" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'PRIVATE_IFACE' "$PRIVATE_IFACE" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'UAC_REG_ADDR' "$UAC_REG_ADDR" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'GIT_REPO_URL' "$GIT_REPO_URL" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'GIT_RELEASE_URL' "$GIT_RELEASE_URL" ${DSIP_CONFIG_FILE} -q # TODO: the following are updated in setCredentials() and the config file should only be updated here # i.e. settings the variables elsewhere is fine but any changes to the config file or DB should be centralised here # DSIP_GUI_USER # DSIP_GUI_PASS # DSIP_API_TOKEN # DSIP_MAIL_USER # DSIP_MAIL_PASS # DSIP_IPC_PASS # KAM_DB_USER # KAM_DB_PASS # KAM_DB_HOST # KAM_DB_PORT # KAM_DB_NAME # ROOT_DB_HOST # ROOT_DB_PORT # ROOT_DB_USER # ROOT_DB_PASS # ROOT_DB_NAME # DSIP_SESSION_KEY # TODO: the following settings are only updatable via the GUI # TRANSNEXUS_AUTHSERVICE_ENABLED # TRANSNEXUS_AUTHSERVICE_HOST # TRANSNEXUS_LICENSE_KEY # TRANSNEXUS_VERIFYSERVICE_ENABLED # TRANSNEXUS_VERIFYSERVICE_HOST # STIR_SHAKEN_ENABLED # STIR_SHAKEN_PREFIX_A # STIR_SHAKEN_PREFIX_B # STIR_SHAKEN_PREFIX_C # STIR_SHAKEN_PREFIX_INVALID # STIR_SHAKEN_BLOCK_INVALID # STIR_SHAKEN_CERT_URL # STIR_SHAKEN_KEY_PATH # MSTEAMS_DNS_ENDPOINTS # MSTEAMS_IP_ENDPOINTS # TODO: workaround to update DB settings until next major release (v0.80) if [[ "$LOAD_SETTINGS_FROM" == "db" ]]; then setConfigAttrib 'LOAD_SETTINGS_FROM' 'file' ${DSIP_CONFIG_FILE} -q ${PYTHON_CMD} -c "import os,sys; os.chdir('${DSIP_PROJECT_DIR}/gui'); sys.path.insert(0, '${DSIP_SYSTEM_CONFIG_DIR}/gui'); from dsiprouter import syncSettings; syncSettings();" setConfigAttrib 'LOAD_SETTINGS_FROM' 'db' ${DSIP_CONFIG_FILE} -q fi return 0 } # TODO: these variables should be ephemeral, set as environment variables when running the service, no need to store them function updateDsiprouterConfigRuntimeSettings() { if (( ${DEBUG} == 1 )); then setConfigAttrib 'DEBUG' 'True' ${DSIP_CONFIG_FILE} else setConfigAttrib 'DEBUG' 'False' ${DSIP_CONFIG_FILE} fi } function updateDsiprouterStartup { local KAM_UPDATE_OPTS="" # update dsiprouter configs on reboot removeInitCmd "/usr/bin/dsiprouter updatedsipconfig" addInitCmd "/usr/bin/dsiprouter updatedsipconfig $KAM_UPDATE_OPTS" # make sure dsip-init service runs prior to dsiprouter service removeDependsOnInit "dsiprouter.service" addDependsOnInit "dsiprouter.service" } # supported methods for renewing certificates: # 1. using Let's Encrypt / certbot # 2. issuing a new self-signed cert function renewSSLCert() { local DEFAULT_CERT_UPLOADED CERT_ISSUER RENEW_START_TS LAST_CHANGE_TS # Do not renew if the admin uploaded a default cert DEFAULT_CERT_UPLOADED=$( withKamDB mysql -sN -e "select count(*) from dsip_certificates where domain='default'" 2>/dev/null ) if (( ${DEFAULT_CERT_UPLOADED:-0} == 1 )); then printwarn "Current X509 certificate for dSIPRouter can not be automatically renewed" return 1 fi CERT_ISSUER=$( openssl x509 -in ${DSIP_SSL_CERT} -noout -nameopt compat -issuer 2>/dev/null | perl -pe 's%^.*?/O=([^/]*).*?$%\1%' ) case "$CERT_ISSUER" in "Let's Encrypt") if certbot -n certificates | grep -q 'No certs found' &>/dev/null; then printwarn "No LetsEncrypt certificates managed by Certbot found" return 1 fi RENEW_START_TS=$(date '+%s') certbot -n renew if (( $? == 0 )); then # we only want to reload the live cert if it was actually changed LAST_CHANGE_TS=$(stat -c '%Y' /etc/letsencrypt/live/${EXTERNAL_FQDN}/fullchain.pem) if (( $? != 0 )); then printerr "Could not find new certificate for ${EXTERNAL_FQDN}" return 1 fi if (( $LAST_CHANGE_TS < $RENEW_START_TS )); then return 0 fi rm -f ${DSIP_CERTS_DIR}/dsiprouter* cp -f /etc/letsencrypt/live/${EXTERNAL_FQDN}/fullchain.pem ${DSIP_SSL_CERT} cp -f /etc/letsencrypt/live/${EXTERNAL_FQDN}/privkey.pem ${DSIP_SSL_KEY} else printerr "Failed renewing certificate for ${EXTERNAL_FQDN} using LetsEncrypt" return 1 fi ;; dSIPRouter) openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes \ -out ${DSIP_SSL_CERT} \ -keyout ${DSIP_SSL_KEY} \ -subj "/C=US/ST=MI/L=Detroit/O=dSIPRouter/CN=${EXTERNAL_FQDN}" if (( $? != 0 )); then printerr "Failed renewing self-signed certificate for ${EXTERNAL_FQDN}" return 1 fi ;; *) printwarn "Current X509 certificate for dSIPRouter can not be automatically renewed" return 1 ;; esac updatePermissions -certs && kamcmd tls.reload && return 0 || return 1 } function configureSSL() { # Check if certificates already exists. If so, use them and exit if [[ -f "${DSIP_SSL_CERT}" && -f "${DSIP_SSL_KEY}" ]]; then printwarn "Using certificates found in ${DSIP_CERTS_DIR}" updatePermissions -certs return fi # Stop nginx if started so that LetsEncrypt can leverage port 80 if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then if docker ps | grep -q dsiprouter-nginx; then docker stop dsiprouter-nginx 2>/dev/null fi else firewall-cmd --zone=public --add-port=80/tcp fi # Override the hostname if -o or --override=<hostname> is provided if [[ -n "${DNS_NAME_OVERRIDE}" ]]; then EXTERNAL_FQDN=${DNS_NAME_OVERRIDE} fi # Try to create cert using LetsEncrypt's first printdbg "Generating Certs for ${EXTERNAL_FQDN} using LetsEncrypt" certbot certonly --standalone --non-interactive --agree-tos -d ${EXTERNAL_FQDN} -m ${DSIP_SSL_EMAIL} \ --server https://acme-v02.api.letsencrypt.org/directory --force-renewal --preferred-chain "ISRG Root X1" if (( $? == 0 )); then rm -f ${DSIP_CERTS_DIR}/dsiprouter* cp -f /etc/letsencrypt/live/${EXTERNAL_FQDN}/fullchain.pem ${DSIP_SSL_CERT} cp -f /etc/letsencrypt/live/${EXTERNAL_FQDN}/privkey.pem ${DSIP_SSL_KEY} else printwarn "Failed Generating Certs for ${EXTERNAL_FQDN} using LetsEncrypt" # Worst case, generate a Self-Signed Certificate printdbg "Generating dSIPRouter Self-Signed Certificates" openssl req -new -newkey rsa:4096 -x509 -sha256 -days 365 -nodes -out ${DSIP_SSL_CERT} -keyout ${DSIP_SSL_KEY} -subj "/C=US/ST=MI/L=Detroit/O=dSIPRouter/CN=${EXTERNAL_FQDN}" fi # Add Nightly Cronjob to renew certs if not already there if ! crontab -u root -l 2>/dev/null | grep -q '/usr/bin/dsiprouter renewsslcert' 2>/dev/null; then cronAppend -u root '0 0 * * * /usr/bin/dsiprouter renewsslcert' fi updatePermissions -certs # Start nginx if dSIP was installed if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then if docker ps | grep -q dsiprouter-nginx; then docker stop dsiprouter-nginx 2>/dev/null fi else firewall-cmd --zone=public --remove-port=80/tcp fi } # updates and settings in kam config that may change # should be run after changing settings.py or change in network configurations # TODO: support configuring separate asterisk realtime db conns / clusters (would need separate setting in settings.py) function updateKamailioConfig() { local DSIP_ID=${DSIP_ID:-$(getConfigAttrib 'DSIP_ID' ${DSIP_CONFIG_FILE})} local DSIP_CLUSTER_ID=${DSIP_CLUSTER_ID:-$(getConfigAttrib 'DSIP_CLUSTER_ID' ${DSIP_CONFIG_FILE})} local DSIP_CLUSTER_SYNC=${DSIP_CLUSTER_SYNC:-$([[ "$(getConfigAttrib 'DSIP_CLUSTER_SYNC' ${DSIP_CONFIG_FILE})" == "True" ]] && echo '1' || echo '0')} local DSIP_VERSION=${DSIP_VERSION:-$(getConfigAttrib 'VERSION' ${DSIP_CONFIG_FILE})} local HOMER_ID=${HOMER_ID:-$(getConfigAttrib 'HOMER_ID' ${DSIP_CONFIG_FILE})} local DSIP_API_BASEURL="$(getConfigAttrib 'DSIP_API_PROTO' ${DSIP_CONFIG_FILE})://127.0.0.1:$(getConfigAttrib 'DSIP_API_PORT' ${DSIP_CONFIG_FILE})" local DSIP_API_TOKEN=${DSIP_API_TOKEN:-$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE} 2>/dev/null)} local DEBUG=${DEBUG:-$([[ "$(getConfigAttrib 'DEBUG' ${DSIP_CONFIG_FILE})" == "True" ]] && echo '1' || echo '0')} local ROLE=${ROLE:-$(getConfigAttrib 'ROLE' ${DSIP_CONFIG_FILE})} local TELEBLOCK_GW_ENABLED=${TELEBLOCK_GW_ENABLED:-$(getConfigAttrib 'TELEBLOCK_GW_ENABLED' ${DSIP_CONFIG_FILE})} local TELEBLOCK_GW_IP=${TELEBLOCK_GW_IP:-$(getConfigAttrib 'TELEBLOCK_GW_IP' ${DSIP_CONFIG_FILE})} local TELEBLOCK_GW_PORT=${TELEBLOCK_GW_PORT:-$(getConfigAttrib 'TELEBLOCK_GW_PORT' ${DSIP_CONFIG_FILE})} local TELEBLOCK_MEDIA_IP=${TELEBLOCK_MEDIA_IP:-$(getConfigAttrib 'TELEBLOCK_MEDIA_IP' ${DSIP_CONFIG_FILE})} local TELEBLOCK_MEDIA_PORT=${TELEBLOCK_MEDIA_PORT:-$(getConfigAttrib 'TELEBLOCK_MEDIA_PORT' ${DSIP_CONFIG_FILE})} local KAM_WSS_PORT=${KAM_WSS_PORT:-$(getConfigAttrib 'KAM_WSS_PORT' ${DSIP_CONFIG_FILE})} local KAM_SIP_PORT=${KAM_SIP_PORT:-$(getConfigAttrib 'KAM_SIP_PORT' ${DSIP_CONFIG_FILE})} local KAM_SIPS_PORT=${KAM_SIPS_PORT:-$(getConfigAttrib 'KAM_SIPS_PORT' ${DSIP_CONFIG_FILE})} local KAM_DMQ_PORT=${KAM_DMQ_PORT:-$(getConfigAttrib 'KAM_DMQ_PORT' ${DSIP_CONFIG_FILE})} local HOMER_HEP_HOST=${HOMER_HEP_HOST:-$(getConfigAttrib 'HOMER_HEP_HOST' ${DSIP_CONFIG_FILE})} local HOMER_HEP_PORT=${HOMER_HEP_PORT:-$(getConfigAttrib 'HOMER_HEP_PORT' ${DSIP_CONFIG_FILE})} local NETWORK_MODE=${NETWORK_MODE:-$(getConfigAttrib 'NETWORK_MODE' ${DSIP_CONFIG_FILE})} local RTPENGINE_URI=${RTPENGINE_URI:-$(getConfigAttrib 'RTPENGINE_URI' ${DSIP_CONFIG_FILE})} local RTPENGINE_HOST=$(cut -s -d ':' -f 2 <<<"$RTPENGINE_URI") # update kamailio config file if (( $DEBUG == 1 )); then enableKamailioConfigAttrib 'WITH_DEBUG' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_DEBUG' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( $SIGNAL_SERVERNAT == 1 )); then enableKamailioConfigAttrib 'WITH_SIGNAL_SERVERNAT' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_SIGNAL_SERVERNAT' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( $SIGNAL_SERVERNAT6 == 1 )); then enableKamailioConfigAttrib 'WITH_SIGNAL_SERVERNAT6' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_SIGNAL_SERVERNAT6' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( $IPV6_ENABLED == 1 )); then enableKamailioConfigAttrib 'WITH_IPV6' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_IPV6' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( $NETWORK_MODE == 2 )); then enableKamailioConfigAttrib 'WITH_DMZ' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_DMZ' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( $DSIP_CLUSTER_SYNC == 1 )); then enableKamailioConfigAttrib 'WITH_DMQ' ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'DMQ_REPLICATE_ENABLED' '1' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_DMQ' ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'DMQ_REPLICATE_ENABLED' '0' ${DSIP_KAMAILIO_CONFIG_FILE} fi if [[ -n "$HOMER_HEP_HOST" && -n "$HOMER_HEP_PORT" ]]; then enableKamailioConfigAttrib 'WITH_HOMER' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_HOMER' ${DSIP_KAMAILIO_CONFIG_FILE} fi if [[ -n "$DSIP_ID" && "$DSIP_ID" != "None" ]]; then setKamailioConfigSubst 'DSIP_ID' "$DSIP_ID" ${DSIP_KAMAILIO_CONFIG_FILE} fi if [[ -n "$HOMER_ID" && "$HOMER_ID" != "None" ]]; then setKamailioConfigSubst 'HOMER_ID' "$HOMER_ID" ${DSIP_KAMAILIO_CONFIG_FILE} fi if lsmod | awk '$1 == "sctp" {rc=1; exit;}; END {exit !rc;}'; then enableKamailioConfigAttrib 'WITH_SCTP' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_SCTP' ${DSIP_KAMAILIO_CONFIG_FILE} fi if isHostLocal "$RTPENGINE_HOST"; then enableKamailioConfigAttrib 'WITH_MEDIA_SERVERNAT' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_MEDIA_SERVERNAT' ${DSIP_KAMAILIO_CONFIG_FILE} fi setKamailioConfigSubst 'DSIP_CLUSTER_ID' "${DSIP_CLUSTER_ID}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'DSIP_VERSION' "${DSIP_VERSION}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'INTERNAL_IP_ADDR' "${INTERNAL_IP_ADDR}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'INTERNAL_IP6_ADDR' "${INTERNAL_IP6_ADDR}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'INTERNAL_IP_NET' "${INTERNAL_IP_NET}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'INTERNAL_IP6_NET' "${INTERNAL_IP_NET6}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'EXTERNAL_IP_ADDR' "${EXTERNAL_IP_ADDR}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'EXTERNAL_IP6_ADDR' "${EXTERNAL_IP6_ADDR}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'INTERNAL_FQDN' "${INTERNAL_FQDN}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'EXTERNAL_FQDN' "${EXTERNAL_FQDN}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'UAC_REG_ADDR' "${UAC_REG_ADDR}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'WSS_PORT' "${KAM_WSS_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'SIP_PORT' "${KAM_SIP_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'SIPS_PORT' "${KAM_SIPS_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'DMQ_PORT' "${KAM_DMQ_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'HOMER_HOST' "${HOMER_HEP_HOST}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'HEP_PORT' "${HOMER_HEP_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigSubst 'RTPENGINE_URI' "$RTPENGINE_URI" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'server.api_server' "${DSIP_API_BASEURL}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'server.api_token' "${DSIP_API_TOKEN}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'server.role' "${ROLE}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'teleblock.gw_enabled' "${TELEBLOCK_GW_ENABLED}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'teleblock.gw_ip' "${TELEBLOCK_GW_IP}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'teleblock.gw_port' "${TELEBLOCK_GW_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'teleblock.media_ip' "${TELEBLOCK_MEDIA_IP}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigGlobal 'teleblock.media_port' "${TELEBLOCK_MEDIA_PORT}" ${DSIP_KAMAILIO_CONFIG_FILE} # hot reloading global settings if systemctl is-active --quiet kamailio 2>/dev/null; then sendKamCmd cfg.sets server role "${ROLE}" &>/dev/null sendKamCmd cfg.sets server api_server "${DSIP_API_BASEURL}" &>/dev/null sendKamCmd cfg.sets server api_token "${DSIP_API_TOKEN}" &>/dev/null sendKamCmd cfg.seti teleblock gw_enabled "${TELEBLOCK_GW_ENABLED}" &>/dev/null sendKamCmd cfg.sets teleblock gw_ip "${TELEBLOCK_GW_IP}" &>/dev/null sendKamCmd cfg.seti teleblock gw_port "${TELEBLOCK_GW_PORT}" &>/dev/null sendKamCmd cfg.sets teleblock media_ip "${TELEBLOCK_MEDIA_IP}" &>/dev/null sendKamCmd cfg.seti teleblock media_port "${TELEBLOCK_MEDIA_PORT}" &>/dev/null fi # check for cluster db connection and set kam db config settings appropriately # note: the '@' symbol must be escaped in perl regex if printf '%s' "$KAM_DB_HOST" | grep -q -oP '(\[.*\]|.*,.*)'; then # db connection is clustered enableKamailioConfigAttrib 'WITH_DBCLUSTER' ${DSIP_KAMAILIO_CONFIG_FILE} # TODO: support different type/user/pass/port/name per connection # TODO: support multiple clusters local KAM_DB_CLUSTER_CONNS="" local KAM_DB_CLUSTER_MODES="" local KAM_DB_CLUSTER_NODES=$(printf '%s' "$KAM_DB_HOST" | tr -d '[]'"'"'"' | tr ',' ' ') local i=1 for NODE in $KAM_DB_CLUSTER_NODES; do KAM_DB_CLUSTER_CONNS+="modparam('db_cluster', 'connection', 'c${i}=>${KAM_DB_TYPE}://${KAM_DB_USER}:${KAM_DB_PASS}\\@${NODE}:${KAM_DB_PORT}/${KAM_DB_NAME}')\n" KAM_DB_CLUSTER_MODES+="c${i}=9r9r;" i=$((i+1)) done KAM_DB_CLUSTER_MODES="modparam('db_cluster', 'cluster', 'dbcluster=>${KAM_DB_CLUSTER_MODES}')" perl -e "\$dbcluster='${KAM_DB_CLUSTER_CONNS}${KAM_DB_CLUSTER_MODES}';" \ -0777 -i -pe 's~(modparam\("db_cluster", "connection".*\s)+(modparam\("db_cluster", "cluster".*)~${dbcluster}~gm' ${DSIP_KAMAILIO_CONFIG_FILE} else local DBURL="${KAM_DB_TYPE}://${KAM_DB_USER}:${KAM_DB_PASS}@${KAM_DB_HOST}:${KAM_DB_PORT}/${KAM_DB_NAME}" setKamailioConfigDburl "DBURL" "${DBURL}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigDburl "SQLCONN_KAM" "kam=>${DBURL}" ${DSIP_KAMAILIO_CONFIG_FILE} setKamailioConfigDburl "SQLCONN_AST" "asterisk=>${DBURL}" ${DSIP_KAMAILIO_CONFIG_FILE} fi # update kamailio TLS config file # if (( ${IPV6_ENABLED} == 1 )); then # perl -e "\$external_ip='${EXTERNAL_IP_ADDR}'; \$wss_port='${KAM_WSS_PORT}'; "'$ipv6_config= # "[server:['"${EXTERNAL_IP6_ADDR}"']:'"${KAM_WSS_PORT}"']\n" . # "method = TLSv1.2+\n" . # "verify_certificate = no\n" . # "require_certificate = no\n" . # "private_key = /etc/dsiprouter/certs/dsiprouter-key.pem\n" . # "certificate = /etc/dsiprouter/certs/dsiprouter-cert.pem\n" . # "ca_list = /etc/dsiprouter/certs/ca-list.pem\n" . # "#crl = /etc/dsiprouter/certs/crl.pem\n";' \ # -0777 -i -pe 's%(#========== webrtc_ipv4_start ==========#.*?\[server:).*?:.*?(\].*#========== webrtc_ipv4_stop ==========#)%\1${external_ip}:${wss_port}\2%s; # s%(#========== webrtc_ipv6_start ==========#[\s]+).*(#========== webrtc_ipv6_stop ==========#)%\1${ipv6_config}\2%s;' \ # ${DSIP_KAMAILIO_TLS_CONFIG_FILE} # else # perl -e "\$external_ip='${EXTERNAL_IP_ADDR}'; \$wss_port='${KAM_WSS_PORT}';" -0777 -i \ # -pe 's%(#========== webrtc_ipv4_start ==========#.*?\[server:).*?:.*?(\].*#========== webrtc_ipv4_stop ==========#)%\1${external_ip}:${wss_port}\2%s; # s%(#========== webrtc_ipv6_start ==========#[\s]+).*(#========== webrtc_ipv6_stop ==========#)%\1\2%s;' \ # ${DSIP_KAMAILIO_TLS_CONFIG_FILE} # fi return 0 } # update kamailio service startup commands accounting for any changes function updateKamailioStartup { local KAM_UPDATE_OPTS="" # update kamailio configs on reboot removeInitCmd "/usr/bin/dsiprouter updatekamconfig" addInitCmd "/usr/bin/dsiprouter updatekamconfig $KAM_UPDATE_OPTS" # make sure dsip-init service runs prior to kamailio service removeDependsOnInit "kamailio.service" addDependsOnInit "kamailio.service" } function generateRtpengineConfig() { mkdir -p ${BACKUPS_DIR}/rtpengine/ cp -af ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/. ${BACKUPS_DIR}/rtpengine/ 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/rtpengine.conf ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/ ln -sft ${SYSTEM_RTPENGINE_CONFIG_DIR}/ ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/* } # updates and settings in rtpengine config that may change # should be run after reboot or change in network configurations function updateRtpengineConfig() { local INTERFACE="" local RTP_PORT_MIN=${RTP_PORT_MIN:-$(getRtpengineConfigAttrib 'RTP_PORT_MIN' ${SYSTEM_RTPENGINE_CONFIG_FILE})} local RTP_PORT_MAX=${RTP_PORT_MAX:-$(getRtpengineConfigAttrib 'RTP_PORT_MAX' ${SYSTEM_RTPENGINE_CONFIG_FILE})} local HOMER_ID=${HOMER_ID:-$(getConfigAttrib 'HOMER_ID' ${DSIP_CONFIG_FILE})} local HOMER_HEP_HOST=${HOMER_HEP_HOST:-$(getConfigAttrib 'HOMER_HEP_HOST' ${DSIP_CONFIG_FILE})} local HOMER_HEP_PORT=${HOMER_HEP_PORT:-$(getConfigAttrib 'HOMER_HEP_PORT' ${DSIP_CONFIG_FILE})} if (( ${NETWORK_MODE} == 2 )); then # TODO: ipv6 support broken here INTERFACE="public/${EXTERNAL_IP_ADDR}; private/${INTERNAL_IP_ADDR}" else if (( ${SIGNAL_SERVERNAT} == 1 )); then INTERFACE="ipv4/${INTERNAL_IP_ADDR}!${EXTERNAL_IP_ADDR}" else INTERFACE="ipv4/${INTERNAL_IP_ADDR}" fi if (( ${IPV6_ENABLED} == 1 )); then if (( ${SIGNAL_SERVERNAT6} == 1 )); then INTERFACE="${INTERFACE}; ipv6/${INTERNAL_IP6_ADDR}!${EXTERNAL_IP6_ADDR}" else INTERFACE="${INTERFACE}; ipv6/${INTERNAL_IP6_ADDR}" fi fi fi setRtpengineConfigAttrib 'interface' "$INTERFACE" ${SYSTEM_RTPENGINE_CONFIG_FILE} setRtpengineConfigAttrib 'port-min' "$RTP_PORT_MIN" ${SYSTEM_RTPENGINE_CONFIG_FILE} setRtpengineConfigAttrib 'port-max' "$RTP_PORT_MAX" ${SYSTEM_RTPENGINE_CONFIG_FILE} setRtpengineConfigAttrib 'homer' "${HOMER_HEP_HOST}:${HOMER_HEP_PORT}" ${SYSTEM_RTPENGINE_CONFIG_FILE} if [[ -n "$HOMER_ID" && "$HOMER_ID" != "None" ]]; then setRtpengineConfigAttrib 'homer-id' "$HOMER_ID" ${SYSTEM_RTPENGINE_CONFIG_FILE} fi if [[ -n "$HOMER_HEP_HOST" && -n "$HOMER_HEP_PORT" ]]; then enableRtpengineConfigAttrib 'homer' ${SYSTEM_RTPENGINE_CONFIG_FILE} enableRtpengineConfigAttrib 'homer-protocol' ${SYSTEM_RTPENGINE_CONFIG_FILE} enableRtpengineConfigAttrib 'homer-id' ${SYSTEM_RTPENGINE_CONFIG_FILE} else disableRtpengineConfigAttrib 'homer' ${SYSTEM_RTPENGINE_CONFIG_FILE} disableRtpengineConfigAttrib 'homer-protocol' ${SYSTEM_RTPENGINE_CONFIG_FILE} disableRtpengineConfigAttrib 'homer-id' ${SYSTEM_RTPENGINE_CONFIG_FILE} fi return 0 } # update rtpengine service startup commands accounting for any changes function updateRtpengineStartup() { reconfigureRtpengineSystemdService # always clear out the dsip-init entries for rtpengine removeInitCmd "/usr/bin/dsiprouter updatertpconfig" removeDependsOnInit "rtpengine.service" # conditionally add the dsip-init entries (MEDIA_SERVERNAT==1 only when rtpengine service is local) if (( ${MEDIA_SERVERNAT} == 1 )); then # update rtpengine configs on reboot addInitCmd "/usr/bin/dsiprouter updatertpconfig" # make sure dsip-init service runs prior to rtpengine service addDependsOnInit "rtpengine.service" fi } # updates DNSmasq configs from DB # TODO: dynamically update pacemaker IPs in /etc/hosts using getPacemakerInternalIP() # they would need loaded in the DB prior on boot (can not wait for dsiprouter.py) # because the pacemaker and corosync services start prior to dsiprouter.service function updateDnsConfig() { local KAM_DB_HOST=${KAM_DB_HOST:-$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE})} local KAM_DB_PORT=${KAM_DB_PORT:-$(getConfigAttrib 'KAM_DB_PORT' ${DSIP_CONFIG_FILE})} local KAM_DB_NAME=${KAM_DB_NAME:-$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE})} local KAM_DB_USER=${KAM_DB_USER:-$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE})} local KAM_DB_PASS=${KAM_DB_PASS:-$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE})} local DSIP_CLUSTER_ID=${DSIP_CLUSTER_ID:-$(getConfigAttrib 'DSIP_CLUSTER_ID' ${DSIP_CONFIG_FILE})} local DNS_CONFIG="" # grab hosts from db # NOTE: we don't add IPV6 addresses here as it is not needed and would only add more traffic to DMQ replication local INTERNAL_CLUSTER_HOSTS=( $(withKamDB mysql -sN \ -e "SELECT INTERNAL_IP_ADDR FROM dsip_settings WHERE DSIP_CLUSTER_ID = ${DSIP_CLUSTER_ID};" 2>/dev/null) ) local EXTERNAL_CLUSTER_HOSTS=( $(withKamDB mysql -sN \ -e "SELECT EXTERNAL_IP_ADDR FROM dsip_settings WHERE DSIP_CLUSTER_ID = ${DSIP_CLUSTER_ID};" 2>/dev/null) ) local NUM_HOSTS=${#INTERNAL_CLUSTER_HOSTS[@]} # only search through cluster hosts if we got results if (( ${NUM_HOSTS} > 0 )); then # find valid connections on dmq port: # try internal ip first and then external ip for i in $(seq 0 $((${NUM_HOSTS}-1))); do if checkConn ${INTERNAL_CLUSTER_HOSTS[$i]} ${KAM_DMQ_PORT}; then DNS_CONFIG+="${INTERNAL_CLUSTER_HOSTS[$i]} local.cluster\n" elif checkConn ${EXTERNAL_CLUSTER_HOSTS[$i]} ${KAM_DMQ_PORT}; then DNS_CONFIG+="${EXTERNAL_CLUSTER_HOSTS[$i]} local.cluster\n" fi done # otherwise make sure local node is resolvable when querying cluster else DNS_CONFIG+="${INTERNAL_IP_ADDR} local.cluster\n" fi # update hosts file perl -e "\$cluster_hosts=\"${DNS_CONFIG}\";" \ -0777 -i -pe 's|(#+DSIP_CONFIG_START).*?(#+DSIP_CONFIG_END)|\1\n${cluster_hosts}\2|gms' /etc/hosts # tell dnsmasq to reload configs if [ -f /run/dnsmasq/dnsmasq.pid ]; then kill -SIGHUP $(cat /run/dnsmasq/dnsmasq.pid) 2>/dev/null elif [ -f /run/dnsmasq.pid ]; then kill -SIGHUP $(cat /run/dnsmasq.pid) 2>/dev/null else kill -SIGHUP $(pidof dnsmasq) 2>/dev/null fi return 0 } function updateCACertsDir() { awk -v dsip_certs_dir="${DSIP_CERTS_DIR}" \ 'BEGIN {c=0;} /BEGIN CERT/{c++} { print > dsip_certs_dir "/ca/cert." c ".pem" }' <${DSIP_SSL_CA} && openssl rehash ${DSIP_CERTS_DIR}/ca/ && updatePermissions -certs && return 0 || return 1 } export -f updateCACertsDir function generateKamailioConfig() { local KAM_MAJ_MIN_INT=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1\2%' <<<"$KAM_VERSION") # Backup kamcfg, generate fresh config from templates, and link it in where kamailio wants it mkdir -p ${BACKUPS_DIR}/kamailio cp -af ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ cp -f ${PROJECT_KAMAILIO_CONFIG_DIR}/*.cfg ${DSIP_SYSTEM_CONFIG_DIR}/kamailio/ ln -sft ${SYSTEM_KAMAILIO_CONFIG_DIR}/ ${DSIP_SYSTEM_CONFIG_DIR}/kamailio/* # version specific settings if (( ${KAM_MAJ_MIN_INT} >= 52 )); then sed -i -r -e 's~#+(modparam\(["'"'"']htable["'"'"'], ?["'"'"']dmq_init_sync["'"'"'], ?[0-9]\))~\1~g' ${DSIP_KAMAILIO_CONFIG_FILE} fi if (( ${KAM_MAJ_MIN_INT} <= 57 )); then sed -i -r -e 's~#*(modparam\(["'"'"']rtpengine["'"'"'], ?["'"'"']ping_mode["'"'"'], ?[0-9]\))~#\1~g' ${DSIP_KAMAILIO_CONFIG_FILE} fi # Fix the mpath and export $mpath fixMPATH # non-module features to enable # TODO: add check for WITH_TRANSNEXUS if (( ${WITH_LCR} == 1 )); then enableKamailioConfigAttrib 'WITH_LCR' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_LCR' ${DSIP_KAMAILIO_CONFIG_FILE} fi if [[ -f ${mpath}/stirshaken.so ]]; then enableKamailioConfigAttrib 'WITH_STIRSHAKEN' ${DSIP_KAMAILIO_CONFIG_FILE} else disableKamailioConfigAttrib 'WITH_STIRSHAKEN' ${DSIP_KAMAILIO_CONFIG_FILE} fi updatePermissions -kamailio } function configureKamailioDB() { # make sure kamailio user and privileges exist if ! checkDBUserExists "${KAM_DB_USER}@localhost"; then withRootDBConn mysql \ -e "CREATE USER '$KAM_DB_USER'@'localhost' IDENTIFIED BY '$KAM_DB_PASS';" \ -e "GRANT ALL PRIVILEGES ON $KAM_DB_NAME.* TO '$KAM_DB_USER'@'localhost';" \ -e "FLUSH PRIVILEGES;" fi if ! checkDBUserExists "${KAM_DB_USER}@%"; then withRootDBConn mysql \ -e "CREATE USER '$KAM_DB_USER'@'%' IDENTIFIED BY '$KAM_DB_PASS';" \ -e "GRANT ALL PRIVILEGES ON $KAM_DB_NAME.* TO '$KAM_DB_USER'@'%';" \ -e "FLUSH PRIVILEGES;" fi # Install schema for drouting module withRootDBConn --db="$KAM_DB_NAME" mysql \ -e "delete from version where table_name in ('dr_gateways','dr_groups','dr_gw_lists','dr_custom_rules','dr_rules')" withRootDBConn --db="$KAM_DB_NAME" mysql \ -e "drop table if exists dr_gateways,dr_groups,dr_gw_lists,dr_custom_rules,dr_rules" if [ -e /usr/share/kamailio/mysql/drouting-create.sql ]; then withRootDBConn --db="$KAM_DB_NAME" mysql \ < /usr/share/kamailio/mysql/drouting-create.sql else sqlscript=$(find / -name '*drouting-create.sql' | grep 'mysql' | head -1) withRootDBConn --db="$KAM_DB_NAME" mysql \ < $sqlscript fi # Update schema for dr_gateways table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dr_gateways.sql # Update schema for dr_gw_lists table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dr_gw_lists.sql # Update schema for dr_rules table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dr_rules.sql # Update schema for dispatcher table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dispatcher.sql # Update schema for address table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/address.sql # Update schema for subscribers table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/subscribers.sql # Update schema for uacreg table withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/uacreg.sql # Install schema for custom LCR logic withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_lcr.sql # Install schema for custom MaintMode logic withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_maintmode.sql # Install schema for gwgroup call settings withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_call_settings.sql # Install schema for Notifications withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_notification.sql # Install schema for dsip_gw2gwgroup withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_gw2gwgroup.sql # Install schema for dsip_gwgroup2lb withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_gwgroup2lb.sql # Install schema for dsip_cdrinfo withRootDBConn --db="$KAM_DB_NAME" mysql \ < ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_cdrinfo.sql # Install schema for dsip_settings perl -e "\$hlen='$HASHED_CREDS_ENCODED_MAX_LEN'; \$clen='$AESCTR_CREDS_ENCODED_MAX_LEN';" \ -pe 's%\@HASHED_CREDS_ENCODED_MAX_LEN%$hlen%g; s%\@AESCTR_CREDS_ENCODED_MAX_LEN%$clen%g;' \ ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_settings.sql | withRootDBConn --db="$KAM_DB_NAME" mysql # Install schema for dsip_hardfwd and dsip_failfwd and dsip_prefix_mapping sed -e "s|FLT_INBOUND_REPLACE|${FLT_INBOUND}|g" ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_forwarding.sql | withRootDBConn --db="$KAM_DB_NAME" mysql # TODO: we need to test and re-implement this. # # required if tables exist and we are updating # function resetIncrementers { # SQL_TABLES=$( # (for t in "$@"; do printf ",'$t'"; done) | cut -d ',' -f '2-' # ) # # # reset auto increment for related tables to max btwn the related tables # INCREMENT=$( # withRootDBConn mysql --skip-column-names -e "\ # SELECT MAX(AUTO_INCREMENT) FROM INFORMATION_SCHEMA.TABLES \ # WHERE TABLE_SCHEMA = '$KAM_DB_NAME' \ # AND TABLE_NAME IN($SQL_TABLES);" # ) # for t in "$@"; do # withRootDBConn --db="$KAM_DB_NAME" mysql \ # -e "ALTER TABLE $t AUTO_INCREMENT=$INCREMENT" # done # } # # # reset auto incrementers for related tables # resetIncrementers "dr_gw_lists" # resetIncrementers "uacreg" # truncate tables first if kamailio already installed if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then withRootDBConn --db="$KAM_DB_NAME" mysql \ -e "TRUNCATE TABLE dr_gw_lists; TRUNCATE TABLE address; TRUNCATE TABLE dr_gateways; TRUNCATE TABLE dr_rules;" fi # import default carriers and outbound routes mkdir -p /tmp/defaults # generate defaults subbing in dynamic values cp -f ${PROJECT_DSIP_DEFAULTS_DIR}/dr_gw_lists.csv /tmp/defaults/dr_gw_lists.csv sed "s/FLT_CARRIER/$FLT_CARRIER/g; s/FLT_PBX/$FLT_PBX/g; s/FLT_MSTEAMS/$FLT_MSTEAMS/g" \ ${PROJECT_DSIP_DEFAULTS_DIR}/address.csv > /tmp/defaults/address.csv sed "s/FLT_CARRIER/$FLT_CARRIER/g; s/FLT_PBX/$FLT_PBX/g; s/FLT_MSTEAMS/$FLT_MSTEAMS/g" \ ${PROJECT_DSIP_DEFAULTS_DIR}/dr_gateways.csv > /tmp/defaults/dr_gateways.csv sed "s/FLT_OUTBOUND/$FLT_OUTBOUND/g; s/FLT_INBOUND/$FLT_INBOUND/g" \ ${PROJECT_DSIP_DEFAULTS_DIR}/dr_rules.csv > /tmp/defaults/dr_rules.csv # import default carriers withRootDBConn mysqlimport \ --fields-terminated-by=';' --ignore-lines=0 -L $KAM_DB_NAME /tmp/defaults/dr_gw_lists.csv withRootDBConn mysqlimport \ --fields-terminated-by=';' --ignore-lines=0 -L $KAM_DB_NAME /tmp/defaults/address.csv withRootDBConn mysqlimport \ --fields-terminated-by=';' --ignore-lines=0 -L $KAM_DB_NAME /tmp/defaults/dr_gateways.csv withRootDBConn mysqlimport \ --fields-terminated-by=',' --ignore-lines=0 -L $KAM_DB_NAME ${PROJECT_DSIP_DEFAULTS_DIR}/dispatcher.csv withRootDBConn mysqlimport \ --fields-terminated-by=';' --ignore-lines=0 -L $KAM_DB_NAME /tmp/defaults/dr_rules.csv # cleanup temp files rm -rf /tmp/defaults } # Try to locate the Kamailio modules directory. It will use the last modules directory found function fixMPATH() { mpath=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h/' -quit 2>/dev/null) if [ "$mpath" != '' ]; then setKamailioConfigGlobal 'mpath' "${mpath}" ${DSIP_KAMAILIO_CONFIG_FILE} printdbg "The Kamailio mpath has been updated to: $mpath" else printerr "Can't find the module path for Kamailio. Please ensure Kamailio is installed and try again!" exit 1 fi } # Requirements to run this script / any imported functions function installScriptRequirements() { if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.requirementsinstalled" ]; then return fi printdbg 'Installing one-time script requirements' case "$DISTRO" in rocky|almalinux) dnf install -y curl wget gawk perl sed git bind-utils openssl python3.11 jq vim-common coreutils ;; *) if cmdExists 'apt-get'; then apt-get update -y && apt-get install -y curl wget gawk perl sed git dnsutils openssl python3 jq xxd coreutils elif cmdExists 'dnf'; then dnf install -y curl wget gawk perl sed git bind-utils openssl python3 jq vim-common coreutils elif cmdExists 'yum'; then yum install -y curl wget gawk perl sed git bind-utils openssl python3 jq vim-common coreutils fi ;; esac if (( $? != 0 )); then printerr 'Could not install script requirements' exit 1 fi # initialize the openssl rnd generator dd if=/dev/urandom of="${HOME}/.rnd" bs=1024 count=1 2>/dev/null printdbg 'One-time script requirements installed' touch ${DSIP_SYSTEM_CONFIG_DIR}/.requirementsinstalled return 0 } # Any setup that needs to be done before the script can run properly function setupScriptRequiredFiles() { # make sure dirs exist required for this script mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}{,/gui,/kamailio,/rtpengine} ${SRC_DIR} ${DSIP_RUN_DIR} ${DSIP_LIB_DIR} ${DSIP_CERTS_DIR}{,/ca} ${BACKUPS_DIR} # only copy the template file over to the DSIP_CONFIG_FILE if it doesn't already exist if [[ ! -f "${DSIP_CONFIG_FILE}" ]]; then # copy over the template settings.py to be worked on (used throughout this script as well) cp -f ${DSIP_PROJECT_DIR}/gui/settings.py ${DSIP_CONFIG_FILE} fi } function configureSystemPath() { # fix PATH if needed # we are using the default install paths but these may change in the future if [[ ! -e "$PATH_UPDATE_FILE" ]]; then mkdir -p $(dirname ${PATH_UPDATE_FILE}) (cat << 'EOF' #export PATH="/usr/local/bin${PATH:+:$PATH}" #export PATH="${PATH:+$PATH:}/usr/sbin" #export PATH="${PATH:+$PATH:}/usr/bin" #export PATH="${PATH:+$PATH:}/sbin" EOF ) > ${PATH_UPDATE_FILE} fi # minimalistic approach avoids growing duplicates # enable (uncomment) and import only what we need local PATH_UPDATED=0 # - sipsak if ! pathCheck /usr/local/bin; then sed -i -r 's|^#(export PATH="/usr/local/bin\$\{PATH:\+:\$PATH\}")$|\1|' ${PATH_UPDATE_FILE} PATH_UPDATED=1 fi # - rtpengine if ! pathCheck /usr/sbin; then sed -i -r 's|^#(export PATH="\$\{PATH:\+\$PATH:\}/usr/sbin")$|\1|' ${PATH_UPDATE_FILE} PATH_UPDATED=1 fi # - dsiprouter if ! pathCheck /usr/bin; then sed -i -r 's|^#(export PATH="\$\{PATH:\+\$PATH:\}/usr/bin")$|\1|' ${PATH_UPDATE_FILE} PATH_UPDATED=1 fi # - kamailio if ! pathCheck /sbin; then sed -i -r 's|^#(export PATH="\$\{PATH:\+\$PATH:\}/sbin")$|\1|' ${PATH_UPDATE_FILE} PATH_UPDATED=1 fi # import new path definition if it was updated (( ${PATH_UPDATED} == 1 )) && . ${PATH_UPDATE_FILE} return 0 } function revertSystemPath() { rm -f ${PATH_UPDATE_FILE} return 0 } # Configure system repo sources to ensure we get the right package versions # TODO: dynamic mirror resolution based on RTT # TODO support multiple mirrors in repo configs # - ubuntu refs: # https://repogen.simplylinux.ch/ # https://mirrors.ustc.edu.cn/repogen/ # https://gist.github.com/rhuancarlos/c4d3c0cf4550db5326dca8edf1e76800 # - centos refs: # https://unix.stackexchange.com/questions/52666/how-do-i-install-the-stock-centos-repositories # https://wiki.centos.org/PackageManagement/Yum/Priorities function configureSystemRepos() { if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.reposconfigured" ]; then return 0 fi printdbg 'Configuring system repositories' case "$DISTRO" in debian|ubuntu) if [[ ! -f "$APT_DSIP_CONFIG" ]]; then # default dpkg to noninteractive modes for installs cat <<'EOF' >${APT_DSIP_CONFIG} Dpkg::Options { "--force-confdef"; "--force-confnew"; } Dpkg::Lock::Timeout "300"; APT::Get::Fix-Missing "1"; EOF fi # comment out cdrom in sources as it can halt install sed -i -E 's/(^\w.*cdrom.*)/#\1/g' /etc/apt/sources.list apt-get install -y apt-transport-https mv -f ${APT_OFFICIAL_SOURCES} ${APT_OFFICIAL_SOURCES_BAK} mv -f ${APT_OFFICIAL_PREFS} ${APT_OFFICIAL_PREFS_BAK} 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/apt/${DISTRO}/${DISTRO_VER}/official-releases.list ${APT_OFFICIAL_SOURCES} cp -f ${DSIP_PROJECT_DIR}/resources/apt/${DISTRO}/${DISTRO_VER}/official-releases.pref ${APT_OFFICIAL_PREFS} apt-get update -y ;; almalinux) # ref: https://almalinux.org/blog/2023-12-20-almalinux-8-key-update/ rpm --import https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux ;; # TODO: create official repo file (rhel/amzn/rocky/alma repo's?) # TODO: install yum priorities plugin # TODO: set priorities on official repo #amzn) # ;; esac if (( $? == 1 )); then printerr 'Could not configure system repositories' exit 1 elif (( $? >= 100 )); then printwarn 'Some issues occurred configuring system repositories, attempting to continue...' touch ${DSIP_SYSTEM_CONFIG_DIR}/.reposconfigured else printdbg 'System repositories configured successfully' touch ${DSIP_SYSTEM_CONFIG_DIR}/.reposconfigured fi } # remove dsiprouter system configs function revertSystemRepos() { if [[ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.reposconfigured" ]]; then case "$DISTRO" in debian|ubuntu) if [[ "$DISTRO" == "ubuntu" ]] && (( ${DISTRO_MAJOR_VER} >= 24 )); then APT_OFFICIAL_SOURCES="/etc/apt/sources.d/ubuntu.sources" APT_OFFICIAL_SOURCES_BAK="${BACKUPS_DIR}/original-sources.sources" else APT_OFFICIAL_SOURCES="/etc/apt/sources.list" APT_OFFICIAL_SOURCES_BAK="${BACKUPS_DIR}/original-sources.list" fi APT_OFFICIAL_PREFS="/etc/apt/preferences" APT_OFFICIAL_PREFS_BAK="${BACKUPS_DIR}/original-sources.pref" APT_DSIP_CONFIG="/etc/apt/apt.conf.d/99dsiprouter" mv -f ${APT_OFFICIAL_SOURCES_BAK} ${APT_OFFICIAL_SOURCES} mv -f ${APT_OFFICIAL_PREFS_BAK} ${APT_OFFICIAL_PREFS} 2>/dev/null apt-get update -y ;; esac fi if [[ -f "$APT_DSIP_CONFIG" ]]; then rm -f "$APT_DSIP_CONFIG" fi rm -rf ${DSIP_SYSTEM_CONFIG_DIR} } # Install and configure mysql server function installMysql() { if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.mysqlinstalled" ]]; then printwarn "MySQL is already installed" return 0 fi printdbg "Attempting to install / configure MySQL..." ${DSIP_PROJECT_DIR}/mysql/${DISTRO}/${DISTRO_MAJOR_VER}.sh install if (( $? != 0 )); then printerr "MySQL install failed" exit 1 fi # Restart MySQL with the new configurations systemctl restart mariadb if systemctl is-active --quiet mariadb; then touch ${DSIP_SYSTEM_CONFIG_DIR}/.mysqlinstalled printdbg "------------------------------------" pprint "MySQL Installation is complete!" printdbg "------------------------------------" return 0 else printerr "MySQL install failed" exit 1 fi } # Remove mysql and its configs function uninstallMysql() { if [[ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.mysqlinstalled" ]]; then printwarn "MySQL is not installed - skipping removal" return 0 fi printdbg "Attempting to uninstall MySQL..." ${DSIP_PROJECT_DIR}/mysql/${DISTRO}/${DISTRO_MAJOR_VER}.sh uninstall if (( $? != 0 )); then printerr "MySQL uninstall failed" exit 1 fi # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.mysqlinstalled printdbg "MySQL was uninstalled" } # Install and configure nginx server function installNginx() { if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then printwarn "nginx is already installed" return 0 fi printdbg "Attempting to install / configure nginx..." ${DSIP_PROJECT_DIR}/nginx/${DISTRO}/${DISTRO_MAJOR_VER}.sh install if (( $? != 0 )); then printerr "nginx install failed" exit 1 fi # Restart nginx with the new configurations systemctl restart nginx if systemctl is-active --quiet nginx; then touch ${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled printdbg "------------------------------------" pprint "nginx Installation is complete!" printdbg "------------------------------------" return 0 else printerr "nginx install failed" exit 1 fi } # Remove nginx and its configs function uninstallNginx() { if [[ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then printwarn "nginx is not installed - skipping removal" return 0 fi printdbg "Attempting to uninstall nginx..." ${DSIP_PROJECT_DIR}/nginx/${DISTRO}/${DISTRO_MAJOR_VER}.sh uninstall if (( $? != 0 )); then printerr "nginx uninstall failed" exit 1 fi # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled printdbg "nginx was uninstalled" } # Install the RTPEngine from sipwise function installRTPEngine() { if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]; then printwarn "RTPEngine is already installed" return 0 fi printdbg "Attempting to install RTPEngine..." ${DSIP_PROJECT_DIR}/rtpengine/${DISTRO}/install.sh install if (( $? != 0 )); then printerr "RTPEngine install failed" exit 1 fi generateRtpengineConfig # config updates that are the same across all OS updateRtpengineConfig # add the config updates to dsip-init service updateRtpengineStartup # restart RTPEngine with the new configurations systemctl restart rtpengine # did the service actually start with the changes? if ! systemctl is-active --quiet rtpengine; then printerr "RTPEngine install failed" exit 1 fi # sanity check, did the new kernel module load? if ! lsmod | grep -q 'xt_RTPENGINE' 2>/dev/null; then printwarn "RTPEngine setup in userspace forwarding mode" printwarn "you may need to reboot the system to load the new kernel" fi # if we got here we know everything installed properly, update kamailio to use rtpengine if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then enableKamailioConfigAttrib 'WITH_RTPENGINE' ${DSIP_KAMAILIO_CONFIG_FILE} systemctl restart kamailio fi touch ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled printdbg "------------------------------------" pprint "RTPEngine Installation is complete!" printdbg "------------------------------------" return 0 } # Remove RTPEngine function uninstallRTPEngine() { if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]; then printwarn "RTPEngine is not installed! - uninstalling anyway to be safe" fi printdbg "Attempting to uninstall RTPEngine..." ${DSIP_PROJECT_DIR}/rtpengine/${DISTRO}/install.sh uninstall if (( $? == 0 )); then if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then disableKamailioConfigAttrib 'WITH_RTPENGINE' ${DSIP_KAMAILIO_CONFIG_FILE} systemctl restart kamailio fi else printerr "RTPEngine uninstall failed" exit 1 fi # remove rtpengine service dependencies removeInitCmd "/usr/bin/dsiprouter updatertpconfig" removeDependsOnInit "rtpengine.service" # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled printdbg "RTPEngine was uninstalled" } function installDsiprouterCli() { local MAN_PROGS_DIR="/usr/share/man/man1" if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled" ]; then printwarn "dSIPRouter CLI is already installed" return 0 fi # TODO: add support for 'su' privilege escalation printdbg "Installing dSIPRouter CLI" if cmdExists 'apt-get'; then apt-get install -y sudo elif cmdExists 'dnf'; then dnf install -y sudo elif cmdExists 'yum'; then yum install -y sudo else printerr 'Could not install dSIPRouter CLI - failed installing required packages' exit 1 fi # add dsiprouter CLI command to the path ln -sf ${DSIP_PROJECT_DIR}/dsiprouter.sh /usr/bin/dsiprouter # add specific commands to sudoers that dsiprouter can run with escalated privileges cp -f ${DSIP_PROJECT_DIR}/dsiprouter/sudoers.d/99-dsiprouter ${DSIP_SUDOERS_FILE} printdbg "Installing 'dsiprouter' tab completion" if cmdExists 'apt-get'; then apt-get install -y bash-completion elif cmdExists 'dnf'; then dnf install -y bash-completion elif cmdExists 'yum'; then yum install -y bash-completion else printerr 'Could not install bash tab completion - failed installing required packages' exit 1 fi # enable bash command line completion if not already if [[ -f /etc/bash.bashrc ]]; then perl -i -0777 -pe 's%#(if ! shopt -oq posix; then\n)#([ \t]+if \[ -f /usr/share/bash-completion/bash_completion \]; then\n)#(.*?\n)#(.*?\n)#(.*?\n)#(.*?\n)#(.*?\n)%\1\2\3\4\5\6\7%s' /etc/bash.bashrc fi # add command line completion for dsiprouter CLI cp -f ${DSIP_PROJECT_DIR}/dsiprouter/dsip_completion.sh /etc/bash_completion.d/dsiprouter printdbg "Installing 'dsiprouter' manpages" if cmdExists 'apt-get'; then apt-get install -y manpages man-db elif cmdExists 'dnf'; then dnf install -y man-pages man-db man elif cmdExists 'yum'; then yum install -y man-pages man-db man else printerr 'Could not install manpages - failed installing required packages' exit 1 fi # setup the manpages cp -f ${DSIP_PROJECT_DIR}/resources/man/dsiprouter.1 ${MAN_PROGS_DIR}/ && gzip -f ${MAN_PROGS_DIR}/dsiprouter.1 && mandb || printwarn 'Updating the mandb failed. Continuing the installation..' touch "${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled" printdbg "dSIPRouter CLI installed" echo '' printbold "To enable tab completion in this terminal session run:" echo -ne "source /etc/bash_completion\n\n" return 0 } function uninstallDsiprouterCli() { local MAN_PROGS_DIR="/usr/share/man/man1" if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled" ]; then printwarn "dSIPRouter CLI is not installed, skipping..." return 0 else printdbg "Uninstalling dSIPRouter CLI" fi # remove dsiprouter and dsiprouterd commands from the path rm -f /usr/bin/dsiprouter # remove command line completion for dsiprouter.sh rm -f /etc/bash_completion.d/dsiprouter # remove dsiprouter sudoers file rm -f ${DSIP_SUDOERS_FILE} printdbg "uninstalling dsiprouter manpages" rm -f ${MAN_PROGS_DIR}/dsiprouter.1 rm -f ${MAN_PROGS_DIR}/dsiprouter.1.gz mandb printdbg "dsiprouter manpages uninstalled" rm -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled" printdbg "dSIPRouter CLI uninstalled" } # TODO: move documentation generation into its own separate function # TODO: allow password changes on cloud instances (remove password reset after image creation) # we should be starting the web server as root and dropping root privilege after # this is standard practice, but we would have to consider file permissions # it would be easier to manage if we moved dsiprouter configs to /etc/dsiprouter function installDsiprouter() { local DSIP_CURRENT_PYTHON_VER if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]; then printwarn "dSIPRouter is already installed" return 0 fi printdbg "Attempting to install dSIPRouter..." ${DSIP_PROJECT_DIR}/dsiprouter/${DISTRO}/${DISTRO_MAJOR_VER}.sh install if (( $? != 0 )); then printerr "dSIPRouter install failed - OS install script failure" exit 1 fi # extra check to ensure the OS specific script gave us the required python version # this just needs to be true for the virtual environment so may be different than # the system python version installed DSIP_CURRENT_PYTHON_VER=$(${PYTHON_CMD} -V | cut -d ' ' -f 2) versionCompare $DSIP_CURRENT_PYTHON_VER gteq $DSIP_MIN_PYTHON_VER if (( $? != 0 )); then printerr "dSIPRouter install failed - minimum python version not installed" exit 1 fi printdbg "Configuring dSIPRouter settings" if [[ ! -f "$DSIP_CONFIG_FILE" ]]; then generateDsiprouterConfig fi # Set dsip private key (used for encryption across services) by following precedence: # 1: set via cmdline arg # 2: set prior to externally # 3: generate new key # TODO: create bash-native equivalent function for creating the private key if [[ -n ${SET_DSIP_PRIV_KEY+set} ]]; then printf '%s' "${SET_DSIP_PRIV_KEY}" > ${DSIP_PRIV_KEY} elif [ -f "${DSIP_PRIV_KEY}" ]; then : elif (( ${RUNNING_UPGRADE:-0} == 0 )); then # only generate if running a fresh install ${PYTHON_CMD} -c "import os,sys; os.chdir('${DSIP_PROJECT_DIR}/gui'); sys.path.insert(0, '${DSIP_SYSTEM_CONFIG_DIR}/gui'); from util.security import AES_CTR; AES_CTR.genKey()" fi # Set credentials for our services, will either use credentials from CLI or generate them if [[ -z ${SET_DSIP_GUI_PASS+set} ]] && (( ${RUNNING_UPGRADE:-0} == 0 )); then if (( ${IMAGE_BUILD} == 1 || ${RESET_FORCE_INSTANCE_ID:-0} == 1 )); then if [[ -z "$CLOUD_PLATFORM" ]]; then printerr "Cloud Instance password generation requested, but Cloud Platform is unsupported or not found" exit 1 fi SET_DSIP_GUI_PASS=$(getInstanceID) else SET_DSIP_GUI_PASS=$(urandomChars 64) fi fi if [[ -z ${SET_DSIP_API_TOKEN+set} ]] && (( ${RUNNING_UPGRADE:-0} == 0 )); then SET_DSIP_API_TOKEN=$(urandomChars 64) fi if [[ -z ${SET_DSIP_IPC_TOKEN+set} ]] && (( ${RUNNING_UPGRADE:-0} == 0 )); then SET_DSIP_IPC_TOKEN=$(urandomChars 64) fi if [[ -z ${SET_KAM_DB_PASS+set} ]] && (( ${RUNNING_UPGRADE:-0} == 0 )); then SET_KAM_DB_PASS=$(urandomChars 64) fi SET_DSIP_SESSION_KEY=${SET_DSIP_SESSION_KEY:-$(decryptConfigAttrib 'DSIP_SESSION_KEY' ${DSIP_CONFIG_FILE})} if [[ "$SET_DSIP_SESSION_KEY" == "None" ]] || [[ -z "$SET_DSIP_SESSION_KEY" ]]; then SET_DSIP_SESSION_KEY=$(urandomChars 32) fi # pass the variables on to setCredentials() SERVICE_RELOAD_DISABLED=1 setCredentials unset SERVICE_RELOAD_DISABLED # configure dsiprouter modules (must be run after potential credential updates above) installModules # update the rest of the settings updateDsiprouterConfig # update systemd startup dependencies updateDsiprouterStartup # NOTE: some of the previous files/dirs get updated here to allow dsiprouter access updatePermissions -certs -kamailio -dsiprouter # for cloud images the instance-id may change (could be a clone) # add to cloud-init startup process a password reset to ensure its set correctly # this is only for cloud image builds and will run when the instance is initialized or the instance-id is changed if (( $IMAGE_BUILD == 1 )) && (( $AWS_ENABLED == 1 || $DO_ENABLED == 1 || $GCE_ENABLED == 1 || $AZURE_ENABLED == 1 || $VULTR_ENABLED == 1 )); then (cat << EOF #!/usr/bin/env bash # reset admin user password /usr/bin/dsiprouter resetpassword -q -fid exit 0 EOF ) >/var/lib/cloud/scripts/per-instance/99-dsip-reset-guiadminpass.sh chmod +x /var/lib/cloud/scripts/per-instance/99-dsip-reset-guiadminpass.sh # Required changes for Debian-based images case "$DISTRO" in debian|ubuntu) # Remove debian-sys-maint password for initial image scan sed -i "s/password =.*/password = /g" /etc/mysql/debian.cnf # Change default password for debian-sys-maint to instance-id at next boot # we must also change the corresponding password in /etc/mysql/debian.cnf # to comply with AWS AMI image standards # this must run at startup as well so create temp script and add to dsip-init (cat << EOF #!/usr/bin/env bash # declare imported functions from library $(declare -f isInstanceAMI) $(declare -f isInstanceDO) $(declare -f isInstanceGCE) $(declare -f isInstanceAZURE) $(declare -f isInstanceVULTR) $(declare -f getInstanceID) # wait for mysql to start while ! systemctl is-active --quiet mariadb; do sleep 2 done # reset debian user password INSTANCE_ID=\$(getInstanceID) mysql -e "DROP USER 'debian-sys-maint'@'localhost'; CREATE USER 'debian-sys-maint'@'localhost' IDENTIFIED BY '\${INSTANCE_ID}'; GRANT ALL ON *.* TO 'debian-sys-maint'@'localhost' IDENTIFIED BY '\${INSTANCE_ID}'; FLUSH PRIVILEGES;" sed -i "s|password =.*|password = \${INSTANCE_ID}|g" /etc/mysql/debian.cnf exit 0 EOF ) >/var/lib/cloud/scripts/per-instance/99-dsip-reset-debsysuser.sh chmod +x /var/lib/cloud/scripts/per-instance/99-dsip-reset-debsysuser.sh ;; esac fi # generate documentation for the GUI # TODO: we should fix these errors # TODO: we should move generated docs to DSIP_LIB_DIR to keep clean repo ( cd ${DSIP_PROJECT_DIR}/docs && make -j $(nproc) html ) # Restart mysql / dSIPRouter / nginx / Kamailio with new configurations if [[ -f ${DSIP_SYSTEM_CONFIG_DIR}/.mysqlinstalled ]]; then systemctl restart mariadb fi if [[ -f ${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled ]]; then systemctl restart nginx fi if [[ -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled ]]; then systemctl restart kamailio fi systemctl restart dsiprouter if systemctl is-active --quiet dsiprouter; then # custom dsiprouter MOTD banner for ssh logins # only update on successful install so we don't confuse user updateBanner touch ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled printdbg "-------------------------------------" pprint "dSIPRouter Installation is complete! " printdbg "-------------------------------------" return 0 else printerr "dSIPRouter install failed" exit 1 fi } function uninstallDsiprouter() { cd ${DSIP_PROJECT_DIR} if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]; then printwarn "dSIPRouter is not installed or failed during install - uninstalling anyway to be safe" fi # stop the process systemctl stop dsiprouter printdbg "Attempting to uninstall dSIPRouter UI..." ${DSIP_PROJECT_DIR}/dsiprouter/${DISTRO}/${DISTRO_MAJOR_VER}.sh uninstall if [ $? -ne 0 ]; then printerr "dsiprouter uninstall failed" exit 1 fi # Remove all dsiprouter crontab entries printdbg "Removing dsiprouter crontab entries" cronRemove -u root 'dsiprouter_cron.py' cronRemove -u dsiprouter 'dsiprouter_cron.py' # Remove dsip private key rm -f ${DSIP_PRIV_KEY} # revert to previous MOTD ssh login banner revertBanner # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled printdbg "dSIPRouter was uninstalled" } function installKamailio() { local KAMDB_DATABASE_BACKUP_FILE="${CURR_BACKUP_DIR}/db.sql" local KAMDB_USER_BACKUP_FILE="${CURR_BACKUP_DIR}/user.sql" if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then printwarn "kamailio is already installed" return 0 else printdbg "Attempting to install Kamailio..." fi # backup and drop kam db if it exists already mkdir -p ${CURR_BACKUP_DIR} if cmdExists 'mysql'; then if checkDB "$KAM_DB_NAME"; then printdbg "Backing up kamailio DB to ${KAMDB_DATABASE_BACKUP_FILE} before fresh install" dumpDB "$KAM_DB_NAME" > ${KAMDB_DATABASE_BACKUP_FILE} withRootDBConn mysql -e "DROP DATABASE $KAM_DB_NAME;" printdbg "Backing up kamailio DB Users to ${KAMDB_USER_BACKUP_FILE} before fresh install" dumpDBUser "${KAM_DB_USER}@${KAM_DB_NAME}" > ${KAMDB_USER_BACKUP_FILE} withRootDBConn mysql -e "DROP USER IF EXISTS '$KAM_DB_USER'@'%'; DROP USER IF EXISTS '$KAM_DB_USER'@'localhost';" fi fi ${DSIP_PROJECT_DIR}/kamailio/${DISTRO}/${DISTRO_MAJOR_VER}.sh install if (( $? == 0 )); then configureSSL configureKamailioDB generateKamailioConfig updateKamailioConfig updateKamailioStartup else printerr "kamailio install failed" exit 1 fi # Restart Kamailio with the new configurations systemctl restart kamailio if systemctl is-active --quiet kamailio; then touch ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled printdbg "-----------------------------------" pprint "Kamailio Installation is complete!" printdbg "-----------------------------------" return 0 else printerr "Kamailio install failed" exit 1 fi } function uninstallKamailio() { cd ${DSIP_PROJECT_DIR} if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then printwarn "kamailio is not installed or failed during install - uninstalling anyway to be safe" fi # stop the process systemctl stop kamailio printdbg "Attempting to uninstall Kamailio..." ${DSIP_PROJECT_DIR}/kamailio/${DISTRO}/${DISTRO_MAJOR_VER}.sh uninstall if [ $? -ne 0 ]; then printerr "kamailio uninstall failed" exit 1 fi # remove kam service dependencies removeInitCmd "/usr/bin/dsiprouter updatekamconfig" removeDependsOnInit "kamailio.service" # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled printdbg "kamailio was uninstalled" } function installModules() { # Install / Uninstall dSIPModules for dir in ${DSIP_PROJECT_DIR}/gui/modules/*; do if [[ -e ${dir}/install.sh ]]; then ${dir}/install.sh fi done # priming function restart() to start/stop services # restart() may or may not be called after this depending on context STOP_DSIPROUTER=1 START_DSIPROUTER=1 STOP_KAMAILIO=1 START_KAMAILIO=1 STOP_RTPENGINE=0 START_RTPENGINE=0 return 0 } function installCron() { if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.croninstalled" ]; then printwarn "cron is already installed" return 0 else printdbg "Attempting to install cron" fi if cmdExists 'apt-get'; then apt-get install -y cron elif cmdExists 'dnf'; then dnf install -y cronie elif cmdExists 'yum'; then yum install -y cronie fi if (( $? != 0 )); then printerr "cron install failed" exit 1 fi pprint "cron was installed" touch ${DSIP_SYSTEM_CONFIG_DIR}/.croninstalled return 0 } # Install Sipsak # Used for testing and troubleshooting function installSipsak() { local NPROC if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.sipsakinstalled" ]; then printwarn "SipSak is already installed" return 0 else printdbg "Attempting to install SipSak" fi # Install sipsak requirements if cmdExists 'apt-get'; then apt-get install -y make gcc g++ automake autoconf openssl check git dirmngr pkg-config dh-autoreconf elif cmdExists 'dnf'; then dnf install -y make gcc gcc-c++ automake autoconf openssl check git perl-core elif cmdExists 'yum'; then yum install -y make gcc gcc-c++ automake autoconf openssl check git perl-core fi if (( $? != 0 )); then printwarn "SipSak install failed.. continuing without it" return 0 fi NPROC=$(nproc) # Install cpanm and perl deps (faster than cpan) curl -L http://cpanmin.us | perl - --self-upgrade cpanm URI::Escape # compile and install from src if [[ ! -d ${SRC_DIR}/sipsak ]]; then git clone https://github.com/nils-ohlmeier/sipsak.git ${SRC_DIR}/sipsak fi ( cd ${SRC_DIR}/sipsak && autoreconf -i && ./configure && make -j $NPROC && make -j $NPROC install && exit 0 || exit 1 ) if (( $? == 0 )); then pprint "SipSak was installed" touch ${DSIP_SYSTEM_CONFIG_DIR}/.sipsakinstalled else printwarn "SipSak install failed.. continuing without it" fi return 0 } # Remove Sipsak from the machine completely function uninstallSipsak() { local START_DIR="$(pwd)" if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.sipsakinstalled" ]; then printwarn "sipsak is not installed or failed during install - uninstalling anyway to be safe" fi if [ -d ${SRC_DIR}/sipsak ]; then cd ${SRC_DIR}/sipsak make uninstall rm -rf ${SRC_DIR}/sipsak fi # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.sipsakinstalled cd ${START_DIR} } # Install DNSmasq stub resolver for local DNS # - used by kamailio dmq replication function installDnsmasq() { if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]; then printwarn "DNSmasq is already installed" return 0 fi # create dnsmasq user and group # output removed, some cloud providers (DO) use caching and output is misleading # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel dnsmasq &>/dev/null; groupdel dnsmasq &>/dev/null useradd --system --user-group --shell /bin/false --comment "DNSmasq DNS Resolver" dnsmasq &>/dev/null printdbg "Attempting to install DNSmasq..." if (( ${DISTRO_VER} == 12 )); then ${DSIP_PROJECT_DIR}/dnsmasq/${DISTRO}/${DISTRO_VER}.sh install else ${DSIP_PROJECT_DIR}/dnsmasq/${DISTRO}/install.sh install fi if (( $? != 0 )); then printerr "DNSmasq install failed - OS install script failure" exit 1 fi # make sure run dir is created with correct permissions updatePermissions -dnsmasq # setup hosts in cluster node is resolvable # cron and kam service will configure these dynamically if grep -q 'DSIP_CONFIG_START' /etc/hosts 2>/dev/null; then perl -e "\$int_ip='${INTERNAL_IP_ADDR}'; \$ext_ip='${EXTERNAL_IP_ADDR}'; \$int_fqdn='${INTERNAL_FQDN}'; \$ext_fqdn='${EXTERNAL_FQDN}';" \ -0777 -i -pe 's|(#+DSIP_CONFIG_START).*?(#+DSIP_CONFIG_END)|\1\n${int_ip} ${int_fqdn} local.cluster\n${ext_ip} ${ext_fqdn} local.cluster\n\2|gms' /etc/hosts else printf '\n%s\n%s\n%s\n%s\n' \ '#####DSIP_CONFIG_START' \ "${INTERNAL_IP_ADDR} ${INTERNAL_FQDN} local.cluster" \ "${EXTERNAL_IP_ADDR} ${EXTERNAL_FQDN} local.cluster" \ '#####DSIP_CONFIG_END' >>/etc/hosts fi # add section for the pacemaker features if ! grep -q 'PACEMAKER_CONFIG_START' /etc/hosts 2>/dev/null; then printf '\n%s\n%s\n' \ '#####PACEMAKER_CONFIG_START' \ '#####PACEMAKER_CONFIG_END' >>/etc/hosts fi # update DNS hosts prior to dSIPRouter startup addInitCmd "/usr/bin/dsiprouter updatednsconfig" # update DNS hosts every minute if ! crontab -u root -l 2>/dev/null | grep -q '/usr/bin/dsiprouter updatednsconfig' 2>/dev/null; then cronAppend -u root '0 * * * * /usr/bin/dsiprouter updatednsconfig' fi systemctl restart dnsmasq if systemctl is-active --quiet dnsmasq; then touch ${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled pprint "DNSmasq was installed" return 0 else printerr "DNSmasq install failed" exit 1 fi } function uninstallDnsmasq() { if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]; then printwarn "DNSmasq is not installed or failed during install - uninstalling anyway to be safe" fi printdbg "Attempting to uninstall DNSmasq..." ${DSIP_PROJECT_DIR}/dnsmasq/${DISTRO}/install.sh uninstall if (( $? != 0 )); then printerr "DNSmasq uninstall failed - OS install script failure" exit 1 fi # remove dnsmasq configuration rm -f /etc/dnsmasq.conf /etc/dnsmasq.conf.bak 2>/dev/null # remove localhost from name servers sed -ir -e '/#+DSIP_CONFIG_START/,/#+DSIP_CONFIG_END/d' /etc/dhcp/dhclient.conf sed -i -e '/nameserver 127.0.0.1/d' /etc/resolv.conf # remove cluster hosts from /etc/hosts sed -ir -e '/#+DSIP_CONFIG_START/,/#+DSIP_CONFIG_END/d' /etc/hosts # remove pacemaker section from /etc/hosts sed -ir -e '/#+PACEMAKER_CONFIG_START/,/#+PACEMAKER_CONFIG_END/d' /etc/hosts # remove cron job and init command removeInitCmd "/usr/bin/dsiprouter updatednsconfig" cronRemove -u root '/usr/bin/dsiprouter updatednsconfig' # Remove the hidden installed file, which denotes if it's installed or not rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled printdbg "DNSmasq was uninstalled" return 0 } function start() { local START_DSIPROUTER=${START_DSIPROUTER:-1} local START_KAMAILIO=${START_KAMAILIO:-0} local START_RTPENGINE=${START_RTPENGINE:-0} # Start Kamailio if told to and installed if (( $START_KAMAILIO == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled ]; then systemctl start kamailio # Make sure process is still running if ! systemctl is-active --quiet kamailio; then printerr "Unable to start Kamailio" exit 1 else pprint "Kamailio was started" fi fi # Start RTPEngine if told to and installed if (( $START_RTPENGINE == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled ]; then systemctl start rtpengine # Make sure process is still running if ! systemctl is-active --quiet rtpengine; then printerr "Unable to start RTPEngine" exit 1 else pprint "RTPEngine was started" fi fi # Start dSIPRouter if told to and installed if (( $START_DSIPROUTER == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled ]; then # update runtime settings from CLI args updateDsiprouterConfigRuntimeSettings if (( $DEBUG == 1 )); then # start the reverse proxy first systemctl start nginx # perform pre-startup commands systemd would normally do in dsiprouter.service updatePermissions -dsiprouter # keep dSIPRouter in the foreground, only used for debugging issues (blocking) sudo -E -u dsiprouter -g dsiprouter ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter.py exit $? else # start the reverse proxy first systemctl start nginx # normal startup, fork dSIPRouter as background process systemctl start dsiprouter # Make sure process is still running if ! systemctl is-active --quiet dsiprouter || ! systemctl is-active --quiet nginx; then printerr "Unable to start dSIPRouter" exit 1 else pprint "dSIPRouter was started" fi fi fi } function stop() { local STOP_DSIPROUTER=${STOP_DSIPROUTER:-1} local STOP_KAMAILIO=${STOP_KAMAILIO:-0} local STOP_RTPENGINE=${STOP_RTPENGINE:-0} # Stop Kamailio if told to and installed if (( $STOP_KAMAILIO == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled ]; then systemctl stop kamailio # Make sure process is not running if systemctl is-active --quiet kamailio; then printerr "Unable to stop Kamailio" exit 1 else pprint "Kamailio was stopped" fi fi # Stop RTPEngine if told to and installed if (( $STOP_RTPENGINE == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled ]; then systemctl stop rtpengine # Make sure process is not running if systemctl is-active --quiet rtpengine; then printerr "Unable to stop RTPEngine" exit 1 else pprint "RTPEngine was stopped" fi fi # Stop the dSIPRouter if told to and installed if (( $STOP_DSIPROUTER == 1 )) && [ -e ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled ]; then systemctl stop nginx # if started in debug mode we have to manually kill the process if ! systemctl is-active --quiet dsiprouter; then pkill -SIGTERM -f dsiprouter.py if pgrep -f 'nginx|dsiprouter.py' &>/dev/null; then printerr "Unable to stop dSIPRouter" exit 1 else pprint "dSIPRouter was stopped" fi else systemctl stop nginx systemctl stop dsiprouter if systemctl is-active --quiet dsiprouter || systemctl is-active --quiet nginx; then printerr "Unable to stop dSIPRouter" exit 1 else pprint "dSIPRouter was stopped" fi fi fi } function restart() { # escape the systemd control group if told to daemonize if (( ${RESTART_DAEMONIZE:-0} == 1 )); then systemd-run --unit='dsip-daemon' --collect --slice=user.slice $0 ${RESTART_ARGS[@]} exit 0 fi stop start } function displayLoginInfo() { local DSIP_USERNAME=${DSIP_USERNAME:-$(getConfigAttrib 'DSIP_USERNAME' ${DSIP_CONFIG_FILE})} local DSIP_PASSWORD=${DSIP_PASSWORD:-"<HASH CAN NOT BE UNDONE> (reset password if you forgot it)"} local DSIP_API_TOKEN=${DSIP_API_TOKEN:-$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE})} local DSIP_IPC_SOCK="$(getConfigAttrib 'DSIP_IPC_SOCK' ${DSIP_CONFIG_FILE})" local DSIP_IPC_PASS=${DSIP_IPC_PASS:-$(decryptConfigAttrib 'DSIP_IPC_PASS' ${DSIP_CONFIG_FILE})} local KAM_DB_USER=${KAM_DB_USER:-$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE})} local KAM_DB_PASS=${KAM_DB_PASS:-$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE})} echo -ne '\n' printdbg "Your systems credentials are below (keep in a safe place)" pprint "dSIPRouter GUI Username: ${DSIP_USERNAME}" pprint "dSIPRouter GUI Password: ${DSIP_PASSWORD}" pprint "dSIPRouter API Token: ${DSIP_API_TOKEN}" pprint "dSIPRouter IPC Password: ${DSIP_IPC_PASS}" pprint "Kamailio DB Username: ${KAM_DB_USER}" pprint "Kamailio DB Password: ${KAM_DB_PASS}" echo -ne '\n' printdbg "You can access the dSIPRouter WEB GUI here" pprint "Domain Name: ${DSIP_PROTO}://${EXTERNAL_FQDN}:${DSIP_PORT}" if [ "$EXTERNAL_IP_ADDR" != "$INTERNAL_IP_ADDR" ];then pprint "External IP: ${DSIP_PROTO}://${EXTERNAL_IP_ADDR}:${DSIP_PORT}" pprint "Internal IP: ${DSIP_PROTO}://${INTERNAL_IP_ADDR}:${DSIP_PORT}" else pprint "IP Address: ${DSIP_PROTO}://${EXTERNAL_IP_ADDR}:${DSIP_PORT}" fi echo -ne '\n' printdbg "You can access the dSIPRouter REST API here" if [ "$EXTERNAL_IP_ADDR" != "$INTERNAL_IP_ADDR" ];then pprint "External IP: ${DSIP_API_PROTO}://${EXTERNAL_IP_ADDR}:${DSIP_PORT}" pprint "Internal IP: ${DSIP_API_PROTO}://${INTERNAL_IP_ADDR}:${DSIP_PORT}" else pprint "IP Address: ${DSIP_API_PROTO}://${EXTERNAL_IP_ADDR}:${DSIP_PORT}" fi echo -ne '\n' printdbg "You can access the dSIPRouter IPC API here" pprint "UNIX Domain Socket: ${DSIP_IPC_SOCK}" echo -ne '\n' printdbg "You can access the Kamailio DB here" pprint "Database Host: ${KAM_DB_HOST}:${KAM_DB_PORT}" pprint "Database Name: ${KAM_DB_NAME}" echo -ne '\n' } # updates credentials in dsip / kam config files / kam db # also exports credentials to variables for latter commands # note: updating KAM_DB_HOST will implicitly update ROOT_DB_HOST if not set # TODO: currently there is no way to set values to the empty string function setCredentials() { printdbg 'Setting credentials' # variables that can be set prior to running # SET_DSIP_GUI_USER # SET_DSIP_GUI_PASS # SET_DSIP_API_TOKEN # SET_DSIP_MAIL_USER # SET_DSIP_MAIL_PASS # SET_DSIP_IPC_TOKEN # SET_KAM_DB_USER # SET_KAM_DB_PASS # SET_KAM_DB_HOST # SET_KAM_DB_PORT # SET_KAM_DB_NAME # SET_ROOT_DB_USER # SET_ROOT_DB_PASS # SET_ROOT_DB_HOST # SET_ROOT_DB_PORT # SET_ROOT_DB_NAME # SET_DSIP_SESSION_KEY # SERVICE_RELOAD_DISABLED if [[ -z ${SET_ROOT_DB_HOST+unset} && -n ${SET_KAM_DB_HOST+set} ]]; then SET_ROOT_DB_HOST="$SET_KAM_DB_HOST" fi local LOAD_SETTINGS_FROM=${LOAD_SETTINGS_FROM:-$(getConfigAttrib 'LOAD_SETTINGS_FROM' ${DSIP_CONFIG_FILE})} local DSIP_ID=${DSIP_ID:-$(getConfigAttrib 'DSIP_ID' ${DSIP_CONFIG_FILE})} local DSIP_CLUSTER_ID=${DSIP_CLUSTER_ID:-$(getConfigAttrib 'DSIP_CLUSTER_ID' ${DSIP_CONFIG_FILE})} local DSIP_CLUSTER_SYNC=${DSIP_CLUSTER_SYNC:-$([[ "$(getConfigAttrib 'DSIP_CLUSTER_SYNC' ${DSIP_CONFIG_FILE})" == "True" ]] && echo '1' || echo '0')} # the commands to execute for these updates local SHELL_CMDS=() SQL_STATEMENTS=() DEFERRED_SQL_STATEMENTS=() # how settings will be propagated to live systems # 0 == no reload required, 1 == hot reload required, 2 == service reload required # note that parsing variables for higher numbered reloading should take precedence local DSIP_RELOAD_TYPE=1 KAM_RELOAD_TYPE=0 MYSQL_RELOAD_TYPE=0 # if calling script does not want changes propagated yet, set this variable local SERVICE_RELOAD_DISABLED=${SERVICE_RELOAD_DISABLED:-0} # whether or not we will be running logic to update settings on the DB local RUN_SQL_STATEMENTS=1 # the type of mysql DB server detected local MYSQL_VARIANT='mariadb' local TMP_VAL # sanity check, can we connect to the DB as the root user? # we determine if user already changed DB creds (and just want dsiprouter to store them accordingly) if withGivenDB --user="$ROOT_DB_USER" --pass="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" \ mysql -e 'SELECT VERSION();' &>/dev/null; then : elif withGivenDB --user="${SET_ROOT_DB_USER:-$ROOT_DB_USER}" --pass="${SET_ROOT_DB_PASS:-$ROOT_DB_PASS}" \ --host="${SET_ROOT_DB_HOST:-$ROOT_DB_HOST}" --port="${SET_ROOT_DB_PORT:-$ROOT_DB_PORT}" \ --db="${SET_ROOT_DB_NAME:-$ROOT_DB_NAME}" mysql -e 'SELECT VERSION();' &>/dev/null; then ROOT_DB_HOST=${SET_ROOT_DB_HOST:-$ROOT_DB_HOST} ROOT_DB_PORT=${SET_ROOT_DB_PORT:-$ROOT_DB_PORT} ROOT_DB_USER=${SET_ROOT_DB_USER:-$ROOT_DB_USER} ROOT_DB_PASS=${SET_ROOT_DB_PASS:-$ROOT_DB_PASS} ROOT_DB_NAME=${SET_ROOT_DB_NAME:-$ROOT_DB_NAME} else # allow for updating settings prior to mysql being started but make sure it would be a valid update # no update that requires the DB access will work if we reached here so we validate or exit if [[ "$LOAD_SETTINGS_FROM" == "db" ]] || [[ -n ${SET_KAM_DB_USER+set} ]] || [[ -n ${SET_KAM_DB_PASS+set} ]] || [[ -n ${SET_KAM_DB_HOST+set} ]] || [[ -n ${SET_KAM_DB_PORT+set} ]] || [[ -n ${SET_KAM_DB_NAME+set} ]] || [[ -n ${SET_ROOT_DB_USER+set} ]] || [[ -n ${SET_ROOT_DB_PASS+set} ]] || [[ -n ${SET_ROOT_DB_HOST+set} ]] || [[ -n ${SET_ROOT_DB_PORT+set} ]] || [[ -n ${SET_ROOT_DB_NAME+set} ]]; then printerr 'Connection to DB failed' exit 1 fi # no DB updates necessary RUN_SQL_STATEMENTS=0 fi # determine type of DB server if we need to make updates if (( $RUN_SQL_STATEMENTS == 1 )); then if ! withRootDBConn mysql -se "SELECT PASSWORD('');" &>/dev/null; then MYSQL_VARIANT='mysql' fi sqlAsTransaction --user="$ROOT_DB_USER" --pass="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" "${SQL_STATEMENTS[@]}" fi # update non-encrypted settings locally and gather statements for updating DB if [[ -n ${SET_DSIP_GUI_USER+set} ]]; then SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_USERNAME='$SET_DSIP_GUI_USER' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_USERNAME='$SET_DSIP_GUI_USER' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'DSIP_USERNAME' '$SET_DSIP_GUI_USER' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_DSIP_GUI_PASS+set} ]]; then TMP_VAL=$(hashCreds "$SET_DSIP_GUI_PASS") SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_PASSWORD='$TMP_VAL' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_PASSWORD='$TMP_VAL' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'DSIP_PASSWORD' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") fi if [[ -n ${SET_DSIP_MAIL_USER+set} ]]; then SQL_STATEMENTS+=("update kamailio.dsip_settings SET MAIL_USERNAME='$SET_DSIP_MAIL_USER' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET MAIL_USERNAME='$SET_DSIP_MAIL_USER' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'MAIL_USERNAME' '$SET_DSIP_MAIL_USER' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_DSIP_MAIL_PASS+set} ]]; then TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_DSIP_MAIL_PASS") SQL_STATEMENTS+=("update kamailio.dsip_settings SET MAIL_PASSWORD='$TMP_VAL' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET MAIL_PASSWORD='$TMP_VAL' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'MAIL_PASSWORD' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") fi if [[ -n ${SET_DSIP_API_TOKEN+set} ]]; then TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_DSIP_API_TOKEN") SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_API_TOKEN='$TMP_VAL' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_API_TOKEN='$TMP_VAL' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'DSIP_API_TOKEN' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") KAM_RELOAD_TYPE=1 fi if [[ -n ${SET_DSIP_IPC_TOKEN+set} ]]; then TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_DSIP_IPC_TOKEN") SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_IPC_PASS='$TMP_VAL' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET DSIP_IPC_PASS='$TMP_VAL' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'DSIP_IPC_PASS' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") DSIP_RELOAD_TYPE=2 fi if [[ -n ${SET_KAM_DB_USER+set} ]]; then DEFERRED_SQL_STATEMENTS+=("DROP USER IF EXISTS '$KAM_DB_USER'@'localhost';") DEFERRED_SQL_STATEMENTS+=("DROP USER IF EXISTS '$KAM_DB_USER'@'%';") DEFERRED_SQL_STATEMENTS+=("DROP USER IF EXISTS '$SET_KAM_DB_USER'@'localhost';") DEFERRED_SQL_STATEMENTS+=("DROP USER IF EXISTS '$SET_KAM_DB_USER'@'%';") DEFERRED_SQL_STATEMENTS+=("CREATE USER '$SET_KAM_DB_USER'@'localhost' IDENTIFIED BY '${SET_KAM_DB_PASS:-$KAM_DB_PASS}';") DEFERRED_SQL_STATEMENTS+=("GRANT ALL PRIVILEGES ON $KAM_DB_NAME.* TO '$SET_KAM_DB_USER'@'localhost';") DEFERRED_SQL_STATEMENTS+=("CREATE USER '$SET_KAM_DB_USER'@'%' IDENTIFIED BY '${SET_KAM_DB_PASS:-$KAM_DB_PASS}';") DEFERRED_SQL_STATEMENTS+=("GRANT ALL PRIVILEGES ON $KAM_DB_NAME.* TO '$SET_KAM_DB_USER'@'%';") SQL_STATEMENTS+=("UPDATE kamailio.dsip_settings SET KAM_DB_USER='$SET_KAM_DB_USER' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("UPDATE kamailio.dsip_settings SET KAM_DB_USER='$SET_KAM_DB_USER' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'KAM_DB_USER' '$SET_KAM_DB_USER' ${DSIP_CONFIG_FILE} -q;") DSIP_RELOAD_TYPE=2 KAM_RELOAD_TYPE=2 fi if [[ -n ${SET_KAM_DB_PASS+set} ]]; then if [[ "$MYSQL_VARIANT" == 'mysql' ]]; then DEFERRED_SQL_STATEMENTS+=("ALTER USER '${SET_KAM_DB_USER:-$KAM_DB_USER}'@'localhost' IDENTIFIED BY '$SET_KAM_DB_PASS';") DEFERRED_SQL_STATEMENTS+=("ALTER USER '${SET_KAM_DB_USER:-$KAM_DB_USER}'@'%' IDENTIFIED BY '$SET_KAM_DB_PASS';") else DEFERRED_SQL_STATEMENTS+=("SET PASSWORD FOR '${SET_KAM_DB_USER:-$KAM_DB_USER}'@'localhost' = PASSWORD('$SET_KAM_DB_PASS');") DEFERRED_SQL_STATEMENTS+=("SET PASSWORD FOR '${SET_KAM_DB_USER:-$KAM_DB_USER}'@'%' = PASSWORD('$SET_KAM_DB_PASS');") fi TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_KAM_DB_PASS") SQL_STATEMENTS+=("update kamailio.dsip_settings SET KAM_DB_PASS='$TMP_VAL' WHERE DSIP_ID='$DSIP_ID';") if (( $DSIP_CLUSTER_SYNC == 1 )); then SQL_STATEMENTS+=("update kamailio.dsip_settings SET KAM_DB_PASS='$TMP_VAL' WHERE DSIP_CLUSTER_ID='$DSIP_CLUSTER_ID' AND DSIP_CLUSTER_SYNC='1' AND DSIP_ID!='$DSIP_ID';") fi SHELL_CMDS+=("setConfigAttrib 'KAM_DB_PASS' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") DSIP_RELOAD_TYPE=2 KAM_RELOAD_TYPE=2 fi # NOTE: since the host is required in the DB URI when parsing args we also check if it actually changed to determine if we need to run this logic if [[ -n ${SET_KAM_DB_HOST+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'KAM_DB_HOST' '$SET_KAM_DB_HOST' ${DSIP_CONFIG_FILE} -q;") if [[ "${SET_KAM_DB_HOST}" != "${KAM_DB_HOST}" ]]; then reconfigureMysqlSystemdService MYSQL_RELOAD_TYPE=2 fi DSIP_RELOAD_TYPE=2 KAM_RELOAD_TYPE=2 fi if [[ -n ${SET_KAM_DB_PORT+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'KAM_DB_PORT' '$SET_KAM_DB_PORT' ${DSIP_CONFIG_FILE} -q;") DSIP_RELOAD_TYPE=2 KAM_RELOAD_TYPE=2 fi # TODO: allow changing live database name if [[ -n ${SET_KAM_DB_NAME+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'KAM_DB_NAME' '$SET_KAM_DB_NAME' ${DSIP_CONFIG_FILE} -q;") DSIP_RELOAD_TYPE=2 KAM_RELOAD_TYPE=2 fi # we do not support creating new root accounts, therefore we have a few extra checks here if [[ -n ${SET_ROOT_DB_USER+set} ]] && [[ "$ROOT_DB_USER" != "$SET_ROOT_DB_USER" ]]; then if checkDBUserExists "${ROOT_DB_USER}@localhost"; then DEFERRED_SQL_STATEMENTS+=("RENAME USER '${ROOT_DB_USER}'@'localhost' TO '${SET_ROOT_DB_USER}'@'localhost';") fi if checkDBUserExists "${ROOT_DB_USER}@%"; then DEFERRED_SQL_STATEMENTS+=("RENAME USER '${ROOT_DB_USER}'@'%' TO '${SET_ROOT_DB_USER}'@'%';") fi SHELL_CMDS+=("setConfigAttrib 'ROOT_DB_USER' '$SET_ROOT_DB_USER' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_ROOT_DB_PASS+set} ]]; then if [[ -n ${SET_ROOT_DB_USER+set} ]]; then TMP_VAL="$SET_ROOT_DB_USER" else TMP_VAL="$ROOT_DB_USER" fi if [[ "$MYSQL_VARIANT" == 'mysql' ]]; then DEFERRED_SQL_STATEMENTS+=("ALTER USER '$TMP_VAL'@'localhost' IDENTIFIED BY '$SET_ROOT_DB_PASS';") if checkDBUserExists "$TMP_VAL@%"; then DEFERRED_SQL_STATEMENTS+=("ALTER USER '$TMP_VAL'@'%' IDENTIFIED BY '$SET_ROOT_DB_PASS';") fi else DEFERRED_SQL_STATEMENTS+=("SET PASSWORD FOR '$TMP_VAL'@'localhost' = PASSWORD('$SET_ROOT_DB_PASS');") if checkDBUserExists "$TMP_VAL@%"; then DEFERRED_SQL_STATEMENTS+=("SET PASSWORD FOR '$TMP_VAL'@'%' = PASSWORD('$SET_ROOT_DB_PASS');") fi fi TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_ROOT_DB_PASS") SHELL_CMDS+=("setConfigAttrib 'ROOT_DB_PASS' '$TMP_VAL' ${DSIP_CONFIG_FILE} -qb;") fi if [[ -n ${SET_ROOT_DB_HOST+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'ROOT_DB_HOST' '$SET_ROOT_DB_HOST' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_ROOT_DB_PORT+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'ROOT_DB_PORT' '$ROOT_KAM_DB_PORT' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_ROOT_DB_NAME+set} ]]; then SHELL_CMDS+=("setConfigAttrib 'ROOT_DB_NAME' '$SET_ROOT_DB_NAME' ${DSIP_CONFIG_FILE} -q;") fi if [[ -n ${SET_DSIP_SESSION_KEY+set} ]]; then TMP_VAL=$(encryptCreds -kf "$DSIP_PRIV_KEY" "$SET_DSIP_SESSION_KEY") SHELL_CMDS+=("setConfigAttrib 'DSIP_SESSION_KEY' '$TMP_VAL' ${DSIP_CONFIG_FILE} -q;") DSIP_RELOAD_TYPE=2 fi DEFERRED_SQL_STATEMENTS+=("flush privileges;") # allow settings that don't require DB to be running to be updated (we verified at the start of this func whether we needed DB) if (( ${RUN_SQL_STATEMENTS} == 1 )); then # update non-encrypted settings on DB sqlAsTransaction --user="$ROOT_DB_USER" --pass="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" "${SQL_STATEMENTS[@]}" if (( $? != 0 )); then printerr 'Failed setting credentials on DB' exit 1 fi # update live DB settings (DB user passwords, privileges, etc..) sqlAsTransaction --user="$ROOT_DB_USER" --pass="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" "${DEFERRED_SQL_STATEMENTS[@]}" if (( $? != 0 )); then printerr 'Failed setting credentials on DB' exit 1 fi fi # finally update the local config files eval "${SHELL_CMDS[@]}" # export variables for later usage in this script export DSIP_USERNAME=${SET_DSIP_GUI_USER:-$DSIP_USERNAME} export DSIP_PASSWORD=${SET_DSIP_GUI_PASS:-$DSIP_PASSWORD} export DSIP_API_TOKEN=${SET_DSIP_API_TOKEN:-$DSIP_API_TOKEN} export MAIL_USERNAME=${SET_DSIP_MAIL_USER:-$MAIL_USERNAME} export MAIL_PASSWORD=${SET_DSIP_MAIL_PASS:-$MAIL_PASSWORD} export DSIP_IPC_PASS=${SET_DSIP_IPC_TOKEN:-$DSIP_IPC_PASS} export KAM_DB_USER=${SET_KAM_DB_USER:-$KAM_DB_USER} export KAM_DB_PASS=${SET_KAM_DB_PASS:-$KAM_DB_PASS} export KAM_DB_HOST=${SET_KAM_DB_HOST:-$KAM_DB_HOST} export KAM_DB_PORT=${SET_KAM_DB_PORT:-$KAM_DB_PORT} export KAM_DB_NAME=${SET_KAM_DB_NAME:-$KAM_DB_NAME} export ROOT_DB_USER=${SET_ROOT_DB_USER:-$ROOT_DB_USER} export ROOT_DB_PASS=${SET_ROOT_DB_PASS:-$ROOT_DB_PASS} export ROOT_DB_HOST=${SET_ROOT_DB_HOST:-$ROOT_DB_HOST} export ROOT_DB_PORT=${SET_ROOT_DB_PORT:-$ROOT_DB_PORT} export ROOT_DB_NAME=${SET_ROOT_DB_NAME:-$ROOT_DB_NAME} # reload/synchronize settings for each service # note: we reload the service only if it is currently running (otherwise it messes with boot ordering) # note: updateKamailioConfig() combines configuring kam config and hot reloading in the same function if (( ${KAM_RELOAD_TYPE} > 0 )); then updateKamailioConfig fi if (( $SERVICE_RELOAD_DISABLED == 0 )); then if (( ${MYSQL_RELOAD_TYPE} == 2 )); then if systemctl is-active --quiet mariadb; then systemctl restart mariadb fi fi if (( ${KAM_RELOAD_TYPE} == 2 )); then if systemctl is-active --quiet kamailio; then systemctl restart kamailio fi fi if (( ${DSIP_RELOAD_TYPE} == 1 )); then # synchronize settings (between local disk, DB, and cluster) systemctl kill -s SIGUSR1 dsiprouter elif (( ${DSIP_RELOAD_TYPE} == 2 )); then if systemctl is-active --quiet dsiprouter; then systemctl restart dsiprouter fi fi fi printdbg 'Credentials have been updated' } # update MOTD banner for ssh login function updateBanner() { # don't write multiple times if [ -f /etc/update-motd.d/00-dsiprouter ]; then return 0 fi # move old banner files mkdir -p /etc/update-motd.d cp -f /etc/motd ${BACKUPS_DIR}/motd.bak truncate -s 0 /etc/motd chmod -x /etc/update-motd.d/* 2>/dev/null # add our custom banner script (dynamically updates MOTD banner) (cat << EOF #!/usr/bin/env bash # redefine variables and functions here ESC_SEQ="$ESC_SEQ" ANSI_NONE="$ANSI_NONE" ANSI_GREEN="$ANSI_GREEN" IPV6_ENABLED=${IPV6_ENABLED:-0} $(declare -f printdbg) $(declare -f getConfigAttrib) $(declare -f displayLogo) # updated variables on login INTERNAL_IP_ADDR=\$(getConfigAttrib 'INTERNAL_IP_ADDR' ${DSIP_CONFIG_FILE}) EXTERNAL_IP_ADDR=\$(getConfigAttrib 'EXTERNAL_IP_ADDR' ${DSIP_CONFIG_FILE}) DSIP_PORT=\$(getConfigAttrib 'DSIP_PORT' ${DSIP_CONFIG_FILE}) DSIP_GUI_PROTOCOL=\$(getConfigAttrib 'DSIP_PROTO' ${DSIP_CONFIG_FILE}) VERSION=\$(getConfigAttrib 'VERSION' ${DSIP_CONFIG_FILE}) # displaying information to user clear displayLogo printdbg "Version: \$VERSION" printf '\n' printdbg "You can access the dSIPRouter GUI by going to:" printdbg "External IP: \${DSIP_GUI_PROTOCOL}://\${EXTERNAL_IP_ADDR}:\${DSIP_PORT}" if [ "\$EXTERNAL_IP_ADDR" != "\$INTERNAL_IP_ADDR" ];then printdbg "Internal IP: \${DSIP_GUI_PROTOCOL}://\${INTERNAL_IP_ADDR}:\${DSIP_PORT}" fi printf '\n' exit 0 EOF ) > /etc/update-motd.d/00-dsiprouter chmod +x /etc/update-motd.d/00-dsiprouter # debian-based distro's will update it automatically # for rhel-based distro's we simply update it via cronjob case "$DISTRO" in amzn|rhel|almalinux|rocky) /etc/update-motd.d/00-dsiprouter > /etc/motd if ! crontab -u root -l 2>/dev/null | grep -q "/etc/update-motd.d/00-dsiprouter" 2>/dev/null; then cronAppend -u root '*/5 * * * * /etc/update-motd.d/00-dsiprouter >/etc/motd' fi ;; esac } # revert to old MOTD banner for ssh logins function revertBanner() { mv -f ${BACKUPS_DIR}/motd.bak /etc/motd rm -f /etc/update-motd.d/00-dsiprouter chmod +x /etc/update-motd.d/* 2>/dev/null # remove cron entry for rhel-based distros case "$DISTRO" in amzn|rhel|almalinux|rocky) cronRemove -u root '/etc/update-motd.d/00-dsiprouter' ;; esac } # ================= # dsip-init service # ================= # # Initially the init service does nothing but startup required services on boot # # 1. Primary usage is to ensure required services are started for dependent services # 2. Secondary usage is to add startup commands to run on reboot (init cmds for services) # # This service will ensure the following services are started: # - networking # - syslog # - mysql # - dnsmasq # - nginx # TODO: replace this with a systemd target instead (dsip-init.target) function createInitService() { # imported from dsip_lib.sh local DSIP_INIT_FILE="$DSIP_INIT_FILE" # only create if it doesn't exist if [[ -f "$DSIP_INIT_FILE" ]]; then printwarn "dsip-init service already exists" return 0 else printdbg "creating dsip-init service" fi # configure cloud-init to work with alongside our init services if [[ -n "$CLOUD_PLATFORM" ]]; then cp -f ${DSIP_PROJECT_DIR}/cloud/cloud-init/configs/${CLOUD_PLATFORM}.cfg /etc/cloud/cloud.cfg.d/99-dsip-init.cfg cp -f ${DSIP_PROJECT_DIR}/cloud/cloud-init/templates/hosts.${DISTRO}.tmpl $(${DSIP_PROJECT_DIR}/cloud/find_hosts_tmpl.sh) # patch for cloud-init.service circular ordering dependency # TODO: commit this upstream to cloud-init project case "$DISTRO" in debian|ubuntu) perl -i -pe 's%(Before\=sysinit\.target)%#\1%' /lib/systemd/system/cloud-init.service systemctl daemon-reload ;; esac fi case "$DISTRO" in amzn|rhel|almalinux|rocky) # TODO: this should be moved to a separate install dir called syslog # alias and link rsyslog to syslog service as in debian # allowing rsyslog to be accessible via syslog namespace # the settings are already there just commented out by default sed -i -r 's|^[;](.*)|\1|g' /lib/systemd/system/rsyslog.service ln -sf /lib/systemd/system/rsyslog.service /etc/systemd/system/syslog.service systemctl daemon-reload ;; *) ;; esac cp -f ${DSIP_PROJECT_DIR}/dsiprouter/dsip-net-cfg.py /usr/sbin/dsip-net-cfg chmod +x /usr/sbin/dsip-net-cfg (cat << EOF [Unit] Description=dSIPRouter Init Service DefaultDependencies=no Requires=basic.target network.target Wants=rsyslog.service mariadb.service dnsmasq.service nginx.service After=network.target network-online.target systemd-journald.socket basic.target cloud-init.target After=networking.service systemd-networkd.service NetworkManager.service After=rsyslog.service mariadb.service dnsmasq.service nginx.service Before= ReloadPropagatedFrom=networking.service systemd-networkd.service NetworkManager.service [Service] Type=oneshot ExecStart=/usr/sbin/dsip-net-cfg RemainAfterExit=true TimeoutSec=0 [Install] WantedBy=multi-user.target WantedBy=networking.service systemd-networkd.service NetworkManager.service EOF ) > ${DSIP_INIT_FILE} # set default permissions chmod 0644 ${DSIP_INIT_FILE} # enable dsip-init service on boot systemctl daemon-reload systemctl enable dsip-init } function removeInitService() { # imported from dsip_lib.sh local DSIP_INIT_FILE="$DSIP_INIT_FILE" # remove our custom cloud-init configs rm -f /etc/cloud/cloud.cfg.d/99-dsip-init.cfg rm -f /usr/sbin/dsip-net-cfg systemctl stop dsip-init systemctl disable dsip-init rm -f $DSIP_INIT_FILE systemctl daemon-reload printdbg "dsip-init service removed" } function upgrade() { local UPGRADE_VER CURRENT_VERSION UPGRADE_DEPENDS local REPO_URL=${UPGRADE_REPO:-"$GIT_REPO_URL"} REPO_URL=${REPO_URL:-https://github.com/dOpensource/dsiprouter.git} local TAG_NAME="${UPGRADE_RELEASE}-rel" export NEW_PROJECT_DIR=/tmp/dsiprouter export RUNNING_UPGRADE=1 # make sure mask is reset to be more permissive # repo must be created with permissions set in the remote repo # and we want to keep permissions from backup files as well umask 022 printdbg 'downloading new dSIPRouter project files' rm -rf "$NEW_PROJECT_DIR" 2>/dev/null git clone --depth 1 -c advice.detachedHead=false -b "$TAG_NAME" "$REPO_URL" "$NEW_PROJECT_DIR" || { printerr 'failed downloading new project files' exit 1 } printdbg 'verifying version requirements' UPGRADE_VER=$(jq -r -e '.version' <"${NEW_PROJECT_DIR}/resources/upgrade/${UPGRADE_RELEASE}/settings.json") CURRENT_VERSION=$(getConfigAttrib "VERSION" "${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py") UPGRADE_DEPENDS=( $(jq -r -e '.depends[]' <"${NEW_PROJECT_DIR}/resources/upgrade/${UPGRADE_RELEASE}/settings.json") ) ( for VER in ${UPGRADE_DEPENDS[@]}; do if [[ "$CURRENT_VERSION" == "$VER" ]]; then exit 0 fi done exit 1 ) || { printerr "unsupported upgrade scenario ($CURRENT_VERSION -> $UPGRADE_VER)" exit 1 } if systemctl is-active -q dsiprouter; then # check shared memory if [[ $(${PYTHON_CMD} -c " import os os.chdir('${DSIP_PROJECT_DIR}/gui') from modules.api.licensemanager.functions import getLicenseStatus print(getLicenseStatus(license_tag='DSIP_CORE')) ") != "3" ]]; then printerr 'dSPIRouter core license is not valid' exit 1 fi else # manually grab license status if [[ $(${PYTHON_CMD} -c " import os, sys os.chdir('${DSIP_PROJECT_DIR}/gui') sys.path.insert(0, '${DSIP_SYSTEM_CONFIG_DIR}/gui') from modules.api.licensemanager.functions import licenseDictToStateDict, getLicenseStatusFromStateDict import settings print(getLicenseStatusFromStateDict(licenseDictToStateDict(settings.DSIP_LICENSE_STORE), 'DSIP_CORE')) ") != "3" ]]; then printerr 'dSPIRouter core license is not valid' exit 1 fi fi printdbg "starting migration from $CURRENT_VERSION to $UPGRADE_VER" ${NEW_PROJECT_DIR}/resources/upgrade/${UPGRADE_RELEASE}/scripts/migrate.sh return $? } # TODO: deprecated code requiring review, marked for review in v0.80 # DSIP_CLUSTER_ID=${DSIP_CLUSTER_ID:-$(getConfigAttrib 'DSIP_CLUSTER_ID' ${DSIP_CONFIG_FILE})} # # CURRENT_RELEASE=$(getConfigAttrib 'VERSION' ${DSIP_CONFIG_FILE}) # # # Check if already upgraded # #rel = $((`echo "$CURRENT_RELEASE" == "$UPGRADE_RELEASE" | bc`)) # #if [ $rel -eq 1 ]; then # # # # pprint "dSIPRouter is already updated to $UPGRADE_RELEASE!" # # return # # #fi # # # Return an error if the release doesn't exist # if ! git branch -a --format='%(refname:short)' | grep -qE "^${UPGRADE_RELEASE}\$" 2>/dev/null; then # printdbg "The $UPGRADE_RELEASE release doesn't exist. Please select another release" # return 1 # fi # # BACKUP_DIR="/var/backups" # CURR_BACKUP_DIR="${BACKUP_DIR}/$(date '+%Y-%m-%d')" # mkdir -p ${BACKUP_DIR} ${CURR_BACKUP_DIR} # mkdir -p ${CURR_BACKUP_DIR}/{etc,var/lib,${HOME},$(dirname "$DSIP_PROJECT_DIR")} # # cp -r ${DSIP_PROJECT_DIR} ${CURR_BACKUP_DIR}/${DSIP_PROJECT_DIR} # cp -r ${SYSTEM_KAMAILIO_CONFIG_DIR} ${CURR_BACKUP_DIR}/${SYSTEM_KAMAILIO_CONFIG_DIR} # # #Stash any changes so that GUI will allow us to pull down a new release # #git stash # #git checkout $UPGRADE_RELEASE # #git stash apply # # generateKamailioConfig # updateKamailioConfig # updateKamailioStartup # # if (( $? == 0 )); then # # Upgrade the version # setConfigAttrib 'VERSION' "$UPGRADE_RELEASE" ${DSIP_CONFIG_FILE} -q # # # Restart Kamailio # systemctl restart kamailio # systemctl restart dsiprouter # fi # TODO: this is unfinished #function upgradeOld { # # TODO: set / handle parsed args # UPGRADE_RELEASE="v0.51" # # BACKUP_DIR="/var/backups" # CURR_BACKUP_DIR="${BACKUP_DIR}/$(date '+%Y-%m-%d')" # mkdir -p ${BACKUP_DIR} ${CURR_BACKUP_DIR} # mkdir -p ${CURR_BACKUP_DIR}/{etc,var/lib,${HOME},$(dirname "$DSIP_PROJECT_DIR")} # # # TODO: more cross platform / cloud RDBMS friendly dump, such as the following: ## VIEWS=$(mysql --skip-column-names --batch -D information_schema -e 'select table_name from tables where table_schema="kamailio" and table_type="VIEW"' | perl -0777 -pe 's/\n(?!\Z)/|/g') ## mysqldump -B kamailio --routines --triggers --hex-blob | sed -e 's|DEFINER=`[a-z0-9A-Z]*`@`[a-z0-9A-Z]*`||g' | perl -0777 -pe 's|(CREATE TABLE `?(?:'"${VIEWS}"')`?.*?)ENGINE=\w+|\1|sgm' > kamdump.sql # # mysqldump --single-transaction --opt --events --routines --triggers --all-databases --add-drop-database --flush-privileges \ # --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" > ${CURR_BACKUP_DIR}/mysql_full.sql # mysqldump --single-transaction --skip-triggers --skip-add-drop-table --insert-ignore \ # --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" ${KAM_DB_NAME} \ # | perl -0777 -pi -e 's/CREATE TABLE (`(.+?)`.+?;)/CREATE TABLE IF NOT EXISTS \1\n\nTRUNCATE TABLE `\2`;\n/gs' \ # > ${CURR_BACKUP_DIR}/kamdb_merge.sql # # systemctl stop rtpengine # systemctl stop kamailio # systemctl stop dsiprouter # systemctl stop mariadb # # mv -f ${DSIP_PROJECT_DIR} ${CURR_BACKUP_DIR}/${DSIP_PROJECT_DIR} # mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${CURR_BACKUP_DIR}/${SYSTEM_KAMAILIO_CONFIG_DIR} # # in case mysqldumps failed silently, backup mysql binary data # mv -f /var/lib/mysql ${CURR_BACKUP_DIR}/var/lib/ # cp -f /etc/my.cnf* ${CURR_BACKUP_DIR}/etc/ # cp -rf /etc/my.cnf* ${CURR_BACKUP_DIR}/etc/ # cp -rf /etc/mysql ${CURR_BACKUP_DIR}/etc/ # cp -f ${HOME}/.my.cnf* ${CURR_BACKUP_DIR}/${HOME}/ # # iptables-save > ${CURR_BACKUP_DIR}/iptables.dump # ip6tables-save > ${CURR_BACKUP_DIR}/ip6tables.dump # # git clone https://github.com/dOpensource/dsiprouter.git --branch="$UPGRADE_RELEASE" ${DSIP_PROJECT_DIR} # cd ${DSIP_PROJECT_DIR} # # # TODO: figure out what settings they installed with previously # # or we can simply store them in a text file (./installed) # # after a succesfull install completes # ./dsiprouter.sh uninstall # ./dsiprouter.sh install # # mysql --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$ROOT_DB_HOST" --port="$ROOT_DB_PORT" ${KAM_DB_NAME} < ${CURR_BACKUP_DIR}/kamdb_merge.sql # # # TODO: fix any conflicts that would arise from our new modules / tables in KAMDB # # # TODO: print backup location info to user # # # TODO: transfer / merge backup configs to new configs # # kam configs # # dsip configs # # iptables configs # # mysql configs # # # TODO: restart services, check for good startup #} # TODO: add bash cmd completion for new options provided by gitwrapper.sh # TODO: move installing of testing dependencies here function configGitDevEnv() { ${PYTHON_CMD} -m pip install pipreqs mkdir -p ${BACKUPS_DIR}/git/info ${BACKUPS_DIR}/git/hooks mkdir -p ${DSIP_PROJECT_DIR}/.git/info ${DSIP_PROJECT_DIR}/.git/hooks cp -f ${DSIP_PROJECT_DIR}/.git/info/attributes ${BACKUPS_DIR}/git/info/attributes 2>/dev/null cat ${DSIP_PROJECT_DIR}/resources/git/gitattributes >> ${DSIP_PROJECT_DIR}/.git/info/attributes cp -f ${DSIP_PROJECT_DIR}/.git/config ${BACKUPS_DIR}/git/config 2>/dev/null cat ${DSIP_PROJECT_DIR}/resources/git/gitconfig >> ${DSIP_PROJECT_DIR}/.git/config cp -f ${DSIP_PROJECT_DIR}/.git/info/exclude ${BACKUPS_DIR}/git/info/exclude 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/gitignore ${DSIP_PROJECT_DIR}/.git/info/exclude cp -f ${DSIP_PROJECT_DIR}/.git/commit-msg ${BACKUPS_DIR}/git/commit-msg 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/commit-msg ${DSIP_PROJECT_DIR}/.git/commit-msg cp -f ${DSIP_PROJECT_DIR}/.git/hooks/pre-commit ${BACKUPS_DIR}/git/hooks/pre-commit 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/hooks/pre-commit ${DSIP_PROJECT_DIR}/.git/hooks/pre-commit chmod +x ${DSIP_PROJECT_DIR}/.git/hooks/pre-commit cp -f ${DSIP_PROJECT_DIR}/.git/hooks/prepare-commit-msg ${BACKUPS_DIR}/git/hooks/prepare-commit-msg 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/hooks/prepare-commit-msg ${DSIP_PROJECT_DIR}/.git/hooks/prepare-commit-msg chmod +x ${DSIP_PROJECT_DIR}/.git/hooks/prepare-commit-msg cp -f ${DSIP_PROJECT_DIR}/.git/hooks/commit-msg ${BACKUPS_DIR}/git/hooks/commit-msg 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/hooks/commit-msg ${DSIP_PROJECT_DIR}/.git/hooks/commit-msg chmod +x ${DSIP_PROJECT_DIR}/.git/hooks/commit-msg cp -f ${DSIP_PROJECT_DIR}/.git/hooks/post-commit ${BACKUPS_DIR}/git/hooks/post-commit 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/hooks/post-commit ${DSIP_PROJECT_DIR}/.git/hooks/post-commit chmod +x ${DSIP_PROJECT_DIR}/.git/hooks/post-commit cp -f ${DSIP_PROJECT_DIR}/.git/hooks/pre-push ${BACKUPS_DIR}/git/hooks/pre-push 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/resources/git/hooks/pre-push ${DSIP_PROJECT_DIR}/.git/hooks/pre-push chmod +x ${DSIP_PROJECT_DIR}/.git/hooks/pre-push cp -f ${DSIP_PROJECT_DIR}/resources/git/merge-changelog.sh /usr/local/bin/_merge-changelog chmod +x /usr/local/bin/_merge-changelog cp -f ${DSIP_PROJECT_DIR}/resources/git/check_syntax.py /usr/local/bin/_git_check_syntax chmod +x /usr/local/bin/_git_check_syntax cp -f ${DSIP_PROJECT_DIR}/resources/git/gitwrapper.sh ${GIT_UPDATE_FILE} . ${GIT_UPDATE_FILE} } function cleanGitDevEnv() { mv -f ${BACKUPS_DIR}/git/info/attributes ${DSIP_PROJECT_DIR}/.git/info/attributes 2>/dev/null mv -f ${BACKUPS_DIR}/git/config ${DSIP_PROJECT_DIR}/.git/config 2>/dev/null mv -f ${BACKUPS_DIR}/git/info/exclude ${DSIP_PROJECT_DIR}/.git/info/exclude 2>/dev/null mv -f ${BACKUPS_DIR}/git/commit-msg ${DSIP_PROJECT_DIR}/.git/commit-msg 2>/dev/null mv -f ${BACKUPS_DIR}/git/hooks/pre-commit ${DSIP_PROJECT_DIR}/.git/hooks/pre-commit 2>/dev/null mv -f ${BACKUPS_DIR}/git/hooks/prepare-commit-msg ${DSIP_PROJECT_DIR}/.git/hooks/prepare-commit-msg 2>/dev/null mv -f ${BACKUPS_DIR}/git/hooks/commit-msg ${DSIP_PROJECT_DIR}/.git/hooks/commit-msg 2>/dev/null mv -f ${BACKUPS_DIR}/git/hooks/post-commit ${DSIP_PROJECT_DIR}/.git/hooks/post-commit 2>/dev/null mv -f ${BACKUPS_DIR}/git/hooks/pre-push ${DSIP_PROJECT_DIR}/.git/hooks/pre-push 2>/dev/null rm -f /usr/local/bin/_merge-changelog rm -f /usr/local/bin/_git_check_syntax rm -f ${GIT_UPDATE_FILE} } # run install commands across a cluster of nodes # TODO: parallel ssh execution for install cmds # TODO: need to handle re-attempt better, when one node fails and others did not # we could overwrite key, attempt install (will pass on already installed configs), re-encrypt # this would require some way of knowing whether the credentials changed # or we could check for install and decrypt/store creds before replacing key and re-encrypting # TODO: add support for calling various cluster scripts in HA directory # TODO: on new cluster install the 2nd / tertiary DBs don't exist when updating dsip_settings, so the credentials don't match # kamailio db settings should not be synced in cluster sync mode (since config settings are stored there it would break the cluster) # TODO: handle re-running cluster install after failure (must reset GUI pass and probably should reset configs) function clusterInstall() { ( local i j local USER PASS HOST PORT SSH_REMOTE_HOST local CLUSTER_GUI_USER CLUSTER_GUI_PASS CLUSTER_API_TOKEN CLUSTER_MAIL_USER CLUSTER_MAIL_PASS CLUSTER_IPC_TOKEN local CLUSTER_KAM_DB_USER CLUSTER_KAM_DB_PASS CLUSTER_KAM_DB_NAME CLUSTER_ROOT_DB_USER CLUSTER_ROOT_DB_PASS CLUSTER_ROOT_DB_NAME local SSH_CMD=() RSYNC_CMD=() local TMP_PRIV_KEY="/tmp/dsip_privkey" local CLUSTER_SYNC=0 # default ssh options local SSH_OPTS=(-o StrictHostKeyChecking=no -o CheckHostIp=no -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -x) local RSYNC_OPTS=() # allow local project to be located anywhere local LOCAL_PROJECT_DIR="$DSIP_PROJECT_DIR" DSIP_PROJECT_DIR="/opt/dsiprouter" if ! cmdExists 'ssh' || ! cmdExists 'rsync' || ! cmdExists 'sshpass'; then printdbg 'Installing local requirements for cluster install' if cmdExists 'apt-get'; then sudo apt-get install -y openssh-client sshpass rsync elif cmdExists 'dnf'; then sudo dnf install --enablerepo=epel -y openssh-clients sshpass rsync elif cmdExists 'yum'; then sudo yum install --enablerepo=epel -y openssh-clients sshpass rsync else printerr "Your local OS is currently not supported" exit 1 fi fi # sanity check if (( $? != 0 )); then printerr 'Could not install requirements for cluster install' exit 1 fi # we need to know if cluster sync will be enabled beforehand j=0 while (( $j < ${#SSH_SYNC_ARGS[@]} )); do case "${SSH_SYNC_ARGS[$j]}" in -dsipcsync|--dsip-clustersync=*) if grep -q '=' 2>/dev/null <<<"${SSH_SYNC_ARGS[$j]}"; then CLUSTER_SYNC=$(cut -d '=' -f 2 <<<"${SSH_SYNC_ARGS[$j]}") else CLUSTER_SYNC="${SSH_SYNC_ARGS[$((j + 1))]}" fi break ;; esac j=$((j + 1)) done # if installing in cluster sync mode GUI pass must generate it beforehand (can't undo the hash later) # can still be overwritten by user provided args (probably not wise though) if (( $CLUSTER_SYNC == 1 )); then CLUSTER_GUI_PASS=$(urandomChars 64) fi # create private key if not set on cmdline if [[ -n "${SET_DSIP_PRIV_KEY}" ]]; then printf '%s' "${SET_DSIP_PRIV_KEY}" > ${TMP_PRIV_KEY} unset SET_DSIP_PRIV_KEY else dd bs=1 count=32 if=/dev/urandom of=${TMP_PRIV_KEY} 2>/dev/null fi # protect it until destroyed chmod 0400 ${TMP_PRIV_KEY} # guarantee key will be destroyed when subshell exits cleanupHandler() { rm -f ${TMP_PRIV_KEY} trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM } trap 'cleanupHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # loop through nodes to: # - validate conn # - validate unattended ssh # - collect ssh/scp cmds and pwds i=0 while (( $i < ${#SSH_SYNC_NODES[@]} )); do # parse node info USER=$(printf '%s' "${SSH_SYNC_NODES[$i]}" | cut -s -d '@' -f -1 | cut -d ':' -f -1) PASS=$(printf '%s' "${SSH_SYNC_NODES[$i]}" | cut -s -d '@' -f -1 | cut -s -d ':' -f 2-) HOST=$(printf '%s' "${SSH_SYNC_NODES[$i]}" | cut -d '@' -f 2- | cut -d ':' -f -1) PORT=$(printf '%s' "${SSH_SYNC_NODES[$i]}" | cut -d '@' -f 2- | cut -s -d ':' -f 2-) # default user is root for ssh USER=${USER:-root} # default port is 22 for ssh PORT=${PORT:-22} # host is required per node if [[ -z "$HOST" ]]; then printerr "Node [${SSH_SYNC_NODES[$i]}] does not contain a host" usageOptions exit 1 fi SSH_REMOTE_HOST="${USER}@${HOST}" # select auth method and set vars accordingly if [[ -n "$PASS" ]]; then export SSHPASS="${PASS}" SSH_CMD=(sshpass -e ssh) RSYNC_CMD=(sshpass -e rsync) SSH_OPTS+=(-o PreferredAuthentications=password) else SSH_CMD=(ssh) RSYNC_CMD=(rsync) if [[ -n "$SSH_KEY_FILE" ]]; then SSH_OPTS+=(-o PreferredAuthentications=publickey -i $SSH_KEY_FILE) else SSH_OPTS+=(-o PreferredAuthentications=publickey) fi fi # finalize options RSYNC_OPTS+=(--port=${PORT} -z --exclude=".*") SSH_OPTS+=(-p ${PORT}) # printdbg "Validating tcp connection to ${HOST}" # if ! checkConn ${HOST} ${PORT}; then # printerr "Could not establish connection to host [${HOST}] on port [${PORT}]" # exit 1 # fi printdbg "Validating unattended ssh connection to ${HOST}" if ! checkSSH ${SSH_CMD[@]} ${SSH_OPTS[@]} ${SSH_REMOTE_HOST}; then printerr "Could not establish unattended ssh connection to [${SSH_REMOTE_HOST}] on port [${PORT}]" exit 1 fi printdbg "Installing remote requirements for cluster install" ${SSH_CMD[@]} ${SSH_OPTS[@]} ${SSH_REMOTE_HOST} bash 2>&1 <<- EOSSH $(typeset -f cmdExists) if cmdExists 'apt-get'; then apt-get install -y rsync elif cmdExists 'dnf'; then dnf install -y rsync elif cmdExists 'yum'; then yum install -y rsync else exit 1 fi exit 0 EOSSH if (( $? != 0 )); then printerr "Failed installing requirements on remote node ${HOST_LIST[$i]}" exit 1 fi printdbg "Starting remote install on ${HOST}" # password used by ssh/scp if [[ -n "$PASS" ]]; then export SSHPASS="$PASS" fi printdbg "Copying project files to ${HOST}" ${RSYNC_CMD[@]} ${RSYNC_OPTS[@]} --rsh="ssh ${SSH_OPTS[*]} -o IPQoS=throughput" -a ${LOCAL_PROJECT_DIR}/ ${SSH_REMOTE_HOST}:/tmp/dsiprouter/ 2>&1 && ${RSYNC_CMD[@]} ${RSYNC_OPTS[@]} --rsh="ssh ${SSH_OPTS[*]} -o IPQoS=throughput" ${TMP_PRIV_KEY} ${SSH_REMOTE_HOST}:${TMP_PRIV_KEY} 2>&1 if (( $? != 0 )); then printerr "Copying files to ${HOST} failed" exit 1 fi printdbg "Running remote install on ${HOST}" ${SSH_CMD[@]} ${SSH_OPTS[@]} ${SSH_REMOTE_HOST} bash 2>&1 <<- EOSSH # debug the remote commands if (( $DEBUG == 1 )); then set -x fi # setting up project files on node mkdir -p ${DSIP_PROJECT_DIR} cp -af /tmp/dsiprouter/. ${DSIP_PROJECT_DIR}/ rm -rf /tmp/dsiprouter if [ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.clusterinstallcomplete" ]; then # setup cluster private key on node mkdir -p ${DSIP_SYSTEM_CONFIG_DIR} mv -f ${TMP_PRIV_KEY} ${DSIP_PRIV_KEY} chown root:root ${DSIP_PRIV_KEY} chmod 0400 ${DSIP_PRIV_KEY} # export any settings we wish to override in the install script # these settings are usually set after the first node (in cluster sync mode) is done installing [[ -n "$CLUSTER_GUI_USER" ]] && export SET_DSIP_GUI_USER="$CLUSTER_GUI_USER" [[ -n "$CLUSTER_GUI_PASS" ]] && export SET_DSIP_GUI_PASS="$CLUSTER_GUI_PASS" [[ -n "$CLUSTER_API_TOKEN" ]] && export SET_DSIP_API_TOKEN="$CLUSTER_API_TOKEN" [[ -n "$CLUSTER_MAIL_USER" ]] && export SET_DSIP_MAIL_USER="$CLUSTER_MAIL_USER" [[ -n "$CLUSTER_MAIL_PASS" ]] && export SET_DSIP_MAIL_PASS="$CLUSTER_MAIL_PASS" [[ -n "$CLUSTER_IPC_TOKEN" ]] && export SET_DSIP_IPC_TOKEN="$CLUSTER_IPC_TOKEN" [[ -n "$CLUSTER_KAM_DB_USER" ]] && export SET_KAM_DB_USER="$CLUSTER_KAM_DB_USER" [[ -n "$CLUSTER_KAM_DB_PASS" ]] && export SET_KAM_DB_PASS="$CLUSTER_KAM_DB_PASS" [[ -n "$CLUSTER_KAM_DB_NAME" ]] && export SET_KAM_DB_NAME="$CLUSTER_KAM_DB_NAME" [[ -n "$CLUSTER_ROOT_DB_USER" ]] && export SET_ROOT_DB_USER="$CLUSTER_ROOT_DB_USER" [[ -n "$CLUSTER_ROOT_DB_PASS" ]] && export SET_ROOT_DB_PASS="$CLUSTER_ROOT_DB_PASS" [[ -n "$CLUSTER_ROOT_DB_NAME" ]] && export SET_ROOT_DB_NAME="$CLUSTER_ROOT_DB_NAME" # run script command ${DSIP_PROJECT_DIR}/dsiprouter.sh install -dns ${SSH_SYNC_ARGS[@]} # create indicator in case we iterate over this node again (if this command is re-run) RETVAL=\$? (( \$RETVAL == 0 )) && touch ${DSIP_SYSTEM_CONFIG_DIR}/.clusterinstallcomplete exit \$RETVAL else # if this node was already configured, reconfigure the encrypted credentials to use the new private key source ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh export SET_DSIP_API_TOKEN=\${SET_DSIP_API_TOKEN:-\$(decryptConfigAttrib DSIP_API_TOKEN ${DSIP_CONFIG_FILE})} export SET_DSIP_MAIL_PASS=\${SET_DSIP_MAIL_PASS:-\$(decryptConfigAttrib MAIL_PASSWORD ${DSIP_CONFIG_FILE})} export SET_DSIP_IPC_TOKEN=\${SET_DSIP_IPC_TOKEN:-\$(decryptConfigAttrib DSIP_IPC_PASS ${DSIP_CONFIG_FILE})} export SET_KAM_DB_PASS=\${SET_KAM_DB_PASS:-\$(decryptConfigAttrib KAM_DB_PASS ${DSIP_CONFIG_FILE})} export SET_ROOT_DB_PASS=\${SET_ROOT_DB_PASS:-\$(decryptConfigAttrib ROOT_DB_PASS ${DSIP_CONFIG_FILE})} # setup cluster private key on node mkdir -p ${DSIP_SYSTEM_CONFIG_DIR} mv -f ${TMP_PRIV_KEY} ${DSIP_PRIV_KEY} chown root:root ${DSIP_PRIV_KEY} chmod 0400 ${DSIP_PRIV_KEY} dsiprouter setcredentials fi EOSSH # sanity check, was the install script successful? if (( $? != 0 )); then printerr "Remote install on ${HOST} failed (install script failed)" exit 1 fi # if installing in cluster sync mode reuse the credentials set on the first node # can still be overwritten by user provided args (probably not wise though) if (( $i == 0 )) && (( $CLUSTER_SYNC == 1 )); then . <( ${SSH_CMD[@]} ${SSH_OPTS[@]} ${SSH_REMOTE_HOST} bash 2>/dev/null <<- EOSSH DSIP_PROJECT_DIR="$DSIP_PROJECT_DIR" DSIP_SYSTEM_CONFIG_DIR="$DSIP_SYSTEM_CONFIG_DIR" $(typeset -f getConfigAttrib) $(typeset -f decryptConfigAttrib) echo "CLUSTER_GUI_USER='\$(getConfigAttrib DSIP_USERNAME ${DSIP_CONFIG_FILE})'" echo "CLUSTER_API_TOKEN='\$(decryptConfigAttrib DSIP_API_TOKEN ${DSIP_CONFIG_FILE})'" echo "CLUSTER_MAIL_USER='\$(getConfigAttrib MAIL_USERNAME ${DSIP_CONFIG_FILE})'" echo "CLUSTER_MAIL_PASS='\$(decryptConfigAttrib MAIL_PASSWORD ${DSIP_CONFIG_FILE})'" echo "CLUSTER_IPC_TOKEN='\$(decryptConfigAttrib DSIP_IPC_PASS ${DSIP_CONFIG_FILE})'" echo "CLUSTER_KAM_DB_USER='\$(getConfigAttrib KAM_DB_USER ${DSIP_CONFIG_FILE})'" echo "CLUSTER_KAM_DB_PASS='\$(decryptConfigAttrib KAM_DB_PASS ${DSIP_CONFIG_FILE})'" echo "CLUSTER_KAM_DB_NAME='\$(getConfigAttrib KAM_DB_NAME ${DSIP_CONFIG_FILE})'" echo "CLUSTER_ROOT_DB_USER='\$(getConfigAttrib ROOT_DB_USER ${DSIP_CONFIG_FILE})'" echo "CLUSTER_ROOT_DB_PASS='\$(decryptConfigAttrib ROOT_DB_PASS ${DSIP_CONFIG_FILE})'" echo "CLUSTER_ROOT_DB_NAME='\$(getConfigAttrib ROOT_DB_NAME ${DSIP_CONFIG_FILE})'" EOSSH ) # sanity check, were we able to get the settings from the remote node? if [[ -z "$CLUSTER_GUI_USER$CLUSTER_API_TOKEN" ]]; then printerr "Remote install on ${HOST} failed (could not get cluster credentials)" exit 1 fi fi i=$((i + 1)) done ); exit $?; } # $@ == subset of permissions to update # TODO: update systemd ExecStartPre commands to use this logic instead function updatePermissions() { local OPT="" # set permissions on the X509 certs used by dsiprouter and kamailio # [special use case]: testing kamailio service startup # in this case kamailio needs access before dsiprouter user is created setCertPerms() { if id -u dsiprouter &>/dev/null; then # dsiprouter needs to have control over the certs to allow changes # note that nginx should never have write access chown -R dsiprouter:kamailio ${DSIP_CERTS_DIR} else # dsiprouter user does not yet exist so make sure kamailio user has access chown -R root:kamailio ${DSIP_CERTS_DIR} fi find ${DSIP_CERTS_DIR}/ -type f -exec chmod 640 {} + } # set permissions for files/dirs used by dnsmasq setDnsmasqPerms() { mkdir -p /run/dnsmasq chown -R dnsmasq:dnsmasq /run/dnsmasq chmod 770 /run/dnsmasq } # set permissions for files/dirs used by nginx setNginxPerms() { mkdir -p /run/nginx chown -R nginx:nginx /run/nginx chmod 770 /run/nginx # dsiprouter needs to be able to dynamically update the provisioning site chown root:dsiprouter /etc/nginx/sites-enabled/ chmod 775 /etc/nginx/sites-enabled/ } # set permissions for files/dirs used by kamailio setKamailioPerms() { mkdir -p /run/kamailio chown -R kamailio:kamailio /run/kamailio chmod 770 /run/kamailio # dsiprouter needs to have control over the kamailio dir # this allows dsiprouter to update kamailio dynamically # kamailio configs will contain plaintext passwords / tokens # in the case where the dsiprouter user does not yet exist we set stricter permissions if id -u dsiprouter &>/dev/null; then chown -R dsiprouter:kamailio ${DSIP_SYSTEM_CONFIG_DIR}/kamailio/ else chown -R root:kamailio ${DSIP_SYSTEM_CONFIG_DIR}/kamailio/ fi find ${DSIP_SYSTEM_CONFIG_DIR}/kamailio/ -type f -exec chmod 640 {} + } # set permissions for files/dirs used by dsiprouter setDsiprouterPerms() { mkdir -p ${DSIP_RUN_DIR} chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} chmod 770 ${DSIP_RUN_DIR} # dsiprouter user is the only one making backups chown -R dsiprouter:root ${BACKUPS_DIR} # dsiprouter private key only readable by dsiprouter chown dsiprouter:root ${DSIP_PRIV_KEY} chmod 400 ${DSIP_PRIV_KEY} # dsiprouter gui files readable and writable only by dsiprouter chown -R dsiprouter:root ${DSIP_SYSTEM_CONFIG_DIR}/gui/ find ${DSIP_SYSTEM_CONFIG_DIR}/gui/ -type f -exec chmod 600 {} + # project files can only be edited by root chown -R root:root ${DSIP_PROJECT_DIR}/ # files that should be executable chmod +x ${DSIP_PROJECT_DIR}/dsiprouter.sh chmod +x ${DSIP_PROJECT_DIR}/resources/upgrade/*/scripts/migrate.sh } # set permissions for files/dirs used by rtpengine setRtpenginePerms() { mkdir -p /run/rtpengine chown -R rtpengine:rtpengine /run/rtpengine chmod 770 /run/rtpengine if id -u dsiprouter &>/dev/null; then chown -R dsiprouter:rtpengine ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/ else chown -R root:rtpengine ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/ fi find ${DSIP_SYSTEM_CONFIG_DIR}/rtpengine/ -type f -exec chmod 640 {} + } # no args given set permissions for all services if (( $# == 0 )); then if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]; then setDnsmasqPerms fi if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]; then setNginxPerms fi if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]; then setKamailioPerms fi if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]; then setDsiprouterPerms fi if [ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]; then setRtpenginePerms fi setCertPerms return 0 fi # parse args and select subset of permissions to set while (( $# > 0 )); do OPT="$1" shift case "$OPT" in -certs) setCertPerms ;; -dnsmasq) setDnsmasqPerms ;; -nginx) setNginxPerms ;; -kamailio) setKamailioPerms ;; -dsiprouter) setDsiprouterPerms ;; -rtpengine) setRtpenginePerms ;; *) printerr "$0(): Invalid argument [$ARG]" return 1 ;; esac done return 0 } export -f updatePermissions # really only useful on systems with limited RAM (where we usually test) function createSwapFile() { local SWAP_FILE="${DSIP_LIB_DIR}/swap" if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.memupdatescomplete" ]]; then return 0 fi # only create if system has less than 2GB RAM and no existing swap files if (( $(awk '/^MemTotal/ {print int($2/1024/1024)}' /proc/meminfo) < 2 )) && [[ -z "$(swapon --show=SIZE --noheadings)" ]]; then printdbg 'memory constraints require swapfile, creating now..' # 2GB of swap space dd if=/dev/zero of=${SWAP_FILE} bs=64M count=32 && chmod 600 ${SWAP_FILE} && mkswap ${SWAP_FILE} && swapon ${SWAP_FILE} && ( grep -vF "$SWAP_FILE" /etc/fstab echo "${SWAP_FILE} none swap sw 0 0" ) >/tmp/fstab && mv -f /tmp/fstab /etc/fstab && printdbg 'swapfile created successfully' || { printerr 'failed creating swap file' exit 1 } fi touch "${DSIP_SYSTEM_CONFIG_DIR}/.memupdatescomplete" } function removeSwapFile() { local SWAP_FILE="${DSIP_LIB_DIR}/swap" if [[ ! -f "${DSIP_SYSTEM_CONFIG_DIR}/.memupdatescomplete" ]]; then return 0 fi if [[ ! -e "$SWAP_FILE" ]]; then return 0 fi swapoff ${SWAP_FILE} && sed -i "\%^${SWAP_FILE}%d" /etc/fstab && printdbg 'swapfile removed' || { printerr 'failed removing swap file' exit 1 } rm -f "${DSIP_SYSTEM_CONFIG_DIR}/.memupdatescomplete" } function licenseManager() { ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/modules/api/licensemanager/cli.py "$@" return $? } function usageOptions() { linebreak() { printf '_%.0s' $(seq 1 ${COLUMNS:-100}) && echo '' } linebreak printf '\n%s\n%s\n' \ "$(pprint -n USAGE:)" \ "dsiprouter <command> [options]" linebreak printf "\n%-s%24s%s\n" \ "$(pprint -n COMMAND)" " " "$(pprint -n OPTIONS)" printf "%-30s %s\n%-30s %s\n%-30s %s\n%-30s %s\n%-30s %s\n" \ "install" "[-debug|-all|--all|-kam|--kamailio|-dsip|--dsiprouter|-rtp|--rtpengine|-dns|--dnsmasq" \ " " "-dmz <pub iface>,<priv iface>|--dmz=<pub iface>,<priv iface>|-netm <mode>|--network-mode=<mode>|-homer <homerhost[:heplifyport]>|" \ " " "-db <[user[:pass]@]dbhost[:port][/dbname]>|--database=<[user[:pass]@]dbhost[:port][/dbname]>|-dsipcid <num>|--dsip-clusterid=<num>|" \ " " "-dbadmin <[user[:pass]@]dbhost[:port][/dbname]>|--database-admin=<[user[:pass]@]dbhost[:port][/dbname]>|-dsipcsync <num>|" \ " " "--dsip-clustersync=<num>|-dsipkey <32 chars>|--dsip-privkey=<32 chars>|-with_lcr|--with_lcr=<num>|-with_dev|--with_dev=<num>]" printf "%-30s %s\n" \ "uninstall" "[-debug|-all|--all|-kam|--kamailio|-dsip|--dsiprouter|-rtp|--rtpengine]" printf "%-30s %s\n" \ "clusterinstall" "[-debug] [-i <ssh key file>] <[user1[:pass1]@]node1[:port1]> <[user2[:pass2]@]node2[:port2]> ... -- [INSTALL OPTIONS]" printf "%-30s %s\n" \ "upgrade" "[-debug|-dsipcid <num>|--dsip-clusterid=<num>|-url <repo url>|--repo-url=<repo url>] <-rel <release number>|--release=<release number>>" printf "%-30s %s\n" \ "start" "[-debug|-all|--all|-kam|--kamailio|-dsip|--dsiprouter|-rtp|--rtpengine]" printf "%-30s %s\n" \ "stop" "[-debug|-all|--all|-kam|--kamailio|-dsip|--dsiprouter|-rtp|--rtpengine]" printf "%-30s %s\n" \ "restart" "[-debug|-all|--all|-kam|--kamailio|-dsip|--dsiprouter|-rtp|--rtpengine]" printf "%-30s %s\n" \ "chown" "[-debug|-certs|-dnsmasq|-nginx|-kamailio|-dsiprouter|-rtpengine]" printf "%-30s %s\n" \ "configurekam" "[-debug]" printf "%-30s %s\n" \ "configuredsip" "[-debug]" printf "%-30s %s\n" \ "configurertp" "[-debug]" printf "%-30s %s\n" \ "renewsslcert" "[-debug]" printf "%-30s %s\n" \ "configuresslcert" "[-debug|-f|--force|-o|--override=<[FQDN]>]" printf "%-30s %s\n" \ "installmodules" "[-debug]" printf "%-30s %s\n" \ "resetpassword" "[-debug|-q|--quiet|-all|--all|-dc|--dsip-creds|-ac|--api-creds|-kc|--kam-creds|-ic|--ipc-creds|-fid|--force-instance-id]" printf "%-30s %s\n%-30s %s\n%-30s %s\n%-30s %s\n%-30s %s\n" \ "setcredentials" "[-debug|-dc <[user][:pass]>|--dsip-creds=<[user][:pass]>|-ac <token>|--api-creds=<token>|" \ " " "-kc <[user[:pass]@]dbhost[:port][/dbname]>|--kam-creds=<[user[:pass]@]dbhost[:port][/dbname]>|" \ " " "-mc <[user][:pass]>|--mail-creds=<[user][:pass]>|-ic <token>|--ipc-creds=<token>]|" \ " " "-dac <[user[:pass]@]dbhost[:port][/dbname]>|--db-admin-creds=<[user[:pass]@]dbhost[:port][/dbname]>|" \ " " "-sc <key>|--session-creds=<key>]" printf "%-30s %s\n%-30s %s\n%-30s %s\n" \ "licensemanager" "[-debug] -list|-retrieve <license_key or 'tag=<tag>'>|" \ " " "-activate <license_key>|-import <file containing keys>|" \ " " "-clear|-deactivate <license_key or 'tag=<tag>'>|" \ " " "-check <license_key or 'tag=<tag>'>" printf "%-30s %s\n" \ "backup" "[-debug] [-f <output file>]" printf "%-30s %s\n" \ "restore" "[-debug] [-f <input file>]" printf "%-30s %s\n" \ "version|-v|--version" "" printf "%-30s %s\n" \ "help|-h|--help" "" linebreak printf '\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n' \ "$(pprint -n SUMMARY:)" \ "dSIPRouter is a Web Management GUI for Kamailio based on use case design, with a focus on ITSP and Carrier use cases." \ "This means that we aren’t a general purpose GUI for Kamailio." \ "If that's required then use Siremis, which is located at http://siremis.asipto.com/" \ "This script is used for installing, uninstalling, managing, and configuring dSIPRouter and the various services it manages." \ "That includes starting/stopping/executing the Web GUI, manaing the Nginx reverse proxy, managing Kamailio, manaing RTPEngine and much more." \ "This script can also be used to sync service settings with dSIPRouter, install new modules, renew TLS certs, and configure a cluster." linebreak printf '\n%s\n%s\n%s\n%s\n%s\n\n' \ "$(pprint -n MORE INFO:)" \ "The full documentation available locally on your system: ${DSIP_PROTO}://${EXTERNAL_FQDN}:${DSIP_PORT}/docs/index.html" \ "We also provide the documentation online for your convenience: https://dsiprouter.readthedocs.io" \ "Drop by the project website for the latest information on the project: https://dsiprouter.org/" \ "Support is available from dOpenSource. Visit us at https://dopensource.com/dsiprouter or call us at 888-907-2085" linebreak printf '\n%s\n%s\n%s\n\n' \ "$(pprint -n PROVIDED BY:)" \ "dOpenSource | A Flyball Company" \ "Made in Detroit, MI USA" linebreak } # make the output a little cleaner function setDebugMode() { if [[ "$*" == *"-debug"* ]]; then export DEBUG=1 # start debugging after this function exits debugHandler() { set -x trap - RETURN } trap debugHandler RETURN fi # if [[ "$*" != *"-debug"* ]]; then # # quiet pkg managers when not debugging # if cmdExists 'apt-get'; then # function apt-get() { # command apt-get -qq "$@" # } # export -f apt-get # fi # if cmdExists 'yum'; then # function yum() { # command yum -q -e 0 "$@" # } # export -f yum # fi # if cmdExists 'dnf'; then # function dnf() { # command dnf -q -e 0 "$@" # } # export -f dnf # fi # # quiet make when not debugging # function make() { # command make -s "$@" # } # export -f make # fi return 0 } # prep before processing command function preprocessCMD() { # Display usage options if no command is specified if (( $# == 0 )); then usageOptions exit 1 fi setDebugMode "$@" # Do not run the extra prep on these commands # we only need a portion of the script settings case "$1" in chown|exec|clusterinstall|licensemanager|version|-v|--version|help|-h|--help) setStaticScriptSettings ;; *) initialChecks "$@" ;; esac } # process the commands to be executed # TODO: add help options for each command (with subsection usage info for that command) # TODO: move cli arg parsing to start of dsiprouter.sh (split out into its own file) # TODO: move cli arg/option definitions to separate shared JSON file function processCMD() { # pre-processing / initial checks preprocessCMD "$@" # use options to add commands in any order needed # 1 == defaults on, 0 == defaults off local DISPLAY_LOGIN_INFO=0 # for install / uninstall, if no selections are chosen use some sane defaults local DEFAULT_SERVICES=1 # process all options before running commands declare -a RUN_COMMANDS local ARG="$1" OPT="" RETVAL=0 case $ARG in install) # always add official repo's, set platform, and create init service RUN_COMMANDS+=(configureSystemPath setCloudPlatform createInitService createSwapFile installDsiprouterCli) shift local NEW_ROOT_DB_USER="" NEW_ROOT_DB_PASS="" NEW_ROOT_DB_NAME="" DB_CONN_URI="" TMP_ARG="" while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -dns|--dnsmasq) RUN_COMMANDS+=(installDnsmasq) shift ;; -mysql|--mysql) DEFAULT_SERVICES=0 RUN_CMMANDS+=(installMysql) shift ;; -kam|--kamailio) DEFAULT_SERVICES=0 RUN_COMMANDS+=(installSipsak installCron installKamailio) shift ;; -dsip|--dsiprouter) DEFAULT_SERVICES=0 DISPLAY_LOGIN_INFO=1 RUN_COMMANDS+=(installSipsak installCron installNginx installDsiprouter) shift ;; -rtp|--rtpengine) DEFAULT_SERVICES=0 RUN_COMMANDS+=(installCron installRTPEngine) shift ;; -all|--all) DEFAULT_SERVICES=0 DISPLAY_LOGIN_INFO=1 RUN_COMMANDS+=(installSipsak installCron installMysql installKamailio installNginx installDsiprouter installRTPEngine) shift ;; # DEPRECATED: marked for removal in v0.80 -dmz|--dmz=*) NETWORK_MODE=2 if echo "$1" | grep -q '=' 2>/dev/null; then TMP=$(echo "$1" | cut -d '=' -f 2) shift else shift TMP="$1" shift fi PUBLIC_IFACE=$(echo "$TMP" | cut -d ',' -f 1) PRIVATE_IFACE=$(echo "$TMP" | cut -d ',' -f 2) ;; -netm|--network-mode=*) if echo "$1" | grep -q '=' 2>/dev/null; then NETWORK_MODE=$(echo "$1" | cut -d '=' -f 2) shift else shift NETWORK_MODE="$1" shift fi ;; -db|--database=*) if echo "$1" | grep -q '=' 2>/dev/null; then DB_CONN_URI=$(printf '%s' "$1" | cut -d '=' -f 2) shift else shift DB_CONN_URI="$1" shift fi TMP_VAL=$(parseDBConnURI -user "$DB_CONN_URI") && { export SET_KAM_DB_USER="$TMP_VAL" export KAM_DB_USER="$TMP_VAL" } TMP_VAL=$(parseDBConnURI -pass "$DB_CONN_URI") && { export SET_KAM_DB_PASS="$TMP_VAL" export KAM_DB_PASS="$TMP_VAL" } TMP_VAL=$(parseDBConnURI -host "$DB_CONN_URI") && { export SET_KAM_DB_HOST="$TMP_VAL" export KAM_DB_HOST="$TMP_VAL" } TMP_VAL=$(parseDBConnURI -port "$DB_CONN_URI") && { export SET_KAM_DB_PORT="$TMP_VAL" export KAM_DB_PORT="$TMP_VAL" } TMP_VAL=$(parseDBConnURI -name "$DB_CONN_URI") && { export SET_KAM_DB_NAME="$TMP_VAL" export KAM_DB_NAME="$TMP_VAL" } ;; -dsipcid|--dsip-clusterid=*) if echo "$1" | grep -q '=' 2>/dev/null; then DSIP_CLUSTER_ID="$(echo "$1" | cut -d '=' -f 2)" shift else shift DSIP_CLUSTER_ID="$1" shift fi ;; -dbadmin|--database-admin=*) if echo "$1" | grep -q '=' 2>/dev/null; then DB_CONN_URI=$(printf '%s' "$1" | cut -d '=' -f 2) shift else shift DB_CONN_URI="$1" shift fi TMP_VAL=$(parseDBConnURI -user "$DB_CONN_URI") && export ROOT_DB_USER="$TMP_VAL" TMP_VAL=$(parseDBConnURI -pass "$DB_CONN_URI") && export ROOT_DB_PASS="$TMP_VAL" TMP_VAL=$(parseDBConnURI -host "$DB_CONN_URI") && export ROOT_DB_HOST="$TMP_VAL" TMP_VAL=$(parseDBConnURI -port "$DB_CONN_URI") && export ROOT_DB_PORT="$TMP_VAL" TMP_VAL=$(parseDBConnURI -name "$DB_CONN_URI") && export ROOT_DB_NAME="$TMP_VAL" ;; -dsipcsync|--dsip-clustersync=*) if echo "$1" | grep -q '=' 2>/dev/null; then DSIP_CLUSTER_SYNC="$(echo "$1" | cut -d '=' -f 2)" shift else shift DSIP_CLUSTER_SYNC="$1" shift fi # sanity check value for cluster sync case "$DSIP_CLUSTER_SYNC" in 0|1) : ;; *) printerr 'Invalid value for setting DSIP_CLUSTER_SYNC' exit 1 ;; esac # change default for loading settings to db LOAD_SETTINGS_FROM='db' ;; -dsipkey|--dsip-privkey=*) if echo "$1" | grep -q '=' 2>/dev/null; then SET_DSIP_PRIV_KEY="$(printf '%s' "$1" | cut -d '=' -f 2)" shift else shift SET_DSIP_PRIV_KEY="$1" shift fi # sanity check if (( $(printf '%s' "${SET_DSIP_PRIV_KEY}" | wc -c) != 32 )); then printerr 'dSIPRouter private key must be 32 bytes' exit 1 fi ;; -with_lcr|--with_lcr=*) if echo "$1" | grep -q '=' 2>/dev/null; then if (( $(echo "$1" | cut -d '=' -f 2) > 0 )); then WITH_LCR=1 else WITH_LCR=0 fi shift else WITH_LCR=1 shift fi ;; -with_dev|--with_dev=*) if echo "$1" | grep -q '=' 2>/dev/null; then if (( $(echo "$1" | cut -d '=' -f 2) > 0 )); then RUN_COMMANDS+=(configGitDevEnv) fi shift else RUN_COMMANDS+=(configGitDevEnv) shift fi ;; -homer) shift export HOMER_HEP_HOST=$(printf '%s' "$1" | cut -d ':' -f -1) TMP_ARG="$(printf '%s' "$1" | cut -s -d ':' -f 2)" [[ -n "$TMP_ARG" ]] && export HOMER_HEP_PORT="$TMP_ARG" shift # sanity check if [[ -z "$HOMER_HEP_HOST" ]]; then printerr 'Missing required argument <homer_host> to option -homer' exit 1 fi ;; --rtpengine-uri=*) RTPENGINE_URI=$(cut -s -d '=' -f 2- <<<"$1") shift # sanity check if [[ -z "$RTPENGINE_URI" ]]; then printerr 'Missing required argument to option "--rtpengine-uri="' exit 1 fi RUN_CMMANDS+=(updateRtpengineStartup) ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # only use defaults if no discrete services specified if (( ${DEFAULT_SERVICES} == 1 )); then DISPLAY_LOGIN_INFO=1 RUN_COMMANDS+=(installSipsak installMysql installKamailio installNginx installDsiprouter) fi # add displaying logo and login info to deferred commands RUN_COMMANDS+=(displayLogo) if (( ${DISPLAY_LOGIN_INFO} == 1 )); then RUN_COMMANDS+=(displayLoginInfo) fi ;; uninstall) RUN_COMMANDS+=(setCloudPlatform) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -dns|--dnsmasq) RUN_COMMANDS+=(uninstallDnsmasq) shift ;; -rtp|--rtpengine) DEFAULT_SERVICES=0 RUN_COMMANDS+=(uninstallRTPEngine) shift ;; -dsip|--dsiprouter) DEFAULT_SERVICES=0 RUN_COMMANDS+=(uninstallDsiprouter uninstallNginx) shift ;; -kam|--kamailio) DEFAULT_SERVICES=0 RUN_COMMANDS+=(uninstallKamailio) shift ;; # only remove init and system config dir if all services will be removed (dependency for others) # same goes for official repo configs, we only remove if all dsiprouter configs are being removed -all|--all) DEFAULT_SERVICES=0 RUN_COMMANDS+=(uninstallRTPEngine uninstallDsiprouter uninstallNginx uninstallKamailio uninstallMysql uninstallDnsmasq uninstallSipsak uninstallDsiprouterCli removeSwapFile removeInitService revertSystemRepos revertSystemPath) shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # only use defaults if no discrete services specified if (( ${DEFAULT_SERVICES} == 1 )); then RUN_COMMANDS+=(uninstallDsiprouter uninstallNginx uninstallKamailio uninstallMysql uninstallSipsak uninstallDsiprouterCli removeSwapFile removeInitService) fi # clean dev environment if configured if [[ -e /usr/local/bin/_merge-changelog ]]; then RUN_COMMANDS+=(cleanGitDevEnv) fi # display logo after install / uninstall commands RUN_COMMANDS+=(displayLogo) ;; clusterinstall) # install across remote cluster RUN_COMMANDS+=(clusterInstall) shift SSH_SYNC_NODES=() SSH_SYNC_ARGS=() # loop through args and grab nodes while (( $# > 0 )); do ARG="$1" case $ARG in --) # scrap the -- shift break ;; -debug) # already processed by setDebugMode() shift ;; -i) shift SSH_KEY_FILE="$1" shift ;; *) # add to list of nodes SSH_SYNC_NODES+=( "$ARG" ) shift ;; esac done # loop through args and grab install options while (( $# > 0 )); do ARG="$1" case $ARG in # we will transport securely instead -dsipkey|--dsip-privkey=*) if echo "$ARG" | grep -q '=' 2>/dev/null; then SET_DSIP_PRIV_KEY="$(printf '%s' "$ARG" | cut -d '=' -f 2)" shift else shift SET_DSIP_PRIV_KEY="$1" shift fi # sanity check if (( $(printf '%s' "${SET_DSIP_PRIV_KEY}" | wc -c) != 32 )); then printerr 'dSIPRouter private key must be 32 bytes' exit 1 fi ;; *) # add to list of args SSH_SYNC_ARGS+=( "$ARG" ) shift ;; esac done # sanity check if (( ${#SSH_SYNC_NODES[@]} < 1 )); then printerr "At least 2 nodes are required to setup cluster" usageOptions exit 1 fi ;; upgrade) # upgrade dsiprouter version RUN_COMMANDS+=(upgrade) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -dsipcid|--dsip-clusterid=*) if echo "$1" | grep -q '=' 2>/dev/null; then DSIP_CLUSTER_ID="$(echo "$1" | cut -d '=' -f 2)" shift else shift DSIP_CLUSTER_ID="$1" shift fi ;; -rel|--release=*) if echo "$1" | grep -q '=' 2>/dev/null; then export UPGRADE_RELEASE="$(echo "$1" | cut -d '=' -f 2)" shift else shift export UPGRADE_RELEASE="$1" shift fi if [[ -z "$UPGRADE_RELEASE" ]]; then printerr "Invalid upgrade release specified" usageOptions exit 1 fi # format as per branch name if given as version number if [[ "${UPGRADE_RELEASE:0:1}" != "v" ]]; then UPGRADE_RELEASE="v${UPGRADE_RELEASE}" fi ;; -url|--repo-url=*) if echo "$1" | grep -q '=' 2>/dev/null; then export UPGRADE_REPO="$(echo "$1" | cut -d '=' -f 2)" shift else shift export UPGRADE_REPO="$1" shift fi ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # repo we are upgrading from could have been provided on the CLI if [[ -n "$UPGRADE_REPO" ]]; then UPGRADE_RELEASE_URL="https://api.github.com/repos/$(rev <<<"$UPGRADE_REPO" | cut -d '/' -f -2 | cut -d '.' -f 2- | rev)/releases" else UPGRADE_RELEASE_URL="$GIT_RELEASE_URL" fi # use latest release if none specified if [[ -z "$UPGRADE_RELEASE" ]]; then TMP=$(curl -s "$UPGRADE_RELEASE_URL") && TMP=$(jq -e -r '.[].tag_name | gsub("^(?<rel>v[0-9]+\\.[0-9]+).*?$"; .rel)' <<<"$TMP") && UPGRADE_RELEASE=$(sort -gur <<<"$TMP" | head -1) || { printerr "Could not retrieve latest release candidate" exit 1 } fi ;; start) # start installed services RUN_COMMANDS+=(start) shift DEFAULT_START_OPTIONS=1 while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -all|--all) DEFAULT_START_OPTIONS=0 START_DSIPROUTER=1 START_KAMAILIO=1 START_RTPENGINE=1 shift ;; -dsip|--dsiprouter) DEFAULT_START_OPTIONS=0 START_DSIPROUTER=1 shift ;; -kam|--kamailio) DEFAULT_START_OPTIONS=0 START_KAMAILIO=1 shift ;; -rtp|--rtpengine) DEFAULT_START_OPTIONS=0 START_RTPENGINE=1 shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # default to only starting dsip gui if (( $DEFAULT_START_OPTIONS == 1 )); then START_DSIPROUTER=1 fi ;; stop) # stop installed services RUN_COMMANDS+=(stop) shift DEFAULT_STOP_OPTIONS=1 while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -all|--all) DEFAULT_STOP_OPTIONS=0 STOP_DSIPROUTER=1 STOP_KAMAILIO=1 STOP_RTPENGINE=1 shift ;; -dsip|--dsiprouter) DEFAULT_STOP_OPTIONS=0 STOP_DSIPROUTER=1 shift ;; -kam|--kamailio) DEFAULT_STOP_OPTIONS=0 STOP_KAMAILIO=1 shift ;; -rtp|--rtpengine) DEFAULT_STOP_OPTIONS=0 STOP_RTPENGINE=1 shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # default to only stopping dsip gui if (( $DEFAULT_STOP_OPTIONS == 1 )); then STOP_DSIPROUTER=1 fi ;; restart) RESTART_ARGS=(restart) RESTART_DAEMONIZE=0 # restart installed services RUN_COMMANDS+=(restart) shift DEFAULT_RESTART_OPTIONS=1 while (( $# > 0 )); do OPT="$1" case $OPT in -debug) RESTART_ARGS+=("$OPT") shift ;; -all|--all) DEFAULT_RESTART_OPTIONS=0 STOP_DSIPROUTER=1 START_DSIPROUTER=1 STOP_KAMAILIO=1 START_KAMAILIO=1 STOP_RTPENGINE=1 START_RTPENGINE=1 RESTART_ARGS+=("$OPT") shift ;; -dsip|--dsiprouter) DEFAULT_RESTART_OPTIONS=0 STOP_DSIPROUTER=1 START_DSIPROUTER=1 RESTART_ARGS+=("$OPT") shift ;; -kam|--kamailio) DEFAULT_RESTART_OPTIONS=0 STOP_KAMAILIO=1 START_KAMAILIO=1 RESTART_ARGS+=("$OPT") shift ;; -rtp|--rtpengine) DEFAULT_RESTART_OPTIONS=0 STOP_RTPENGINE=1 START_RTPENGINE=1 RESTART_ARGS+=("$OPT") shift ;; # internal usage only, no need for user to be calling with this option -daemonize) RESTART_DAEMONIZE=1 shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # default to only restarting dsip gui if (( $DEFAULT_RESTART_OPTIONS == 1 )); then STOP_DSIPROUTER=1 START_DSIPROUTER=1 fi ;; # internal command, replace this process with the GUI server immediately exec) exec ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter.py ;; chown) shift # pop off the -debug option if provided OPTS=("$@") for IDX in "${!OPTS[@]}"; do case ${OPTS[$IDX]} in -debug) # already processed by setDebugMode() unset OPTS[$IDX] ;; esac done set -- "${OPTS[@]}" # pass the rest of the user args to the local function # TODO: figure out how to pass variables into staged commands updatePermissions "$@" exit $? ;; configurertp) # reconfigure rtpengine configs RUN_COMMANDS+=(generateRtpengineConfig updateRtpengineConfig updateRtpengineStartup) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; configurekam) # reconfigure kamailio configs RUN_COMMANDS+=(generateKamailioConfig updateKamailioConfig updateKamailioStartup) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; configuredsip) # reconfigure dsiprouter configs RUN_COMMANDS+=(generateDsiprouterConfig updateDsiprouterConfig updateDsiprouterStartup) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; renewsslcert) # reconfigure ssl configs RUN_COMMANDS+=(renewSSLCert) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; configuresslcert) # reconfigure ssl configs RUN_COMMANDS+=(configureSSL) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -f|--force) rm -f $DSIP_CERTS_DIR/dsiprouter-cert.pem rm -f $DSIP_CERTS_DIR/dsiprouter-key.pem shift ;; -o|--override=*) if echo "$1" | grep -q '=' 2>/dev/null; then DNS_NAME_OVERRIDE=$(echo "$1" | cut -d '=' -f 2) shift else shift DNS_NAME_OVERRIDE="$1" shift fi ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; installmodules) # reconfigure dsiprouter modules RUN_COMMANDS+=(installModules restart) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; resetpassword) # reset secure credentials RUN_COMMANDS+=(setCloudPlatform setCredentials) shift # by default we display the new login information DISPLAY_LOGIN_INFO=1 # we default to resetting only the dsip gui password # otherwise only the credentials specified are reset while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -q|--quiet) DISPLAY_LOGIN_INFO=0 shift ;; -all|--all) RESET_DSIP_GUI_PASS=1 RESET_DSIP_API_TOKEN=1 RESET_KAM_DB_PASS=1 RESET_DSIP_IPC_TOKEN=1 shift ;; -dc|--dsip-creds) RESET_DSIP_GUI_PASS=1 shift ;; -ac|--api-creds) RESET_DSIP_API_TOKEN=1 RESET_DSIP_GUI_PASS=${RESET_DSIP_GUI_PASS:-0} shift ;; -kc|--kam-creds) RESET_KAM_DB_PASS=1 RESET_DSIP_GUI_PASS=${RESET_DSIP_GUI_PASS:-0} shift ;; -ic|--ipc-creds) RESET_DSIP_IPC_TOKEN=1 RESET_DSIP_GUI_PASS=${RESET_DSIP_GUI_PASS:-0} shift ;; -fid|--force-instance-id) RESET_FORCE_INSTANCE_ID=1 shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done # preconditions checks and setting variables to pass to setCredentials() if (( ${RESET_DSIP_GUI_PASS:-1} == 1 )); then if (( ${IMAGE_BUILD} == 1 || ${RESET_FORCE_INSTANCE_ID:-0} == 1 )); then SET_DSIP_GUI_PASS=$(getInstanceID) if [[ -z "$SET_DSIP_GUI_PASS" ]]; then printerr "Could not retrieve the instance ID for password reset" exit 1 fi else SET_DSIP_GUI_PASS=$(urandomChars 64) fi fi if (( ${RESET_DSIP_API_TOKEN:-0} == 1 )); then SET_DSIP_API_TOKEN=$(urandomChars 64) fi if (( ${RESET_DSIP_IPC_TOKEN:-0} == 1 )); then SET_DSIP_IPC_TOKEN=$(urandomChars 64) fi if (( ${RESET_KAM_DB_PASS:-0} == 1 )); then export SET_KAM_DB_PASS=$(urandomChars 64) fi # display if not in quiet mode if (( ${DISPLAY_LOGIN_INFO} == 1 )); then RUN_COMMANDS+=(displayLoginInfo) fi ;; setcredentials) # set secure credentials to fixed values RUN_COMMANDS+=(setCredentials) shift local TMP_VAL='' while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; -dc|--dsip-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then CREDS_URI=$(echo "$1" | cut -d '=' -f 2) shift else shift CREDS_URI="$1" shift fi SET_DSIP_GUI_USER=$(perl -pe 's%([^:/\t\r\n\v\f]+)?(?::([^/\t\r\n\v\f]*))?%\1%' <<<"$CREDS_URI") SET_DSIP_GUI_PASS=$(perl -pe 's%([^:/\t\r\n\v\f]+)?(?::([^/\t\r\n\v\f]*))?%\2%' <<<"$CREDS_URI") ;; -ac|--api-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then SET_DSIP_API_TOKEN=$(echo "$1" | cut -d '=' -f 2) shift else shift SET_DSIP_API_TOKEN="$1" shift fi ;; -kc|--kam-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then DB_CONN_URI=$(echo "$1" | cut -d '=' -f 2) shift else shift DB_CONN_URI="$1" shift fi # sanity check if [[ -z "${DB_CONN_URI}" ]]; then printerr "Credentials must be given for option $OPT" exit 1 fi TMP_VAL=$(parseDBConnURI -user "$DB_CONN_URI") && export SET_KAM_DB_USER="$TMP_VAL" TMP_VAL=$(parseDBConnURI -pass "$DB_CONN_URI") && export SET_KAM_DB_PASS="$TMP_VAL" TMP_VAL=$(parseDBConnURI -host "$DB_CONN_URI") && export SET_KAM_DB_HOST="$TMP_VAL" TMP_VAL=$(parseDBConnURI -port "$DB_CONN_URI") && export SET_KAM_DB_PORT="$TMP_VAL" TMP_VAL=$(parseDBConnURI -name "$DB_CONN_URI") && export SET_KAM_DB_NAME="$TMP_VAL" ;; -mc|--mail-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then CREDS_URI=$(echo "$1" | cut -d '=' -f 2) shift else shift CREDS_URI="$1" shift fi SET_DSIP_MAIL_USER=$(perl -pe 's%([^:/\t\r\n\v\f]+)?(?::([^/\t\r\n\v\f]*))?%\1%' <<<"$CREDS_URI") SET_DSIP_MAIL_PASS=$(perl -pe 's%([^:/\t\r\n\v\f]+)?(?::([^/\t\r\n\v\f]*))?%\2%' <<<"$CREDS_URI") ;; -ic|--ipc-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then SET_DSIP_IPC_TOKEN=$(echo "$1" | cut -d '=' -f 2) shift else shift SET_DSIP_IPC_TOKEN="$1" shift fi ;; -dac|--database-admin-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then DB_CONN_URI=$(printf '%s' "$1" | cut -d '=' -f 2) shift else shift DB_CONN_URI="$1" shift fi # sanity check if [[ -z "${DB_CONN_URI}" ]]; then printerr "Credentials must be given for option $OPT" exit 1 fi TMP_VAL=$(parseDBConnURI -user "$DB_CONN_URI") && export SET_ROOT_DB_USER="$TMP_VAL" TMP_VAL=$(parseDBConnURI -pass "$DB_CONN_URI") && export SET_ROOT_DB_PASS="$TMP_VAL" TMP_VAL=$(parseDBConnURI -host "$DB_CONN_URI") && export SET_ROOT_DB_HOST="$TMP_VAL" TMP_VAL=$(parseDBConnURI -port "$DB_CONN_URI") && export SET_ROOT_DB_PORT="$TMP_VAL" TMP_VAL=$(parseDBConnURI -name "$DB_CONN_URI") && export SET_ROOT_DB_NAME="$TMP_VAL" ;; -sc|--session-creds=*) if echo "$1" | grep -q '=' 2>/dev/null; then SET_DSIP_SESSION_KEY=$(echo "$1" | cut -d '=' -f 2) shift else shift SET_DSIP_SESSION_KEY="$1" shift fi ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # DEPRECATED: in favor of using configurekam command, marked for removal in v0.80 generatekamconfig) # generate kamailio configs from templates RUN_COMMANDS+=(generateKamailioConfig) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # internal command, update kamailio config dynamically updatekamconfig) # update kamailio config RUN_COMMANDS+=(updateKamailioConfig) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # internal command, update dsiprouter config dynamically updatedsipconfig) # update kamailio config RUN_COMMANDS+=(updateDsiprouterConfig) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # internal command, update rtpengine config dynamically updatertpconfig) # update rtpengine config RUN_COMMANDS+=(updateRtpengineConfig) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # internal command, update dnsmasq config dynamically updatednsconfig) # update dnsmasq config RUN_COMMANDS+=(updateDnsConfig) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; # internal command, generate CA dir from CA bundle file updatecacertsdir) # update dnsmasq config RUN_COMMANDS+=(updateCACertsDir) shift while (( $# > 0 )); do OPT="$1" case $OPT in -debug) # already processed by setDebugMode() shift ;; *) # fail on unknown option printerr "Invalid option [$OPT] for command [$ARG]" usageOptions exit 1 shift ;; esac done ;; licensemanager) shift # handle the debug option here for OPT in "$@"; do case $OPT in -debug) # already processed by setDebugMode() shift ;; esac done # pass the rest of the user args to the local function licenseManager "$@" exit $? ;; backup) shift for OPT in "$@"; do case $OPT in -debug) # already processed by setDebugMode() shift ;; -f) shift DUMP_FILE="$1" shift ;; esac done if [[ -z "$DUMP_FILE" ]]; then DUMP_FILE=${CURR_BACKUP_DIR}/db.sql fi printdbg "backing up database to $DUMP_FILE" dumpDB "$KAM_DB_NAME" >"$DUMP_FILE" (( $? != 0 )) && { printerr 'failed backing up database' exit 1 } chown dsiprouter:root "$DUMP_FILE" pprint 'database backup created' exit 0 ;; restore) shift for OPT in "$@"; do case $OPT in -debug) # already processed by setDebugMode() shift ;; -f) shift DUMP_FILE="$1" shift ;; esac done if [[ -z "$DUMP_FILE" ]]; then DUMP_FILE=${CURR_BACKUP_DIR}/db.sql fi printdbg "restoring database from $DUMP_FILE" withRootDBConn mysql <"$DUMP_FILE" (( $? != 0 )) && { printerr 'failed restoring database' exit 1 } pprint 'database restored from backup' exit 0 ;; version|-v|--version) printf '%s\n' "$(getConfigAttrib 'VERSION' ${DSIP_CONFIG_FILE})" exit 1 ;; help|-h|--help) usageOptions exit 1 ;; *) printerr "Invalid command [$ARG]" usageOptions exit 1 ;; esac # remove duplicate commands, while preserving order RUN_COMMANDS=( $(printf '%s\n' "${RUN_COMMANDS[@]}" | awk '!x[$0]++') ) # Options are processed... run commands. Processing Notes below. # default priority of install (with rtpengine): # 1. kamailio # 2. dsiprouter # 3. rtpengine # default order of install (without rtpengine): # 1. kamailio # 2. dsiprouter # default order of install (without dsiprouter): # 1. kamailio # 2. rtpengine for RUN_COMMAND in "${RUN_COMMANDS[@]}"; do $RUN_COMMAND || { printerr "[$0:$RUN_CMMAND()] an unhandled fatal error occurred.. halting execution." exit $? } done exit 0 } #end of processCMD processCMD "$@" ================================================ FILE: gui/database/__init__.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import base64, bson, inspect, os from collections import OrderedDict from enum import Enum from datetime import datetime, timedelta from sqlalchemy import create_engine, MetaData, Table, Column, String, exc as sql_exceptions, Integer, event from sqlalchemy.orm import registry, sessionmaker, scoped_session, validates from sqlalchemy.sql import text import settings from shared import IO, debugException, dictToStrFields, rowToDict, objToDict from util.networking import safeUriToHost, safeFormatSipUri, encodeSipUser from util.security import AES_CTR # DB specific settings UnsignedInt = Integer() if settings.KAM_DB_TYPE == "mysql": try: import MySQLdb as db_driver except ImportError: try: import _mysql as db_driver except ImportError: try: import pymysql as db_driver except ImportError: raise except Exception as ex: if settings.DEBUG: debugException(ex) raise from sqlalchemy.dialects.mysql import INTEGER UnsignedInt = UnsignedInt.with_variant(INTEGER(unsigned=True), 'mysql', 'mariadb') # global constants DB_ENGINE_NAME = 'global_db_engine' SESSION_LOADER_NAME = 'global_session_loader' class Gateways(object): """ Schema for dr_gateways table\n Documentation: `dr_gateways table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-dr-gateways>`_\n Allowed address types: SIP URI, IP address or DNS domain name\n The address field can be a full SIP URI, partial URI, or only host; where host portion is an IP or FQDN """ gwid = Column(UnsignedInt, primary_key=True, autoincrement=True, nullable=False) def __init__(self, name, address, strip, prefix, type=0, gwgroup=None, addr_id=None, msteams_domain='', signalling='proxy', media='proxy'): description = {"name": name} if gwgroup is not None: description["gwgroup"] = str(gwgroup) if addr_id is not None: description['addr_id'] = str(addr_id) self.type = type self.address = address self.strip = strip self.pri_prefix = prefix self.attrs = Gateways.buildAttrs(0, type, msteams_domain, signalling, media) self.description = dictToStrFields(description) @staticmethod def buildAttrs(gwid=0, type=0, msteams_domain='', signalling='proxy', media='proxy'): # gwid in dr_attrs is updated via trigger before insert/update return ','.join([str(gwid), str(type), msteams_domain, signalling, media]) def attrsToDict(self): attrs_dict = {} attrs_list = self.attrs.split(',') try: attrs_dict['gwid'] = int(attrs_list[0]) except IndexError: attrs_dict['gwid'] = 0 try: attrs_dict['type'] = int(attrs_list[1]) except IndexError: attrs_dict['type'] = 0 try: attrs_dict['msteams_domain'] = attrs_list[2] except IndexError: attrs_dict['msteams_domain'] = '' try: attrs_dict['signalling'] = attrs_list[3] except IndexError: attrs_dict['signalling'] = 'proxy' try: attrs_dict['media'] = attrs_list[4] except IndexError: attrs_dict['media'] = 'proxy' return attrs_dict class GatewayGroups(object): """ Schema for dr_gw_lists table\n Documentation: `dr_gw_lists table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-dr-gw-lists>`_ """ id = Column(UnsignedInt, primary_key=True, autoincrement=True, nullable=False) class FILTER(Enum): ENDPOINT = f'type:{settings.FLT_PBX}(,|$)' CARRIER = f'type:{settings.FLT_CARRIER}(,|$)' MSTEAMS = f'type:{settings.FLT_MSTEAMS}(,|$)' ENDPOINT_OR_CARRIER = f'type:({settings.FLT_PBX}|{settings.FLT_CARRIER})(,|$)' def __init__(self, name, gwlist=[], type=settings.FLT_CARRIER, dlg_timeout=None): description = {'name': name, 'type': type} if dlg_timeout is not None: description['dlg_timeout'] = dlg_timeout self.description = dictToStrFields(description) self.gwlist = ",".join(str(gw) for gw in gwlist) class Address(object): """ Schema for address table\n Documentation: `address table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-address>`_\n Allowed address types: exact IP address, subnet IP address or DNS domain name\n The ip_addr field is either an IP address or DNS domain name; mask field is for subnet """ def __init__(self, name, ip_addr, mask, type, gwgroup=None, port=0): tag = {"name": name} if gwgroup is not None: tag["gwgroup"] = str(gwgroup) self.grp = type self.ip_addr = ip_addr self.mask = mask self.port = port self.tag = dictToStrFields(tag) pass class InboundMapping(object): """ Partial Schema for modified version of dr_rules table\n Documentation: `dr_rules table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-dr-rules>`_ """ gwname = Column(String) def __init__(self, groupid, prefix, gwlist, description=''): self.groupid = groupid self.prefix = prefix self.gwlist = gwlist self.description = description self.timerec = '' self.routeid = '' pass class OutboundRoutes(object): """ Schema for dr_rules table\n Documentation: `dr_rules table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-dr-rules>`_ """ def __init__(self, groupid, prefix, timerec, priority, routeid, gwlist, description): self.groupid = groupid self.prefix = prefix self.timerec = timerec self.priority = priority self.routeid = routeid self.gwlist = gwlist self.description = description pass class CustomRouting(object): """ Schema for dr_custom_rules table\n """ def __init__(self, locality, ppm, description): self.locality = locality self.ppm = ppm self.description = description pass class dSIPLCR(object): """ Schema for LCR lookup\n Mapped to db table: dsip_lcr The pattern field contains a complex key that includes FROM XNPA and TO NPA\n There the pattern field looks like this XNPA-NPA\n The dr_groupid field contains a dynamic routing group that maps to a gateway """ def __init__(self, pattern, from_prefix, dr_groupid, cost=0.00): self.pattern = pattern self.from_prefix = from_prefix self.dr_groupid = dr_groupid self.cost = cost pass class dSIPMultiDomainMapping(object): """ Schema for Multi-Tenant PBX\n Mapped to db table: dsip_multidomain_mapping """ class FLAGS(Enum): DOMAIN_DISABLED = 0 DOMAIN_ENABLED = 1 TYPE_UNKNOWN = 0 TYPE_FUSIONPBX = 1 TYPE_FUSIONPBX_CLUSTER = 2 TYPE_FREEPBX = 3 def __init__(self, pbx_id, db_host, db_username, db_password, domain_list=None, attr_list=None, type=0, enabled=1): self.pbx_id = pbx_id self.db_host = db_host self.db_username = db_username self.db_password = db_password self.domain_list = ",".join(str(domain_id) for domain_id in domain_list) if domain_list else '' self.attr_list = ",".join(str(attr_id) for attr_id in attr_list) if attr_list else '' self.type = type self.enabled = enabled pass class dSIPDomainMapping(object): """ Schema for Single-Tenant PBX domain mapping\n Mapped to db table: dsip_domain_mapping """ class FLAGS(Enum): DOMAIN_DISABLED = 0 DOMAIN_ENABLED = 1 TYPE_UNKNOWN = 0 TYPE_ASTERISK = 1 TYPE_SIPFOUNDRY = 2 TYPE_ELASTIX = 3 TYPE_FREESWITCH = 4 TYPE_OPENPBX = 5 TYPE_FREEPBX = 6 TYPE_PBXINAFLASH = 7 TYPE_3CX = 8 def __init__(self, pbx_id, domain_id, attr_list, type=0, enabled=1): self.pbx_id = pbx_id self.domain_id = domain_id self.attr_list = ",".join(str(attr_id) for attr_id in attr_list) self.type = type self.enabled = enabled pass class Subscribers(object): """ Schema for subscriber table\n Documentation: `subscriber table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-subscriber>`_ """ def __init__(self, username, password, domain, gwid, email_address=None): self.username = username self.password = password self.domain = domain self.ha1 = '' self.ha1b = '' self.rpid = gwid self.email_address = email_address pass class dSIPLeases(object): """ Schema for dsip_endpoint_leases table\n maintains a list of active leases based on seconds """ def __init__(self, gwid, sid, ttl): self.gwid = gwid self.sid = sid t = datetime.now() + timedelta(seconds=ttl) self.expiration = t.strftime('%Y-%m-%d %H:%M:%S') pass class dSIPMaintModes(object): """ Schema for dsip_maintmode table\n maintains a list of endpoints and carriers that are in maintenance mode """ def __init__(self, ipaddr, gwid, status=1): self.ipaddr = ipaddr self.gwid = gwid self.status = status self.createdate = datetime.now() pass class dSIPCallSettings(object): """ Schema for dsip_call_settings table\n """ def __init__(self, gwgroupid, limit=None, timeout=None): self.gwgroupid = gwgroupid self.limit = limit self.timeout = timeout pass class dSIPNotification(object): """ Schema for dsip_notification table\n maintains the list of notifications """ class FLAGS(Enum): METHOD_EMAIL = 0 METHOD_SLACK = 1 TYPE_OVERLIMIT = 0 TYPE_GWFAILURE = 1 def __init__(self, gwgroupid, type, method, value): self.gwgroupid = gwgroupid self.type = type self.method = method self.value = value self.createdate = datetime.now() pass class dSIPHardFwd(object): """ Schema for dsip_hardfwd table\n """ def __init__(self, dr_ruleid, did, dr_groupid): self.dr_ruleid = dr_ruleid self.did = did self.dr_groupid = dr_groupid pass class dSIPCDRInfo(object): """ Schema for dsip_cdrinfo table\n """ def __init__(self, gwgroupid, email, send_interval): self.gwgroupid = gwgroupid self.email = email self.send_interval = send_interval self.last_sent = datetime.now() pass class dSIPFailFwd(object): """ Schema for dsip_failfwd table\n """ def __init__(self, dr_ruleid, did, dr_groupid): self.dr_ruleid = dr_ruleid self.did = did self.dr_groupid = dr_groupid pass class dSIPCertificates(object): """ Schema for dsip_certificates table\n """ def __init__(self, domain, type, email, cert, key): self.domain = domain self.type = type self.email = email self.cert = cert self.key = key pass class dSIPDNIDEnrichment(object): """ Schema for dsip_dnid_enrich_lnp table\n """ def __init__(self, dnid, country_code='', routing_number='', rule_name=''): description = {'name': rule_name} self.dnid = dnid self.country_code = country_code self.routing_number = routing_number self.description = dictToStrFields(description) pass class UAC(object): """ Schema for uacreg table\n Documentation: `uacreg table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-uacreg>`_ """ class FLAGS(Enum): REG_ENABLED = 0 REG_DISABLED = 1 REG_IN_PROGRESS = 2 REG_SUCCEEDED = 4 REG_IN_PROGRESS_AUTH = 8 REG_INITIALIZED = 16 def __init__(self, uuid, username="", password="", realm="", auth_username="", auth_proxy="", local_domain="", remote_domain="", flags=0): self.l_uuid = uuid self.l_username = username self.l_domain = local_domain self.r_username = encodeSipUser(username) self.auth_username = auth_username self.r_domain = remote_domain self.realm = realm self.auth_password = password self.auth_ha1 = "" self.auth_proxy = auth_proxy self.expires = 60 self.flags = flags self.reg_delay = 0 self.socket = '' def uacMapperEventHandler(mapper, connection, target): target.r_username = encodeSipUser(target.r_username) event.listen(UAC, 'before_insert', uacMapperEventHandler) event.listen(UAC, 'before_update', uacMapperEventHandler) class Domain(object): """ Schema for domain table\n Documentation: `domain table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-domain>`_ """ def __init__(self, domain, did=None, last_modified=datetime.utcnow()): self.domain = domain if did is None: did = domain self.did = did self.last_modified = last_modified @validates("domain") def __validateDomain(self, key, domain): if domain == '': raise ValueError('empty string is an invalid domain') return domain class DomainAttrs(object): """ Schema for domain_attrs table\n Documentation: `domain_attrs table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-domain-attrs>`_ """ class FLAGS(Enum): TYPE_INTEGER = 0 TYPE_STRING = 2 def __init__(self, did, name="pbx_ip", type=2, value=None, last_modified=datetime.utcnow()): self.did = did self.name = name self.type = type self.last_modified = last_modified temp_value = value if value is not None else safeUriToHost(did) self.value = temp_value if temp_value is not None else did pass class Dispatcher(object): """ Schema for dispatcher table\n Documentation: `dispatcher table <https://kamailio.org/docs/db-tables/kamailio-db-5.5.x.html#gen-db-dispatcher>`_ """ DST_ALG = { 'ROUND_ROBIN': 4, 'PRIORITY_BASED': 8, 'WEIGHT_BASED': 9, 'LOAD_DISTRIBUTION': 10, 'RELATIVE_WEIGHT': 11, 'PARALLEL_FORKING': 12 } FLAGS = { 'INACTIVE_DST': 1, 'TRYING_DST': 2, 'DISABLED_DST': 4, 'KEEP_ALIVE': 8, 'SKIP_DNS': 16 } # TODO: setting attrs directly will be removed in the future and each possible attribute identified def __init__(self, setid, destination, flags=None, priority=None, attrs=None, rweight=0, signalling='proxy', media='proxy', name=None, gwid=None): self.setid = setid self.destination = safeFormatSipUri(destination) self.flags = flags self.priority = priority if attrs is not None: self.attrs = attrs else: self.attrs = Dispatcher.buildAttrs(rweight, signalling, media) self.description = Dispatcher.buildDescription(name, gwid) @staticmethod def buildAttrs(rweight=0, signalling='proxy', media='proxy'): attrs = {'signalling': signalling, 'media': media, 'rweight': str(rweight)} return dictToStrFields(attrs, delims=(';', '=')) @staticmethod def buildDescription(name=None, gwid=None): description = {} if name is not None: description['name'] = name if gwid is not None: description['gwid'] = str(gwid) return dictToStrFields(description, delims=(';', '=')) def attrsToDict(self): attrs = {} for attr in self.attrs.split(';'): attr = attr.split('=', maxsplit=1) if len(attr) != 2 or attr[1] == '': continue if attr[1].isnumeric(): attrs[attr[0]] = int(attr[1]) else: attrs[attr[0]] = attr[1] return attrs # TODO: create class for dsip_settings table class dSIPUser(object): """ Schema for the dSIPROuter User table """ def __init__(self, firstname, lastname, username, password, roles, domains, token, token_expiration): self.firstname = firstname self.lastname = lastname self.username = username self.password = password self.roles = roles self.domains = domains self.token = token self.token_expiration = token_expiration pass # TODO: this is temporary and will be refactored class DsipGwgroup2LB(object): pass class DsipSettings(OrderedDict): """ Identifies the contained data as already formatted for the table """ pass # TODO: switch to sqlalchemy.engine.URL API def createDBURI(db_driver=None, db_type=None, db_user=None, db_pass=None, db_host=None, db_port=None, db_name=None, db_charset='utf8mb4'): """ Get any and all DB Connection URI's Facilitates HA DB Server connections through multiple host's defined in settings :return: list of DB URI connection strings """ uri_list = [] # URI is built from the following by order of precedence: # 1: function arguments # 2: environment variables (debug mode only) # 3: settings file if db_driver is None: db_driver = os.getenv('KAM_DB_DRIVER', settings.KAM_DB_DRIVER) if settings.DEBUG else settings.KAM_DB_DRIVER if db_type is None: db_type = os.getenv('KAM_DB_TYPE', settings.KAM_DB_TYPE) if settings.DEBUG else settings.KAM_DB_TYPE if db_user is None: db_user = os.getenv('KAM_DB_USER', settings.KAM_DB_USER) if settings.DEBUG else settings.KAM_DB_USER if db_pass is None: db_pass = os.getenv('KAM_DB_PASS', settings.KAM_DB_PASS) if settings.DEBUG else settings.KAM_DB_PASS if db_host is None: db_host = os.getenv('KAM_DB_HOST', settings.KAM_DB_HOST) if settings.DEBUG else settings.KAM_DB_HOST if db_port is None: db_port = os.getenv('KAM_DB_PORT', settings.KAM_DB_PORT) if settings.DEBUG else settings.KAM_DB_PORT if db_name is None: db_name = os.getenv('KAM_DB_NAME', settings.KAM_DB_NAME) if settings.DEBUG else settings.KAM_DB_NAME # need to decrypt password if isinstance(db_pass, bytes): db_pass = AES_CTR.decrypt(db_pass) # formatting for driver if len(db_driver) > 0: db_driver = '+{}'.format(db_driver) # string template db_uri_str = db_type + db_driver + "://" + db_user + ":" + db_pass + "@" + "{host}" + ":" + db_port + "/" + db_name + "?charset=" + db_charset # for cluster of DB add all hosts if isinstance(db_host, list): for host in db_host: uri_list.append(db_uri_str.format(host=host)) else: uri_list.append(db_uri_str.format(host=db_host)) if settings.DEBUG: IO.printdbg('createDBURI() returned: [{}]'.format(','.join('"{0}"'.format(uri) for uri in uri_list))) return uri_list def createValidEngine(uri_list): """ Create DB engine if connection is valid Attempts each uri in the list until a valid connection is made This method uses a singleton pattern and returns db_engine if created :param uri_list: list of connection uri's :return: DB engine object :raise: SQLAlchemyError if all connections fail """ # globals from the top-level module caller_globals = dict(inspect.getmembers(inspect.stack()[-1][0]))["f_globals"] if DB_ENGINE_NAME in caller_globals: return caller_globals[DB_ENGINE_NAME] errors = [] for conn_uri in uri_list: try: db_engine = create_engine(conn_uri, echo=settings.DEBUG, echo_pool=settings.DEBUG, pool_recycle=300, pool_size=10, isolation_level="READ UNCOMMITTED", connect_args={"connect_timeout": 5}) # test connection _ = db_engine.connect() # conn good return it return db_engine except Exception as ex: errors.append(ex) # we failed to return good connection raise exceptions if settings.DEBUG: for ex in errors: debugException(ex) try: raise sql_exceptions.SQLAlchemyError(errors) except: raise Exception(errors) def startSession(): """ This method uses a singleton pattern to grab the global session loader and start a session """ # globals from the top-level module caller_globals = dict(inspect.getmembers(inspect.stack()[-1][0]))["f_globals"] if SESSION_LOADER_NAME in caller_globals: return caller_globals[SESSION_LOADER_NAME]() db_engine, session_loader = createSessionObjects() return session_loader() def createSessionObjects(): """ Create the DB engine and session factory :return: Session factory and DB Engine :rtype: (:class:`sqlalchemy.orm.Session`,:class:`sqlalchemy.engine.Engine`) """ db_engine = createValidEngine(createDBURI()) mapper = registry(metadata=MetaData(schema=db_engine.url.database)) dr_gateways = Table('dr_gateways', mapper.metadata, autoload_replace=True, autoload_with=db_engine) address = Table('address', mapper.metadata, autoload_replace=True, autoload_with=db_engine) outboundroutes = Table('dr_rules', mapper.metadata, autoload_replace=True, autoload_with=db_engine) inboundmapping = Table('dr_rules', mapper.metadata, autoload_replace=True, autoload_with=db_engine) subscriber = Table('subscriber', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_domain_mapping = Table('dsip_domain_mapping', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_multidomain_mapping = Table('dsip_multidomain_mapping', mapper.metadata, autoload_replace=True, autoload_with=db_engine) # fusionpbx_mappings = Table('dsip_fusionpbx_mappings', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_lcr = Table('dsip_lcr', mapper.metadata, autoload_replace=True, autoload_with=db_engine) uacreg = Table('uacreg', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dr_gw_lists = Table('dr_gw_lists', mapper.metadata, autoload_replace=True, autoload_with=db_engine) # dr_groups = Table('dr_groups', mapper.metadata, autoload_replace=True, autoload_with=db_engine) domain = Table('domain', mapper.metadata, autoload_replace=True, autoload_with=db_engine) domain_attrs = Table('domain_attrs', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dispatcher = Table('dispatcher', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_endpoint_lease = Table('dsip_endpoint_lease', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_maintmode = Table('dsip_maintmode', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_call_settings = Table('dsip_call_settings', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_notification = Table('dsip_notification', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_hardfwd = Table('dsip_hardfwd', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_failfwd = Table('dsip_failfwd', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_cdrinfo = Table('dsip_cdrinfo', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_certificates = Table('dsip_certificates', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_dnid_enrichment = Table('dsip_dnid_enrich_lnp', mapper.metadata, autoload_replace=True, autoload_with=db_engine) dsip_user = Table('dsip_user', mapper.metadata, autoload_replace=True, autoload_with=db_engine) # TODO: this is temporary and will be refactored dsip_gwgroup2lb = Table('dsip_gwgroup2lb', mapper.metadata, autoload_replace=True, autoload_with=db_engine) # dr_gw_lists_alias = select([ # dr_gw_lists.c.id.label("drlist_id"), # dr_gw_lists.c.gwlist, # dr_gw_lists.c.description.label("drlist_description"), # ]).correlate(None).alias() # gw_join = join(dr_gw_lists_alias, dr_groups, # dr_gw_lists_alias.c.drlist_id == dr_groups.c.id, # dr_gw_lists_alias.c.drlist_description == dr_groups.c.description) mapper.map_imperatively(Gateways, dr_gateways) mapper.map_imperatively(Address, address) mapper.map_imperatively(InboundMapping, inboundmapping) mapper.map_imperatively(OutboundRoutes, outboundroutes) mapper.map_imperatively(dSIPDomainMapping, dsip_domain_mapping) mapper.map_imperatively(dSIPMultiDomainMapping, dsip_multidomain_mapping) mapper.map_imperatively(Subscribers, subscriber) # mapper.map_imperatively(CustomRouting, customrouting) mapper.map_imperatively(dSIPLCR, dsip_lcr) mapper.map_imperatively(UAC, uacreg) mapper.map_imperatively(GatewayGroups, dr_gw_lists) mapper.map_imperatively(Domain, domain) mapper.map_imperatively(DomainAttrs, domain_attrs) mapper.map_imperatively(Dispatcher, dispatcher) mapper.map_imperatively(dSIPLeases, dsip_endpoint_lease) mapper.map_imperatively(dSIPMaintModes, dsip_maintmode) mapper.map_imperatively(dSIPCallSettings, dsip_call_settings) mapper.map_imperatively(dSIPNotification, dsip_notification) mapper.map_imperatively(dSIPHardFwd, dsip_hardfwd) mapper.map_imperatively(dSIPFailFwd, dsip_failfwd) mapper.map_imperatively(dSIPCDRInfo, dsip_cdrinfo) mapper.map_imperatively(dSIPCertificates, dsip_certificates) mapper.map_imperatively(dSIPDNIDEnrichment, dsip_dnid_enrichment) mapper.map_imperatively(dSIPUser, dsip_user) # TODO: this is temporary and will be refactored mapper.map_imperatively(DsipGwgroup2LB, dsip_gwgroup2lb) # mapper.map_imperatively(GatewayGroups, gw_join, properties={ # 'id': [dr_groups.c.id, dr_gw_lists_alias.c.drlist_id], # 'description': [dr_groups.c.description, dr_gw_lists_alias.c.drlist_description], # }) session_loader = scoped_session(sessionmaker(bind=db_engine)) # load them into the top level module global namespace caller_globals = dict(inspect.getmembers(inspect.stack()[-1][0]))["f_globals"] caller_globals[DB_ENGINE_NAME] = db_engine caller_globals[SESSION_LOADER_NAME] = session_loader # return references for the calling function return caller_globals[DB_ENGINE_NAME], caller_globals[SESSION_LOADER_NAME] # TODO: change to the global define pattern instead of instantiating dummy objects class DummySession(): """ Sole purpose is to avoid exceptions when startSession fails This allows us to handle exceptions later in the try blocks We also avoid exceptions in the except blocks by using dummy sesh """ @staticmethod def __contains__(self, *args, **kwargs): pass def __iter__(self, *args, **kwargs): pass def add(self, *args, **kwargs): pass def add_all(self, *args, **kwargs): pass def begin(self, *args, **kwargs): pass def begin_nested(self, *args, **kwargs): pass def close(self, *args, **kwargs): pass def commit(self, *args, **kwargs): pass def connection(self, *args, **kwargs): pass def delete(self, *args, **kwargs): pass def execute(self, *args, **kwargs): pass def expire(self, *args, **kwargs): pass def expire_all(self, *args, **kwargs): pass def expunge(self, *args, **kwargs): pass def expunge_all(self, *args, **kwargs): pass def flush(self, *args, **kwargs): pass def get_bind(self, *args, **kwargs): pass def is_modified(self, *args, **kwargs): pass def bulk_save_objects(self, *args, **kwargs): pass def bulk_insert_mappings(self, *args, **kwargs): pass def bulk_update_mappings(self, *args, **kwargs): pass def merge(self, *args, **kwargs): pass def query(self, *args, **kwargs): pass def refresh(self, *args, **kwargs): pass def rollback(self, *args, **kwargs): pass def scalar(self, *args, **kwargs): pass def remove(self, *args, **kwargs): pass def configure(self, *args, **kwargs): pass def query_property(self, *args, **kwargs): pass def settingsToTableFormat(settings, updates=None): data = objToDict(settings) if updates is not None: data.update(updates) # translate db specific fields if isinstance(data['KAM_DB_HOST'], (list, tuple)): data['KAM_DB_HOST'] = ','.join(data['KAM_DB_HOST']) data['DSIP_LICENSE_STORE'] = base64.b64encode(bson.dumps(data['DSIP_LICENSE_STORE'])) # order matters here, as this is used to update table settings as well return DsipSettings([ ('DSIP_ID', data['DSIP_ID']), ('DSIP_CLUSTER_ID', data['DSIP_CLUSTER_ID']), ('DSIP_CLUSTER_SYNC', data['DSIP_CLUSTER_SYNC']), ('DSIP_PROTO', data['DSIP_PROTO']), ('DSIP_PORT', data['DSIP_PORT']), ('DSIP_USERNAME', data['DSIP_USERNAME']), ('DSIP_PASSWORD', data['DSIP_PASSWORD']), ('DSIP_IPC_PASS', data['DSIP_IPC_PASS']), ('DSIP_API_PROTO', data['DSIP_API_PROTO']), ('DSIP_API_PORT', data['DSIP_API_PORT']), ('DSIP_PRIV_KEY', data['DSIP_PRIV_KEY']), ('DSIP_PID_FILE', data['DSIP_PID_FILE']), ('DSIP_UNIX_SOCK', data['DSIP_UNIX_SOCK']), ('DSIP_IPC_SOCK', data['DSIP_IPC_SOCK']), ('DSIP_API_TOKEN', data['DSIP_API_TOKEN']), ('DSIP_LOG_LEVEL', data['DSIP_LOG_LEVEL']), ('DSIP_LOG_FACILITY', data['DSIP_LOG_FACILITY']), ('DSIP_SSL_KEY', data['DSIP_SSL_KEY']), ('DSIP_SSL_CERT', data['DSIP_SSL_CERT']), ('DSIP_SSL_CA', data['DSIP_SSL_CA']), ('DSIP_SSL_EMAIL', data['DSIP_SSL_EMAIL']), ('DSIP_CERTS_DIR', data['DSIP_CERTS_DIR']), ('VERSION', data['VERSION']), ('DEBUG', data['DEBUG']), ('ROLE', data['ROLE']), ('GUI_INACTIVE_TIMEOUT', data['GUI_INACTIVE_TIMEOUT']), ('KAM_DB_HOST', data['KAM_DB_HOST']), ('KAM_DB_DRIVER', data['KAM_DB_DRIVER']), ('KAM_DB_TYPE', data['KAM_DB_TYPE']), ('KAM_DB_PORT', data['KAM_DB_PORT']), ('KAM_DB_NAME', data['KAM_DB_NAME']), ('KAM_DB_USER', data['KAM_DB_USER']), ('KAM_DB_PASS', data['KAM_DB_PASS']), ('KAM_KAMCMD_PATH', data['KAM_KAMCMD_PATH']), ('KAM_CFG_PATH', data['KAM_CFG_PATH']), ('KAM_TLSCFG_PATH', data['KAM_TLSCFG_PATH']), ('RTP_CFG_PATH', data['RTP_CFG_PATH']), ('FLT_CARRIER', data['FLT_CARRIER']), ('FLT_PBX', data['FLT_PBX']), ('FLT_MSTEAMS', data['FLT_MSTEAMS']), ('FLT_OUTBOUND', data['FLT_OUTBOUND']), ('FLT_INBOUND', data['FLT_INBOUND']), ('FLT_LCR_MIN', data['FLT_LCR_MIN']), ('FLT_FWD_MIN', data['FLT_FWD_MIN']), ('DEFAULT_AUTH_DOMAIN', data['DEFAULT_AUTH_DOMAIN']), ('TELEBLOCK_GW_ENABLED', data['TELEBLOCK_GW_ENABLED']), ('TELEBLOCK_GW_IP', data['TELEBLOCK_GW_IP']), ('TELEBLOCK_GW_PORT', data['TELEBLOCK_GW_PORT']), ('TELEBLOCK_MEDIA_IP', data['TELEBLOCK_MEDIA_IP']), ('TELEBLOCK_MEDIA_PORT', data['TELEBLOCK_MEDIA_PORT']), ('FLOWROUTE_ACCESS_KEY', data['FLOWROUTE_ACCESS_KEY']), ('FLOWROUTE_SECRET_KEY', data['FLOWROUTE_SECRET_KEY']), ('FLOWROUTE_API_ROOT_URL', data['FLOWROUTE_API_ROOT_URL']), ('HOMER_ID', data['HOMER_ID']), ('HOMER_HEP_HOST', data['HOMER_HEP_HOST']), ('HOMER_HEP_PORT', data['HOMER_HEP_PORT']), ('NETWORK_MODE', data['NETWORK_MODE']), ('IPV6_ENABLED', data['IPV6_ENABLED']), ('INTERNAL_IP_ADDR', data['INTERNAL_IP_ADDR']), ('INTERNAL_IP_NET', data['INTERNAL_IP_NET']), ('INTERNAL_IP6_ADDR', data['INTERNAL_IP6_ADDR']), ('INTERNAL_IP6_NET', data['INTERNAL_IP6_NET']), ('INTERNAL_FQDN', data['INTERNAL_FQDN']), ('EXTERNAL_IP_ADDR', data['EXTERNAL_IP_ADDR']), ('EXTERNAL_IP6_ADDR', data['EXTERNAL_IP6_ADDR']), ('EXTERNAL_FQDN', data['EXTERNAL_FQDN']), ('PUBLIC_IFACE', data['PUBLIC_IFACE']), ('PRIVATE_IFACE', data['PRIVATE_IFACE']), ('UPLOAD_FOLDER', data['UPLOAD_FOLDER']), ('MAIL_SERVER', data['MAIL_SERVER']), ('MAIL_PORT', data['MAIL_PORT']), ('MAIL_USE_TLS', data['MAIL_USE_TLS']), ('MAIL_USERNAME', data['MAIL_USERNAME']), ('MAIL_PASSWORD', data['MAIL_PASSWORD']), ('MAIL_ASCII_ATTACHMENTS', data['MAIL_ASCII_ATTACHMENTS']), ('MAIL_DEFAULT_SENDER', data['MAIL_DEFAULT_SENDER']), ('MAIL_DEFAULT_SUBJECT', data['MAIL_DEFAULT_SUBJECT']), ('DSIP_LICENSE_STORE', data['DSIP_LICENSE_STORE']), ('RTPENGINE_URI', data['RTPENGINE_URI']), ]) def settingsTableToDict(table_values, updates=None): if updates is not None: table_values.update(updates) if ',' in table_values['KAM_DB_HOST']: table_values['KAM_DB_HOST'] = table_values['KAM_DB_HOST'].split(',') table_values['DSIP_LICENSE_STORE'] = bson.loads(base64.b64decode(table_values['DSIP_LICENSE_STORE'])) return table_values def updateDsipSettingsTable(fields): """ Update the dsip_settings table using our stored procedure :param fields: columns/values to update :type fields: dict :return: None :rtype: None :raises: sql_exceptions.SQLAlchemyError """ db = DummySession() try: if isinstance(fields, DsipSettings): db_fields = fields else: db_fields = settingsToTableFormat(settings, updates=fields) field_mapping = ', '.join([':{}'.format(x, x) for x in db_fields.keys()]) db = startSession() db.execute( text('CALL update_dsip_settings({})'.format(field_mapping)), params=db_fields ) db.commit() except sql_exceptions.SQLAlchemyError: db.rollback() db.flush() raise finally: db.close() def getDsipSettingsTableAsDict(dsip_id, updates=None): db = DummySession() try: db = startSession() data = rowToDict( db.execute( text('SELECT * FROM dsip_settings WHERE DSIP_ID=:dsip_id'), params={'dsip_id': dsip_id} ).first() ) # translate db specific fields return settingsTableToDict(data, updates=updates) except sql_exceptions.SQLAlchemyError: raise finally: db.close() ================================================ FILE: gui/dsiprouter.py ================================================ #!/opt/dsiprouter/venv/bin/python # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') # all of our standard and project file imports import os, json, urllib.parse, glob, datetime, csv, logging, signal, bjoern, secrets, subprocess, time import importlib.util from ansi2html import Ansi2HTMLConverter from copy import copy from importlib import reload from flask import Flask, render_template, request, redirect, flash, session, url_for, send_from_directory, Blueprint, Response from flask_wtf.csrf import CSRFProtect from itsdangerous import URLSafeTimedSerializer from pygtail import Pygtail from sqlalchemy import exc as sql_exceptions, Integer from sqlalchemy.orm import load_only from sqlalchemy.sql import text, func, select from sqlalchemy.orm.session import close_all_sessions from werkzeug import exceptions as http_exceptions from werkzeug.utils import secure_filename from werkzeug.middleware.proxy_fix import ProxyFix from sysloginit import initSyslogLogger from shared import updateConfig, getCustomRoutes, debugException, debugEndpoint, \ stripDictVals, strFieldsToDict, dictToStrFields, allowed_file, showError, IO, objToDict, StatusCodes from util.networking import safeUriToHost, safeFormatSipUri, safeStripPort from database import DummySession, createSessionObjects, startSession, settingsTableToDict, \ DB_ENGINE_NAME, SESSION_LOADER_NAME, settingsToTableFormat, getDsipSettingsTableAsDict, \ Gateways, Address, InboundMapping, OutboundRoutes, Subscribers, dSIPLCR, UAC, GatewayGroups, \ Domain, DomainAttrs, dSIPMultiDomainMapping, dSIPHardFwd, dSIPFailFwd, updateDsipSettingsTable, \ Dispatcher, DsipGwgroup2LB from modules import flowroute from modules.domain.domain_routes import domains from modules.api.api_routes import api from modules.api.mediaserver.routes import mediaserver from modules.api.carriergroups.routes import carriergroups, addCarrierGroups from modules.api.carriergroups.functions import addUpdateCarriers, displayCarrierGroups, displayCarriers from modules.api.kamailio.functions import reloadKamailio from modules.api.licensemanager.classes import WoocommerceError from modules.api.licensemanager.functions import licenseDictToStateDict, getLicenseStatusFromStateDict, \ getLicenseStatus from modules.api.licensemanager.routes import license_manager from modules.api.auth.routes import user from util.security import Credentials, urandomChars, AES_CTR from util.ipc import SETTINGS_SHMEM_NAME, STATE_SHMEM_NAME, createSharedMemoryDict, getSharedMemoryDict from util.parse_json import CreateEncoder from util.persistence import updatePersistentState, setPersistentState from util.pyasync import process from modules.upgrade import UpdateUtils import settings # TODO: unit testing per component # TODO: many of these routes could use some updating... # possibly look into this as well when reworking the architecture for API # TODO: do license checks on login and store in session variables # this alleviates the extra requests and can be updated easily # marked for implementation in v0.80 # TODO: move settings to read/write from shared memory instead of using module replacement # marked for implementation in v0.80 # TODO: go through templates/routes and replace redundant references to settings/globals # they are both injected on every request via the context processor # TODO: remove references to "ipc.sock" / DSIP_IPC_SOCK # we moved to shared memory instead of domain sockets for sharing state across processes # module constants # TODO: create /var/log/dsiprouter/ and move there UPGRADE_LOG = f'{settings.BACKUP_FOLDER}/upgrade.log' UPGRADE_OFFSET = f'{UPGRADE_LOG}.offset' # module variables app = Flask(__name__, static_folder="./static", static_url_path="/static") app.register_blueprint(domains) app.register_blueprint(api) app.register_blueprint(mediaserver) app.register_blueprint(carriergroups) app.register_blueprint(user) app.register_blueprint(license_manager) app.register_blueprint(Blueprint('docs', 'docs', static_url_path='/docs', static_folder=settings.DSIP_DOCS_DIR)) csrf = CSRFProtect(app) csrf.exempt(api) csrf.exempt(mediaserver) csrf.exempt(carriergroups) csrf.exempt(user) csrf.exempt(license_manager) numbers_api = flowroute.Numbers() ansi_converter = Ansi2HTMLConverter(inline=True) auth_modules = [] @app.before_first_request def before_first_request(): # replace werkzeug and sqlalchemy loggers log_handler = initSyslogLogger() replaceAppLoggers(log_handler) @app.before_request def before_request(): # set the session lifetime to gui inactive timeout # if we are in debug mode force it to last for entire day session.permanent = True if settings.DEBUG: app.permanent_session_lifetime = datetime.timedelta(days=1) else: app.permanent_session_lifetime = datetime.timedelta(minutes=settings.GUI_INACTIVE_TIMEOUT) session.modified = True # DEPRECATED: overridden by csrf.exempt(api), need to revist this, marked for review in v0.80 @api.before_request def api_before_request(): # for ua to api w/ api token disable csrf # we only want csrf checks on service to service requests if 'Authorization' not in request.headers: csrf.protect() @app.route('/') def index(): try: if (settings.DEBUG): debugEndpoint() if not session.get('logged_in'): return render_template('index.html', version=settings.VERSION) else: action = request.args.get('action') return render_template('dashboard.html', show_add_onload=action, version=settings.VERSION) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/error') def displayError(): type = request.args.get("type", "") code = int(request.args.get("code", StatusCodes.HTTP_INTERNAL_SERVER_ERROR)) msg = request.args.get("msg", None) return showError(type, code, msg) @app.route('/backupandrestore') def backupandrestore(): try: if (settings.DEBUG): debugEndpoint() if not session.get('logged_in'): return render_template('index.html', version=settings.VERSION) else: action = request.args.get('action') return render_template('backupandrestore.html', show_add_onload=action, version=settings.VERSION) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex, log_ex=False, print_ex=True, showstack=False) error = "server" return showError(type=error) @app.route('/certificates') def certificates(): try: if (settings.DEBUG): debugEndpoint() if not session.get('logged_in'): return render_template('index.html', version=settings.VERSION) else: action = request.args.get('action') return render_template('certificates.html', show_add_onload=action, version=settings.VERSION) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex, log_ex=False, print_ex=True, showstack=False) error = "server" return showError(type=error) @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') @app.route('/login', methods=['GET', 'POST']) def login(): try: if (settings.DEBUG): debugEndpoint() # redirect GET requests to index if request.method == 'GET': return redirect(url_for('index')) form = stripDictVals(request.form.to_dict()) if 'username' not in form or 'password' not in form: raise http_exceptions.BadRequest('Username and Password are required') elif not isinstance(form['username'], str) or not isinstance(form['password'], str): raise http_exceptions.BadRequest('Username or Password is malformed') # Get Environment Variables if in debug mode # This is the only case we allow plain text password comparison if settings.DEBUG: settings.DSIP_USERNAME = os.getenv('DSIP_USERNAME', settings.DSIP_USERNAME) settings.DSIP_PASSWORD = os.getenv('DSIP_PASSWORD', settings.DSIP_PASSWORD) # if username valid, hash password and compare with stored password if form['username'] == settings.DSIP_USERNAME: if isinstance(settings.DSIP_PASSWORD, bytes): pwcheck = Credentials.hashCreds(form['password'], settings.DSIP_PASSWORD[-(Credentials.SALT_LEN * 2):]) else: pwcheck = form['password'] if secrets.compare_digest(pwcheck, settings.DSIP_PASSWORD): session['logged_in'] = True session['username'] = form['username'] return redirect(url_for('index')) # Check for user in other auth modules for auth_mod in auth_modules: if auth_mod.authenticate(form['username'], form['password']): session['logged_in'] = True session['username'] = form['username'] return redirect(url_for('index')) # if we got here auth failed flash('Wrong Username or Password') return redirect(url_for('index')) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/logout') def logout(): try: if (settings.DEBUG): debugEndpoint() session.pop('logged_in', None) session.pop('username', None) return redirect(url_for('index')) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) app.add_url_rule('/carriergroups', view_func=displayCarrierGroups, methods=['GET']) app.add_url_rule('/carriergroups/<int:gwgroup>', view_func=displayCarrierGroups, methods=['GET']) @app.route('/carriergroups', methods=['POST']) def addUpdateCarrierGroups(): """ Add or Update a group of carriers """ # TODO: toss this in a decorator func with error handling and use for gui auth checks if not session.get('logged_in'): return redirect(url_for('index')) return addCarrierGroups() @app.route('/carriergroupdelete', methods=['POST']) def deleteCarrierGroups(): """ Delete a group of carriers """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) gwgroup = form['gwgroup'] gwlist = form['gwlist'] if 'gwlist' in form else '' Gwgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroup) gwgroup_row = Gwgroup.first() if gwgroup_row is None: return displayCarrierGroups() db.query(Address).filter( Address.tag.regexp_match(f'gwgroup:{gwgroup}(,|$)') ).delete(synchronize_session=False) db.query(UAC).filter( UAC.l_uuid == gwgroup_row.id ).delete(synchronize_session=False) db.query(Dispatcher).filter( Dispatcher.setid == gwgroup ).delete(synchronize_session=False) # validate this group has gateways assigned to it if len(gwlist) > 0: GWs = db.query(Gateways).filter(Gateways.gwid.in_(list(map(int, gwlist.split(","))))) GWs.delete(synchronize_session=False) Gwgroup.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayCarrierGroups() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() app.add_url_rule('/carriers', view_func=displayCarriers, methods=['GET']) app.add_url_rule('/carriers/<int:gwid>', view_func=displayCarriers, methods=['GET']) app.add_url_rule('/carriers/group/<int:gwgroup>', view_func=displayCarriers, methods=['GET']) app.add_url_rule('/carriers', view_func=addUpdateCarriers, methods=['POST']) @app.route('/carrierdelete', methods=['POST']) def deleteCarriers(): """ Delete a carrier """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) gwid = form['gwid'] hostname = form['ip_addr'] if len(form['ip_addr']) > 0 else '' gwgroup = form['gwgroup'] if 'gwgroup' in form else '' related_rules = json.loads(form['related_rules']) if 'related_rules' in form else '' sip_addr = safeUriToHost(hostname, default_port=5060) Gateway = db.query(Gateways).filter(Gateways.gwid == gwid) Gateway_Row = Gateway.first() gw_fields = strFieldsToDict(Gateway_Row.description) # find associated gwgroup if not provided if len(gwgroup) <= 0: gwgroup = gw_fields['gwgroup'] if 'gwgroup' in gw_fields else '' # remove associated address if exists if 'addr_id' in gw_fields: Addr = db.query(Address).filter(Address.id == gw_fields['addr_id']) Addr.delete(synchronize_session=False) # grab any related carrier groups Gatewaygroups = db.execute( text('SELECT * FROM dr_gw_lists WHERE FIND_IN_SET(:gwid, dr_gw_lists.gwlist)'), {'gwid': gwid} ) # remove gateway Gateway.delete(synchronize_session=False) # remove from carrier from dispatcher db.query(Dispatcher).filter( Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).delete(synchronize_session=False) # remove carrier from gwlist in carrier group for Gatewaygroup in Gatewaygroups: gwlist = list(filter(None, Gatewaygroup[1].split(","))) gwlist.remove(gwid) db.query(GatewayGroups).filter(GatewayGroups.id == str(Gatewaygroup[0])).update( {'gwlist': ','.join(gwlist)}, synchronize_session=False) # remove carrier from gwlist in carrier rules if len(related_rules) > 0: rule_ids = related_rules.keys() rules = db.query(OutboundRoutes).filter(OutboundRoutes.ruleid.in_(rule_ids)).all() for rule in rules: gwlist = list(filter(None, rule.gwlist.split(","))) gwlist.remove(gwid) rule.gwlist = ','.join(gwlist) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayCarriers(gwgroup=gwgroup) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/endpointgroups') def displayEndpointGroups(): """ Display Endpoint Groups / Endpoints in the view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() # NOTE: not including IPv6 address here as we only need to connect over IPv4 to fusion DB return render_template('endpointgroups.html', dsiprouter_ip=settings.EXTERNAL_IP_ADDR, DEFAULT_auth_domain=settings.DEFAULT_AUTH_DOMAIN) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/numberenrichment') def displayNumberEnrichment(): """ Display Endpoint Groups / Endpoints in the view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() return render_template('dnid_enrichment.html') except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/cdrs') def displayCDRS(): """ Display Endpoint Groups / Endpoints in the view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_CORE') if license_status == 0: return render_template('license_required.html', msg='DSIP_CORE license is required to use this feature') if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') return render_template('cdrs.html') except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/licensing') def displayLicenseManager(): """ Display License Manager page """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() return render_template('license_manager.html') except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) # TODO: why are we doing this weird api workaround, instead of making the gui and api separate?? @app.route('/endpointgroups', methods=['POST']) def addUpdateEndpointGroups(): """ Add or Update a PBX / Endpoint """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() # form = stripDictVals(request.form.to_dict()) # # TODO: change ip_addr field to hostname or domainname, we already support this # gwid = form['gwid'] # gwgroupid = request.form.get("gwgroup", None) name = request.form.get("name", None) # ip_addr = request.form.get("ip_addr", None) # strip = form['strip'] if len(form['strip']) > 0 else "0" # prefix = form['prefix'] if len(form['prefix']) > 0 else "" # authtype = form['authtype'] # fusionpbx_db_server = form['fusionpbx_db_server'] # fusionpbx_db_username = form['fusionpbx_db_username'] # fusionpbx_db_password = form['fusionpbx_db_password'] # auth_username = form['auth_username'] # auth_password = form['auth_password'] # auth_domain = form['auth_domain'] if len(form['auth_domain']) > 0 else settings.DEFAULT_AUTH_DOMAIN # calllimit = form['calllimit'] if len(form['calllimit']) > 0 else "" # # multi_tenant_domain_enabled = False # multi_tenant_domain_type = dSIPMultiDomainMapping.FLAGS.TYPE_UNKNOWN.value # single_tenant_domain_enabled = False # single_tenant_domain_type = dSIPDomainMapping.FLAGS.TYPE_UNKNOWN.value if name is not None: Gwgroup = GatewayGroups(name, type=settings.FLT_PBX) db.add(Gwgroup) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayEndpointGroups() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() # TODO: is this route still in use? @app.route('/pbxdelete', methods=['POST']) def deletePBX(): """ Delete a PBX / Endpoint """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) gwid = form['gwid'] name = form['name'] gateway = db.query(Gateways).filter(Gateways.gwid == gwid) gateway.delete(synchronize_session=False) address = db.query(Address).filter( Address.tag.regexp_match(f'name:{name}(,|$)') ) address.delete(synchronize_session=False) subscriber = db.query(Subscribers).filter(Subscribers.rpid == gwid) subscriber.delete(synchronize_session=False) address.delete(synchronize_session=False) domainmultimapping = db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == gwid) res = domainmultimapping.options(load_only("domain_list", "attr_list")).first() if res is not None: # make sure mapping has domains if len(res.domain_list) > 0: domains = list(map(int, filter(None, res.domain_list.split(",")))) domain = db.query(Domain).filter(Domain.id.in_(domains)) domain.delete(synchronize_session=False) # make sure mapping has attributes if len(res.attr_list) > 0: attrs = list(map(int, filter(None, res.attr_list.split(",")))) domainattrs = db.query(DomainAttrs).filter(DomainAttrs.id.in_(attrs)) domainattrs.delete(synchronize_session=False) # delete the entry in the multi domain mapping table domainmultimapping.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayEndpointGroups() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/inboundmapping') def displayInboundMapping(): """ Displaying of dr_rules table (inbound routes only)\n Partially Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() res = db.execute( text(""" SELECT * FROM ( SELECT r.ruleid, r.groupid, r.prefix, r.gwlist, r.description AS rule_description, g.id AS gwgroupid, g.description AS gwgroup_description FROM dr_rules AS r LEFT JOIN dr_gw_lists AS g ON g.id = REPLACE(r.gwlist, '#', '') WHERE r.groupid = :flt_inbound ) AS t1 LEFT JOIN ( SELECT hf.dr_ruleid AS hf_ruleid, hf.dr_groupid AS hf_groupid, hf.did AS hf_fwddid, g.id AS hf_gwgroupid FROM dsip_hardfwd AS hf LEFT JOIN dr_rules AS r ON hf.dr_groupid = r.groupid LEFT JOIN dr_gw_lists AS g ON g.id = REPLACE(r.gwlist, '#', '') WHERE hf.dr_groupid <> :flt_outbound UNION ALL SELECT hf.dr_ruleid AS hf_ruleid, hf.dr_groupid AS hf_groupid, hf.did AS hf_fwddid, NULL AS hf_gwgroupid FROM dsip_hardfwd AS hf LEFT JOIN dr_rules AS r ON hf.dr_ruleid = r.ruleid WHERE hf.dr_groupid = :flt_outbound ) AS t2 ON t1.ruleid = t2.hf_ruleid LEFT JOIN ( SELECT ff.dr_ruleid AS ff_ruleid, ff.dr_groupid AS ff_groupid, ff.did AS ff_fwddid, g.id AS ff_gwgroupid FROM dsip_failfwd AS ff LEFT JOIN dr_rules AS r ON ff.dr_groupid = r.groupid LEFT JOIN dr_gw_lists AS g ON g.id = REPLACE(r.gwlist, '#', '') WHERE ff.dr_groupid <> :flt_outbound UNION ALL SELECT ff.dr_ruleid AS ff_ruleid, ff.dr_groupid AS ff_groupid, ff.did AS ff_fwddid, NULL AS ff_gwgroupid FROM dsip_failfwd AS ff LEFT JOIN dr_rules AS r ON ff.dr_ruleid = r.ruleid WHERE ff.dr_groupid = :flt_outbound ) AS t3 ON t1.ruleid = t3.ff_ruleid"""), {"flt_inbound": settings.FLT_INBOUND, "flt_outbound": settings.FLT_OUTBOUND}) epgroups = db.query(GatewayGroups).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.ENDPOINT.value) ).all() gwgroups = db.query(GatewayGroups).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.ENDPOINT_OR_CARRIER.value) ).all() gatewayList = db.query(Gateways).all() # sort endpoint groups by name epgroups.sort(key=lambda x: strFieldsToDict(x.description)['name'].lower()) # sort gateway groups by type then by name gwgroups.sort(key=lambda x: (strFieldsToDict(x.description)['type'], strFieldsToDict(x.description)['name'].lower())) dids = [] if len(settings.FLOWROUTE_ACCESS_KEY) > 0 and len(settings.FLOWROUTE_SECRET_KEY) > 0: try: dids = numbers_api.getNumbers() except http_exceptions.HTTPException as ex: debugException(ex) return showError(type="http", code=ex.code, msg="Flowroute Credentials Not Valid") return render_template('inboundmapping.html', rows=res, gwgroups=gwgroups, epgroups=epgroups, imported_dids=dids, gatewayList=gatewayList) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/inboundmapping', methods=['POST']) def addUpdateInboundMapping(): """ Adding and Updating of dr_rules table (inbound routes only)\n Partially Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) # get form data ruleid = form['ruleid'] if 'ruleid' in form else None gwgroupid = form['gwgroupid'] if 'gwgroupid' in form and form['gwgroupid'] != "0" else '' prefix = form['prefix'] if 'prefix' in form else '' desc_dict = {'name': form['rulename']} if 'rulename' in form else {} hardfwd_enabled = int(form['hardfwd_enabled']) hf_gwgroupid = form['hf_gwgroupid'] if 'hf_gwgroupid' in form and form['hf_gwgroupid'] != "0" else '' hf_groupid = form['hf_groupid'] if 'hf_groupid' in form else '' hf_fwddid = form['hf_fwddid'] if 'hf_fwddid' in form else '' failfwd_enabled = int(form['failfwd_enabled']) ff_gwgroupid = form['ff_gwgroupid'] if 'ff_gwgroupid' in form and form['ff_gwgroupid'] != "0" else '' ff_groupid = form['ff_groupid'] if 'ff_groupid' in form else '' ff_fwddid = form['ff_fwddid'] if 'ff_fwddid' in form else '' fwdgroupid = None # TODO: seperate redundant code into functions # TODO need to add support for updating and deleting a LB Rule # Adding if not ruleid: inserts = [] # don't allow duplicate entries if db.query(InboundMapping).filter(InboundMapping.prefix == prefix).filter(InboundMapping.groupid == settings.FLT_INBOUND).scalar(): raise http_exceptions.BadRequest("Duplicate DID's are not allowed") if "lb_" in gwgroupid: x = gwgroupid.split("_") gwgroupid = x[1] desc_dict['lb_enabled'] = '1' else: desc_dict['lb_enabled'] = '0' description = dictToStrFields(desc_dict) # we only support a single gwgroup at this time gwlist = '#{}'.format(gwgroupid) if len(gwgroupid) > 0 else '' # dispatcher_id = x[2].zfill(4) # # Gateway = db.query(Gateways).filter(Gateways.description.like("%drouting_to_dispatcher%"), Gateways.address == "localhost", Gateways.strip == 0, Gateways.pri_prefix == dispatcher_id, Gateways.type == settings.FLT_PBX).first() # # if not Gateway: # # Create a gateway # Gateway = Gateways("drouting_to_dispatcher", "localhost", 0, dispatcher_id, settings.FLT_PBX, gwgroup=gwgroupid) # # db.add(Gateway) # db.flush() # # addresses = [Address("myself", settings.INTERNAL_IP_ADDR, 32, 1, gwgroup=gwgroupid)] # if settings.IPV6_ENABLED: # addresses.append(Address("myself", settings.INTERNAL_IP6_ADDR, 32, 1, gwgroup=gwgroupid)) # db.add_all(addresses) # # # Define an Inbound Mapping that maps to the newly created gateway # gwlist = Gateway.gwid # IMap = InboundMapping(settings.FLT_INBOUND, prefix, gwlist, description) # db.add(IMap) # # db.commit() # return displayInboundMapping() IMap = InboundMapping(settings.FLT_INBOUND, prefix, gwlist, description) inserts.append(IMap) # find last rule in dr_rules res = db.execute( text("SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=:db_name AND TABLE_NAME='dr_rules'"), {"db_name": settings.KAM_DB_NAME} ).first() ruleid = int(res[0]) if hardfwd_enabled: # when gwgroup is set we need to create a dr_rule that maps to the gwlist of that gwgroup if len(hf_gwgroupid) > 0: gwlist = '#{}'.format(hf_gwgroupid) # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 hfwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Hard Forward from {} to DID {}'.format(prefix, hf_fwddid)) inserts.append(hfwd) # if no gwgroup selected we dont need dr_rules we set dr_groupid to FLT_OUTBOUND and we create hardfwd/failfwd rules else: fwdgroupid = settings.FLT_OUTBOUND hfwd_htable = dSIPHardFwd(ruleid, hf_fwddid, fwdgroupid) inserts.append(hfwd_htable) if failfwd_enabled: # when gwgroup is set we need to create a dr_rule that maps to the gwlist of that gwgroup if len(ff_gwgroupid) > 0: gwlist = '#{}'.format(ff_gwgroupid) # skip lookup if we just found the groupid if fwdgroupid is not None: fwdgroupid += 1 else: # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 ffwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Failover Forward from {} to DID {}'.format(prefix, ff_fwddid)) inserts.append(ffwd) # if no gwgroup selected we dont need dr_rules we set dr_groupid to FLT_OUTBOUND and we create hardfwd/failfwd rules else: fwdgroupid = settings.FLT_OUTBOUND ffwd_htable = dSIPFailFwd(ruleid, ff_fwddid, fwdgroupid) inserts.append(ffwd_htable) db.add_all(inserts) # enabling/disabling load balancing on one route must enable them all due to current limitations db.flush() if desc_dict['lb_enabled'] == '1': lb_find = 'lb_enabled:0' lb_repl = 'lb_enabled:1' else: lb_find = 'lb_enabled:1' lb_repl = 'lb_enabled:0' for row in db.query(InboundMapping).filter( InboundMapping.groupid == settings.FLT_INBOUND ).filter( InboundMapping.gwlist == IMap.gwlist ).filter( InboundMapping.ruleid != IMap.ruleid ): row.description = row.description.replace(lb_find, lb_repl) # Updating else: inserts = [] if "lb_" in gwgroupid: # logging.info("In the load balancing update logic") x = gwgroupid.split("_") gwgroupid = x[1] desc_dict['lb_enabled'] = '1' else: desc_dict['lb_enabled'] = '0' description = dictToStrFields(desc_dict) # we only support a single gwgroup at this time gwlist = '#{}'.format(gwgroupid) if len(gwgroupid) > 0 else '' # dispatcher_id = x[2].zfill(4) # # # logging.info("Searching for the gateway by description") # # Create a gateway # Gateway = db.query(Gateways).filter(Gateways.description.like(gwgroupid) & Gateways.description.like("lb:{}".format(dispatcher_id))).first() # if Gateway: # logging.info("Gateway found") # fields = strFieldsToDict(Gateway.description) # fields['lb'] = dispatcher_id # fields['gwgroup'] = gwgroupid # Gateway.update({'prefix': dispatcher_id, 'description': dictToStrFields(fields)}) # else: # # logging.info("Gateway not found") # Gateway = db.query(Gateways).filter(Gateways.description.like("%drouting_to_dispatcher%"), Gateways.address == "localhost", Gateways.strip == 0, Gateways.pri_prefix == dispatcher_id, Gateways.type == settings.FLT_PBX).first() # if not Gateway: # logging.info("Gateway not found, creating new gateway") # # Create a gateway # Gateway = Gateways("drouting_to_dispatcher", "localhost", 0, dispatcher_id, settings.FLT_PBX, gwgroup=gwgroupid) # db.add(Gateway) # db.flush() # # # Gateway = Gateways("drouting_to_dispatcher", "localhost", 0, dispatcher_id, settings.FLT_PBX, gwgroup=gwgroupid) # # db.add(Gateway) # # db.flush() # # Assign Gateway id to the gateway list # gwlist = Gateway.gwid # # try: # if not db.query(Address).filter(Address.ip_addr == settings.INTERNAL_IP_ADDR).scalar(): # db.add(Address("myself", settings.INTERNAL_IP_ADDR, 32, 1, gwgroup=gwgroupid)) # if settings.IPV6_ENABLED: # if not db.query(Address).filter(Address.ip_addr == settings.INTERNAL_IP6_ADDR).scalar(): # db.add(Address("myself", settings.INTERNAL_IP6_ADDR, 32, 1, gwgroup=gwgroupid)) # except sql_exceptions.MultipleResultsFound as ex: # logging.info("Multiple Address rows found") IMap = db.query(InboundMapping).filter(InboundMapping.ruleid == ruleid).first() IMap.prefix = prefix IMap.gwlist = gwlist IMap.description = description hardfwd_exists = True if db.query(dSIPHardFwd).filter(dSIPHardFwd.dr_ruleid == ruleid).scalar() else False db.flush() if hardfwd_enabled: # create fwd htable if it does not exist if not hardfwd_exists: # only create rule if gwgroup has been selected if len(hf_gwgroupid) > 0: gwlist = '#{}'.format(hf_gwgroupid) # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 hfwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Hard Forward from {} to DID {}'.format(prefix, hf_fwddid)) inserts.append(hfwd) # if no gwgroup selected we dont need dr_rules we set dr_groupid to FLT_OUTBOUND and we create htable else: fwdgroupid = settings.FLT_OUTBOUND hfwd_htable = dSIPHardFwd(ruleid, hf_fwddid, fwdgroupid) inserts.append(hfwd_htable) # update existing fwd htable else: # intially set fwdgroupid to update (if we create new rule it will change) fwdgroupid = int(hf_groupid) if len(hf_gwgroupid) > 0 else settings.FLT_OUTBOUND # only update rule if one exists, which we know only happens if groupid != FLT_OUTBOUND if hf_groupid != str(settings.FLT_OUTBOUND): # if gwgroup is selected we update the rule, if not selected we delete the rule if len(hf_gwgroupid) > 0: gwlist = '#{}'.format(hf_gwgroupid) db.query(InboundMapping).filter(InboundMapping.groupid == hf_groupid).update( {'prefix': '', 'gwlist': gwlist, 'description': 'name:Hard Forward from {} to DID {}'.format(prefix, hf_fwddid)}, synchronize_session=False) else: hf_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == hf_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if hf_rule.scalar(): hf_rule.delete(synchronize_session=False) else: # if gwgroup is selected we create the rule if len(hf_gwgroupid) > 0: gwlist = '#{}'.format(hf_gwgroupid) # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 hfwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Hard Forward from {} to DID {}'.format(prefix, hf_fwddid)) inserts.append(hfwd) db.query(dSIPHardFwd).filter(dSIPHardFwd.dr_ruleid == ruleid).update( {'did': hf_fwddid, 'dr_groupid': fwdgroupid}, synchronize_session=False) else: # delete existing fwd htable and possibly fwd rule if hardfwd_exists: hf_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == hf_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if hf_rule.scalar(): hf_rule.delete(synchronize_session=False) hf_htable = db.query(dSIPHardFwd).filter(dSIPHardFwd.dr_ruleid == ruleid) hf_htable.delete(synchronize_session=False) failfwd_exists = True if db.query(dSIPFailFwd).filter(dSIPFailFwd.dr_ruleid == ruleid).scalar() else False db.flush() if failfwd_enabled: # create fwd htable if it does not exist if not failfwd_exists: # only create rule if gwgroup has been selected if len(ff_gwgroupid) > 0: gwlist = '#{}'.format(ff_gwgroupid) # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 ffwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Failover Forward from {} to DID {}'.format(prefix, ff_fwddid)) inserts.append(ffwd) # if no gwgroup selected we dont need dr_rules we set dr_groupid to FLT_OUTBOUND and we create htable else: fwdgroupid = settings.FLT_OUTBOUND ffwd_htable = dSIPFailFwd(ruleid, ff_fwddid, fwdgroupid) inserts.append(ffwd_htable) # update existing fwd htable else: # intially set fwdgroupid to update (if we create new rule it will change) fwdgroupid = int(ff_groupid) if len(ff_gwgroupid) > 0 else settings.FLT_OUTBOUND # only update rule if one exists, which we know only happens if groupid != FLT_OUTBOUND if ff_groupid != str(settings.FLT_OUTBOUND): # if gwgroup is selected we update the rule, if not selected we delete the rule if len(ff_gwgroupid) > 0: gwlist = '#{}'.format(ff_gwgroupid) db.query(InboundMapping).filter(InboundMapping.groupid == ff_groupid).update( {'prefix': '', 'gwlist': gwlist, 'description': 'name:Failover Forward from {} to DID {}'.format(prefix, ff_fwddid)}, synchronize_session=False) else: ff_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == ff_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if ff_rule.scalar(): ff_rule.delete(synchronize_session=False) else: # if gwgroup is selected we create the rule if len(ff_gwgroupid) > 0: gwlist = '#{}'.format(ff_gwgroupid) # find last forwarding rule lastfwd = db.query(InboundMapping).filter(InboundMapping.groupid >= settings.FLT_FWD_MIN).order_by( InboundMapping.groupid.desc()).first() # Start forwarding routes with a groupid set in settings (default is 20000) if lastfwd is None: fwdgroupid = settings.FLT_FWD_MIN else: fwdgroupid = int(lastfwd.groupid) + 1 ffwd = InboundMapping(fwdgroupid, '', gwlist, 'name:Failover Forward from {} to DID {}'.format(prefix, ff_fwddid)) inserts.append(ffwd) db.query(dSIPFailFwd).filter(dSIPFailFwd.dr_ruleid == ruleid).update( {'did': ff_fwddid, 'dr_groupid': fwdgroupid}, synchronize_session=False) else: # delete existing fwd htable and possibly fwd rule if failfwd_exists: ff_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == ff_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if ff_rule.scalar(): ff_rule.delete(synchronize_session=False) ff_htable = db.query(dSIPFailFwd).filter(dSIPFailFwd.dr_ruleid == ruleid) ff_htable.delete(synchronize_session=False) if len(inserts) > 0: db.add_all(inserts) # enabling/disabling load balancing on one route must enable them all due to current limitations db.flush() if desc_dict['lb_enabled'] == '1': lb_find = 'lb_enabled:0' lb_repl = 'lb_enabled:1' else: lb_find = 'lb_enabled:1' lb_repl = 'lb_enabled:0' for row in db.query(InboundMapping).filter( InboundMapping.groupid == settings.FLT_INBOUND ).filter( InboundMapping.gwlist == IMap.gwlist ).filter( InboundMapping.ruleid != IMap.ruleid ): row.description = row.description.replace(lb_find, lb_repl) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayInboundMapping() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/inboundmappingdelete', methods=['POST']) def deleteInboundMapping(): """ Deleting of dr_rules table (inbound routes only)\n Partially Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) ruleid = form['ruleid'] hf_ruleid = form['hf_ruleid'] hf_groupid = form['hf_groupid'] ff_ruleid = form['ff_ruleid'] ff_groupid = form['ff_groupid'] im_rule = db.query(InboundMapping).filter(InboundMapping.ruleid == ruleid).first() # Delete the gateway if it's being used for Load Balancing if '#' not in im_rule.gwlist: dispatcher_gateway = db.query(Gateways).filter(Gateways.gwid == im_rule.gwlist) dispatcher_gateway.delete(synchronize_session=False) # Delete the rule now db.delete(im_rule) if len(hf_ruleid) > 0: # no dr_rules created for fwding without a gwgroup selected hf_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == hf_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if hf_rule.scalar(): hf_rule.delete(synchronize_session=False) hf_htable = db.query(dSIPHardFwd).filter(dSIPHardFwd.dr_ruleid == ruleid) hf_htable.delete(synchronize_session=False) if len(ff_ruleid) > 0: # no dr_rules created for fwding without a gwgroup selected ff_rule = db.query(InboundMapping).filter( ((InboundMapping.groupid == ff_groupid) & (InboundMapping.groupid != settings.FLT_OUTBOUND)) | ((InboundMapping.ruleid == ruleid) & (InboundMapping.groupid == settings.FLT_OUTBOUND))) if ff_rule.scalar(): ff_rule.delete(synchronize_session=False) ff_htable = db.query(dSIPFailFwd).filter(dSIPFailFwd.dr_ruleid == ruleid) ff_htable.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayInboundMapping() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() # TODO: add support for hard/fail forwarding def processInboundMappingImport(filename, override_gwgroupid, db): try: # sql = insert( # InboundMapping # ).values( # groupid=str(settings.FLT_INBOUND), # prefix=bindparam("prefix"), # gwlist=bindparam("gwlist"), # description=f'name:{bindparam("name")}', # ).on_duplicate_key_update( # prefix=bindparam("prefix"), # gwlist=bindparam("gwlist"), # # description=bindparam('name', callable_=lambda name: text(f"REGEXP_REPLACE(description, '(.*?)name:[^,|$]*(.*?)', '\\1name:{name}\\2')"), # ) insert_sql = text(""" INSERT INTO dr_rules (groupid, prefix, timerec, routeid, gwlist, description) VALUES (:groupid, :prefix, '', '', :gwlist, CONCAT('name:', :name)) """) update_sql = text(""" UPDATE dr_rules SET prefix = :prefix, gwlist = :gwlist, description = REGEXP_REPLACE(description, '(.*?)name:[^,|$]*(.*?)', CONCAT('\\\\1name:', :name, '\\\\2')) WHERE prefix = :prefix """) insert_sql_args = [] update_sql_args = [] # prefix: sql_args dids_uploaded = {} with open(os.path.join(settings.UPLOAD_FOLDER, filename), 'r', newline='') as csv_file: csv_file = csv.DictReader(csv_file) for row in csv_file: if len(row) == 0: continue prefix = row['DID'] if override_gwgroupid is not None: gwlist = override_gwgroupid else: gwlist = f'#{row["EndpointGroupID"]}' name = row["Name"] dids_uploaded[prefix] = {'groupid': str(settings.FLT_INBOUND), 'prefix': prefix, 'gwlist': gwlist, 'name': name} # bulk SELECT select_sql = select(InboundMapping.prefix).filter( (InboundMapping.groupid == settings.FLT_INBOUND) & (InboundMapping.prefix.in_(dids_uploaded.keys())) ) existing_dids = db.execute(select_sql).scalars().all() for did in dids_uploaded: if did in existing_dids: update_sql_args.append(dids_uploaded[did]) else: insert_sql_args.append(dids_uploaded[did]) db.execute(insert_sql, insert_sql_args) db.execute(update_sql, update_sql_args) db.commit() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) @app.route('/inboundmappingimport', methods=['POST']) def importInboundMapping(): db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) # get form data gwgroupid = form['gwgroupid'] if 'gwgroupid' in form else None if 'file' not in request.files: flash('No file part') return redirect(url_for('displayInboundMapping')) file = request.files['file'] # if user does not select file, browser also # submit a empty part without filename if file.filename == '': flash('No selected file') return redirect(request.url) if file and allowed_file(file.filename, ALLOWED_EXTENSIONS={'csv'}): filename = secure_filename(file.filename) file.save(os.path.join(settings.UPLOAD_FOLDER, filename)) processInboundMappingImport(filename, gwgroupid, db) flash('DIDs were successfully imported') getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return redirect(url_for('displayInboundMapping', filename=filename)) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/teleblock') def displayTeleBlock(): """ Display teleblock settings in view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() teleblock = {} teleblock["gw_enabled"] = settings.TELEBLOCK_GW_ENABLED teleblock["gw_ip"] = settings.TELEBLOCK_GW_IP teleblock["gw_port"] = settings.TELEBLOCK_GW_PORT teleblock["media_ip"] = settings.TELEBLOCK_MEDIA_IP teleblock["media_port"] = settings.TELEBLOCK_MEDIA_PORT return render_template('teleblock.html', teleblock=teleblock) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/teleblock', methods=['POST']) def addUpdateTeleBlock(): """ Update teleblock config file settings """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() form = stripDictVals(request.form.to_dict()) # Update the teleblock settings teleblock = {} teleblock['TELEBLOCK_GW_ENABLED'] = int(form.get('gw_enabled', 0)) teleblock['TELEBLOCK_GW_IP'] = form['gw_ip'] teleblock['TELEBLOCK_GW_PORT'] = form['gw_port'] teleblock['TELEBLOCK_MEDIA_IP'] = form['media_ip'] teleblock['TELEBLOCK_MEDIA_PORT'] = form['media_port'] updateConfig(settings, teleblock, hot_reload=True) getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayTeleBlock() except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/transnexus') def displayTransNexus(msg=None): """ Display TransNexus settings in view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_TRANSNEXUS') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') return render_template( 'transnexus.html', msg=msg ) except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/transnexus', methods=['POST']) def addUpdateTransNexus(): """ Update TransNexus config file settings """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_TRANSNEXUS') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') form = stripDictVals(request.form.to_dict()) # Update the TransNexus settings tn_settings = {} if 'authservice_enabled' in form: tn_settings['TRANSNEXUS_AUTHSERVICE_ENABLED'] = int(form['authservice_enabled']) tn_settings["TRANSNEXUS_AUTHSERVICE_HOST"] = form['authservice_host'] else: # It was disabled so the form field was not sent over tn_settings['TRANSNEXUS_AUTHSERVICE_ENABLED'] = 0 if 'verifyservice_enabled' in form: tn_settings['TRANSNEXUS_VERIFYSERVICE_ENABLED'] = int(form['verifyservice_enabled']) tn_settings["TRANSNEXUS_VERIFYSERVICE_HOST"] = form['verifyservice_host'] else: # It was disabled so the form field was not sent over tn_settings['TRANSNEXUS_VERIFYSERVICE_ENABLED'] = 0 if len(tn_settings) != 0: updateConfig(settings, tn_settings, hot_reload=True) getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayTransNexus() except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/outboundroutes') def displayOutboundRoutes(): """ Displaying of dr_rules table (outbound routes only)\n Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() rows = db.query(OutboundRoutes).filter( (OutboundRoutes.groupid == settings.FLT_OUTBOUND) | ((OutboundRoutes.groupid >= settings.FLT_LCR_MIN) & (OutboundRoutes.groupid < settings.FLT_FWD_MIN))).outerjoin( dSIPLCR, dSIPLCR.dr_groupid == OutboundRoutes.groupid).outerjoin( GatewayGroups, func.REPLACE(OutboundRoutes.gwlist, '#', '') == GatewayGroups.id).add_columns( dSIPLCR.from_prefix, dSIPLCR.cost, dSIPLCR.dr_groupid, OutboundRoutes.ruleid, OutboundRoutes.prefix, OutboundRoutes.routeid, func.REPLACE(OutboundRoutes.gwlist, '#', '').label('gwgroupid'), OutboundRoutes.timerec, OutboundRoutes.priority, OutboundRoutes.description, GatewayGroups.description.label('gwgroup_description'), GatewayGroups.gwlist) cgroups = db.query(GatewayGroups).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.CARRIER.value) ).all() # sort carrier groups by name cgroups.sort(key=lambda x: strFieldsToDict(x.description)['name'].lower()) teleblock = {} teleblock["gw_enabled"] = settings.TELEBLOCK_GW_ENABLED teleblock["gw_ip"] = settings.TELEBLOCK_GW_IP teleblock["gw_port"] = settings.TELEBLOCK_GW_PORT teleblock["media_ip"] = settings.TELEBLOCK_MEDIA_IP teleblock["media_port"] = settings.TELEBLOCK_MEDIA_PORT return render_template('outboundroutes.html', rows=rows, cgroups=cgroups, teleblock=teleblock, custom_routes=getCustomRoutes()) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/outboundroutes', methods=['POST']) def addUpateOutboundRoutes(): """ Adding and Updating dr_rules table (outbound routes only)\n Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) # get form data ruleid = form['ruleid'] if 'ruleid' in form and len(form['ruleid']) > 0 else None groupid = form['groupid'] if 'groupid' in form and len(form['groupid']) > 0 \ and form['groupid'] != "None" else settings.FLT_OUTBOUND from_prefix = form['from_prefix'] if 'from_prefix' in form and len(form['from_prefix']) > 0 else None prefix = form['prefix'] if 'prefix' in form else '' timerec = form['timerec'] if 'timerec' in form else '' priority = int(form['priority']) if 'priority' in form and len(form['priority']) > 0 else 0 routeid = form['routeid'] if 'routeid' in form else '' gwgroupid = form['gwgroupid'] if 'gwgroupid' in form and form['gwgroupid'] != "0" else '' description = 'name:{}'.format(form['name']) if 'name' in form else '' pattern = None gwlist = '#{}'.format(gwgroupid) if len(gwgroupid) > 0 else '' # Adding if not ruleid: # if len(from_prefix) > 0 and len(prefix) == 0 : # return displayOutboundRoutes() if from_prefix is not None: print("from_prefix: {}".format(from_prefix)) # Grab the lastest groupid and increment mlcr = db.query(dSIPLCR).filter((dSIPLCR.dr_groupid >= settings.FLT_LCR_MIN) & (dSIPLCR.dr_groupid < settings.FLT_FWD_MIN)).order_by(dSIPLCR.dr_groupid.desc()).first() db.commit() # Start LCR routes with a groupid in settings (default is 10000) if mlcr is None: groupid = settings.FLT_LCR_MIN else: groupid = int(mlcr.dr_groupid) + 1 pattern = from_prefix + "-" + prefix OMap = OutboundRoutes(groupid=groupid, prefix=prefix, timerec=timerec, priority=priority, routeid=routeid, gwlist=gwlist, description=description) db.add(OMap) # Add the lcr map if pattern != None: OLCRMap = dSIPLCR(pattern, from_prefix, groupid) db.add(OLCRMap) # Updating else: # no from_prefix we can update outbound route and remove any LCR entries if from_prefix is None and prefix is not None: oldgroupid = groupid groupid = settings.FLT_OUTBOUND #if (groupid is None) or (groupid == "None"): # groupid = settings.FLT_OUTBOUND # Convert the dr_rule back to a default carrier rule db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).update({ 'prefix': prefix, 'groupid': groupid, 'timerec': timerec, 'priority': priority, 'routeid': routeid, 'gwlist': gwlist, 'description': description }, synchronize_session=False) # Delete from LCR table using the old group id d1 = db.query(dSIPLCR).filter(dSIPLCR.dr_groupid == oldgroupid) d1.delete(synchronize_session=False) # from_prefix given, we update outbound route and create/update LCR entry elif from_prefix is not None: # Setup pattern pattern = from_prefix + "-" + prefix # Check if pattern already exists exists = db.query(dSIPLCR).filter(dSIPLCR.pattern == pattern).scalar() if exists: db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).update({ 'prefix': prefix, 'groupid': groupid, 'timerec': timerec, 'priority': priority, 'routeid': routeid, 'gwlist': gwlist, 'description': description }, synchronize_session=False) # Adding a From prefix to an existing To elif prefix is not None and groupid == settings.FLT_OUTBOUND: # Create a new groupid mlcr = db.query(dSIPLCR).filter((dSIPLCR.dr_groupid >= settings.FLT_LCR_MIN) & (dSIPLCR.dr_groupid < settings.FLT_FWD_MIN)).order_by(dSIPLCR.dr_groupid.desc()).first() # Start LCR routes with a groupid set in settings (default is 10000) if mlcr is None: groupid = settings.FLT_LCR_MIN else: groupid = int(mlcr.dr_groupid) + 1 # Setup new pattern pattern = from_prefix + "-" + prefix # Add the lcr map if pattern != None: OLCRMap = dSIPLCR(pattern, from_prefix, groupid) db.add(OLCRMap) db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).update({ 'groupid': groupid, 'prefix': prefix, 'timerec': timerec, 'priority': priority, 'routeid': routeid, 'gwlist': gwlist, 'description': description }, synchronize_session=False) # Update existing pattern elif (int(groupid) >= settings.FLT_LCR_MIN) and (int(groupid) < settings.FLT_FWD_MIN): db.query(dSIPLCR).filter(dSIPLCR.dr_groupid == groupid).update({ 'pattern': pattern, 'from_prefix': from_prefix}) # Update the dr_rules table db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).update({ 'prefix': prefix, 'groupid': groupid, 'timerec': timerec, 'priority': priority, 'routeid': routeid, 'gwlist': gwlist, 'description': description }, synchronize_session=False) # no to/from_prefix remove any LCR entries and update outbound route # TODO: not sure how to correlate stale LCR entries without old from-prefix (ideally we match by id) else: # Update the dr_rules table db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).update({ 'prefix': prefix, 'groupid': groupid, 'timerec': timerec, 'priority': priority, 'routeid': routeid, 'gwlist': gwlist, 'description': description }, synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayOutboundRoutes() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/outboundroutesdelete', methods=['POST']) def deleteOutboundRoute(): """ Deleting of dr_rules table (outbound routes only)\n Supports rule definitions for Kamailio Drouting module\n Documentation: `Drouting module <https://kamailio.org/docs/modules/4.4.x/modules/drouting.html>`_ """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() form = stripDictVals(request.form.to_dict()) ruleid = form['ruleid'] OMap = db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid).first() if OMap: d1 = db.query(dSIPLCR).filter(dSIPLCR.dr_groupid == OMap.groupid) d1.delete(synchronize_session=False) d = db.query(OutboundRoutes).filter(OutboundRoutes.ruleid == ruleid) d.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayOutboundRoutes() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @app.route('/stirshaken') def displayStirShaken(msg=None): """ Display TransNexus settings in view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_STIRSHAKEN') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') if settings.STIR_SHAKEN_ENABLED == 1: toggle_checked = 'checked' options_hidden = 'hidden' else: toggle_checked = '' options_hidden = '' return render_template( 'stirshaken.html', msg=msg, toggle_checked=toggle_checked, options_hidden=options_hidden ) except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/stirshaken', methods=['POST']) def addUpdateStirShaken(): """ Update STIR/SHAKEN config file settings """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_STIRSHAKEN') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') form = stripDictVals(request.form.to_dict()) # Update the STIR/SHAKEN settings ss_settings = {} if 'stir_shaken_enabled' in form: tmp = form.get('stir_shaken_enabled', 0) ss_settings["STIR_SHAKEN_ENABLED"] = 1 if tmp == "1" else 0 else: ss_settings["STIR_SHAKEN_ENABLED"] = 0 if 'stir_shaken_prefix_a' in form: ss_settings["STIR_SHAKEN_PREFIX_A"] = form.get('stir_shaken_prefix_a', '') if 'stir_shaken_prefix_b' in form: ss_settings["STIR_SHAKEN_PREFIX_B"] = form.get('stir_shaken_prefix_b', '') if 'stir_shaken_prefix_c' in form: ss_settings["STIR_SHAKEN_PREFIX_C"] = form.get('stir_shaken_prefix_c', '') if 'stir_shaken_prefix_invalid' in form: ss_settings["STIR_SHAKEN_PREFIX_INVALID"] = form.get('stir_shaken_prefix_invalid', '') if 'stir_shaken_block_invalid' in form: tmp = form.get('stir_shaken_block_invalid', 0) ss_settings["STIR_SHAKEN_BLOCK_INVALID"] = 1 if tmp == "on" else 0 if 'stir_shaken_cert_url' in form: ss_settings["STIR_SHAKEN_CERT_URL"] = form.get('stir_shaken_cert_url', '') if 'stir_shaken_key_path' in form: ss_settings["STIR_SHAKEN_KEY_PATH"] = form.get('stir_shaken_key_path', '') if len(ss_settings) != 0: updateConfig(settings, ss_settings, hot_reload=True) getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayStirShaken() except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @app.route('/upgrade') def displayUpgrade(msg=None): """ Display Upgrade settings in view """ try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_CORE') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') # make sure we remove offset file when navigating to top level page if os.path.exists(UPGRADE_OFFSET): os.remove(UPGRADE_OFFSET) latest = UpdateUtils.get_latest_version() upgrade_settings = { "current_version": settings.VERSION, "latest_version": latest['ver_num'], "upgrade_available": 1 if latest['ver_num'] > float(settings.VERSION) else 0 } return render_template('upgrade.html', upgrade_settings=upgrade_settings, msg=msg) except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) @process def runUpgrade(cmd, env): try: getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_upgrade_ongoing'] = True with open(UPGRADE_LOG, 'wb', buffering=0) as f: run_info = subprocess.run( cmd, stdout=f, stderr=subprocess.STDOUT, universal_newlines=True, env=env, restore_signals=False, close_fds=True ) run_info.check_returncode() # getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True # kamailio will be reloaded when dsiprouter is reloaded getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_required'] = True finally: getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_upgrade_ongoing'] = False @app.route('/upgrade/start', methods=['POST']) def startUpgrade(): try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() if getLicenseStatus(license_tag='DSIP_CORE') != 3: raise WoocommerceError('invalid license') IO.loginfo("starting upgrade") form = stripDictVals(request.form.to_dict()) cmd = ['sudo', '-E', 'dsiprouter', 'upgrade', '-rel', f"{form['latest_version']}", '-url', settings.GIT_REPO_URL] runUpgrade(cmd, None) IO.loginfo("upgrade started") return Response(), StatusCodes.HTTP_OK except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) # format a message as a server sent event # idea from: https://maxhalford.github.io/blog/flask-sse-no-deps/ # TODO: separate into SSE specific file def formatSSE(data: str, event: str = None) -> str: msg = f'data: {data}\n\n' if event is not None: msg = f'event: {event}\n{msg}' return msg # inspired by: https://gist.github.com/kapb14/87255efffa173bb76cf5c1ed9db1d047 # TODO: move to file handling # TODO: figure out why the SSE are not being sent every 1 second as expected def readLogChunk(log_file): state = getSharedMemoryDict(STATE_SHMEM_NAME) while state['dsip_upgrade_ongoing'] == True: try: lines = Pygtail(log_file, offset_file=f'{log_file}.offset').read() yield formatSSE( ansi_converter.convert( lines, full=False ).replace('\n', '<br>') ) except: pass time.sleep(1) # process the reset of the lines try: lines = Pygtail(log_file, offset_file=f'{log_file}.offset').read() yield formatSSE( ansi_converter.convert( lines, full=False ).replace('\n', '<br>') ) except: pass def checkUpgradeStatus(): state = getSharedMemoryDict(STATE_SHMEM_NAME) while state['dsip_upgrade_ongoing'] == True: yield formatSSE('1') time.sleep(30) yield formatSSE('0') @app.route('/upgrade/status') def getUpgradeStatus(): try: if not session.get('logged_in'): return 'Unauthorized', 401 if (settings.DEBUG): debugEndpoint() return Response( checkUpgradeStatus(), mimetype="text/event-stream", headers={ 'X-Accel-Buffering': 'no', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }, ) except Exception as ex: debugException(ex) return 'Failed Checking Status', StatusCodes.HTTP_INTERNAL_SERVER_ERROR @app.route('/upgrade/log') def getUpgradeLog(): try: if not session.get('logged_in'): return 'Unauthorized', 401 if (settings.DEBUG): debugEndpoint() accept = request.headers.get('Accept', '').lower() if accept == 'text/event-stream': return Response( readLogChunk(UPGRADE_LOG), mimetype="text/event-stream", headers={ 'X-Accel-Buffering': 'no', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' }, ) else: with open(UPGRADE_LOG, 'r') as f: return Response( ansi_converter.convert(f.read(), full=False).replace('\n', '<br>'), mimetype="text/html", ) except FileNotFoundError: return 'File not found', 404 except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" return showError(type=error) # custom jinja filters def yesOrNoFilter(list, field): if list == 1: return "Yes" else: return "No" def noneFilter(list): if list is None: return "" else: return list def attrFilter(s, attr, delims=(',', ':')): if s is None: return '' try: return dict( item.split(delims[1], maxsplit=1) for item in s.split(delims[0]) )[attr] except: return '' def domainTypeFilter(list): if list is None: return "Unknown" elif list == "0": return "Static" elif list == "1": return "Dynamic" elif list == "2": return "Static using Dispatcher" elif list == "3": return "MSTeams" else: return "Other" def imgFilter(name): images_url = urllib.parse.urljoin(app.static_url_path, 'images') search_path = os.path.join(app.static_folder, 'images', name) filenames = glob.glob(search_path + '.*') if len(filenames) > 0: return urllib.parse.urljoin(images_url, filenames[0]) else: return "" # custom jinja context processors @app.context_processor def injectGlobals(): state = getSharedMemoryDict(STATE_SHMEM_NAME) return { 'settings': settings, 'state': state, 'licenseValid': lambda tag: getLicenseStatusFromStateDict(state['dsip_license_store'], tag) } # DEPRECATED: now done by dsiprouter.sh (CLI commands run by dsip-init.service), marked for removal in v0.80 # def getDynamicNetworkSettings(): # """ # Get the network settings depending on the state of NETWORK_MODE when called # # :return: network settings or empty dict # :rtype: dict # """ # # if settings.NETWORK_MODE == 1: # return {} # elif settings.NETWORK_MODE == 2: # raise NotImplementedError() # # int_ip = getInternalIP('4') # int_ip6 = getInternalIP('6') # int_net = getInternalCIDR('4') # int_net6 = getInternalCIDR('6') # int_fqdn = socket.getfqdn() # ext_ip = getExternalIP('4') # ext_ip6 = getExternalIP('6') # ext_fqdn = ipToHost(ext_ip) # if os.path.exists('/proc/net/if_inet6'): # try: # with socket.socket(socket.AF_INET6, socket.SOCK_DGRAM) as s: # s.connect((int_ip6, 0)) # ipv6_enabled = True # except: # ipv6_enabled = False # else: # ipv6_enabled = False # # return { # 'IPV6_ENABLED': ipv6_enabled, # 'INTERNAL_IP_ADDR': int_ip if int_ip is not None else '', # 'INTERNAL_IP_NET': int_net if int_net is not None else '', # 'INTERNAL_IP6_ADDR': int_ip6 if int_ip6 is not None else '', # 'INTERNAL_IP6_NET': int_net6 if int_net6 is not None else '', # 'EXTERNAL_IP_ADDR': ext_ip if ext_ip is not None else int_ip if int_ip is not None else '', # 'EXTERNAL_IP6_ADDR': ext_ip6 if ext_ip6 is not None else int_ip6 if int_ip6 is not None else '', # 'EXTERNAL_FQDN': ext_fqdn if ext_fqdn is not None else int_fqdn if int_fqdn is not None else '' # } # DEPRECATED: updating network settings portion of this function has moved to the CLI # marked for refactoring in v0.80 as shown below # def syncSettings(new_fields={}): def syncSettings(new_fields={}, update_net=False): """ Synchronize settings.py with shared mem / db :param new_fields: fields to override when syncing :type new_fields: dict :return: None :rtype: None """ try: # sync settings from settings.py if settings.LOAD_SETTINGS_FROM == 'file' or settings.DSIP_ID is None: # need to grab any changes on disk b4 merging reload(settings) # if DSIP_ID is not set, generate it if settings.DSIP_ID is None: with open('/etc/machine-id', 'r') as f: settings.DSIP_ID = Credentials.hashCreds(f.read().rstrip()) # if HOMER_ID is not set, generate it if settings.HOMER_ID is None: with open('/etc/machine-id', 'r') as f: settings.HOMER_ID = int(Credentials.hashCreds(f.read().rstrip(), dklen=4)[0:8], 16) # sync settings from settings.py if settings.LOAD_SETTINGS_FROM == 'file': # format fields for DB fields = settingsToTableFormat(settings, updates=new_fields) # update the table updateDsipSettingsTable(fields) # revert db formatting fields = settingsTableToDict(fields) # sync settings from dsip_settings table elif settings.LOAD_SETTINGS_FROM == 'db': fields = getDsipSettingsTableAsDict(settings.DSIP_ID, updates=new_fields) # no configured storage device to sync settings to/from else: raise ValueError('invalid value for LOAD_SETTINGS_FROM, acceptable values: "file" or "db"') # update and reload settings file if len(fields) > 0: updateConfig(settings, fields, hot_reload=True) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) IO.printerr('Could Not Update dsip_settings Database Table') raise def sigHandler(signum=None, frame=None): """ Logic for trapped signals """ # ignore SIGHUP if signum == signal.SIGHUP.value: IO.printwarn("Received SIGHUP.. ignoring signal") IO.logwarn("Received SIGHUP.. ignoring signal") # sync settings from db or file elif signum == signal.SIGUSR1.value: IO.printdbg("Received SIGUSR1 syncing settings from {}".format(settings.LOAD_SETTINGS_FROM)) IO.logdbg("Received SIGUSR1 syncing settings from {}".format(settings.LOAD_SETTINGS_FROM)) syncSettings() # sync settings from shared memory elif signum == signal.SIGUSR2.value: IO.printdbg("Received SIGUSR2 syncing settings from shared memory") IO.logdbg("Received SIGUSR2 syncing settings from shared memory") shared_settings = dict(getSharedMemoryDict(SETTINGS_SHMEM_NAME).items()) syncSettings(shared_settings) # run teardown logic before exiting elif signum == signal.SIGINT.value: IO.printdbg("Received SIGINT tearing down services") IO.logdbg("Received SIGINT tearing down services") teardown() sys.exit(0) # run teardown logic before exiting elif signum == signal.SIGTERM.value: IO.printdbg("Received SIGTERM tearing down services") IO.logdbg("Received SIGTERM tearing down services") teardown() sys.exit(0) def replaceAppLoggers(log_handler): """ Handle configuration of web server loggers """ # close current log handlers for handler in copy(logging.getLogger('werkzeug').handlers): logging.getLogger('werkzeug').removeHandler(handler) handler.close() for handler in copy(logging.getLogger('sqlalchemy').handlers): logging.getLogger('sqlalchemy').removeHandler(handler) handler.close() # replace vanilla werkzeug and sqlalchemy log handler logging.getLogger('werkzeug').addHandler(log_handler) logging.getLogger('werkzeug').setLevel(settings.DSIP_LOG_LEVEL) logging.getLogger('sqlalchemy.engine').addHandler(log_handler) logging.getLogger('sqlalchemy.engine').setLevel(settings.DSIP_LOG_LEVEL) logging.getLogger('sqlalchemy.dialects').addHandler(log_handler) logging.getLogger('sqlalchemy.dialects').setLevel(settings.DSIP_LOG_LEVEL) logging.getLogger('sqlalchemy.pool').addHandler(log_handler) logging.getLogger('sqlalchemy.pool').setLevel(settings.DSIP_LOG_LEVEL) logging.getLogger('sqlalchemy.orm').addHandler(log_handler) logging.getLogger('sqlalchemy.orm').setLevel(settings.DSIP_LOG_LEVEL) def intializeGlobalSettings(): """ Initialize global settings and make it accessible in shared memory :return: None :rtype: None """ syncSettings() updated_settings = objToDict(settings) createSharedMemoryDict(updated_settings, name=SETTINGS_SHMEM_NAME) if settings.DEBUG: IO.printinfo(f'global settings initialized: {updated_settings}') def intializeGlobalState(): """ Initialize global state and make it accessible in shared memory :return: None :rtype: None """ # create/update the shared state file # if the state got corrupted throw it away and start fresh # NOTE: upgrade process does not daemonize, it is always gone on startup try: state = updatePersistentState({ 'dsip_reload_ongoing': False, 'dsip_reload_required': False, 'dsip_upgrade_ongoing': False, }) except json.JSONDecodeError: state = {} # license checks are always performed on startup, not loaded from state file state['dsip_license_store'] = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) state['kam_reload_required'] = state.get('kam_reload_required', False) state['dsip_reload_required'] = state.get('dsip_reload_required', False) state['dsip_reload_ongoing'] = state.get('dsip_reload_ongoing', False) state['dsip_upgrade_ongoing'] = state.get('dsip_upgrade_ongoing', False) createSharedMemoryDict(state, name=STATE_SHMEM_NAME) if settings.DEBUG: IO.printinfo(f'global state initialized: {state}') def intializeAuthModules(): global auth_modules for modname in settings.AUTH_MODULES.keys(): # Use the Base Dir to specify the location of the plugin required for this domain spec = importlib.util.spec_from_file_location( f'auth.{modname}', f'{settings.DSIP_PROJECT_DIR}/gui/modules/api/auth/{modname}/interface.py' ) auth_mod = importlib.util.module_from_spec(spec) spec.loader.exec_module(auth_mod) auth_mod.initialize() auth_modules.append(auth_mod) def guiLicenseCheck(tag): global state return getLicenseStatusFromStateDict(state['dsip_license_store'], tag) def initApp(flask_app): # trap signals we handle as soon as possible. # we do not want any allocated shared memory / DB connections / socket files hanging around on startup failure signal.signal(signal.SIGHUP, sigHandler) signal.signal(signal.SIGUSR1, sigHandler) signal.signal(signal.SIGUSR2, sigHandler) signal.signal(signal.SIGINT, sigHandler) signal.signal(signal.SIGTERM, sigHandler) # load the DB objects into memory global global_db_engine, global_session_loader global_db_engine, global_session_loader = createSessionObjects() # make sure we did not break the global naming contract with the database module assert DB_ENGINE_NAME in globals() and SESSION_LOADER_NAME in globals() # Setup the Flask session manager with a random secret key if settings.DSIP_SESSION_KEY is None: flask_app.secret_key = os.urandom(32) else: flask_app.secret_key = AES_CTR.decrypt(settings.DSIP_SESSION_KEY, decode=False) # Setup Flask timed url serializer flask_app.config['EMAIL_SALT'] = urandomChars() flask_app.config['TIMED_SERIALIZER'] = URLSafeTimedSerializer(flask_app.config["SECRET_KEY"]) # Add jinja2 filters flask_app.jinja_env.filters["attrFilter"] = attrFilter flask_app.jinja_env.filters["yesOrNoFilter"] = yesOrNoFilter flask_app.jinja_env.filters["noneFilter"] = noneFilter flask_app.jinja_env.filters["imgFilter"] = imgFilter flask_app.jinja_env.filters["domainTypeFilter"] = domainTypeFilter # Add jinja2 functions (these must be independent from the context processor) flask_app.jinja_env.globals.update(zip=zip) flask_app.jinja_env.globals.update(jsonLoads=json.loads) # Dynamically update settings intializeGlobalSettings() # Reload Kamailio with the settings from dSIPRouter settings config reloadKamailio() # configs depending on updated settings go here # DEPRECATED: flask_app.env is deprecated and only flask_app.debug will be used in the future, marked for removal in v0.80 flask_app.env = "development" if settings.DEBUG else "production" flask_app.debug = settings.DEBUG # DEPRECATED: class interface changed and will be removed in Flask 2.3, marked for review in v0.80 # customize 'app.json_provider_class' or 'app.json' instead # Set flask JSON encoder flask_app.json_encoder = CreateEncoder() # Set session / cookie options flask_app.config.update( SESSION_PROTECTION='strong', SESSION_COOKIE_SECURE=True, SESSION_COOKIE_HTTPONLY=True, SESSION_COOKIE_SAMESITE='Lax', REMEMBER_COOKIE_SECURE=True, REMEMBER_COOKIE_HTTPONLY=True, REMEMBER_COOKIE_DURATION=datetime.timedelta(minutes=settings.GUI_INACTIVE_TIMEOUT), PERMANENT_SESSION_LIFETIME=datetime.timedelta(minutes=settings.GUI_INACTIVE_TIMEOUT), ) # Setup ProxyFix middleware flask_app = ProxyFix(flask_app, 1, 1, 1, 1, 1) # change umask to 660 before creating sockets # this allows members of the dsiprouter group access os.umask(~0o660 & 0o777) # remove sockets / PID file from previous runs (only possible on SIGKILL or system halt) if os.path.exists(settings.DSIP_UNIX_SOCK): os.remove(settings.DSIP_UNIX_SOCK) # Initialize global variables based on persistent state intializeGlobalState() # Initialize authentication modules intializeAuthModules() # write out the main proc's PID with open(settings.DSIP_PID_FILE, 'w') as pidfd: pidfd.write(str(os.getpid())) # start the Flask App server bjoern.run(flask_app, 'unix:{}'.format(settings.DSIP_UNIX_SOCK), reuse_port=True) def teardown(): global global_db_engine try: setPersistentState(objToDict(globals)) except: pass try: for auth_mod in auth_modules: auth_mod.teardown() except: pass try: getSharedMemoryDict(SETTINGS_SHMEM_NAME).unlink() except: pass try: getSharedMemoryDict(STATE_SHMEM_NAME).unlink() except: pass try: close_all_sessions() except: pass try: global_db_engine.dispose(close=True) except: pass try: os.remove(settings.DSIP_PID_FILE) except: pass def main(): try: # start the main proc here # from this point on shutdown is handled by signalling to the main proc initApp(app) # should not be reachable, we always exit with an exception or signal handler IO.printerr('a fatal logic error occurred, bypassing the signal handler') IO.logerr('a fatal logic error occurred, bypassing the signal handler') sys.exit(1) except SystemExit as ex: # normal exit, call underlying C-API with exit code if ex.code == 0: IO.printinfo('exited successfully') IO.loginfo('exited successfully') os._exit(0) # an exception bubbled up, allow caller to handler it raise if __name__ == "__main__": main() ================================================ FILE: gui/dsiprouter_cron.py ================================================ #!/opt/dsiprouter/venv/bin/python # # summary: # dsiprouter cronjob interface script # usage: # /opt/dsiprouter/gui/dsiprouter_cron.py <module> <command> [<args...>] # supported modules: # api # cdr # fusionpbx # supported commands: # api - cleanleases # - synclicenses # cdr - sendreport <gwgroupid> # fusionpbx - sync # import sys if __name__ == '__main__': args = sys.argv[1:] if len(args) < 2: sys.exit(1) mod = args.pop(0) cmd = args.pop(0) if mod == 'api': if cmd == 'cleanleases': from modules.api.cron_functions import cleanupLeases cleanupLeases() sys.exit(0) elif cmd == 'synclicenses': from modules.api.licensemanager.functions import syncLicensesToGlobalState syncLicensesToGlobalState() sys.exit(0) elif mod == 'cdr': if cmd == 'sendreport' and len(args) > 0: from modules.cdr.cron_functions import sendCdrReport gwgroupid = args[0] sendCdrReport(gwgroupid) sys.exit(0) elif mod == 'fusionpbx': if cmd == 'sync': from modules.fusionpbx.fusionpbx_sync_functions import run_sync import settings run_sync(settings) sys.exit(0) sys.exit(1) ================================================ FILE: gui/dsiprouter_ut.py ================================================ import unittest from modules.domain.domain_service import * class TestUnit(unittest.TestCase): def setUp(self): pass def test_add_static_domain(self): self.assertTrue(addDomain('xyc.com')) def test_getDomains(self): res = getDomains() assert res is not None if __name__ == '__main__': unittest.main() ================================================ FILE: gui/modules/api/api.sql ================================================ DROP TABLE IF EXISTS `dsip_endpoint_lease`; CREATE TABLE `dsip_endpoint_lease` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwid` int(10) unsigned NOT NULL, `sid` int(10) unsigned NOT NULL, `expiration` datetime NOT NULL, PRIMARY KEY (`id`) ); DROP TABLE IF EXISTS `dsip_user`; CREATE TABLE `dsip_user` ( `id` INT NOT NULL auto_increment unique, `firstname` VARCHAR(255) NOT NULL, `lastname` VARCHAR(255) NULL, `username` VARCHAR(255) NOT NULL unique, `password` VARCHAR(255) NOT NULL, `roles` VARCHAR(255) NULL, `domains` VARCHAR(255) NULL, `token` VARCHAR(255) NULL, `token_expiration` DATETIME NULL, PRIMARY KEY (`id`)); ================================================ FILE: gui/modules/api/api_functions.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import re from functools import wraps from flask import jsonify, render_template, request, session from sqlalchemy import exc as sql_exceptions from werkzeug import exceptions as http_exceptions from shared import debugException, StatusCodes from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict from util.security import APIToken from modules.api.licensemanager.functions import getLicenseStatus import settings def createApiResponse(error=None, msg=None, kamreload=None, dsipreload=None, data=None, status_code=StatusCodes.HTTP_OK, **kwargs): """ Standardize a response from the API :param error: Type of error if one occurred (db|http|server|other) :type error: str :param msg: response message string :type msg: str :param kamreload: if kamailio requires a reload for latest changes to be live :type kamreload: bool :param dsipreload: if the entire dsiprouter platform requires a reload :type dsipreload: bool :param data: requested data to be returned, if any :type data: list :param status_code: HTTP status code :type status_code: int :param kwargs: eats any extra parameters :type kwargs: None :return: a response flask can serve to the user :rtype: (:class:`~flask.Response`, int) """ if error is None: error = '' if msg is None: msg = '' if kamreload is None: kamreload = getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] if dsipreload is None: dsipreload = getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_required'] if data is None: data = [] return jsonify({ 'error': error, 'msg': msg, 'kamreload': kamreload, 'dsipreload': dsipreload, 'data': data, }), status_code def showApiError(ex, payload=None): debugException(ex) if payload is None: payload = {} if isinstance(ex, sql_exceptions.SQLAlchemyError): payload['error'] = "db" if len(str(ex)) > 0: payload['msg'] = str(ex) else: payload['msg'] = "Unknown DB Error Occurred" status_code = StatusCodes.HTTP_INTERNAL_SERVER_ERROR elif isinstance(ex, http_exceptions.HTTPException): payload['error'] = "http" if len(ex.description) > 0: payload['msg'] = ex.description else: payload['msg'] = "Unknown HTTP Error Occurred" status_code = ex.code or StatusCodes.HTTP_INTERNAL_SERVER_ERROR else: payload['error'] = "server" if len(str(ex)) > 0: payload['msg'] = str(ex) else: payload['msg'] = "Unknown Error Occurred" status_code = StatusCodes.HTTP_INTERNAL_SERVER_ERROR return createApiResponse(**payload, status_code=status_code) def api_security(func): @wraps(func) def wrapper(*args, **kwargs): apiToken = APIToken(request) accept_header = request.headers.get('Accept', '') # If user is logged into a session return right away if session.get('logged_in'): return func(*args, **kwargs) else: if 'text/html' in accept_header: return render_template('index.html', version=settings.VERSION), StatusCodes.HTTP_UNAUTHORIZED # If API Request check for license if not re.match('text/html|text/css', accept_header, flags=re.IGNORECASE): flask_rule = request.url_rule.rule if request.url_rule is not None else '' # Check if they have a Core Subscription if flask_rule[:17] != '/api/v1/licensing' and getLicenseStatus(license_tag='DSIP_CORE') != 3: return createApiResponse( error='http', msg='Unauthorized - Core Subscription Required. Purchase from https://dopensource.com/product/dsiprouter-core/', status_code=StatusCodes.HTTP_UNAUTHORIZED ) # Check if token is valid if not apiToken.isValid(): return createApiResponse( error='http', msg='Unauthorized', status_code=StatusCodes.HTTP_UNAUTHORIZED ) # checks succeeded allow the request return func(*args, **kwargs) return wrapper ================================================ FILE: gui/modules/api/api_routes.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import os, time, random, subprocess, requests, csv, base64, codecs, re, socket, json from contextlib import closing from datetime import datetime from flask import Blueprint, jsonify, request, send_file, g from sqlalchemy import exc as sql_exceptions, and_, or_ from sqlalchemy.sql import text from werkzeug import exceptions as http_exceptions from werkzeug.utils import secure_filename from database import startSession, DummySession, Address, dSIPNotification, dSIPMultiDomainMapping, Gateways, \ GatewayGroups, Subscribers, dSIPLeases, dSIPMaintModes, dSIPCallSettings, InboundMapping, dSIPCDRInfo, \ dSIPCertificates, Dispatcher, dSIPDNIDEnrichment from shared import allowed_file, dictToStrFields, isCertValid, rowToDict, debugEndpoint, StatusCodes, \ strFieldsToDict, getRequestData, IO from util.pyasync import daemonize from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict from modules.api.api_functions import createApiResponse, showApiError, api_security from modules.api.kamailio.functions import reloadKamailio from util.networking import getExternalIP, hostToIP, safeUriToHost, safeStripPort from util.notifications import sendEmail from util.security import AES_CTR, urandomChars, KeyCertPair from util.file_handling import change_owner from util import kamtls, letsencrypt from util.cron import addTaggedCronjob, updateTaggedCronjob, deleteTaggedCronjob from sysloginit import initSyslogLogger import settings api = Blueprint('api', __name__) # TODO: we need to abstract out common code between gui and api @api.route("/api/v1/kamailio/stats", methods=['GET']) @api_security def getKamailioStats(): try: if (settings.DEBUG): debugEndpoint() jsonrpc_payload = {"jsonrpc": "2.0", "method": "tm.stats", "id": 1} r = requests.get('http://127.0.0.1:5060/api/kamailio', json=jsonrpc_payload) if r.status_code >= 400: ex = http_exceptions.HTTPException(r.reason) ex.code = r.status_code raise ex return createApiResponse( msg='Successfully retrieved kamailio stats', data=[r.json()['result']], ) except Exception as ex: return showApiError(ex) @api.route("/api/v1/reload/kamailio", methods=['POST']) @api_security def handleReloadKamailio(): try: if (settings.DEBUG): debugEndpoint() reloadKamailio() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = False return createApiResponse( msg='Kamailio reload succeeded', kamreload=False, ) except Exception as ex: return showApiError(ex) # TODO: state file is not thread/process safe, move to shared memory manager @api.route("/api/v1/reload/dsiprouter", methods=['GET', 'POST']) @api_security def handleReloadDsiprouter(): try: if (settings.DEBUG): debugEndpoint() # check on a current reload if request.method == 'GET': if getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_ongoing']: return createApiResponse( msg='dSIPRouter reload in progress', data=[False], status_code=StatusCodes.HTTP_ACCEPTED, ) return createApiResponse( msg='dSIPRouter reload complete', data=[True], ) # try the reload elif request.method == 'POST': if getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_ongoing']: return createApiResponse( msg='dSIPRouter reload in progress', status_code=StatusCodes.HTTP_ACCEPTED, ) # update globals before we reload so server stores them on teardown getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_ongoing'] = True getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = False getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_reload_required'] = False daemonize(['sudo', 'dsiprouter', 'restart', '-all', '-daemonize']) return createApiResponse( msg='dSIPRouter reload started', ) # method not allowed else: return createApiResponse( msg='Invalid HTTP method for this route', status_code=StatusCodes.HTTP_METHOD_NOT_ALLOWED, ) except Exception as ex: return showApiError(ex) # TODO: this func actually adds an endpoint lease # it should be renamed and changed to use POST method # TODO: the last lease id/username generated must be tracked (just query DB) # and used to determine next lease id, otherwise conflicts may occur @api.route("/api/v1/lease/endpoint", methods=['GET']) @api_security def getEndpointLease(): db = DummySession() DEF_PASSWORD_LEN = 32 lease_data = {} try: if (settings.DEBUG): debugEndpoint() db = startSession() email = request.args.get('email') if not email: raise Exception("email parameter is missing") ttl = request.args.get('ttl', None) if ttl is None: raise http_exceptions.BadRequest("time to live (ttl) parameter is missing") # Convert TTL to Seconds r = re.compile('\d*m|M') if r.match(ttl): ttl = 60 * int(ttl[0:(len(ttl) - 1)]) # Generate some values rand_num = random.randint(1, 200) name = "lease" + str(rand_num) auth_username = name auth_password = urandomChars(DEF_PASSWORD_LEN) auth_domain = settings.DEFAULT_AUTH_DOMAIN # Set some defaults host_addr = '' strip = 0 prefix = '' # Add the Gateways table Gateway = Gateways(name, host_addr, strip, prefix, settings.FLT_PBX) db.add(Gateway) db.flush() # Add the Subscribers table Subscriber = Subscribers(auth_username, auth_password, auth_domain, Gateway.gwid, email) db.add(Subscriber) db.flush() # Add to the Leases table Lease = dSIPLeases(Gateway.gwid, Subscriber.id, int(ttl)) db.add(Lease) db.flush() lease_data['leaseid'] = Lease.id lease_data['username'] = auth_username lease_data['password'] = auth_password lease_data['domain'] = auth_domain lease_data['ttl'] = ttl db.commit() # Install Cron job to clean up the leases #cron_cmd = '{} cleanleases'.format(settings.DSIP_PROJECT_DIR + '/gui/dsiprouter_cron.py') #if not addTaggedCronjob("lease_management", "* * * * *", cron_cmd): # raise Exception('Crontab entry could not be created') getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Lease created', data=[lease_data], kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/lease/endpoint/<int:leaseid>/revoke", methods=['DELETE']) @api_security def revokeEndpointLease(leaseid): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() # Query the Lease ID Lease = db.query(dSIPLeases).filter(dSIPLeases.id == leaseid).first() # Remove the entry in the Subscribers table Subscriber = db.query(Subscribers).filter(Subscribers.id == Lease.sid).first() db.delete(Subscriber) # Remove the entry in the Gateway table Gateway = db.query(Gateways).filter(Gateways.gwid == Lease.gwid).first() db.delete(Gateway) # Remove the entry in the Lease table db.delete(Lease) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Lease revoked', kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() # TODO: is this endpoint still used? @api.route("/api/v1/endpoint/<int:id>", methods=['POST']) @api_security def updateEndpoint(id): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() # Covert JSON message to Dictionary Object request_payload = getRequestData() # Only handling the maintmode attribute on this release if 'maintmode' not in request_payload: raise http_exceptions.BadRequest('maintmode attribute missing') if request_payload['maintmode'] == 0: MaintMode = db.query(dSIPMaintModes).filter(dSIPMaintModes.gwid == id).first() if MaintMode: db.delete(MaintMode) elif request_payload['maintmode'] == 1: # Lookup Gateway ip adddess Gateway = db.query(Gateways).filter(Gateways.gwid == id).first() if Gateway != None: MaintMode = dSIPMaintModes(Gateway.address, id) db.add(MaintMode) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Endpoint updated', kamreload=True, ) except sql_exceptions.IntegrityError as ex: db.rollback() db.flush() payload = {'msg': "endpoint {} is already in maintmode".format(id)} return showApiError(ex, payload) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() # TODO: we should optimize this and cleanup reused code @api.route("/api/v1/inboundmapping", methods=['GET', 'POST', 'PUT', 'DELETE']) @api_security def handleInboundMapping(): """ Endpoint for Inbound DID Rule Mapping """ db = DummySession() # use a whitelist to avoid possible SQL Injection vulns VALID_REQUEST_ARGS = {'ruleid', 'did'} # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {'did', 'servers', 'name'} payload = {'data': []} try: if (settings.DEBUG): debugEndpoint() db = startSession() # ========================================= # get rule for DID mapping or list of rules # ========================================= if request.method == "GET": # sanity check for arg in request.args: if arg not in VALID_REQUEST_ARGS: raise http_exceptions.BadRequest("Request argument not recognized") # get single rule by ruleid rule_id = request.args.get('ruleid') if rule_id is not None: res = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.ruleid == rule_id).first() if res is not None: data = rowToDict(res) data = {'ruleid': data['ruleid'], 'did': data['prefix'], 'name': strFieldsToDict(data['description'])['name'] if 'name' in strFieldsToDict( data['description']) else '', 'servers': data['gwlist'].split(',')} payload['data'].append(data) payload['msg'] = 'Rule Found' else: payload['msg'] = 'No Matching Rule Found' payload['status_code'] = StatusCodes.HTTP_NOT_FOUND # get single rule by did else: did_pattern = request.args.get('did') if did_pattern is not None: res = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.prefix == did_pattern).first() if res is not None: data = rowToDict(res) data = {'ruleid': data['ruleid'], 'did': data['prefix'], 'name': strFieldsToDict(data['description'])['name'] if 'name' in strFieldsToDict( data['description']) else '', 'servers': data['gwlist'].split(',')} payload['data'].append(data) payload['msg'] = 'DID Found' else: payload['msg'] = 'No Matching DID Found' payload['status_code'] = StatusCodes.HTTP_NOT_FOUND # get list of rules else: res = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).all() if len(res) > 0: for row in res: data = rowToDict(row) data = {'ruleid': data['ruleid'], 'did': data['prefix'], 'name': strFieldsToDict(data['description'])['name'] if 'name' in strFieldsToDict( data['description']) else '', 'servers': data['gwlist'].split(',')} payload['data'].append(data) payload['msg'] = 'Rules Found' else: payload['msg'] = 'No Rules Found' return createApiResponse(**payload) # =========================== # create rule for DID mapping # =========================== elif request.method == "POST": data = getRequestData() # sanity checks for arg in data: if arg not in VALID_REQUEST_DATA_ARGS: raise http_exceptions.BadRequest("Request data argument not recognized") if 'servers' not in data: raise http_exceptions.BadRequest('Servers to map DID to are required') elif len(data['servers']) < 1 or len(data['servers']) > 2: raise http_exceptions.BadRequest('Primary Server missing or More than 2 Servers Provided') elif 'did' not in data: raise http_exceptions.BadRequest('DID is required') # TODO: we should be checking dr_gateways table to make sure the servers exist for i in range(0, len(data['servers'])): try: data['servers'][i] = str(data['servers'][i]) # _ = int(data['servers'][i]) except: raise http_exceptions.BadRequest('Invalid Server ID') for c in data['did']: if c not in settings.DID_PREFIX_ALLOWED_CHARS: raise http_exceptions.BadRequest( 'DID improperly formatted. Allowed characters: {}'.format( ','.join(settings.DID_PREFIX_ALLOWED_CHARS))) gwlist = ','.join(data['servers']) prefix = data['did'] description = 'name:{}'.format(data['name']) if 'name' in data else '' # don't allow duplicate entries if db.query(InboundMapping).filter(InboundMapping.prefix == prefix).filter( InboundMapping.groupid == settings.FLT_INBOUND).scalar(): raise http_exceptions.BadRequest("Duplicate DID's are not allowed") IMap = InboundMapping(settings.FLT_INBOUND, prefix, gwlist, description) db.add(IMap) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True payload['kamreload'] = getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] payload['msg'] = 'Rule Created' return createApiResponse(**payload) # =========================== # update rule for DID mapping # =========================== elif request.method == "PUT": # sanity check for arg in request.args: if arg not in VALID_REQUEST_ARGS: raise http_exceptions.BadRequest("Request argument not recognized") data = getRequestData() updates = {} # sanity checks for arg in data: if arg not in VALID_REQUEST_DATA_ARGS: raise http_exceptions.BadRequest("Request data argument not recognized") if 'did' not in data and 'servers' not in data and 'name' not in data: raise http_exceptions.BadRequest("No data args supplied, {did, and servers} is required") # TODO: we should be checking dr_gateways table to make sure the servers exist if 'servers' in data: if len(data['servers']) < 1 or len(data['servers']) > 2: raise http_exceptions.BadRequest('Primary Server missing or More than 2 Servers Provided') else: for i in range(0, len(data['servers'])): try: data['servers'][i] = str(data['servers'][i]) # _ = int(data['servers'][i]) except: raise http_exceptions.BadRequest('Invalid Server ID') updates['gwlist'] = ','.join(data['servers']) if 'did' in data: for c in data['did']: if c not in settings.DID_PREFIX_ALLOWED_CHARS: raise http_exceptions.BadRequest( 'DID improperly formatted. Allowed characters: {}'.format( ','.join(settings.DID_PREFIX_ALLOWED_CHARS))) updates['prefix'] = data['did'] if 'name' in data: updates['description'] = 'name:{}'.format(data['name']) # update single rule by ruleid rule_id = request.args.get('ruleid') if rule_id is not None: res = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.ruleid == rule_id).update( updates, synchronize_session=False) if res > 0: payload['msg'] = 'Rule Updated' else: payload['msg'] = 'No Matching Rule Found' # update single rule by did else: did_pattern = request.args.get('did') if did_pattern is not None: res = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.prefix == did_pattern).update( updates, synchronize_session=False) if res > 0: payload['msg'] = 'Rule Updated' else: payload['msg'] = 'No Matching Rule Found' # no other options else: raise http_exceptions.BadRequest('One of the following is required: {ruleid, or did}') db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True payload['kamreload'] = getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] return createApiResponse(**payload) # =========================== # delete rule for DID mapping # =========================== elif request.method == "DELETE": # sanity check for arg in request.args: if arg not in VALID_REQUEST_ARGS: raise http_exceptions.BadRequest("Request argument not recognized") # delete single rule by ruleid rule_id = request.args.get('ruleid') if rule_id is not None: rule = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.ruleid == rule_id) if rule.delete(synchronize_session=False) == 0: payload['msg'] = 'No Rules Found' payload['status_code'] = StatusCodes.HTTP_NOT_FOUND # delete single rule by did else: did_pattern = request.args.get('did') if did_pattern is not None: rule = db.query(InboundMapping).filter(InboundMapping.groupid == settings.FLT_INBOUND).filter( InboundMapping.prefix == did_pattern) if rule.delete(synchronize_session=False) == 0: payload['msg'] = 'No Rules Found' payload['status_code'] = StatusCodes.HTTP_NOT_FOUND # no other options else: raise http_exceptions.BadRequest('One of the following is required: {ruleid, or did}') db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True payload['kamreload'] = True payload['msg'] = 'Rule Deleted' return createApiResponse(**payload) # not possible else: payload['msg'] = 'Invalid HTTP method for this route' return createApiResponse( **payload, status_code=StatusCodes.HTTP_METHOD_NOT_ALLOWED ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/notification/gwgroup", methods=['POST']) @api_security def handleNotificationRequest(): """ Endpoint for Sending Notifications """ db = DummySession() # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {'gwgroupid': int, 'type': int, 'text_body': str, 'gwid': int, 'subject': str, 'sender': str} try: if (settings.DEBUG): debugEndpoint() db = startSession() # ============================ # create and send notification # ============================ data = getRequestData() # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) if 'gwgroupid' not in data: raise http_exceptions.BadRequest('Gateway Group ID is required') elif 'type' not in data: raise http_exceptions.BadRequest('Notification Type is required') elif 'text_body' not in data: raise http_exceptions.BadRequest('Text Body is required') # lookup recipients gwgroupid = data.pop('gwgroupid') notif_type = data.pop('type') notification_row = db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid).filter( dSIPNotification.type == notif_type).first() if notification_row is None: raise sql_exceptions.SQLAlchemyError('DB Entry Missing for {}'.format(str(gwgroupid))) # customize message based on type gwid = data.pop('gwid', None) gw_row = db.query(Gateways).filter(Gateways.gwid == gwid).first() if gwid is not None else None gw_name = strFieldsToDict(gw_row.description)['name'] if gw_row is not None else '' gwgroup_row = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid).first() gwgroup_name = strFieldsToDict(gwgroup_row.description)['name'] if gwgroup_row is not None else '' if notif_type == dSIPNotification.FLAGS.TYPE_OVERLIMIT.value: data['html_body'] = ( '<html><head><style>.error{{border: 1px solid; margin: 10px 0px; padding: 15px 10px 15px 50px; background-color: #FF5555;}}</style></head>' '<body><div class="error"><strong>Call Limit Exceeded in Endpoint Group [{}] on Endpoint [{}]</strong></div></body>').format( gwgroup_name, gw_name) elif notif_type == dSIPNotification.FLAGS.TYPE_GWFAILURE.value: data['html_body'] = ( '<html><head><style>.error{{border: 1px solid; margin: 10px 0px; padding: 15px 10px 15px 50px; background-color: #FF5555;}}</style></head>' '<body><div class="error"><strong>Failure Detected in Endpoint Group [{}] on Endpoint [{}]</strong></div></body>').format( gwgroup_name, gw_name) # # get attachments if any uploaded # data['attachments'] = [] # if len(request.files) > 0: # for upload in request.files: # if upload.filename != '' and isValidFile(upload.filename): # data['attachments'].append(upload) # TODO: we only support email at this time, add support for slack if notification_row.method == dSIPNotification.FLAGS.METHOD_EMAIL.value: data['recipients'] = [notification_row.value] sendEmail(**data) elif notification_row.method == dSIPNotification.FLAGS.METHOD_SLACK.value: pass return createApiResponse(msg='Email Sent') except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/endpointgroups/<int:gwgroupid>", methods=['DELETE']) @api_security def deleteEndpointGroup(gwgroupid): db = DummySession() try: if settings.DEBUG: debugEndpoint() db = startSession() endpointgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid) if endpointgroup is not None: endpointgroup.delete(synchronize_session=False) else: raise http_exceptions.NotFound("The endpoint group doesn't exist") call_settings = db.query(dSIPCallSettings).filter(dSIPCallSettings.gwgroupid == gwgroupid) if call_settings is not None: call_settings.delete(synchronize_session=False) subscriber = db.query(Subscribers).filter(Subscribers.rpid == gwgroupid) if subscriber is not None: subscriber.delete(synchronize_session=False) endpoints = db.query(Gateways).filter( Gateways.description.regexp_match(f'gwgroup:{gwgroupid}(,|$)') ) if endpoints is not None: address_ids = [] for endpoint in endpoints: description_dict = strFieldsToDict(endpoint.description) if 'addr_id' in description_dict: address_ids.append(description_dict['addr_id']) db.query(Address).filter(Address.id.in_(address_ids)).delete(synchronize_session=False) endpoints.delete(synchronize_session=False) notifications = db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid) if notifications is not None: notifications.delete(synchronize_session=False) cdrinfo = db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid) if cdrinfo is not None: cdrinfo.delete(synchronize_session=False) deleteTaggedCronjob(gwgroupid) # if not deleteTaggedCronjob(gwgroupid): # raise Exception('Crontab entry could not be deleted') domainmapping = db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == gwgroupid) if domainmapping.count() > 0: # Get list of all domains managed by the endpoint group did_list = db.execute( text("SELECT DISTINCT did FROM domain_attrs WHERE name = 'created_by' AND value=:gwgroupid"), {'gwgroupid': gwgroupid} ).all() # Delete all domains db.execute( text("DELETE FROM domain WHERE did IN (SELECT did FROM domain_attrs WHERE name='created_by' AND value=:gwgroupid)"), {'gwgroupid': gwgroupid} ) # Delete all domains_attrs if len(did_list) > 0: for did in did_list: db.execute( text("DELETE FROM domain_attrs WHERE did IN (:dids)"), {'dids': str(did[0])} ) # Delete domain mapping, which will stop the fusionpbx sync domainmapping.delete(synchronize_session=False) dispatcher = db.query(Dispatcher).filter( (Dispatcher.setid == gwgroupid) | (Dispatcher.setid == int(gwgroupid) + 1000) ) if dispatcher is not None: dispatcher.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse(msg='EndpointGroup deleted', kamreload=True) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/endpointgroups/<int:gwgroupid>", methods=['GET']) @api_security def getEndpointGroup(gwgroupid): db = DummySession() gwgroup_data = {} try: if settings.DEBUG: debugEndpoint() db = startSession() gwgroupid = int(gwgroupid) endpointgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid).first() if endpointgroup is not None: gwgroup_data['name'] = strFieldsToDict(endpointgroup.description)['name'] else: raise http_exceptions.NotFound("Endpoint Group Does Not Exist") # Send back the gateway groupid that was requested gwgroup_data['gwgroupid'] = gwgroupid call_settings = db.query(dSIPCallSettings).filter(dSIPCallSettings.gwgroupid == gwgroupid).first() if call_settings is not None: gwgroup_data['call_settings'] = { 'limit': call_settings.limit, 'timeout': call_settings.timeout } else: gwgroup_data['call_settings'] = {} # Check to see if a subscriber record exists. If so, auth is userpwd auth = {} subscriber = db.query(Subscribers).filter(Subscribers.rpid == gwgroupid).first() if subscriber is not None: auth['type'] = "userpwd" auth['user'] = subscriber.username auth['pass'] = subscriber.password auth['domain'] = subscriber.domain else: auth['type'] = "ip" gwgroup_data['auth'] = auth gwgroup_data['endpoints'] = [] endpoint_weights = {} endpoint_keepalives = {} endpoints = db.query(Gateways).filter( Gateways.gwid.in_(endpointgroup.gwlist.split(',')) ).all() dispatcher_rows = db.query(Dispatcher).filter( (Dispatcher.setid == gwgroupid) | (Dispatcher.setid == gwgroupid + 1000) ).all() if dispatcher_rows is not None: for row in dispatcher_rows: dst_host = row.destination.split(':', maxsplit=1)[1] attrs = row.attrsToDict() if attrs.get('rweight', None) is not None: endpoint_weights[dst_host] = attrs['rweight'] # TODO: make this check part of the DB object methods if Dispatcher.FLAGS['KEEP_ALIVE'] & row.flags: endpoint_keepalives[dst_host] = 1 else: endpoint_keepalives[dst_host] = 0 for endpoint in endpoints: host_split = endpoint.address.split(':') attrs_dict = endpoint.attrsToDict() gwgroup_data['endpoints'].append({ 'gwid': endpoint.gwid, 'host': host_split[0], 'port': int(host_split[1]) if len(host_split) > 1 else 5060, 'signalling': attrs_dict['signalling'], 'media': attrs_dict['media'], 'description': strFieldsToDict(endpoint.description)['name'], 'rweight': endpoint_weights[endpoint.address] if endpoint.address in endpoint_weights else 0, 'keepalive': endpoint_keepalives[endpoint.address] if endpoint.address in endpoint_keepalives else 0, 'maintmode': '' }) gwgroup_data['strip'] = endpoint.strip gwgroup_data['prefix'] = endpoint.pri_prefix # Notifications notifications = {} n = db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid) for notification in n: # if notification.type == dSIPNotification.FLAGS.TYPE_MAXCALLLIMIT.value: if notification.type == 0: notifications['overmaxcalllimit'] = notification.value # if notification.type == dSIPNotification.FLAGS.TYPE_ENDPOINTFAILURE.value: if notification.type == 1: notifications['endpointfailure'] = notification.value gwgroup_data['notifications'] = notifications gwgroup_data['fusionpbx'] = {} domainmapping = db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == gwgroupid).first() if domainmapping is not None: gwgroup_data['fusionpbx']['enabled'] = "1" gwgroup_data['fusionpbx']['dbhost'] = domainmapping.db_host gwgroup_data['fusionpbx']['dbuser'] = domainmapping.db_username gwgroup_data['fusionpbx']['dbpass'] = domainmapping.db_password # CDR info gwgroup_data['cdr'] = {} cdrinfo = db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid).first() if cdrinfo is not None: gwgroup_data['cdr']['cdr_email'] = cdrinfo.email gwgroup_data['cdr']['cdr_send_interval'] = cdrinfo.send_interval return createApiResponse( msg='Endpoint group found', data=[gwgroup_data], ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() # TODO: should reuse getEndpointGroup() function and return all settings for each gwgroup @api.route("/api/v1/endpointgroups", methods=['GET']) @api_security def listEndpointGroups(): db = DummySession() response_data = [] try: if settings.DEBUG: debugEndpoint() db = startSession() endpointgroups = db.query(GatewayGroups).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.ENDPOINT.value) ).all() for endpointgroup in endpointgroups: # Grap the description field, which is comma seperated key/value pair fields = strFieldsToDict(endpointgroup.description) # append summary of endpoint group data response_data.append({ 'gwgroupid': endpointgroup.id, 'name': fields['name'], 'gwlist': endpointgroup.gwlist }) return createApiResponse( msg='Endpoint groups found', data=response_data, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/endpointgroups", methods=['PUT']) @api.route("/api/v1/endpointgroups/<int:gwgroupid>", methods=['PUT']) @api_security def updateEndpointGroups(gwgroupid=None): """ Update a single Endpoint Group\n The gwgroupid can be provided in url path or payload =============== Request Payload =============== .. code-block:: json { name: <string>, call_settings: { limit: <int>, timeout: <int> }, auth: { type: "ip"|"userpwd", user: <string> pass: <string> domain: <string> }, endpoints [ { gwid:<int>, host:<string>, port:<int>, signalling:<string>, media:<string>, description:<string>, rweight:<int>, keepalive:<int> }, ... ], strip: <int>, prefix: <string>, notifications: { overmaxcalllimit: <string>, endpointfailure: <string> }, cdr: { cdr_email: <string>, cdr_send_interval: <string> } fusionpbx: { enabled: <bool>, dbhost: <string>, dbuser: <string>, dbpass: <string> } } ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ { gwgroupid: <int>, endpoints: [ <int>, ... ] } ] } """ db = DummySession() # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"gwgroupid": int, "name": str, "call_settings": dict, "auth": dict, "strip": int, "prefix": str, "notifications": dict, "cdr": dict, "fusionpbx": dict, "endpoints": list} # ensure requred args are provided REQUIRED_ARGS = {'gwgroupid'} gwgroup_data = {} try: if (settings.DEBUG): debugEndpoint() db = startSession() # get request data request_payload = getRequestData() if gwgroupid is not None: gwgroupid = int(gwgroupid) gwgroupid_str = str(gwgroupid) request_payload['gwgroupid'] = gwgroupid else: if 'gwgroupid' in request_payload: gwgroupid = int(request_payload['gwgroupid']) request_payload['gwgroupid'] = gwgroupid gwgroupid_str = str(gwgroupid) # sanity checks for k, v in request_payload.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in REQUIRED_ARGS: raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) if 'prefix' in request_payload: for c in request_payload['prefix']: if c not in settings.DID_PREFIX_ALLOWED_CHARS: raise http_exceptions.BadRequest( "Request argument 'prefix' not valid. Allowed characters: {}".format( ','.join(settings.DID_PREFIX_ALLOWED_CHARS))) # Update gateway group name Gwgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid).first() if Gwgroup is None: raise http_exceptions.NotFound('gwgroup does not exist') gwgroup_data['gwgroupid'] = gwgroupid gwgroup_desc_dict = strFieldsToDict(Gwgroup.description) gwgroup_desc_dict['name'] = request_payload['name'] if 'name' in request_payload else '' Gwgroup.description = dictToStrFields(gwgroup_desc_dict) db.flush() # Update the gateway group call settings call_settings_data = request_payload['call_settings'] if 'call_settings' in request_payload else {} if db.query(dSIPCallSettings).filter(dSIPCallSettings.gwgroupid == gwgroupid).update({ 'limit': call_settings_data['limit'], 'timeout': call_settings_data['timeout'] }, synchronize_session=False): pass else: call_settings = dSIPCallSettings(gwgroupid, **call_settings_data) db.add(call_settings) # runtime defaults for this route strip = request_payload['strip'] if 'strip' in request_payload else 0 prefix = request_payload['prefix'] if 'prefix' in request_payload else "" authtype = request_payload['auth']['type'] \ if 'auth' in request_payload and 'type' in request_payload['auth'] else "" if 'fusionpbx' in request_payload: fusionpbxenabled = int(request_payload['fusionpbx']['enabled']) \ if 'enabled' in request_payload['fusionpbx'] else 0 fusionpbxdbhost = request_payload['fusionpbx']['dbhost'] \ if 'dbhost' in request_payload['fusionpbx'] else None fusionpbxdbuser = request_payload['fusionpbx']['dbuser'] \ if 'dbuser' in request_payload['fusionpbx'] else None fusionpbxdbpass = request_payload['fusionpbx']['dbpass'] \ if 'dbpass' in request_payload['fusionpbx'] else None else: fusionpbxenabled = 0 # Update the AuthType userpwd settings if authtype == "userpwd": authuser = request_payload['auth']['user'] \ if 'user' in request_payload['auth'] and len(request_payload['auth']['user']) > 0 else None authpass = request_payload['auth']['pass'] \ if 'pass' in request_payload['auth'] and len(request_payload['auth']['pass']) > 0 else None authdomain = request_payload['auth']['domain'] \ if 'domain' in request_payload['auth'] and len( request_payload['auth']['domain']) > 0 else settings.DEFAULT_AUTH_DOMAIN authdomain = safeUriToHost(authdomain) if authuser is None or authpass is None: raise http_exceptions.BadRequest("Auth username or password invalid") if authdomain is None: raise http_exceptions.BadRequest("Auth domain is malformed") # Get the existing username and domain_name if it exists currentSubscriberInfo = db.query(Subscribers).filter(Subscribers.rpid == gwgroupid).first() if currentSubscriberInfo is not None: # Check if the new username and domain is unique if db.query(Subscribers).filter( Subscribers.username == authuser, Subscribers.domain == authdomain, Subscribers.id != currentSubscriberInfo.id ).scalar(): raise http_exceptions.BadRequest("Subscriber username already taken") # Update the Subscriber Info else: currentSubscriberInfo.username = authuser currentSubscriberInfo.password = authpass currentSubscriberInfo.domain = authdomain # Create a new Suscriber entry else: Subscriber = Subscribers(authuser, authpass, authdomain, gwgroupid_str) db.add(Subscriber) # Delete the Subscriber info if IP is selected elif authtype == "ip": subscriber = db.query(Subscribers).filter(Subscribers.rpid == gwgroupid) if subscriber is not None: subscriber.delete(synchronize_session=False) # Update endpoints # Get List of existing endpoints # If endpoint sent over is not in the existing endpoint list then remove it gwgroup_filter = f'gwgroup:{gwgroupid_str}(,|$)' current_endpoints_lut = { x.gwid: { 'address': x.address, 'type': x.type, 'description_dict': strFieldsToDict(x.description), 'attrs_dict': x.attrsToDict() } \ for x in db.query(Gateways).filter( Gateways.description.regexp_match(gwgroup_filter) ).all() } updated_endpoints = request_payload['endpoints'] if "endpoints" in request_payload else [] unprocessed_endpoints_lut = {} gwlist = [] # endpoints to add unconditionally for endpoint in updated_endpoints: gwid = endpoint['gwid'] if 'gwid' in endpoint else None # If gwid is empty then this is a new endpoint if gwid is None: if 'host' not in endpoint: raise http_exceptions.BadRequest("Endpoint hostname/address is required") host = endpoint['host'] port = int(endpoint['port']) if endpoint.get('port', None) is not None else 5060 signalling = endpoint['signalling'] if 'signalling' in endpoint else 'proxy' media = endpoint['media'] if 'media' in endpoint else 'proxy' name = endpoint['description'] if 'description' in endpoint else '' rweight = int(endpoint['rweight']) if endpoint.get('rweight', '') != '' else 1 keepalive = int(endpoint['keepalive']) if endpoint.get('keepalive', '') != '' else 0 flags = 0 if keepalive > 0: flags += Dispatcher.FLAGS['KEEP_ALIVE'] if rweight == 0: flags += Dispatcher.FLAGS['DISABLED_DST'] sip_addr = safeUriToHost(host, default_port=port) if sip_addr is None: raise http_exceptions.BadRequest("Endpoint hostname/address is malformed") if authtype == "ip": # for ip auth we must create address records for the endpoint host_ip = hostToIP(host) # TODO: address entries should include port user specified Addr = Address(name, host_ip, 32, settings.FLT_PBX, gwgroup=gwgroupid) db.add(Addr) db.flush() Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, addr_id=Addr.id, signalling=signalling, media=media) else: # we know this a new endpoint so we don't have to check for any address records here Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, signalling=signalling, media=media) db.add(Gateway) db.flush() # Create dispatcher group with the set id being the gateway group id sip_uri = f'sip:{sip_addr}' dispatcher = Dispatcher(setid=gwgroupid, destination=sip_uri, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(dispatcher) # Create dispatcher for FusionPBX external interface if FusionPBX feature is enabled if fusionpbxenabled: sip_uri_ext = f'sip:{safeStripPort(sip_addr)}:5080' setid_ext = gwgroupid + 1000 # Add 1000 to the gwgroupid so that the setid for the FusionPBX external interface is 1000 apart dispatcher = Dispatcher(setid=setid_ext, destination=sip_uri_ext, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(dispatcher) gwlist.append(Gateway.gwid) # Process separately else: unprocessed_endpoints_lut[gwid] = endpoint # conditionally adding/updating/deleting endpoints (using set theory) # generally endpoints won't be added with gwid specified but its possible current_gwids = set(current_endpoints_lut.keys()) updated_gwids = set(unprocessed_endpoints_lut.keys()) add_gwids = updated_gwids - current_gwids upd_gwids = current_gwids & updated_gwids del_gwids = current_gwids - updated_gwids # conditional endpoints to add for gwid in add_gwids: endpoint = unprocessed_endpoints_lut[gwid] if 'host' not in endpoint: raise http_exceptions.BadRequest("Endpoint hostname/address is required") host = endpoint['host'] port = int(endpoint['port']) if endpoint.get('port', None) is not None else 5060 signalling = endpoint['signalling'] if 'signalling' in endpoint else 'proxy' media = endpoint['media'] if 'media' in endpoint else 'proxy' name = endpoint['description'] if 'description' in endpoint else '' rweight = int(endpoint['rweight']) if endpoint.get('rweight', '') != '' else 1 keepalive = int(endpoint['keepalive']) if endpoint.get('keepalive', '') != '' else 0 flags = 0 if keepalive > 0: flags += Dispatcher.FLAGS['KEEP_ALIVE'] if rweight == 0: flags += Dispatcher.FLAGS['DISABLED_DST'] sip_addr = safeUriToHost(host, default_port=port) if sip_addr is None: raise http_exceptions.BadRequest("Endpoint hostname/address is malformed") if authtype == "ip": # for ip auth we must create address records for the endpoint host_ip = hostToIP(host) # TODO: address entries should include port user specified Addr = Address(name, host_ip, 32, settings.FLT_PBX, gwgroup=gwgroupid) db.add(Addr) db.flush() Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, addr_id=Addr.id, signalling=signalling, media=media) else: # we know this a new endpoint so we don't have to check for any address records here Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, signalling=signalling, media=media) db.add(Gateway) db.flush() # Create dispatcher group with the set id being the gateway group id sip_uri = f'sip:{sip_addr}' dispatcher = Dispatcher(setid=gwgroupid, destination=sip_uri, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(dispatcher) # Create dispatcher for FusionPBX external interface if FusionPBX feature is enabled if fusionpbxenabled: sip_uri_ext = f'sip:{safeStripPort(sip_addr)}:5080' setid_ext = gwgroupid + 1000 # Add 1000 to the gwgroupid so that the setid for the FusionPBX external interface is 1000 apart dispatcher = Dispatcher(setid=setid_ext, destination=sip_uri_ext, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(dispatcher) # we ignore the given gwid and allow DB to assign one instead gwlist.append(Gateway.gwid) # conditional endpoints to update for gwid in upd_gwids: endpoint = unprocessed_endpoints_lut[gwid] current_endpoint = current_endpoints_lut[gwid] endpoint_fields = current_endpoint['description_dict'] endpoint_attrs = current_endpoint['attrs_dict'] # allow updating single fields (if not provided set to current value) host = endpoint['host'] if 'host' in endpoint else current_endpoint['address'].split(':')[0] if 'port' in endpoint and endpoint['port'] is not None: port = int(endpoint['port']) else: tmp = current_endpoint['address'].split(':') port = int(tmp[1]) if len(tmp) > 1 else 5060 signalling = endpoint['signalling'] if 'signalling' in endpoint else endpoint_attrs['signalling'] media = endpoint['media'] if 'media' in endpoint else endpoint_attrs['media'] name = endpoint['description'] if 'description' in endpoint else endpoint_fields['name'] endpoint_fields['name'] = name # fields not in dr_gateways attributes we set to None if not updated and check it after querying dispatcher rweight = int(endpoint['rweight']) if endpoint.get('rweight', None) is not None else None keepalive = int(endpoint['keepalive']) if endpoint.get('keepalive', None) is not None else None if len(host) == 0: raise http_exceptions.BadRequest("Endpoint hostname/address is required") sip_addr = safeUriToHost(host, default_port=port) if sip_addr is None: raise http_exceptions.BadRequest("Endpoint hostname/address is malformed") if authtype == "ip": # for ip auth we must create address records for the endpoint host_ip = hostToIP(host) # if address exists update, otherwise create it address_exists = False if 'addr_id' in endpoint_fields and len(endpoint_fields['addr_id']) > 0: Addr = db.query(Address).filter(Address.id == endpoint_fields['addr_id']).first() if Addr is not None: address_exists = True Addr.ip_addr = host_ip addr_fields = strFieldsToDict(Addr.tag) addr_fields['name'] = name addr_fields['gwgroup'] = gwgroupid_str Addr.tag = dictToStrFields(addr_fields) if not address_exists: Addr = Address(name, host_ip, 32, settings.FLT_PBX, gwgroup=gwgroupid) db.add(Addr) db.flush() endpoint_fields['addr_id'] = str(Addr.id) else: # if not using ip auth make sure we delete any old address records for the endpoint if 'addr_id' in endpoint_fields and len(endpoint_fields['addr_id']) > 0: Addr = db.query(Address).filter(Address.id == endpoint_fields['addr_id']) if Addr is not None: Addr.delete(synchronize_session=False) # remove addr_id field from endpoint description endpoint_fields.pop("addr_id", None) # find the current entry in dr_gateways ep_gateway = db.query(Gateways).filter(Gateways.gwid == gwid).first() # find the current dispatcher entry (load balancing one is always there and has same params as other "feature" sets) ep_dispatcher = db.query(Dispatcher).filter( (Dispatcher.setid == gwgroupid) & Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).first() if ep_dispatcher is not None: if rweight is None: rweight = ep_dispatcher.attrsToDict()['rweight'] if keepalive is None: keepalive = 8 & ep_dispatcher.flags else: if rweight is None: rweight = 0 if keepalive is None: keepalive = 0 flags = 0 if keepalive > 0: flags += Dispatcher.FLAGS['KEEP_ALIVE'] if rweight == 0: flags += Dispatcher.FLAGS['DISABLED_DST'] # update the endpoint in dispatcher sip_uri = f'sip:{sip_addr}' if ep_dispatcher is not None: ep_dispatcher.destination = sip_uri ep_dispatcher.flags = flags ep_dispatcher.attrs = Dispatcher.buildAttrs(rweight, signalling, media) ep_dispatcher.description = Dispatcher.buildDescription(name, gwid) else: ep_dispatcher = Dispatcher(setid=gwgroupid, destination=sip_uri, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(ep_dispatcher) # update the fusionpbx dispatcher entries for the endpoint sip_uri_ext = f'sip:{safeStripPort(sip_addr)}:5080' setid_ext = int(gwgroupid) + 1000 if fusionpbxenabled: ep_dispatcher_ext = db.query(Dispatcher).filter( (Dispatcher.setid == setid_ext) & Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).first() if ep_dispatcher_ext is not None: ep_dispatcher_ext.destination = sip_uri_ext ep_dispatcher_ext.flags = flags ep_dispatcher_ext.attrs = Dispatcher.buildAttrs(rweight, signalling, media) ep_dispatcher_ext.description = Dispatcher.buildDescription(name, gwid) else: ep_dispatcher_ext = Dispatcher(setid=setid_ext, destination=sip_uri_ext, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=gwid) db.add(ep_dispatcher_ext) else: # remove the entries if fusionpbx integration is now disabled db.query(Dispatcher).filter( (Dispatcher.setid == setid_ext) & Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).delete(synchronize_session=False) # update the endpoint in dr_gateways ep_gateway.description = dictToStrFields(endpoint_fields) ep_gateway.address = sip_addr ep_gateway.strip = strip ep_gateway.pri_prefix = prefix ep_gateway.attrs = Gateways.buildAttrs(gwid=gwid, type=current_endpoint['type'], signalling=signalling, media=media) gwlist.append(gwid) # conditional endpoints to delete # we also cleanup house here in case of stray entries del_gateways = db.query(Gateways).filter( Gateways.gwid.in_(del_gwids) & (Gateways.address != "localhost") ) del_gateways_cleanup = db.query(Gateways).filter( Gateways.description.regexp_match(gwgroup_filter) & Gateways.gwid.notin_(gwlist) & (Gateways.address != "localhost") ) # make sure we delete any associated address entries del_addr_ids = [] for gateway in del_gateways.union(del_gateways_cleanup): description_dict = strFieldsToDict(gateway.description) if 'addr_id' in description_dict: del_addr_ids.append(description_dict['addr_id']) db.query(Address).filter(Address.id.in_(del_addr_ids)).delete(synchronize_session=False) # delete the dispatcher entries that correspond to the endpoints/gateways that was Deleted del_gw_filters = [ f'gwid={gateway.gwid}(;|$)' for gateway in del_gateways.union(del_gateways_cleanup) ] # the default match for REGEXP is true, make sure we actually have gwids to match against if len(del_gw_filters) > 0: db.query(Dispatcher).filter( (Dispatcher.setid == gwgroupid) & Dispatcher.description.regexp_match('|'.join(del_gw_filters)) ).delete(synchronize_session=False) del_gateways.delete(synchronize_session=False) del_gateways_cleanup.delete(synchronize_session=False) # set return gwlist and update the endpoint group's gwlist gwgroup_data['endpoints'] = gwlist gwlist_str = ','.join([str(gw) for gw in gwlist]) db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid).update( {"gwlist": gwlist_str}, synchronize_session=False) # Update notifications if 'notifications' in request_payload: overmaxcalllimit = request_payload['notifications']['overmaxcalllimit'] \ if 'overmaxcalllimit' in request_payload['notifications'] else None endpointfailure = request_payload['notifications']['endpointfailure'] \ if 'endpointfailure' in request_payload['notifications'] else None if overmaxcalllimit is not None: # Try to update if db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid).filter( dSIPNotification.type == 0).update({"value": overmaxcalllimit}, synchronize_session=False): pass # Otherwise Add else: notif_type = 0 notification = dSIPNotification(gwgroupid, notif_type, 0, overmaxcalllimit) db.add(notification) # Remove else: db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid).filter( dSIPNotification.type == 0).delete(synchronize_session=False) if endpointfailure is not None: # Try to update if db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid).filter( dSIPNotification.type == 1).update({"value": endpointfailure}, synchronize_session=False): pass # Otherwise Add else: notif_type = 1 notification = dSIPNotification(gwgroupid, notif_type, 0, endpointfailure) db.add(notification) # Remove else: db.query(dSIPNotification).filter(dSIPNotification.gwgroupid == gwgroupid).filter( dSIPNotification.type == 1).delete(synchronize_session=False) # Update CDR if 'cdr' in request_payload: cdr_email = request_payload['cdr']['cdr_email'] \ if 'cdr_email' in request_payload['cdr'] else None cdr_send_interval = request_payload['cdr']['cdr_send_interval'] \ if 'cdr_send_interval' in request_payload['cdr'] else None if len(cdr_email) > 0 and len(cdr_send_interval) > 0: cron_cmd = '{} cdr sendreport {}'.format( (settings.DSIP_PROJECT_DIR + '/gui/dsiprouter_cron.py'), gwgroupid_str ) # Try to update if db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid).update( {"email": cdr_email, "send_interval": cdr_send_interval}, synchronize_session=False): if not updateTaggedCronjob(gwgroupid, cdr_send_interval): # in-case the entry was modified elsewhere we can just create it again if not addTaggedCronjob(gwgroupid, cdr_send_interval, cron_cmd): raise Exception('Crontab entry could not be updated') else: cdrinfo = dSIPCDRInfo(gwgroupid, cdr_email, cdr_send_interval) db.add(cdrinfo) if not addTaggedCronjob(gwgroupid, cdr_send_interval, cron_cmd): raise Exception('Crontab entry could not be created') else: # Remove CDR Info cdrinfo = db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid).first() if cdrinfo is not None: db.delete(cdrinfo) if not deleteTaggedCronjob(gwgroupid): raise Exception('Crontab entry could not be deleted') # Update FusionPBX # Update if fusionpbxenabled == 1: # Update if db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == gwgroupid).update( {"pbx_id": gwgroupid, "db_host": fusionpbxdbhost, "db_username": fusionpbxdbuser, "db_password": fusionpbxdbpass}, synchronize_session=False): pass else: # Create new record domainmapping = dSIPMultiDomainMapping(gwgroupid, fusionpbxdbhost, fusionpbxdbuser, fusionpbxdbpass, type=dSIPMultiDomainMapping.FLAGS.TYPE_FUSIONPBX.value) db.add(domainmapping) # Delete elif fusionpbxenabled == 0: db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == gwgroupid).delete( synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Endpoint group updated', data=[gwgroup_data], kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() # TODO: fix up like updateEnpointGroups() # TODO: this is the only route that has the proper updated documentation format # use this route as an example to populate the other route docstings @api.route("/api/v1/endpointgroups", methods=['POST']) @api_security def addEndpointGroups(data=None, endpointGroupType=None, domain=None): """ Add a single Endpoint Group :<json string name: the endpoint group name :<json integer calllimit: limit concurrent calls to this endpoint group (0=unlimited) :<json object auth: authentication settings for this endpoint group :reqheader Accept: optional, assumed to be application/json :reqheader Authorization: required, dSIPRouter API Bearer token :resheader Content-Type: required, should always be application/json :status 200: on success :status 401: on auth failure :status 500: on unhandled application server error :status 502: when application server is down but reverse proxy is still up Example Request: .. code-block:: json { "name": "example", "call_settings": { limit: 5, timeout: 3600 }, "auth": { "type": "userpwd", "user": "example", "pass": "example", "domain": "example.com" }, "endpoints": [ { "host": "www.example.com", "port": 5060, "signalling": "proxy", "media": "proxy", "description": "example", "rweight": 1, "keepalive": 1 } ], "strip": 0, "prefix": "", "notifications": { "overmaxcalllimit": "email@example.com", "endpointfailure": "email@example.com" }, "cdr": { "cdr_email": "email@example.com", "cdr_send_interval": "email@example.com" }, "fusionpbx": { "enabled": true, "dbhost": "example", "dbuser": "example", "dbpass": "example" } } Example Response: .. code-block:: json { "error": "", "msg": "Endpoint group created", "kamreload": true, "dsipreload": false, "data": [ { "gwgroupid": 101, "gwlist": "375", "name": "example" } ] } """ db = DummySession() fusionpbxenabled = 0 # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = { "name": str, "call_settings": dict, "auth": dict, "strip": int, "prefix": str, "notifications": dict, "cdr": dict, "fusionpbx": dict, "endpoints": list } gwgroup_data = {} try: if settings.DEBUG: debugEndpoint() db = startSession() if data == None: # Convert Request message to Dictionary Object request_payload = getRequestData() else: # Use the data parameter as the payload request_payload = data # sanity checks for k, v in request_payload.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) if 'prefix' in request_payload: for c in request_payload['prefix']: if c not in settings.DID_PREFIX_ALLOWED_CHARS: raise http_exceptions.BadRequest( "Request argument 'prefix' not valid. Allowed characters: {}".format( ','.join(settings.DID_PREFIX_ALLOWED_CHARS))) # Process Gateway Name name = request_payload['name'] Gwgroup = GatewayGroups(name, type=settings.FLT_PBX) db.add(Gwgroup) db.flush() gwgroupid = Gwgroup.id gwgroup_data['gwgroupid'] = gwgroupid # create the call settings for the new gateway group call_settings_data = request_payload['call_settings'] if 'call_settings' in request_payload else {} call_settings = dSIPCallSettings(gwgroupid, **call_settings_data) db.add(call_settings) # runtime defaults for this route strip = request_payload['strip'] if 'strip' in request_payload else 0 prefix = request_payload['prefix'] if 'prefix' in request_payload else "" authtype = request_payload['auth']['type'] \ if 'auth' in request_payload and 'type' in request_payload['auth'] else "" if authtype == "userpwd": # Store Endpoint IP's in address tables authuser = request_payload['auth']['user'] \ if 'user' in request_payload['auth'] \ and len(request_payload['auth']['user']) > 0 else None authpass = request_payload['auth']['pass'] \ if 'pass' in request_payload['auth'] \ and len(request_payload['auth']['pass']) > 0 else None authdomain = request_payload['auth']['domain'] \ if 'domain' in request_payload['auth'] \ and len(request_payload['auth']['domain']) > 0 else settings.DEFAULT_AUTH_DOMAIN authdomain = safeUriToHost(authdomain) if authuser is None or authpass is None: raise http_exceptions.BadRequest("Auth username or password invalid") if authdomain is None: raise http_exceptions.BadRequest("Auth domain is malformed") if db.query(Subscribers).filter(Subscribers.username == authuser, Subscribers.domain == authdomain).scalar(): raise sql_exceptions.SQLAlchemyError( 'DB Entry Taken for Username {} in Domain {}'.format(authuser, authdomain)) else: Subscriber = Subscribers(authuser, authpass, authdomain, gwgroupid) db.add(Subscriber) # Enable FusionPBX Support for the Endpoint Group if 'fusionpbx' in request_payload: fusionpbxenabled = request_payload['fusionpbx']['enabled'] \ if 'enabled' in request_payload['fusionpbx'] else None fusionpbxclustersupport = request_payload['fusionpbx']['clustersupport'] \ if 'clustersupport' in request_payload['fusionpbx'] else None fusionpbxdbhost = request_payload['fusionpbx']['dbhost'] \ if 'dbhost' in request_payload['fusionpbx'] else None fusionpbxdbuser = request_payload['fusionpbx']['dbuser'] \ if 'dbuser' in request_payload['fusionpbx'] else None fusionpbxdbpass = request_payload['fusionpbx']['dbpass'] \ if 'dbpass' in request_payload['fusionpbx'] else None # Convert fusionpbxenabled variable to int if isinstance(fusionpbxenabled, str): fusionpbxenabled = int(fusionpbxenabled) # Convert fusionclustersupport variable to int if isinstance(fusionpbxclustersupport, str): fusionpbxclustersupport = int(fusionpbxclustersupport) if fusionpbxenabled: if fusionpbxclustersupport == 1: domainType = dSIPMultiDomainMapping.FLAGS.TYPE_FUSIONPBX_CLUSTER.value else: domainType = dSIPMultiDomainMapping.FLAGS.TYPE_FUSIONPBX.value domainmapping = dSIPMultiDomainMapping(gwgroupid, fusionpbxdbhost, fusionpbxdbuser, fusionpbxdbpass, type=domainType) db.add(domainmapping) # Add the FusionPBX server as an Endpoint if it's not just the DB server if fusionpbxclustersupport is None or fusionpbxclustersupport == False: endpoint = {} endpoint['host'] = fusionpbxdbhost endpoint['description'] = "FusionPBX Server" if "endpoints" not in request_payload: request_payload['endpoints'] = [] request_payload['endpoints'].append(endpoint) msteams_domain = domain if domain is not None else '' # Setup Endpoints endpoints = request_payload['endpoints'] if "endpoints" in request_payload else [] gwlist = [] # endpoints to add unconditionally for endpoint in endpoints: if 'host' not in endpoint: raise http_exceptions.BadRequest("Endpoint hostname/address is required") host = endpoint['host'] port = int(endpoint['port']) if endpoint.get('port', None) is not None else 5060 signalling = endpoint['signalling'] if 'signalling' in endpoint else 'proxy' media = endpoint['media'] if 'media' in endpoint else 'proxy' name = endpoint['description'] if 'description' in endpoint else '' rweight = int(endpoint['rweight']) if endpoint.get('rweight', '') != '' else 1 keepalive = int(endpoint['keepalive']) if endpoint.get('keepalive', '') != '' else 0 flags = 0 if keepalive > 0: flags += Dispatcher.FLAGS['KEEP_ALIVE'] if rweight == 0: flags += Dispatcher.FLAGS['DISABLED_DST'] sip_addr = safeUriToHost(host, default_port=port) if sip_addr is None: raise http_exceptions.BadRequest("Endpoint hostname/address is malformed") if authtype == "ip": host_ip = hostToIP(host) # TODO: address entries should include port user specified Addr = Address(name, host_ip, 32, settings.FLT_PBX, gwgroup=gwgroupid) db.add(Addr) db.flush() Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, addr_id=Addr.id, msteams_domain=msteams_domain, signalling=signalling, media=media) else: Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_PBX, gwgroup=gwgroupid, msteams_domain=msteams_domain, signalling=signalling, media=media) db.add(Gateway) db.flush() # Create dispatcher group with the set id being the gateway group id # Don't create a dispatcher set for endpoint groups that was created for MSTeams domains if endpointGroupType != "msteams": dispatcher = Dispatcher(setid=gwgroupid, destination=sip_addr, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=Gateway.gwid) db.add(dispatcher) # Create dispatcher for FusionPBX external interface if FusionPBX feature is enabled if fusionpbxenabled: sip_uri_ext = f'sip:{safeStripPort(sip_addr)}:5080' setid_ext = gwgroupid + 1000 # Add 1000 to the gwgroupid so that the setid for the FusionPBX external interface is 1000 apart dispatcher = Dispatcher(setid=setid_ext, destination=sip_uri_ext, flags=flags, rweight=rweight, signalling=signalling, media=media, name=name, gwid=Gateway.gwid) db.add(dispatcher) gwlist.append(Gateway.gwid) # set return gwlist and update the endpoint group's gwlist gwgroup_data['endpoints'] = gwlist gwlist_str = ','.join([str(gw) for gw in gwlist]) # Update Gateway group with the Dispatcher ID. It's denoted with the LB field fields = strFieldsToDict(Gwgroup.description) # Don't add a load balancing group if the domain is a MSTeams domain if endpointGroupType != "msteams": fields['lb'] = gwgroupid if fusionpbxenabled: fields['lb_ext'] = gwgroupid + 1000 # Update the GatewayGroup with the lists of gateways Gwgroup.gwlist = gwlist_str Gwgroup.description = dictToStrFields(fields) # Setup notifications if 'notifications' in request_payload: overmaxcalllimit = request_payload['notifications']['overmaxcalllimit'] \ if 'overmaxcalllimit' in request_payload['notifications'] else None endpointfailure = request_payload['notifications']['endpointfailure'] \ if 'endpointfailure' in request_payload['notifications'] else None if overmaxcalllimit is not None: notif_type = dSIPNotification.FLAGS.TYPE_OVERLIMIT.value method = dSIPNotification.FLAGS.METHOD_EMAIL.value notification = dSIPNotification(gwgroupid, notif_type, method, overmaxcalllimit) db.add(notification) if endpointfailure is not None: notif_type = dSIPNotification.FLAGS.TYPE_GWFAILURE.value method = dSIPNotification.FLAGS.METHOD_EMAIL.value notification = dSIPNotification(gwgroupid, notif_type, method, endpointfailure) db.add(notification) # Enable CDR if 'cdr' in request_payload: cdr_email = request_payload['cdr']['cdr_email'] \ if 'cdr_email' in request_payload['cdr'] else '' cdr_send_interval = request_payload['cdr']['cdr_send_interval'] \ if 'cdr_send_interval' in request_payload['cdr'] else '' if len(cdr_email) > 0 and len(cdr_send_interval) > 0: # create DB entry cdrinfo = dSIPCDRInfo(gwgroupid, cdr_email, cdr_send_interval) db.add(cdrinfo) # create crontab entry cron_cmd = '{} cdr sendreport {}'.format((settings.DSIP_PROJECT_DIR + '/gui/dsiprouter_cron.py'), gwgroupid) if not addTaggedCronjob(gwgroupid, cdr_send_interval, cron_cmd): raise Exception('Crontab entry could not be created') db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Endpoint group created', data=[gwgroup_data], kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/numberenrichment", methods=['GET']) @api.route("/api/v1/numberenrichment/<int:rule_id>", methods=['GET']) @api_security def getNumberEnrichment(rule_id=None): """ Get one or multiple number enrichment rules ================ Response Payload ================ .. code-block:: json { error: <str>, msg: <str>, kamreload: <bool>, data: [ { rule_id: <int>, dnid: <str>, country_code: <str>, routing_number: <str>, rule_name: <str> }, ... ] } """ db = DummySession() response_data = [] try: if settings.DEBUG: debugEndpoint() db = startSession() # get a single enrichment rule if rule_id is not None: rule = db.query(dSIPDNIDEnrichment).filter(dSIPDNIDEnrichment.id == rule_id).first() if rule is not None: response_data.append({ 'rule_id': rule.id, 'dnid': rule.dnid, 'country_code': rule.country_code, 'routing_number': rule.routing_number, 'rule_name': strFieldsToDict(rule.description)['name'] }) else: raise http_exceptions.NotFound("Enrichment Rule Does Not Exist") # get all enrichment rules else: rules = db.query(dSIPDNIDEnrichment).all() for rule in rules: response_data.append({ 'rule_id': rule.id, 'dnid': rule.dnid, 'country_code': rule.country_code, 'routing_number': rule.routing_number, 'rule_name': strFieldsToDict(rule.description)['name'] }) return createApiResponse( msg='Enrichment Rule(s) found', data=response_data, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/numberenrichment", methods=['POST']) @api_security def addNumberEnrichment(request_payload=None): """ Add a one or multiple enrichment rules =============== Request Payload =============== .. code-block:: json { data: [ { dnid: <str>, country_code: <str>, routing_number: <str>, rule_name: <str> }, ... ] } ================ Response Payload ================ .. code-block:: json { error: <str>, msg: <str>, kamreload: <bool>, data: [ { rule_id: <int> }, ... ] } """ db = DummySession() # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = { "dnid": str, "country_code": str, "routing_number": str, "rule_name": str } # ensure requred args are provided REQUIRED_REQUEST_DATA_ARGS = {'dnid'} response_data = [] try: if settings.DEBUG: debugEndpoint() db = startSession() # allow calling function with payload data if request_payload is None: request_payload = getRequestData() # sanity checks if 'data' not in request_payload or len(request_payload['data']) == 0: raise http_exceptions.BadRequest("Request argument 'data' is required and must be non-zero length") for data in request_payload['data']: if not isinstance(data, dict): raise http_exceptions.BadRequest("Request argument 'data' not valid") for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for k in REQUIRED_REQUEST_DATA_ARGS: if k not in data: raise http_exceptions.BadRequest("Required argument '{}' missing in data entry".format(k)) # don't allow duplicate dnid's new_dnid_list = [x['dnid'] for x in request_payload['data']] if db.query(dSIPDNIDEnrichment).filter(dSIPDNIDEnrichment.dnid.in_(new_dnid_list)).scalar(): raise http_exceptions.BadRequest("Duplicate DNID's are not allowed") for rule_data in request_payload['data']: rule = dSIPDNIDEnrichment(**rule_data) db.add(rule) db.flush() response_data.append({'rule_id': rule.id}) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Enrichment Rule(s) created', data=response_data, kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/numberenrichment", methods=['PUT']) @api.route("/api/v1/numberenrichment/<int:rule_id>", methods=['PUT']) @api_security def updateNumberEnrichment(rule_id=None, request_payload=None): """ Update one or multiple enrichment rules\n The rule_id can be provided in url path or payload =============== Request Payload =============== .. code-block:: json { data: [ { rule_id: <int> dnid: <str>, country_code: <str>, routing_number: <str>, rule_name: <str> }, ... ] } ================ Response Payload ================ .. code-block:: json { error: <str>, msg: <str>, kamreload: <bool>, data: [ { rule_id: <int> }, ... ] } """ db = DummySession() # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = { "rule_id": int, "dnid": str, "country_code": str, "routing_number": str, "rule_name": str } # ensure requred args are provided REQUIRED_REQUEST_DATA_ARGS = {'dnid'} response_data = [] try: if (settings.DEBUG): debugEndpoint() db = startSession() # allow calling function with payload data if request_payload is None: request_payload = getRequestData() # sanity checks if 'data' not in request_payload or len(request_payload['data']) == 0: raise http_exceptions.BadRequest("Request argument 'data' is required and must be non-zero length") else: if rule_id is not None and isinstance(request_payload['data'][0], dict): request_payload['data'][0]['rule_id'] = int(rule_id) for data in request_payload['data']: if not isinstance(data, dict): raise http_exceptions.BadRequest("Request argument 'data' not valid") for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for k in REQUIRED_REQUEST_DATA_ARGS: if k not in data: raise http_exceptions.BadRequest("Required argument '{}' missing in data entry".format(k)) # don't allow duplicate dnid's upd_ruleids = [x['rule_id'] for x in request_payload['data'] if 'rule_id' in x] old_dnids = set(x.dnid for x in db.query(dSIPDNIDEnrichment).all() if x.id not in upd_ruleids) add_dnids = set(x['dnid'] for x in request_payload['data'] if not 'rule_id' in x) upd_dnids = set(x['dnid'] for x in request_payload['data'] if 'rule_id' in x) if len(list(add_dnids) + list(upd_dnids) + list(old_dnids)) > len(set.union(add_dnids, upd_dnids, old_dnids)): raise http_exceptions.BadRequest("Duplicate DNID's are not allowed") for rule_data in request_payload['data']: # adding new rules if not 'rule_id' in rule_data: rule = dSIPDNIDEnrichment(**rule_data) db.add(rule) db.flush() response_data.append({'rule_id': rule.id}) # updating existing rules else: rule_id = rule_data.pop('rule_id') description = {'name': rule_data.pop('rule_name')} rule_data['description'] = dictToStrFields(description) if not db.query(dSIPDNIDEnrichment).filter(dSIPDNIDEnrichment.id == rule_id).update( rule_data, synchronize_session=False): raise http_exceptions.BadRequest("Enrichment Rule with id '{}' does not exist".format(str(rule_id))) response_data.append({'rule_id': rule_id}) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Enrichment Rule(s) updated', data=response_data, kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/numberenrichment", methods=['DELETE']) @api.route("/api/v1/numberenrichment/<int:rule_id>", methods=['DELETE']) @api_security def deleteNumberEnrichment(rule_id, request_payload=None): """ Delete one or multiple enrichment rules\n The rule_id can be provided in url path or payload =============== Request Payload =============== .. code-block:: json { data: [ { rule_id: <int> }, ... ] } ================ Response Payload ================ .. code-block:: json { error: <str>, msg: <str>, kamreload: <bool>, data: [] } """ db = DummySession() # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"rule_id": int} # ensure requred args are provided REQUIRED_REQUEST_DATA_ARGS = {'rule_id'} try: if settings.DEBUG: debugEndpoint() db = startSession() # allow calling function with payload data if request_payload is None: request_payload = getRequestData() # sanity checks if rule_id is not None: request_payload['data'] = [{'rule_id': int(rule_id)}] elif 'data' not in request_payload or len(request_payload['data']) == 0: raise http_exceptions.BadRequest("Request argument 'data' is required and must be non-zero length") for data in request_payload['data']: if not isinstance(data, dict): raise http_exceptions.BadRequest("Request argument 'data' not valid") for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for k in REQUIRED_REQUEST_DATA_ARGS: if k not in data: raise http_exceptions.BadRequest("Required argument '{}' missing in data entry".format(k)) rule_ids = [x['rule_id'] for x in request_payload['data']] rules = db.query(dSIPDNIDEnrichment).filter(dSIPDNIDEnrichment.id.in_(rule_ids)) if rules is not None: rules.delete(synchronize_session=False) else: raise http_exceptions.NotFound("The enrichment rule(s) do not exist") db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Enrichment Rule(s) deleted', kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/numberenrichment/fetch", methods=['POST']) @api_security def fetchNumberEnrichment(request_payload=None): """ Fetch a CSV to import from an external server\n Updates and adds rules by default\n If replace_rules is True then current rules are replaced =============== Request Payload =============== .. code-block:: json { data: [ { url: <str> }, ... ], replace_rules: <bool> } ================ Response Payload ================ .. code-block:: json { error: <str>, msg: <str>, kamreload: <bool>, data: [ { rule_id: <int>, dnid: <str>, country_code: <str>, routing_number: <str>, rule_name: <str> }, ... ] } """ # for validation of request args VALID_REQUEST_ARGS = {"data": list, "replace_rules": bool} # for validation of data args VALID_REQUEST_DATA_ARGS = {"url": str} # ensure required args are provided REQUIRED_REQUEST_DATA_ARGS = {'url'} response_payload = {'data': []} try: if settings.DEBUG: debugEndpoint() db = startSession() # allow calling function with payload data if request_payload is None: request_payload = getRequestData() # sanity checks if len(request_payload['data']) == 0: raise http_exceptions.BadRequest("Request argument 'data' must be non-zero length") for k, v in request_payload.items(): if k not in VALID_REQUEST_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_ARGS[k]: try: request_payload[k] = VALID_REQUEST_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for data in request_payload['data']: for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: try: request_payload[k] = VALID_REQUEST_DATA_ARGS[k](v) continue except: raise http_exceptions.BadRequest("Request argument '{}' not valid".format(k)) for k in REQUIRED_REQUEST_DATA_ARGS: if k not in data: raise http_exceptions.BadRequest("Required argument '{}' missing in data entry".format(k)) # prep for gathering data from csv new_rules = [] resource_urls = [x['url'] for x in request_payload['data']] # TODO: we should not blindly download the file, we need to some security checks here # TODO: we should be doing downloads in separate threads for url in resource_urls: with closing(requests.get(url, stream=True)) as resp: # iterator for csv stream lines iter_lines = codecs.iterdecode(resp.iter_lines(), 'utf-8') # lines for sniffer to detect csv type line_1 = next(iter_lines, None) if line_1 is None: raise http_exceptions.BadRequest("File at fetch URL is invalid") line_2 = next(iter_lines, None) # detect csv dialect and if header is present sniffer_lines = line_1 + '\n' + line_2 if line_2 is not None else line_1 dialect = csv.Sniffer().sniff(sniffer_lines, delimiters=',;|') has_header = csv.Sniffer().has_header(sniffer_lines) # add valid values from sniffer lines to rules start_lines = [] if not has_header: start_lines.append(line_1) if line_2 is not None: start_lines.append(line_2) for row in csv.reader((line for line in start_lines), dialect): new_rules.append({ "dnid": row[0], "country_code": row[1], "routing_number": row[2], "rule_name": row[3] }) # read rest of csv stream and add to rules for row in csv.reader(iter_lines, dialect): new_rules.append({ "dnid": row[0], "country_code": row[1], "routing_number": row[2], "rule_name": row[3] }) # if replacing rules delete all then add # if updating rules merge based on dnid (must be unique still) replace_rules = request_payload['replace_rules'] if 'replace_rules' in request_payload else False if replace_rules: db.query(dSIPDNIDEnrichment).delete(synchronize_session=False) db.flush() ret = addNumberEnrichment(request_payload={'data': new_rules}) if ret[1] != StatusCodes.HTTP_OK: resp = ret[0].get_json(force=True) response_payload['error'] = resp['error'] response_payload['msg'] = resp['msg'] return createApiResponse(**response_payload, status_code=ret[1]) response_payload['msg'] = "Enrichment Rule(s) replaced" else: current_rules_lut = { x.dnid: {'rule_id': x.id} for x in db.query(dSIPDNIDEnrichment).all() } current_dnids = set(current_rules_lut.keys()) new_rules_lut = { x['dnid']: {'dnid': x['dnid'], 'country_code': x['country_code'], 'routing_number': x['routing_number'], 'rule_name': x['rule_name']} for x in new_rules } new_dnids = set(new_rules_lut.keys()) add_dnids = new_dnids - current_dnids upd_dnids = current_dnids & new_dnids updated_rules = [] for dnid in add_dnids: updated_rules.append(new_rules_lut[dnid]) for dnid in upd_dnids: updated_rules.append({'rule_id': current_rules_lut[dnid]['rule_id'], **new_rules_lut[dnid]}) ret = updateNumberEnrichment(request_payload={'data': updated_rules}) if ret[1] != StatusCodes.HTTP_OK: resp = ret[0].get_json(force=True) response_payload['error'] = resp['error'] response_payload['msg'] = resp['msg'] return createApiResponse(**response_payload, status_code=ret[1]) response_payload['msg'] = "Enrichment Rule(s) updated" # let requestor know what rules are now available rules = db.query(dSIPDNIDEnrichment).all() for rule in rules: response_payload['data'].append({ 'rule_id': rule.id, 'dnid': rule.dnid, 'country_code': rule.country_code, 'routing_number': rule.routing_number, 'rule_name': strFieldsToDict(rule.description)['name'] }) getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( **response_payload, kamreload=True, ) except Exception as ex: return showApiError(ex, response_payload) # TODO: standardize response payload (use data param) # TODO: stop shadowing builtin functions -> type == builtin # TODO: too manu use cases in this one function, split it up into constituent pieces def generateCDRS( gwgroupid, report_type=None, send_email=None, dtfilter=None, cdrfilter=None, nonCompletedCalls=None, run_standalone=False, filter_params=None, ): """ Generate CDRs Report for a gwgroup :param gwgroupid: gwgroup to generate cdr's for :type gwgroupid: int|str :param report_type: type of report (json|csv) :type report_type: str :param send_email: whether the report should be emailed :type send_email: bool :param dtfilter: time before which cdr's are not returned :type dtfilter: datetime :param cdrfilter: comma seperated cdr id's to include :type cdrfilter: str :return: returns a json response or file :rtype: flask.Response """ # if run in standalone mode we need to setup logging initSyslogLogger() db = DummySession() # defaults.. keep data returned separate from returned metadata response_payload = {'error': None, 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} # define here so we can cleanup in finally statement csv_file = '' try: db = startSession() if isinstance(gwgroupid, int): gwgroupid = str(gwgroupid) if report_type is None: report_type = 'json' else: report_type = report_type.lower() if send_email is None: send_email = False if dtfilter is None: dtfilter = datetime.min if cdrfilter is None: cdrfilter = tuple() else: cdrfilter = tuple(cdrfilter.split(',')) if nonCompletedCalls is None: nonCompletedCalls = True if filter_params is None: filter_params = {'paging': False, 'searching': False, 'ordering': False} gwgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroupid).first() if gwgroup is not None: gwgroupName = strFieldsToDict(gwgroup.description)['name'] else: response_payload['status'] = "0" response_payload['message'] = "Endpont group doesn't exist" if run_standalone: IO.logerr(f'Endpont group {gwgroupid} does not exist') return None return jsonify(response_payload) if len(cdrfilter) > 0: sql_params = {"gwgroupid": gwgroupid, "dtfilter": dtfilter, "cdrfilter": cdrfilter} query1 = ( """SELECT t1.cdr_id, t1.call_start_time, t1.duration AS call_duration, t1.calltype AS call_direction, t2.id AS src_gwgroupid, substring_index(substring_index(t2.description, 'name:', -1), ',', 1) AS src_gwgroupname, t3.id AS dst_gwgroupid, substring_index(substring_index(t3.description, 'name:', -1), ',', 1) AS dst_gwgroupname, t1.src_username, t1.dst_username, t1.src_ip AS src_address, t1.dst_domain AS dst_address, t1.sip_call_id AS call_id FROM cdrs t1 JOIN dr_gw_lists t2 ON (t1.src_gwgroupid = t2.id) JOIN dr_gw_lists t3 ON (t1.dst_gwgroupid = t3.id) WHERE (t2.id = :gwgroupid OR t3.id = :gwgroupid) AND t1.call_start_time >= :dtfilter AND t1.cdr_id IN :cdrfilter ORDER BY t1.call_start_time DESC""" ) else: sql_params = {"gwgroupid": gwgroupid, "dtfilter": dtfilter} query1 = ( """SELECT t1.cdr_id, t1.call_start_time, t1.duration AS call_duration, t1.calltype AS call_direction, t2.id AS src_gwgroupid, substring_index(substring_index(t2.description, 'name:', -1), ',', 1) AS src_gwgroupname, t3.id AS dst_gwgroupid, substring_index(substring_index(t3.description, 'name:', -1), ',', 1) AS dst_gwgroupname, t1.src_username, t1.dst_username, t1.src_ip AS src_address, t1.dst_domain AS dst_address, t1.sip_call_id AS call_id FROM cdrs t1 JOIN dr_gw_lists t2 ON (t1.src_gwgroupid = t2.id) JOIN dr_gw_lists t3 ON (t1.dst_gwgroupid = t3.id) WHERE (t2.id = :gwgroupid OR t3.id = :gwgroupid) AND t1.call_start_time >= :dtfilter ORDER BY t1.call_start_time DESC""" ) if nonCompletedCalls: query2 = ( """SELECT acc.id AS cdr_id, acc.time, 0, acc.calltype, acc.src_gwgroupid, SUBSTRING_INDEX(SUBSTRING_INDEX(t2.description, 'name:', -1), ',', 1) AS src_gwgroupname, acc.dst_gwgroupid, SUBSTRING_INDEX(SUBSTRING_INDEX(t3.description, 'name:', -1), ',', 1) AS dst_gwgroupname, acc.src_user,acc.dst_user,acc.src_ip,acc.dst_domain,acc.callid FROM acc JOIN dr_gw_lists t2 ON (acc.src_gwgroupid = t2.id) LEFT JOIN dr_gw_lists t3 ON (acc.dst_gwgroupid = t3.id) WHERE (t2.id = :gwgroupid OR t3.id = :gwgroupid) AND acc.time >= :dtfilter ORDER BY acc.time DESC""" ) sql = f'SELECT SQL_CALC_FOUND_ROWS * FROM (({query1}) UNION ({query2})) t_all' else: sql = f'SELECT SQL_CALC_FOUND_ROWS * FROM ({query1}) t_all' if filter_params['searching']: if filter_params['search_regex']: sql_params['search_val'] = filter_params['search_val'] sql = sql + ' WHERE ' + ''' `cdr_id` REGEXP :search_val OR `call_start_time` REGEXP :search_val OR `call_duration` REGEXP :search_val OR `call_direction` REGEXP :search_val OR `src_gwgroupname` REGEXP :search_val OR `dst_gwgroupname` REGEXP :search_val OR `src_username` REGEXP :search_val OR `dst_username` REGEXP :search_val OR `src_address` REGEXP :search_val OR `dst_address` REGEXP :search_val OR `call_id` REGEXP :search_val ''' else: sql_params['search_val'] = f"%{filter_params['search_val']}%" sql = sql + ' WHERE ' + ''' `cdr_id` LIKE :search_val OR `call_start_time` LIKE :search_val OR `call_duration` LIKE :search_val OR `call_direction` LIKE :search_val OR `src_gwgroupname` LIKE :search_val OR `dst_gwgroupname` LIKE :search_val OR `src_username` LIKE :search_val OR `dst_username` LIKE :search_val OR `src_address` LIKE :search_val OR `dst_address` LIKE :search_val OR `call_id` LIKE :search_val ''' if filter_params['ordering']: sql_params['order_col'] = filter_params['order_col'] if filter_params['order_dir'] == 'desc': sql = sql + ' ORDER BY :order_col DESC' else: sql = sql + ' ORDER BY :order_col ASC' if filter_params['paging']: sql_params['page_start'] = filter_params['page_start'] sql_params['page_len'] = filter_params['page_len'] sql = sql + ' LIMIT :page_len OFFSET :page_start' sql = text(sql) rows = db.execute(sql, sql_params).all() total_rows = db.execute(text('SELECT FOUND_ROWS()')).scalar() # TODO: does not work as described by datatables, clientside JS expects the count before limiting # only effects the text displayed on the bottom left (filter shown when searching) #filtered_rows = len(rows) filtered_rows = total_rows cdrs = [] dataFields = [ 'cdr_id', 'call_start_time', 'call_duration', 'call_direction', 'src_gwgroupid', 'src_gwgroupname', 'dst_gwgroupid', 'dst_gwgroupname', 'src_username', 'dst_username', 'src_address', 'dst_address', 'call_id' ] for row in rows: data = {} data['cdr_id'] = int(row[0]) data['call_start_time'] = row[1] data['call_duration'] = str(row[2]) data['call_direction'] = row[3] data['src_gwgroupid'] = row[4] data['src_gwgroupname'] = row[5] data['dst_gwgroupid'] = row[6] data['dst_gwgroupname'] = row[7] data['src_username'] = row[8] data['dst_username'] = row[9] data['src_address'] = row[10] data['dst_address'] = row[11] data['call_id'] = row[12] cdrs.append(data) response_payload['status'] = "200" response_payload['data'] = cdrs response_payload['total_rows'] = total_rows response_payload['filtered_rows'] = filtered_rows # Convert array of dicts to csv format if report_type == "csv": now = time.strftime('%Y%m%d-%H%M%S') filename = secure_filename('{}_{}.csv'.format(gwgroupName, now)) csv_file = '/tmp/{}'.format(filename) with open(csv_file, 'w', newline='') as csv_fp: if len(cdrs) > 0: dict_writer = csv.DictWriter(csv_fp, fieldnames=cdrs[0].keys()) dict_writer.writeheader() dict_writer.writerows(cdrs) else: dict_writer = csv.DictWriter(csv_fp, fieldnames=dataFields) dict_writer.writeheader() if send_email: # recipients required cdr_info = db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid).first() if cdr_info is not None: # Setup the parameters to send the email data = {} data['html_body'] = "<html>CDR Report for {}</html>".format(gwgroupName) data['text_body'] = "CDR Report for {}".format(gwgroupName) data['subject'] = "CDR Report for {}".format(gwgroupName) data['attachments'] = [csv_file] data['recipients'] = cdr_info.email.split(',') sendEmail(**data) # remove CDRs from the payload that is being returned response_payload.pop('data') response_payload['format'] = 'csv' response_payload['type'] = 'email' if run_standalone: IO.loginfo(f'Sent CDR report for endpoint group {gwgroupid}') return None return jsonify(response_payload) if run_standalone: IO.logerr(f'Nowhere to send CDR report for endpoint group {gwgroupid} run in standalone mode') return None return send_file(csv_file, as_attachment=True), StatusCodes.HTTP_OK if run_standalone: IO.logerr(f'Nowhere to send CDR report for endpoint group {gwgroupid} run in standalone mode') return None return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() if os.path.exists(csv_file): os.remove(csv_file) @api.route("/api/v1/cdrs/endpointgroups/<int:gwgroupid>", methods=['GET']) @api_security def getGatewayGroupCDRS(gwgroupid=None): """ Purpose Getting the cdrs for a gatewaygroup """ if (settings.DEBUG): debugEndpoint() report_type = request.args.get('type', 'json') if 'email' in request.args: send_email = json.loads(request.args['email']) else: send_email = False cdrfilter = request.args.get('filter', None) if 'dtfilter' in request.args: dtfilter = datetime.strptime(request.args['dtfilter'], "%Y-%m-%d") else: dtfilter = None if 'nonCompletedCalls' in request.args: nonCompletedCalls = json.loads(request.args['nonCompletedCalls']) else: nonCompletedCalls = True if 'start' in request.args or 'length' in request.args: page_start = int(request.args.get('start', 0)) page_len = int(request.args.get('length', 100)) filter_params = { 'paging': True, 'page_start': page_start, 'page_len': page_len, } else: filter_params = { 'paging': False, } if 'search[value]' in request.args and len(request.args['search[value]']) > 0: filter_params['searching'] = True filter_params['search_val'] = request.args['search[value]'] if 'search[regex]' in request.args: filter_params['search_regex'] = json.loads(request.args['search[regex]']) else: filter_params['search_regex'] = False else: filter_params['searching'] = False if 'order[0][column]' in request.args: filter_params['ordering'] = True filter_params['order_col'] = int(request.args['order[0][column]']) + 1 filter_params['order_dir'] = request.args['order[0][dir]'] else: filter_params['ordering'] = False return generateCDRS(gwgroupid, report_type, send_email, dtfilter, cdrfilter, nonCompletedCalls, filter_params=filter_params) # TODO: standardize response payload (use createApiResponse()) @api.route("/api/v1/cdrs/endpoint/<int:gwid>", methods=['GET']) @api_security def getGatewayCDRS(gwid=None): """ Purpose Getting the cdrs for a gateway thats part of a gateway group """ db = DummySession() # defaults.. keep data returned separate from returned metadata response_payload = {'error': None, 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: db = startSession() if (settings.DEBUG): debugEndpoint() query = text( "SELECT cdr_id, gwid, call_start_time, calltype AS direction, dst_username AS number,src_ip, dst_domain, duration,sip_call_id " "FROM dr_gateways, cdrs " "WHERE cdrs.src_ip=dr_gateways.address AND gwid=:gwid " "ORDER BY call_start_time DESC" ) cdrs = db.execute(query, {'gwid':gwid}) rows = [] for cdr in cdrs: row = {} row['cdr_id'] = cdr[0] row['gwid'] = cdr[1] row['call_start_time'] = str(cdr[2]) row['calltype'] = cdr[3] row['number'] = cdr[4] row['src_ip'] = cdr[5] row['dst_domain'] = cdr[6] row['duration'] = cdr[7] row['sip_call_id'] = cdr[8] rows.append(row) response_payload['cdrs'] = rows response_payload['recordCount'] = len(rows) response_payload['status'] = "200" return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/backupandrestore/backup", methods=['GET']) @api_security def createBackup(): """ Generate a backup of the database """ # in case we error early make sure the variable is set backup_path = '' try: if (settings.DEBUG): debugEndpoint() backup_path = os.path.join(settings.BACKUP_FOLDER, f'{time.strftime("%s")}-db.sql') dumpcmd = ['sudo', 'dsiprouter', 'backup', '-f', backup_path] proc = subprocess.run( dumpcmd, capture_output=True, text=True ) proc.check_returncode() return send_file(backup_path, as_attachment=True), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) finally: if os.path.exists(backup_path): os.remove(backup_path) @api.route("/api/v1/backupandrestore/restore", methods=['POST']) @api_security def restoreBackup(): """ Restore backup of the database """ # in case we error early make sure the variable is set restore_path = '' try: if (settings.DEBUG): debugEndpoint() if 'file' not in request.files: raise http_exceptions.BadRequest("No file was sent") file = request.files['file'] if file.filename == '': raise http_exceptions.BadRequest("No file name was sent") if file and allowed_file(file.filename, ALLOWED_EXTENSIONS={'sql'}): filename = secure_filename(file.filename) restore_path = os.path.join(settings.BACKUP_FOLDER, filename) file.save(restore_path) else: raise http_exceptions.BadRequest("Improper file upload") restorecmd = ['sudo', 'dsiprouter', 'restore', '-f', restore_path] proc = subprocess.run( restorecmd, capture_output=True, text=True ) proc.check_returncode() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='The restore was successful', kamreload=True, ) except Exception as ex: return showApiError(ex) finally: if os.path.exists(restore_path): os.remove(restore_path) @api.route("/api/v1/sys/generatepassword", methods=['GET']) @api_security def generatePassword(): """ Generate a random password """ DEF_PASSWORD_LEN = 32 try: return createApiResponse( msg='Successfully generated password', data=[urandomChars(DEF_PASSWORD_LEN)], ) except Exception as ex: return showApiError(ex) # TODO: The response coming back from Kamailio Command Line # is not in proper JSON format. The field and Attributes # are missing double quotes. So, we need to fix the JSON # payload and loop thru the result vs doing a find. # Or if we do a JSONRPC call instead we will get proper JSON def getOptionMessageStatus(domain): """ Check if Domain is receiving replies from OPTION Messages :param domain: domain to check :type domain: str :return: whether domain handled OPTION message correctly :rtype: bool """ domain_active = False cmdset = {"method": "dispatcher.list", "jsonrpc": "2.0", "id": 1} r = requests.get('http://127.0.0.1:5060/api/kamailio', json=cmdset) if r.status_code >= 400: try: msg = r.json()['error']['message'] except: msg = r.reason ex = http_exceptions.HTTPException(msg) ex.code = r.status_code raise ex else: response = r.json() records = response['result']['RECORDS'] if not response: return False try: # Loop thru each record in the dispatcher list for record in range(0, len(records)): sets = records[record] # Loop thru each set for set in sets: # print("{},{}".format(sets[set]['ID'],domain)) # Loop thru each target targets = sets[set]['TARGETS'] # Loop thru each destination within a target for dest in range(0, len(targets)): # Grab the destination body and flags # The Body contains the domain name of the destionation dest_body = format(targets[dest]['DEST']['ATTRS']['BODY']) # The Flags specify is the destionation is sending and recieving option messages dest_flags = format(targets[dest]['DEST']['FLAGS']) if domain in dest_body: if dest_flags == "AP": domain_active = True continue except Exception as ex: raise ex if not domain_active: return False return True @api.route("/api/v1/domains/msteams/test/<string:domain>", methods=['GET']) @api_security def testConnectivity(domain): try: test_data = {"hostname_check": False, "tls_check": False, "option_check": False} external_ip_addr = getExternalIP() internal_ip_address = hostToIP(domain) # Check the external ip matchs the ip set on the server if external_ip_addr == internal_ip_address: test_data['hostname_check'] = True # Try again, but use Google DNS resolver if the check fails with local DNS else: # Does the IP address of this server resolve to the domain import dns.resolver # Get the IP address of the domain from Google DNS resolver = dns.resolver.Resolver() resolver.nameservers = ['8.8.8.8'] try: answers = resolver.query(domain, 'A') for a in answers: # If the External IP and IP from DNS match then it passes the check if a.to_text() == external_ip_addr: test_data['hostname_check'] = True except: pass # Check if Domain is the root of the CN # GoDaddy Certs Don't work with Microsoft Direct routing certInfo = isCertValid(domain, external_ip_addr, 5061) if certInfo: test_data['tls_check'] = certInfo # check if domain can process OPTIONS messages if getOptionMessageStatus(domain): test_data['option_check'] = True return createApiResponse( msg='Tests ran without error', data=[test_data], ) except Exception as ex: return showApiError(ex) # TODO: implement GET for single domain @api.route("/api/v1/certificates", methods=['GET']) @api.route("/api/v1/certificates/<string:domain>", methods=['GET']) @api_security def getCertificates(domain=None): db = DummySession() response_data = [] try: if settings.DEBUG: debugEndpoint() db = startSession() # if domain is not None: # domain_configs = getCustomTLSConfigs(domain) # else: # domain_configs = getCustomTLSConfigs() if domain == None: certificates = db.query(dSIPCertificates).all() else: certificates = db.query(dSIPCertificates).filter(dSIPCertificates.domain == domain).all() for certificate in certificates: # append summary of endpoint group data response_data.append({ 'id': certificate.id, 'domain': certificate.domain, 'type': certificate.type, 'assigned_domains': '' }) db.commit() return createApiResponse( msg='Certificates found' if len(certificates) > 0 else 'No Certificates', data=response_data, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/certificates", methods=['POST', 'PUT']) @api_security def createCertificate(): """ Create TLS cert for a domain =============== Request Payload =============== .. code-block:: json { domain: <string>, ip: <int>, port: <int> server_name_mode: <int> } """ db = DummySession() CERT_TYPE_GENERATED = "generated" CERT_TYPE_UPLOADED = "uploaded" # Check parameters and raise exception if missing required parameters requiredParameters = ['domain'] try: if settings.DEBUG: debugEndpoint() db = startSession() request_payload = getRequestData() for parameter in requiredParameters: if parameter not in request_payload: raise http_exceptions.BadRequest("Request Argument '{}' is Required".format(parameter)) elif request_payload[parameter] is None or len(request_payload[parameter]) == 0: raise http_exceptions.BadRequest("Value for Request Argument '{}' is Not Valid".format(parameter)) # Process Request domain = request_payload['domain'] if domain == "default": domain = socket.gethostname() ip = request_payload['ip'] if 'ip' in request_payload else settings.EXTERNAL_IP_ADDR port = request_payload['port'] if 'port' in request_payload else 5061 server_name_mode = request_payload['server_name_mode'] \ if 'server_name_mode' in request_payload else kamtls.KAM_TLS_SNI_ALL key = request_payload['key'] if 'key' in request_payload else None replace_default_cert = request_payload[ 'replace_default_cert'] if 'replace_default_cert' in request_payload else None cert = request_payload['cert'] if 'cert' in request_payload else None email = request_payload['email'] if 'email' in request_payload else "admin@" + settings.DEFAULT_AUTH_DOMAIN # Request Certificate via Let's Encrypt if key is None and cert is None: type = CERT_TYPE_GENERATED try: if settings.DEBUG: # Use the LetsEncrypt Staging Server key, cert = letsencrypt.generateCertificate(domain, email, debug=True, default=replace_default_cert) else: # Use the LetsEncrypt Prod Server key, cert = letsencrypt.generateCertificate(domain, email, default=replace_default_cert) except Exception as ex: getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = False raise http_exceptions.BadRequest( "Issue with validating ownership of the domain. Please add a DNS record for this domain and try again") # Convert Certificate and key to base64 so that they can be stored in the database cert_base64 = base64.b64encode(cert.encode('ascii')) key_base64 = base64.b64encode(key.encode('ascii')) # Check if the domain exists. If so, update versus writing new certificate = db.query(dSIPCertificates).filter(dSIPCertificates.domain == domain).first() if certificate is not None: # Update db.query(dSIPCertificates).filter(dSIPCertificates.domain == domain).update( {'email': email, 'type': type, 'cert': cert_base64, 'key': key_base64}) db.commit() # Update the Kamailio TLS Configuration if not kamtls.updateCustomTLSConfig(domain, ip, port, server_name_mode): raise Exception('Failed to add Certificate to Kamailio') else: # Store a new Certificate in dSIPCertificate Table certificate = dSIPCertificates(domain, type, email, cert_base64, key_base64) db.add(certificate) db.commit() # Write the Kamailio TLS Configuration if not kamtls.addCustomTLSConfig(domain, ip, port, server_name_mode): raise Exception('Failed to add Certificate to Kamailio') getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg="Certificate creation succeeded", data=[{"id": certificate.id}], kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/certificates/<string:domain>", methods=['DELETE']) @api_security def deleteCertificates(domain=None): db = DummySession() try: if settings.DEBUG: debugEndpoint() db = startSession() # if domain is not None: # domain_configs = getCustomTLSConfigs(domain) # else: # domain_configs = getCustomTLSConfigs() Certificates = db.query(dSIPCertificates).filter(dSIPCertificates.domain == domain) Certificates.delete(synchronize_session=False) # Remove the certificate from the file system letsencrypt.deleteCertificate(domain) # Remove from Kamailio TLS kamtls.deleteCustomTLSConfig(domain) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg='Certificate Deleted', kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @api.route("/api/v1/certificates/upload/<string:domain>", methods=['POST']) @api_security def uploadCertificates(domain=None): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() data = getRequestData() if 'domain' in data.keys(): domain = data['domain'][0] print(domain) else: domain = "default" if 'replace_default_cert' in data.keys(): replace_default_cert = data['replace_default_cert'][0] else: replace_default_cert = None ip = settings.EXTERNAL_IP_ADDR port = 5061 server_name_mode = kamtls.KAM_TLS_SNI_ALL if replace_default_cert == "true": # Replacing the default cert cert_domain_dir = settings.DSIP_CERTS_DIR else: # Adding a another domain cert cert_domain_dir = os.path.join(settings.DSIP_CERTS_DIR, domain) # Create a directory for the domain if not os.path.exists(cert_domain_dir): os.makedirs(cert_domain_dir) os.chmod(cert_domain_dir, 0o770) files = request.files.getlist("certandkey") try: keycert_pair = KeyCertPair(files) keycert_pair.validateKeyCertPair() pkey_bytes = keycert_pair.dumpPkey() cert_bytes = keycert_pair.dumpCerts() except Exception as ex: raise http_exceptions.BadRequest(str(ex)) key_file = os.path.join(cert_domain_dir, 'dsiprouter-key.pem') cert_file = os.path.join(cert_domain_dir, 'dsiprouter-cert.pem') with open(key_file, 'wb') as newfile: newfile.write(pkey_bytes) with open(cert_file, 'wb') as newfile: newfile.write(cert_bytes) # Change owner to dsiprouter:kamailio so that Kamailio can load the configurations change_owner(cert_domain_dir, "dsiprouter", "kamailio") change_owner(key_file, "dsiprouter", "kamailio") change_owner(cert_file, "dsiprouter", "kamailio") # Convert Certificate and key to base64 so that they can be stored in the database key_base64 = base64.b64encode(pkey_bytes) cert_base64 = base64.b64encode(cert_bytes) # Store Certificate in dSIPCertificate Table certificate = dSIPCertificates(domain, "uploaded", None, cert_base64, key_base64) db.add(certificate) if not replace_default_cert: # Write the Kamailio TLS Configuration if it's not the default if not kamtls.addCustomTLSConfig(domain, ip, port, server_name_mode): raise Exception('Failed to add Certificate to Kamailio') db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return createApiResponse( msg="Certificate and Key were uploaded", data=[{"id": certificate.id}], kamreload=True, ) except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() ================================================ FILE: gui/modules/api/auth/__init__.py ================================================ ================================================ FILE: gui/modules/api/auth/functions.py ================================================ from shared import debugException, debugEndpoint, stripDictVals, showError from database import startSession, DummySession, dSIPUser from sqlalchemy import exc as sql_exceptions from werkzeug import exceptions as http_exceptions from flask import request import settings import datetime from util.security import AES_CTR def addDSIPUser(data=None): """ Add or Update a group of carriers """ db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() if data is not None: # Set the form variables to data parameter form = data else: form = stripDictVals(request.form.to_dict()) username = form['username'] password = AES_CTR.encrypt(form['password']) firstname = form['firstname'] lastname = form['lastname'] roles = '' domains = '' token = '' token_expiration = datetime.datetime.now() new_user = dSIPUser(firstname, lastname, username, password, roles, domains, token, token_expiration) db.add(new_user) db.flush() db.commit() return new_user except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() ================================================ FILE: gui/modules/api/auth/ldap/__init__.py ================================================ ================================================ FILE: gui/modules/api/auth/ldap/interface.py ================================================ import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import ldap import settings # required plugin global METADATA = { 'name': 'LDAP Authentication', 'version': '1.0.0' } # required plugin interface def initialize(): """ Validate the module settings and perform any verification needed :return: None :rtype: None :raises: ValueError - when settings are invalid """ mod_settings = settings.AUTH_MODULES['ldap'] for req_key in ['LDAP_HOST', 'USER_ATTRIBUTE', 'USER_SEARCH_BASE']: if req_key not in mod_settings: raise ValueError(f'ldap module failed initialization: missing required setting "{req_key}"') if 'REQUIRED_GROUP' in mod_settings: if not 'GROUP_SEARCH_BASE' in mod_settings or not 'GROUP_MEMBER_ATTRIBUTE' in mod_settings: raise ValueError(f'ldap module failed initialization: "REQUIRED_GROUP" requires "GROUP_SEARCH_BASE" avd "GROUP_MEMBER_ATTRIBUTE" to be set') # TODO: validate ldap connection / store connection object # required plugin interface def teardown(): """ Cleanup any artifacts from the module :return: None :rtype: None """ pass # required plugin interface def authenticate(username, password): """ Authenticate a user via an external LDAP server :param username: The username to authenticate with :type username: str :param password: The password to authenticate with :type password: str :return: Whether authentication was successful or not :rtype: bool """ mod_settings = settings.AUTH_MODULES['ldap'] try: # Enable TLS if ldaps is specified in the URI if mod_settings['LDAP_HOST'][0:4].lower() == "ldaps": ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) connect = ldap.initialize(mod_settings['LDAP_HOST']) connect.set_option(ldap.OPT_REFERRALS, 0) ldap_bind_user = "{}={},{}".format( mod_settings['USER_ATTRIBUTE'], username, mod_settings['USER_SEARCH_BASE'] ) connect.simple_bind_s(ldap_bind_user, password) if settings.DEBUG: print(f'{METADATA["name"]} - User authenticated: {ldap_bind_user}') if 'REQUIRED_GROUP' in mod_settings: ldap_member_filter = "{}={}".format(mod_settings['GROUP_MEMBER_ATTRIBUTE'], username) if settings.DEBUG: print("LDAP Member Filter: {}".format(ldap_member_filter)) groups = connect.search_s( mod_settings['GROUP_SEARCH_BASE'], ldap.SCOPE_SUBTREE, ldap_member_filter, ['dn'] ) if settings.DEBUG: print("List of groups found: {}".format(groups)) for group in groups: if mod_settings['REQUIRED_GROUP'] in group[0]: return True return False return True except ldap.INVALID_CREDENTIALS: return False ================================================ FILE: gui/modules/api/auth/routes.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import datetime, uuid from flask import Blueprint, jsonify from util.security import AES_CTR from shared import debugEndpoint, StatusCodes, getRequestData from database import DummySession, startSession, dSIPUser from modules.api.api_functions import showApiError, createApiResponse, api_security from modules.api.auth.functions import addDSIPUser from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict import settings user = Blueprint('user', __name__) # TODO: standardize response payloads using new createApiResponse() # marked for implementation in v0.74 @user.route('/api/v1/auth/login', methods=['POST']) # @api_security def login(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"username": str, "password": str} # ensure requred args are provided REQUIRED_ARGS = {'username', 'password'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} db = DummySession() try: if (settings.DEBUG): debugEndpoint() # get request data request_data = getRequestData() db = startSession() # Check for existing user existing_user = db.query(dSIPUser).filter(dSIPUser.username == (request_data['username'])).first() if existing_user: print("Saved Password: ", existing_user.password) print("Decrypted: ", AES_CTR.decrypt(existing_user.password)) print("Provided: ", request_data['password']) if str(request_data['password']) == AES_CTR.decrypt(existing_user.password): if (not existing_user.token) or (datetime.datetime.now() > existing_user.token_expiration): existing_user.token = uuid.uuid4() existing_user.token_expiration = datetime.datetime.now() + datetime.timedelta(days=1) db.commit() response_payload = { 'message': 'Login successful', 'token': existing_user.token, 'expiration_date': existing_user.token_expiration } return jsonify(response_payload), StatusCodes.HTTP_OK else: response_payload = { "message": 'Invalid credentials', 'payload': request_data } return jsonify(response_payload), StatusCodes.HTTP_UNAUTHORIZED else: # If user does not exist return an invalid credentials message response_payload['data'] = { "message": 'Invalid credentials', 'payload': request_data } return jsonify(response_payload), StatusCodes.HTTP_UNAUTHORIZED except Exception as ex: return showApiError(ex) finally: db.close() @user.route('/api/v1/auth/user', methods=['POST']) @api_security def createUser(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"firstname": str, "lastname": str, "username": str, "password": str, "roles": dict, "domains": dict} # ensure requred args are provided REQUIRED_ARGS = {'firstname', 'lastname', 'username', 'password', 'roles', 'domains'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} db = DummySession() try: if (settings.DEBUG): debugEndpoint() # get request data request_data = getRequestData() db = startSession() # Check for existing user existing_user = db.query(dSIPUser).filter(dSIPUser.username.like(request_data['username'])).all() if (existing_user): response_payload = {'error': 'User Already Exists', 'data': request_data} return jsonify(response_payload), StatusCodes.HTTP_CONFLICT # If user does not exist proceed to creat the new user new_user = addDSIPUser(request_data) # sanity checks response_payload['data'] = { "message": 'User Created Successfully', 'payload': request_data } return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) finally: db.close() @user.route('/api/v1/auth/user', methods=['GET']) def listUsers(): # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() # Check for existing user user_list = db.query(dSIPUser).all() response_payload = [] for the_user in user_list: response_payload.append( { "id": the_user.id, "username": the_user.username, "firstname": the_user.firstname, "lastname": the_user.lastname, "roles": [], "domains": [] } ) return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) finally: db.close() @user.route('/api/v1/auth/user/<int:id>', methods=['GET']) # @api_security def getUser(id=None): # use a whitelist to avoid possible buffer overflow vulns or crashes # VALID_REQUEST_DATA_ARGS = {"firstname": str, "lastname": str, "username": str, "password": str, "roles": dict, "domains": dict} # ensure requred args are provided # REQUIRED_ARGS = {'firstname', 'lastname', 'username', 'password', 'roles', 'domains'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} db = DummySession() try: if (settings.DEBUG): debugEndpoint() if id is None: response_payload = {'error': 'Invalid Request', 'msg': 'Invalid Request. You seem to be missing the ID parameter.'} return jsonify(response_payload), StatusCodes.HTTP_BAD_REQUEST db = startSession() # Check for existing user existing_user = db.query(dSIPUser).get(id) if (existing_user): response_payload = { "username": existing_user.username, "firstname": existing_user.firstname, "lastname": existing_user.lastname, "roles": [], "domains": [] } return jsonify(response_payload), StatusCodes.HTTP_OK else: response_payload = {'error': 'User Not Found', 'msg': 'User Not Found'} return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND except Exception as ex: return showApiError(ex) finally: db.close() @user.route('/api/v1/auth/user/<int:id>', methods=['PUT']) # @api_security def updateUser(id=None): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"firstname": str, "lastname": str, "username": str, "password": str, "roles": dict, "domains": dict} # ensure requred args are provided REQUIRED_ARGS = {'firstname', 'lastname', 'username', 'password', 'roles', 'domains'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} db = DummySession() try: if (settings.DEBUG): debugEndpoint() if id is None: response_payload = {'error': 'Invalid Request', 'msg': 'Invalid Request. You seem to be missing the ID parameter.'} return jsonify(response_payload), StatusCodes.HTTP_BAD_REQUEST # get request data request_data = getRequestData() db = startSession() # Check for existing user existing_user = db.query(dSIPUser).get(id) if existing_user: existing_user.firstname = request_data['firstname'] existing_user.lastname = request_data['lastname'] existing_user.username = request_data['username'] existing_user.password = AES_CTR.encrypt(request_data['password']) existing_user.roles = '' existing_user.domains = '' db.commit() response_payload = {"message": 'User updated successfully', 'data': { "username": existing_user.username, "firstname": existing_user.firstname, "lastname": existing_user.lastname, "roles": [], "domains": [] }} return jsonify(response_payload), StatusCodes.HTTP_OK else: response_payload = {'error': 'User Not Found', 'msg': 'User Not Found'} return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND except Exception as ex: return showApiError(ex) finally: db.close() @user.route('/api/v1/auth/user/<int:id>', methods=['DELETE']) # @api_security def deleteUser(id=None): db = DummySession() try: if (settings.DEBUG): debugEndpoint() if id is None: response_payload = {'error': 'Invalid Request', 'msg': 'Invalid Request. You seem to be missing the ID parameter.'} return jsonify(response_payload), StatusCodes.HTTP_BAD_REQUEST db = startSession() # Check for existing user existing_user = db.query(dSIPUser).get(id) if existing_user: db.delete(existing_user) db.commit() response_payload = {"message": 'User deleted successfully'} return jsonify(response_payload), StatusCodes.HTTP_OK else: response_payload = {'error': 'User Not Found', 'msg': 'User Not Found'} return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND except Exception as ex: return showApiError(ex) finally: db.close() ================================================ FILE: gui/modules/api/carriergroups/functions.py ================================================ import json, sys from flask import request, session, redirect, url_for, render_template from sqlalchemy import exc as sql_exceptions, text, cast, Integer, func from werkzeug import exceptions as http_exceptions # make sure the generated source files are imported instead of the template ones if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import settings from shared import debugException, debugEndpoint, stripDictVals, strFieldsToDict, dictToStrFields, showError from database import startSession, DummySession, Gateways, Address, UAC, GatewayGroups, Dispatcher, DsipGwgroup2LB, \ OutboundRoutes from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict from util.networking import safeUriToHost, safeFormatSipUri, safeStripPort, encodeSipUser def displayCarrierGroups(gwgroup=None): """ Display the carrier groups in the view :param gwgroup: """ # TODO: track related dr_rules and update lists on delete db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() query = db.query( GatewayGroups.id, GatewayGroups.description, GatewayGroups.gwlist, UAC.r_username, UAC.auth_password, UAC.r_domain, UAC.auth_username, UAC.auth_proxy, cast(DsipGwgroup2LB.enabled, Integer).label('lb_enabled') ).outerjoin( UAC, GatewayGroups.id == UAC.l_uuid ).outerjoin( DsipGwgroup2LB, GatewayGroups.id == DsipGwgroup2LB.gwgroupid ).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.CARRIER.value) ) # res must be a list() if gwgroup is not None and gwgroup != "": res = [ query.filter( GatewayGroups.id == gwgroup ).first() ] else: res = query.all() return render_template('carriergroups.html', rows=res) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() def addUpdateCarrierGroups(data=None): """ Add or Update a group of carriers """ db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() if data is None: # called from flask url router, make sure user is logged in # TODO: toss this in a decorator func with error handling and use for gui auth checks if not session.get('logged_in'): return redirect(url_for('index')) # grab data from gui form form = stripDictVals(request.form.to_dict()) else: # Set the form variables to data parameter form = data gwgroup = form['gwgroupid'] name = form['name'] lb_enabled = int(form['lb_enabled']) if 'lb_enabled' in form else 0 new_name = form['new_name'] if 'new_name' in form else '' plugin_name = form['plugin_name'] if 'plugin_name' in form else '' authtype = form['authtype'] if 'authtype' in form else '' username = form['r_username'] if 'r_username' in form else '' auth_username = form['auth_username'] if 'auth_username' in form else '' auth_password = form['auth_password'] if 'auth_password' in form else '' auth_domain = form['auth_domain'] if 'auth_domain' in form else settings.DEFAULT_AUTH_DOMAIN auth_proxy = form['auth_proxy'] if 'auth_proxy' in form else '' # Workaround: for Twilio Elastic SIP and Programmable SIP # Set the Realm to sip.twilio.com if the domain contains a pstn.twilio.com or sip.twilio.com domain. # Otherwise, set it to the name of the auth domain auth_realm = "sip.twilio.com" if "twilio.com" in auth_domain else auth_domain # format data if authtype == "userpwd" and (plugin_name is None or plugin_name == ''): auth_domain = safeUriToHost(auth_domain) if auth_domain is None: raise http_exceptions.BadRequest("Auth domain hostname/address is malformed") if len(auth_proxy) == 0: auth_proxy = auth_domain auth_proxy = safeFormatSipUri(auth_proxy) if auth_proxy is None: raise http_exceptions.BadRequest('Auth domain or proxy is malformed') if len(auth_username) == 0: auth_username = username auth_username = encodeSipUser(auth_username) # Adding if len(gwgroup) <= 0: Gwgroup = GatewayGroups(name, type=settings.FLT_CARRIER) db.add(Gwgroup) db.flush() gwgroup = Gwgroup.id # Add auth_domain(aka registration server) to the gateway list if authtype == "userpwd" and (plugin_name is None or plugin_name == ''): Uacreg = UAC(gwgroup, username, auth_password, realm=auth_realm, auth_username=auth_username, auth_proxy=auth_proxy, local_domain=settings.EXTERNAL_FQDN, remote_domain=auth_domain) Addr = Address(name + "-uac", auth_domain, 32, settings.FLT_CARRIER, gwgroup=gwgroup) db.add(Uacreg) db.add(Addr) # Updating else: # config form if len(name) > 0: Gwgroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroup).first() gwgroup_fields = strFieldsToDict(Gwgroup.description) old_name = gwgroup_fields['name'] gwgroup_fields['name'] = name Gwgroup.description = dictToStrFields(gwgroup_fields) Addr = db.query(Address).filter( Address.tag.regexp_match(f'name:{old_name}-uac(,|$)') ).first() if Addr is not None: addr_fields = strFieldsToDict(Addr.tag) addr_fields['name'] = 'name:{}-uac'.format(new_name) Addr.tag = dictToStrFields(addr_fields) # auth form if authtype == "userpwd" and (plugin_name is None or plugin_name == ''): # update uacreg if exists, otherwise create if not db.query(UAC).filter(UAC.l_uuid == gwgroup).update( { 'l_username': username, 'r_username': username, 'auth_username': auth_username, 'auth_password': auth_password, 'r_domain': auth_domain, 'realm': auth_realm, 'auth_proxy': auth_proxy, 'flags': UAC.FLAGS.REG_ENABLED.value }, synchronize_session=False ): Uacreg = UAC(gwgroup, username, auth_password, realm=auth_domain, auth_username=auth_username, auth_proxy=auth_proxy, local_domain=settings.EXTERNAL_FQDN, remote_domain=auth_domain) db.add(Uacreg) # update address if exists, otherwise create if not db.query(Address).filter( Address.tag.contains("name:{}-uac".format(name)) ).update({'ip_addr': auth_domain}, synchronize_session=False): Addr = Address(name + "-uac", auth_domain, 32, settings.FLT_CARRIER, gwgroup=gwgroup) db.add(Addr) else: # delete uacreg and address if they exist db.query(UAC).filter(UAC.l_uuid == gwgroup).delete(synchronize_session=False) db.query(Address).filter( Address.tag.regexp_match(f'name:{name}-uac(,|$)') ).delete(synchronize_session=False) # toggle load balancing based on user input # TODO: this WILL be changed in the future when we refactor load balancing fields = strFieldsToDict(Gwgroup.description) fields['lb'] = gwgroup Gwgroup.description = dictToStrFields(fields) db.flush() db.execute( text("UPDATE dsip_gwgroup2lb SET enabled = :enabled WHERE gwgroupid = :gwgroupid"), {'enabled': lb_enabled, 'gwgroupid': gwgroup} ) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True if data is None: return displayCarrierGroups() else: return gwgroup except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() def displayCarriers(gwid=None, gwgroup=None, newgwid=None): """ Display the carriers table :param gwid: :param gwgroup: :param newgwid: """ db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() db = startSession() # carriers is a list of carriers matching query # carrier_routes is a list of associated rules for each carrier carriers = [] carrier_rules = [] # get carrier by id if gwid is not None: carriers = [ db.query( Gateways.gwid, Gateways.description, Gateways.address, Gateways.strip, Gateways.pri_prefix, Dispatcher.attrs.label("dispatcher_attrs") ).filter( Gateways.gwid == gwid ).outerjoin( Dispatcher, Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).first() ] rules = db.query(OutboundRoutes).filter(OutboundRoutes.groupid == settings.FLT_OUTBOUND).all() gateway_rules = {} for rule in rules: if str(gwid) in filter(None, rule.gwlist.split(',')): gateway_rules[rule.ruleid] = strFieldsToDict(rule.description)['name'] carrier_rules.append(json.dumps(gateway_rules, separators=(',', ':'))) # get carriers by carrier group elif gwgroup is not None: Gatewaygroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroup).first() # check if any endpoints in group b4 converting to list(int) if Gatewaygroup is not None and Gatewaygroup.gwlist != "": gwlist = [int(gw) for gw in filter(None, Gatewaygroup.gwlist.split(","))] carriers = db.query( Gateways.gwid, Gateways.description, Gateways.address, Gateways.strip, Gateways.pri_prefix, Dispatcher.attrs.label("dispatcher_attrs") ).filter( Gateways.gwid.in_(gwlist) ).outerjoin( Dispatcher, Dispatcher.description.regexp_match(func.CONCAT('gwid=', Gateways.gwid, '(;|$)')) ).all() rules = db.query(OutboundRoutes).filter(OutboundRoutes.groupid == settings.FLT_OUTBOUND).all() for gateway_id in filter(None, Gatewaygroup.gwlist.split(",")): gateway_rules = {} for rule in rules: if gateway_id in filter(None, rule.gwlist.split(',')): gateway_rules[rule.ruleid] = strFieldsToDict(rule.description)['name'] carrier_rules.append(json.dumps(gateway_rules, separators=(',', ':'))) # get all carriers else: carriers = db.query( Gateways.gwid, Gateways.description, Gateways.address, Gateways.strip, Gateways.pri_prefix, Dispatcher.attrs.label("dispatcher_attrs") ).filter( Gateways.type == settings.FLT_CARRIER ).outerjoin( Dispatcher, Dispatcher.description.regexp_match(func.CONCAT('gwid=', Gateways.gwid, '(;|$)')) ).all() rules = db.query(OutboundRoutes).filter(OutboundRoutes.groupid == settings.FLT_OUTBOUND).all() for gateway in carriers: gateway_rules = {} for rule in rules: if str(gateway.gwid) in filter(None, rule.gwlist.split(',')): gateway_rules[rule.ruleid] = strFieldsToDict(rule.description)['name'] carrier_rules.append(json.dumps(gateway_rules, separators=(',', ':'))) return render_template('carriers.html', rows=carriers, routes=carrier_rules, gwgroup=gwgroup, new_gwid=newgwid, kam_reload_required=getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required']) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() def addUpdateCarriers(data=None): """ Add or Update a carrier """ db = DummySession() newgwid = None try: if (settings.DEBUG): debugEndpoint() db = startSession() if data is None: # called from flask url router, make sure user is logged in # TODO: toss this in a decorator func with error handling and use for gui auth checks if not session.get('logged_in'): return redirect(url_for('index')) # grab data from gui form form = stripDictVals(request.form.to_dict()) else: # Set the form variables to data parameter form = data # match what the API would send us if 'gwgroupid' in form: form['gwgroupid'] = str(form['gwgroupid']) # match what the UI would send us if 'gwgroup' in form: form['gwgroupid'] = str(form['gwgroup']) if 'ip_addr' in form: form['hostname'] = str(form['ip_addr']) gwid = form['gwid'] if 'gwid' in form else '' gwgroup = form['gwgroupid'] if len(form['gwgroupid']) > 0 else '' name = form['name'] if len(form['name']) > 0 else '' hostname = form['hostname'] if len(form['hostname']) > 0 else '' strip = form['strip'] if len(form['strip']) > 0 else '0' prefix = form['prefix'] if len(form['prefix']) > 0 else '' rweight = int(form['rweight']) if form.get('rweight', '') != '' else 1 if len(hostname) == 0: raise http_exceptions.BadRequest("Carrier hostname/address is required") sip_addr = safeUriToHost(hostname, default_port=5060) if sip_addr is None: raise http_exceptions.BadRequest("Endpoint hostname/address is malformed") host_addr = safeStripPort(sip_addr) # Adding if len(gwid) <= 0: if len(gwgroup) > 0: Addr = Address(name, host_addr, 32, settings.FLT_CARRIER, gwgroup=gwgroup) db.add(Addr) db.flush() Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_CARRIER, gwgroup=gwgroup, addr_id=Addr.id) db.add(Gateway) db.flush() newgwid = Gateway.gwid gwid = str(newgwid) Gatewaygroup = db.query(GatewayGroups).filter(GatewayGroups.id == gwgroup).first() gwlist = list(filter(None, Gatewaygroup.gwlist.split(","))) gwlist.append(gwid) Gatewaygroup.gwlist = ','.join(gwlist) # Create dispatcher group with the set id being the gateway group id dispatcher = Dispatcher(setid=gwgroup, destination=sip_addr, rweight=rweight, name=name, gwid=gwid) db.add(dispatcher) else: Addr = Address(name, host_addr, 32, settings.FLT_CARRIER) db.add(Addr) db.flush() Gateway = Gateways(name, sip_addr, strip, prefix, settings.FLT_CARRIER, addr_id=Addr.id) db.add(Gateway) # Updating else: Gateway = db.query(Gateways).filter(Gateways.gwid == gwid).first() Gateway.address = sip_addr Gateway.strip = strip Gateway.pri_prefix = prefix gw_fields = strFieldsToDict(Gateway.description) gw_fields['name'] = name if len(gwgroup) <= 0: gw_fields['gwgroup'] = gwgroup # update dispatcher entry if db.query(Dispatcher).filter( Dispatcher.description.regexp_match(f'gwid={gwid}(;|$)') ).update({ "attrs": Dispatcher.buildAttrs(rweight=rweight) }, synchronize_session=False): pass # add new dispatcher entry if it did not exist (gwgroup must also exist) elif len(gwgroup) > 0: dispatcher = Dispatcher(setid=gwgroup, destination=sip_addr, rweight=rweight, name=name, gwid=gwid) db.add(dispatcher) # if address exists update address_exists = False if 'addr_id' in gw_fields and len(gw_fields['addr_id']) > 0: Addr = db.query(Address).filter(Address.id == gw_fields['addr_id']).first() # if entry is non existent handle in next block if Addr is not None: address_exists = True Addr.ip_addr = host_addr addr_fields = strFieldsToDict(Addr.tag) addr_fields['name'] = name if len(gwgroup) > 0: addr_fields['gwgroup'] = gwgroup Addr.tag = dictToStrFields(addr_fields) # otherwise create the address if not address_exists: if len(gwgroup) > 0: Addr = Address(name, host_addr, 32, settings.FLT_CARRIER, gwgroup=gwgroup) else: Addr = Address(name, host_addr, 32, settings.FLT_CARRIER) db.add(Addr) db.flush() gw_fields['addr_id'] = str(Addr.id) # gw_fields may be updated above so set after Gateway.description = dictToStrFields(gw_fields) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True if data is None: return displayCarriers(gwgroup=gwgroup, newgwid=newgwid) return gwid except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) db.rollback() db.flush() return showError(type='http', code=ex.code, msg=ex.description) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() ================================================ FILE: gui/modules/api/carriergroups/plugin/twilio/carrier_plugintype_version.py ================================================ ================================================ FILE: gui/modules/api/carriergroups/plugin/twilio/interface.py ================================================ import os from twilio.rest import Client # Metadata about plugin plugin_version = 1.0 plugin_name = "twilio" default_twilio_domain_name = "pstn.twilio.com" # Get plugin meta data def getPluginMetaData(): return "{'authfields': ['account_sid':'string','account_token':'string']}, \ 'prefix': '+', \ }" # Initializes the plugin def init(account_sid,auth_token): try: # Find your Account SID and Auth Token at twilio.com/console # and set the environment variables. See http://twil.io/secure account_sid = os.environ['TWILIO_ACCOUNT_SID'] if account_sid == None else account_sid auth_token = os.environ['TWILIO_AUTH_TOKEN'] if auth_token == None else auth_token client = Client(account_sid, auth_token) return client except KeyError as ke: print("The {} key is not set".format(ke)) except Exception as ex: raise Exception( type(ex).__name__ + "-" + str(ex)) print(ex) return False def createTrunk(client,trunk_name,dsip_ip_address,twilio_domain_name=default_twilio_domain_name): try: # Convert trunk name to a lower case trunk_name = trunk_name.lower() fqdn_domain_name = "{}.{}".format(trunk_name,twilio_domain_name) trunk = client.trunking.trunks.create(friendly_name=trunk_name,domain_name=fqdn_domain_name) if trunk: trunk.ip_access_control_lists.create(createIPAccessControlList(client,trunk_name,dsip_ip_address)) except Exception as ex: raise Exception( type(ex).__name__ + "-" + str(ex)) return trunk.sid def createIPAccessControlList(client,trunk_name,dsip_ip_address): ip_access_control_list = client.sip \ .ip_access_control_lists \ .create(friendly_name=trunk_name) ip_address = client.sip \ .ip_access_control_lists(ip_access_control_list.sid) \ .ip_addresses \ .create(friendly_name=trunk_name, ip_address=dsip_ip_address) return ip_access_control_list.sid # Used for unit testing def main(): trunk_name="dSIPRouter" dsip_ip_address="138.197.157.191/32" try: if init(): createTrunk(trunk_name, dsip_ip_address) except Exception as ex: print(ex) if __name__ == "__main__": main() ================================================ FILE: gui/modules/api/carriergroups/routes.py ================================================ import sys, os, importlib.util # make sure the generated source files are imported instead of the template ones if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') from flask import Blueprint, jsonify from database import startSession, DummySession, GatewayGroups from shared import debugEndpoint, StatusCodes, getRequestData, strFieldsToDict from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict from util.networking import getExternalIP from modules.api.api_functions import showApiError, api_security from modules.api.carriergroups.functions import addUpdateCarrierGroups, addUpdateCarriers import settings carriergroups = Blueprint('carriergroups','__name__') # TODO: standardize response payloads using new createApiResponse() # marked for implementation in v0.74 @carriergroups.route('/api/v1/carriergroups',methods=['GET']) @carriergroups.route('/api/v1/carriergroups/<string:id>',methods=['GET']) @carriergroups.route('/api/v1/carriergroups/<string:id>',methods=['DELETE']) @api_security def listCarrierGroups(): """ List all Carrier Groups\n =============== Request Payload =============== .. code-block:: json {} ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ carriergroups: [ { gwgroupid: <int>, name: <string>, gwlist: <string> } ] ] } """ db = DummySession() # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() db = startSession() carriergroups = db.query(GatewayGroups).filter( GatewayGroups.description.regexp_match(GatewayGroups.FILTER.CARRIER.value) ).all() for carriergroup in carriergroups: # Grap the description field, which is comma seperated key/value pair fields = strFieldsToDict(carriergroup.description) # append summary of endpoint group data response_payload['data'].append({ 'gwgroupid': carriergroup.id, 'name': fields['name'], 'gwlist': carriergroup.gwlist }) response_payload['msg'] = 'Endpoint groups found' return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() @carriergroups.route('/api/v1/carriergroups/plugin/<string:plugin_name>/config',methods=['GET']) @api_security def getPluginMetaData(plugin_name): """ Will return meta data about the plug =============== Request Payload =============== .. code-block:: json {} ================ Response Payload ================ .. code-block:: json { authfields: [], config <string>, kamreload: <bool>, data: [ carriergroups: [ { gwgroupid: <int>, name: <string>, gwlist: <string> } ] ] } """ # Import plugin # Returns the Base directory of this file base_dir = os.path.dirname(__file__) try: # Use the Base Dir to specify the location of the plugin required for this domain spec = importlib.util.spec_from_file_location(format(plugin_name), "{}/plugin/{}/interface.py".format(base_dir,plugin_name)) plugin = importlib.util.module_from_spec(spec) spec.loader.exec_module(plugin) if plugin: return plugin.getPluginMetaData() else: return None except Exception as ex: print(ex) @carriergroups.route('/api/v1/carriergroups',methods=['PUT']) @carriergroups.route('/api/v1/carriergroups/<string:id>',methods=['PUT']) @carriergroups.route('/api/v1/carriergroups',methods=['POST']) @api_security def addCarrierGroups(id=None): """ ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ { gwgroupid: <int>, endpoints: [ <int>, ... ] } ] } """ db = DummySession() try: if settings.DEBUG: debugEndpoint() db = startSession() # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} # Dictionary to store request parameters data = {} # get request data request_payload = getRequestData() data['name'] = request_payload['name'] data['lb_enabled'] = int(request_payload['lb_enabled']) if 'lb_enabled' in request_payload else 0 if id == None: data['gwgroupid'] = request_payload['gwgroupid'] if 'gwgroupid' in request_payload else '' else: data['gwgroupid'] = id data['strip'] = request_payload['strip'] if 'strip' in request_payload else '' data['prefix'] = request_payload['prefix'] if 'prefix' in request_payload else '' auth = request_payload['auth'] if 'auth' in request_payload else None if auth: data['authtype'] = auth['type'] data['r_username'] = auth['r_username'] if 'r_username' in auth else '' data['auth_username'] = auth['auth_username'] if 'auth_username' in auth else '' data['auth_password'] = auth['auth_password'] if 'auth_password' in auth else '' data['auth_domain'] = auth['auth_domain'] if 'auth_domain' in auth else settings.DEFAULT_AUTH_DOMAIN data['auth_proxy'] = auth['auth_proxy'] if 'auth_proxy' in auth else '' plugin = request_payload['plugin'] if 'plugin' in request_payload else None plugin_made_updates = False if plugin is not None: data['plugin_name'] = plugin['name'] data['plugin_prefix'] = plugin['plugin_prefix'] if 'plugin_prefix' in plugin else 'dsip-' data['plugin_account_sid'] = plugin['account_sid'] if 'account_sid' in plugin else '' data['plugin_account_token'] = plugin['account_token'] if 'account_token' in plugin else '' if data['plugin_name'] != "": # Import Plugin from modules.api.carriergroups.plugin.twilio.interface import init, createTrunk, createIPAccessControlList client = init(data['plugin_account_sid'], data['plugin_account_token']) if client: trunk_name = "{}{}".format(data['plugin_prefix'], data['name']) trunk_sid = createTrunk(client, trunk_name, getExternalIP()) if trunk_sid: createIPAccessControlList(client, trunk_name, getExternalIP()) plugin_made_updates = True endpoints = request_payload['endpoints'] if 'endpoints' in request_payload else [] # This creates the carrier group only gwgroupid = addUpdateCarrierGroups(data) # This creates the Twilio Elastic SIP Entry in the Carrier Group if plugin_made_updates: carrier_data = {} carrier_data['gwgroupid'] = gwgroupid carrier_data['name'] = trunk_name carrier_data['ip_addr'] = "{}.{}".format(trunk_name, "pstn.twilio.com") carrier_data['strip'] = '' carrier_data['prefix'] = '' addUpdateCarriers(carrier_data) # Add endpoints for endpoint in endpoints: #Add the gwgroupdid endpoint['gwgroupid'] = gwgroupid addUpdateCarriers(endpoint) gwgroup_data = {} gwgroup_data['gwgroupid'] = gwgroupid response_payload['data'].append(gwgroup_data) return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: db.rollback() db.flush() return showApiError(ex) finally: db.close() ================================================ FILE: gui/modules/api/cron_functions.py ================================================ from datetime import datetime from shared import debugException from database import startSession, DummySession, Subscribers, dSIPLeases, Gateways def cleanupLeases(): db = DummySession() try: db = startSession() Leases = db.query(dSIPLeases).filter(datetime.now() >= dSIPLeases.expiration).all() for Lease in Leases: # Remove the entry in the Subscribers table db.query(Subscribers).filter(Subscribers.id == Lease.sid).delete(synchronize_session=False) # Remove the entry in the Gateway table db.query(Gateways).filter(Gateways.gwid == Lease.gwid).delete(synchronize_session=False) # Remove the entry in the Lease table db.delete(Lease) db.commit() except Exception as ex: debugException(ex) db.rollback() db.flush() finally: db.close() ================================================ FILE: gui/modules/api/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { # Check to see if the acc table or cdr tables are in use MERGE_DATA=0 count=$(withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from dsip_endpoint_lease limit 10" 2> /dev/null) if [ ${count:-0} -gt 0 ]; then MERGE_DATA=1 fi if [ ${MERGE_DATA} -eq 1 ]; then printwarn "The endpoint lease table (dsip_endpoint_lease) in Kamailio already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/api/api.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table \ --no-create-info --insert-ignore dsip_endpoint_lease ) | withRootDBConn --db="$KAM_DB_NAME" mysql else # Replace the api tables printwarn "Adding/Replacing the tables needed for API module within dSIPRouter..." withRootDBConn --db="$KAM_DB_NAME" mysql -sN <${DSIP_PROJECT_DIR}/gui/modules/api/api.sql fi } function install { installSQL cronRemove -u dsiprouter 'dsiprouter_cron.py api' cronAppend -u dsiprouter "*/1 * * * * ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter_cron.py api cleanleases" cronAppend -u dsiprouter "0 0 * * * ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter_cron.py api synclicenses" printdbg "API module installed" } function uninstall { cronRemove -u dsiprouter 'dsiprouter_cron.py api' printdbg "API module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/api/kamailio/errors.py ================================================ class KamailioError(Exception): """ There was an error communicating with Kamailio """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) class NoDispatcherSets(KamailioError): """ No dispatcher sets exist but the module is loaded """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) ================================================ FILE: gui/modules/api/kamailio/functions.py ================================================ import requests, sys from time import sleep, time from typing import Union from werkzeug import exceptions as http_exceptions if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') from shared import IO from util.security import AES_CTR from modules.api.kamailio.errors import NoDispatcherSets, KamailioError import settings def sendJsonRpcCmd(host, method, params=(), timeout=settings.KAM_JSONRPC_TIMEOUT): """ Send a JSONRPC command to Kamailio :param host: the host to send the request to :type host: str :param method: method as parsed by `kamcmd <https://github.com/kamailio/kamailio/tree/master/utils/kamcmd>`_ :type method: str :param params: parameters for the command :type params: tuple|list :param timeout: timeout in seconds for command to finish :type timeout: int :return: The result from Kamailio :rtype: dict :raises requests.exceptions.HTTPError: if an HTTP error occurred :raises requests.exceptions.JSONDecodeError: if a JSON parsing error occurred :raises KamailioError: if communicating with Kamailio failed :raises Exception: for any other error """ if settings.DEBUG: IO.printdbg(f'sending jsonrpc command to {host}: {method} {str(params)}') headers = { "Accept": "application/json", "Content-Type": "application/json" } payload = { "method": method, "jsonrpc": settings.KAM_JSONRPC_VERSION, "id": settings.KAM_JSONRPC_ID } if len(params) > 0: payload['params'] = params def sendit(host): return requests.post( f'http://{host}:5060{settings.KAM_JSONRPC_ROOTPATH}', headers=headers, json=payload, timeout=timeout, ) r: Union[requests.Response, None] = None cutoff_time = time() + timeout while time() < cutoff_time: try: r = sendit(host) r.raise_for_status() break except requests.exceptions.HTTPError as ex: if ex.response.status_code == 500 and ex.response.json()['error']['message'] == 'ongoing reload': sleep(settings.KAM_JSONRPC_RETRYIVAL) continue raise data = r.json() if "error" in data: # specific cases if data["error"] == "No Destination Sets": raise NoDispatcherSets(data["error"]) # general case raise KamailioError(data["error"]) return data['result'] def reloadKamailio(): try: # format some settings for kam config dsip_api_url = settings.DSIP_API_PROTO + '://' + '127.0.0.1' + ':' + str(settings.DSIP_API_PORT) if isinstance(settings.DSIP_API_TOKEN, bytes): dsip_api_token = AES_CTR.decrypt(settings.DSIP_API_TOKEN) else: dsip_api_token = settings.DSIP_API_TOKEN # data that is always reloaded, part of core dsiprouter rpc_args = [ ('127.0.0.1', 'cfg.sets', ['server', 'role', settings.ROLE]), ('127.0.0.1', 'cfg.sets', ['server', 'api_server', dsip_api_url]), ('127.0.0.1', 'cfg.sets', ['server', 'api_token', dsip_api_token]), ('127.0.0.1', 'htable.reload', ['maintmode']), ('127.0.0.1', 'htable.reload', ['gw2gwgroup']), ('127.0.0.1', 'htable.reload', ['gwgroup2lb']), ('127.0.0.1', 'htable.reload', ['inbound_hardfwd']), ('127.0.0.1', 'htable.reload', ['inbound_failfwd']), ('127.0.0.1', 'htable.reload', ['prefix_to_route']), ] # reload data depending on the features enabled features_enabled = { x['name'] for x in sendJsonRpcCmd('127.0.0.1', 'core.ppdefines_full') \ if x['value'] == 'none' } if 'WITH_AUTH' in features_enabled and 'WITH_IPAUTH' in features_enabled: rpc_args.append(('127.0.0.1', 'permissions.addressReload')) if 'WITH_UAC' in features_enabled: rpc_args.append(('127.0.0.1', 'uac.reg_reload')) if 'WITH_DROUTE' in features_enabled: rpc_args.append(('127.0.0.1', 'drouting.reload')) if 'WITH_DISPATCHER' in features_enabled: rpc_args.append(('127.0.0.1', 'dispatcher.reload')) rpc_args.append(('127.0.0.1', 'keepalive.flush')) if 'WITH_CALL_SETTINGS' in features_enabled: rpc_args.append(('127.0.0.1', 'htable.reload', ['call_settings'])) if 'WITH_MULTIDOMAIN' in features_enabled: rpc_args.append(('127.0.0.1', 'domain.reload')) if 'WITH_TELEBLOCK' in features_enabled: rpc_args.append(('127.0.0.1', 'cfg.sets', ['teleblock', 'gw_enabled', str(settings.TELEBLOCK_GW_ENABLED)])) if 'WITH_LCR' in features_enabled: rpc_args.append(('127.0.0.1', 'htable.reload', ['tofromprefix'])) #if 'WITH_TLS' in features_enabled: # # TODO: tls.reload is VERY slow on some systems. Commented out until we get a resolution # rpc_args.append(('127.0.0.1', 'tls.reload', [], 20)) if 'WITH_WEBSOCKETS' in features_enabled: rpc_args.append(('127.0.0.1', 'ws.enable')) if 'WITH_DNID_LNP_ENRICHMENT' in features_enabled: rpc_args.append(('127.0.0.1', 'htable.reload', ['enrichdnid_lnpmap'])) if 'WITH_RTPENGINE' in features_enabled: rpc_args.append(('127.0.0.1', 'rtpengine.enable', ['all', 1])) if 'WITH_TRANSNEXUS' in features_enabled: rpc_args.append(('127.0.0.1', 'cfg.sets', ['transnexus', 'authservice_enabled', str(settings.TRANSNEXUS_AUTHSERVICE_ENABLED)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['transnexus', 'authservice_host', str(settings.TRANSNEXUS_AUTHSERVICE_HOST)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['transnexus', 'verifyservice_enabled', str(settings.TRANSNEXUS_VERIFYSERVICE_ENABLED)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['transnexus', 'verifyservice_host', str(settings.TRANSNEXUS_VERIFYSERVICE_HOST)])) if 'WITH_STIRSHAKEN' in features_enabled: rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_enabled', str(settings.STIR_SHAKEN_ENABLED)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_prefix_a', str(settings.STIR_SHAKEN_PREFIX_A)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_prefix_b', str(settings.STIR_SHAKEN_PREFIX_B)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_prefix_c', str(settings.STIR_SHAKEN_PREFIX_C)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_prefix_invalid', str(settings.STIR_SHAKEN_PREFIX_INVALID)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_block_invalid', str(settings.STIR_SHAKEN_BLOCK_INVALID)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_key_path', str(settings.STIR_SHAKEN_KEY_PATH)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['stir_shaken', 'stir_shaken_cert_url', str(settings.STIR_SHAKEN_CERT_URL)])) # data that is conditionally reloaded based on dsiprouter settings if settings.TELEBLOCK_GW_ENABLED: rpc_args.append(('127.0.0.1', 'cfg.sets', ['teleblock', 'gw_ip', str(settings.TELEBLOCK_GW_IP)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['teleblock', 'gw_port', str(settings.TELEBLOCK_GW_PORT)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['teleblock', 'media_ip', str(settings.TELEBLOCK_MEDIA_IP)])) rpc_args.append(('127.0.0.1', 'cfg.sets', ['teleblock', 'media_port', str(settings.TELEBLOCK_MEDIA_PORT)])) # send off all the jsonrpc requests and handle any failures if possible for cmdset in rpc_args: try: sendJsonRpcCmd(*cmdset) except NoDispatcherSets: pass except KamailioError as ex: err = http_exceptions.HTTPException(str(ex)) err.code = 500 raise err except requests.exceptions.HTTPError as ex: err = http_exceptions.HTTPException(response=ex.response) raise err IO.printinfo("[---- Reloaded Kamailio with dSIPRouter Settings ----]") except Exception as ex: IO.printerr("[---- Could not reload Kamailio with dSIPRouter Settings ----]") raise ex ================================================ FILE: gui/modules/api/licensemanager/classes.py ================================================ import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import requests, secrets, datetime, functools from util.security import Credentials, AES_CTR def wcApiPropagateHttpError(method): @functools.wraps(method) def wrapper(self, *args, **kwargs): try: return method(self, *args, **kwargs) except requests.HTTPError as ex: ex.response._content = ex.response.content.replace(self._license_key.encode('utf-8'), b'<redacted>') raise WoocommerceError(response=ex.response) return wrapper class WoocommerceError(requests.HTTPError): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def __str__(self): r_json = self.response.json() return '\n'.join(' '.join(r_json['data']['errors'][k]) for k in r_json['data']['errors']) class WoocommerceLicense(object): # this API key is locked down to only allow functionality below SERVER = { "baseurl": "https://dopensource.com/wp-json/lmfwc/v2/licenses/", "key": "ck_068f510a518ff5ecf1cbdcbc7db7f9bac2331613", "secret": "cs_5ae2f3decfa59f427a59b41f2e41459d18023dd7", } # constant salt used for equality checks # should not be used for generating a hash that is stored CMP_SALT = 'A' * Credentials.SALT_LEN # the woocommerce license length may change due to application specific requirements # therefore we parse it out based on the length of the machine id, which will not change MACHINE_ID_LEN = 32 def __init__(self, license_key=None, key_combo=None, decrypt=False): # set attributes based on clientside data if license_key is not None: if key_combo is not None: raise ValueError('license_key and key_combo are mutually exclusive') if isinstance(license_key, str): if len(license_key) == 0: raise ValueError('license_key must not be empty') if decrypt: license_key = AES_CTR.decrypt(license_key) elif isinstance(license_key, bytes): if len(license_key) == 0: raise ValueError('license_key must not be empty') if decrypt: license_key = AES_CTR.decrypt(license_key) else: license_key = license_key.decode('utf-8') else: raise ValueError('license_key must be a string or byte string') self._license_key = license_key with open('/etc/machine-id', 'r') as f: server_machine_id = f.read(WoocommerceLicense.MACHINE_ID_LEN) if len(server_machine_id) != WoocommerceLicense.MACHINE_ID_LEN: raise Exception("the server machine-id is invalid") self.__machine_id = server_machine_id self.__machine_match = True elif key_combo is not None: if isinstance(key_combo, str): if len(key_combo) == 0: raise ValueError('key_combo must not be empty') if decrypt: key_combo = AES_CTR.decrypt(key_combo.encode('utf-8')) elif isinstance(key_combo, bytes): if len(key_combo) == 0: raise ValueError('key_combo must not be empty') if decrypt: key_combo = AES_CTR.decrypt(key_combo) else: key_combo = key_combo.decode('utf-8') else: raise ValueError('key_combo must be a string or byte string') try: self._license_key = key_combo[:-WoocommerceLicense.MACHINE_ID_LEN] self.__machine_id = key_combo[-WoocommerceLicense.MACHINE_ID_LEN:] except IndexError: raise ValueError('key_combo is invalid') with open('/etc/machine-id', 'r') as f: server_machine_id = f.read(WoocommerceLicense.MACHINE_ID_LEN) if len(server_machine_id) != WoocommerceLicense.MACHINE_ID_LEN: raise Exception("the server machine-id is invalid") self.__machine_match = self.__machine_id == server_machine_id else: raise ValueError('one of license_key or key_combo is required') # set attributes based on serverside data # noinspection PyArgumentList self.sync() def __eq__(self, obj): return isinstance(obj, WoocommerceLicense) and \ secrets.compare_digest(self.hash(WoocommerceLicense.CMP_SALT), obj.hash(WoocommerceLicense.CMP_SALT)) def __ne__(self, obj): return not self.__eq__(obj) # allow passing to dict() and iterable() def __iter__(self): for k, v in self.asDict().items(): yield k, v # only return select attributes in the iterable/dict representation def asDict(self): return { 'id': self.id, 'tags': self.tags, 'expires': self.expires, 'active': self.active, 'valid': self.valid, 'license_key': self.license_key, } @property def machine_match(self): return self.__machine_match @property def tags(self): return self._tags @property def expires(self): if self._expires_at is not None: return datetime.datetime.strptime(self._expires_at, '%Y-%m-%d %H:%M:%S') if self._valid_for is not None: return datetime.datetime.strptime(self._created_at, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(days=self._valid_for) # no expires set, it is valid forever return datetime.datetime.max @property def active(self): return (self._status == 3 or self._status == 2) and self._times_activated > 0 and self.expires > datetime.datetime.now() @property def license_key(self): return self._license_key @property def valid(self): return self.active and self.machine_match @property def id(self): return self._id @wcApiPropagateHttpError def sync(self): res = requests.get( WoocommerceLicense.SERVER['baseurl'] + self._license_key, auth=( WoocommerceLicense.SERVER['key'], WoocommerceLicense.SERVER['secret'] ) ) res.raise_for_status() r_json = res.json() if 'errors' in r_json['data']: raise requests.HTTPError(response=res) self._id = r_json['data']["id"] # self._order_id = r_json['data']["orderId"] # self._user_id = r_json['data']["userId"] self._expires_at = r_json['data']["expiresAt"] self._valid_for = r_json['data']["validFor"] if r_json['data']["validFor"] != 0 else None # self._source = r_json['data']["source"] self._status = r_json['data']["status"] self._times_activated = r_json['data']["timesActivated"] self._times_activated_max = r_json['data']["timesActivatedMax"] self._created_at = r_json['data']["createdAt"] # self._created_by = r_json['data']["createdBy"] self._updated_at = r_json['data']["updatedAt"] # self._updated_by = r_json['data']["updatedBy"] self._tags = r_json['data']['tags'] @wcApiPropagateHttpError def activate(self): res = requests.get( WoocommerceLicense.SERVER['baseurl'] + 'activate/' + self._license_key, auth=( WoocommerceLicense.SERVER['key'], WoocommerceLicense.SERVER['secret'] ) ) res.raise_for_status() r_json = res.json() if 'errors' in r_json['data']: raise requests.HTTPError(response=res) self._times_activated = r_json['data']['timesActivated'] self._times_activated_max = r_json['data']['timesActivatedMax'] self._updated_at = r_json['data']['updatedAt'] return r_json.get('success', False) @wcApiPropagateHttpError def deactivate(self): res = requests.get( WoocommerceLicense.SERVER['baseurl'] + 'deactivate/' + self._license_key, auth=( WoocommerceLicense.SERVER['key'], WoocommerceLicense.SERVER['secret'] ) ) res.raise_for_status() r_json = res.json() if 'errors' in r_json['data']: raise requests.HTTPError(response=res) self._times_activated = r_json['data']['timesActivated'] self._times_activated_max = r_json['data']['timesActivatedMax'] self._updated_at = r_json['data']['updatedAt'] return r_json.get('success', False) def hash(self, salt=None): return Credentials.hashCreds('{}{}'.format(self._license_key, self.__machine_id), salt) # need original key for queries to woocommerce # format: <license_key><machine_id> def encrypt(self): return AES_CTR.encrypt('{}{}'.format(self._license_key, self.__machine_id)) ================================================ FILE: gui/modules/api/licensemanager/cli.py ================================================ # TODO: this is an ugly implementation but better than inline python in the shell script # marked for refactoring in v0.80 import csv, os, sys DSIP_PROJECT_DIR = os.environ.get('DSIP_PROJECT_DIR', '/opt/dsiprouter') DSIP_SYSTEM_CONFIG_DIR = os.environ.get('DSIP_SYSTEM_CONFIG_DIR', '/etc/dsiprouter') sys.path = [ f'{DSIP_SYSTEM_CONFIG_DIR}/gui', f'{DSIP_PROJECT_DIR}/gui', f'{DSIP_PROJECT_DIR}/gui/modules/api/licensemanager' ] + sys.path[1:] if __name__ == '__main__': __package__ = 'modules.api.licensemanager' import settings from shared import IO from .classes import WoocommerceLicense from .functions import getLicenseStatus, licenseDictToStateDict, searchLicenses, addToLicenseStore, removeFromLicenseStore def main(): args = sys.argv[1:] if len(args) == 0: sys.exit(unknownCmd()) sub_cmd = args.pop(0) if sub_cmd == '-retrieve': sys.exit(cmdRetrieve(args)) if sub_cmd == '-list': sys.exit(cmdList(args)) if sub_cmd == '-activate': sys.exit(cmdActivate(args)) if sub_cmd == '-import': sys.exit(cmdImport(args)) if sub_cmd == '-deactivate': sys.exit(cmdDeactivate(args)) if sub_cmd == '-clear': sys.exit(cmdClear(args)) if sub_cmd == '-check': sys.exit(cmdCheck(args)) sys.exit(unknownCmd()) def cmdRetrieve(args): if len(args) != 1: sys.exit(unknownCmd()) if args[0][:4] == 'tag=': key = None tag = args[0][4:] else: key = args[0] tag = None if isDsiprouterRunning(): result = searchLicenses(license_key=key, license_tag=tag) else: result = searchLicenses( license_key=key, license_tag=tag, state_dict=licenseDictToStateDict(settings.DSIP_LICENSE_STORE) ) if len(result) == 0: IO.printwarn('license(s) does not exist on this system') return 1 output_data = [] for license in result: data = dict(license) data['tags'] = ','.join(data['tags']) output_data.append(data) fields = ['id', 'tags', 'expires', 'active', 'valid', 'license_key'] tsv = csv.DictWriter(sys.stdout, delimiter='\t', quoting=csv.QUOTE_NONE, fieldnames=fields) tsv.writeheader() for row in output_data: tsv.writerow(row) return 0 def cmdList(args): if len(args) != 0: sys.exit(unknownCmd()) if isDsiprouterRunning(): result = searchLicenses() else: result = searchLicenses(state_dict=licenseDictToStateDict(settings.DSIP_LICENSE_STORE)) output_data = [] for license in result: data = dict(license) data['tags'] = ','.join(data['tags']) output_data.append(data) fields = ['id', 'tags', 'expires', 'active', 'valid', 'license_key'] tsv = csv.DictWriter(sys.stdout, delimiter='\t', quoting=csv.QUOTE_NONE, fieldnames=fields) tsv.writeheader() for row in output_data: tsv.writerow(row) return 0 def cmdActivate(args): if len(args) != 1: sys.exit(unknownCmd()) key = args[0] if isDsiprouterRunning(): state_dict = None matches = searchLicenses(license_key=key) else: state_dict = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) matches = searchLicenses(license_key=key, state_dict=state_dict) if len(matches) == 0: license = WoocommerceLicense(license_key=key) else: license = matches[0] license.activate() try: addToLicenseStore(license, state_dict=state_dict) except: license.deactivate() raise IO.printinfo('license successfully activated') return 0 def cmdImport(args): if len(args) != 1: sys.exit(unknownCmd()) dsip_runnning = isDsiprouterRunning() if dsip_runnning: state_dict = None else: state_dict = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) with open(args[0], 'r') as fp: license_keys = [line.strip() for line in fp] for license_key in license_keys: if dsip_runnning: matches = searchLicenses(license_key=license_key) else: matches = searchLicenses(license_key=license_key, state_dict=state_dict) if len(matches) == 0: license = WoocommerceLicense(license_key=license_key) else: license = matches[0] license.activate() try: addToLicenseStore(license, state_dict) except: license.deactivate() raise IO.printinfo('license(s) successfully activated') return 0 def cmdDeactivate(args): if len(args) != 1: sys.exit(unknownCmd()) if args[0][:4] == 'tag=': key = None tag = args[0][4:] else: key = args[0] tag = None dsip_runnning = isDsiprouterRunning() if dsip_runnning: state_dict = None else: state_dict = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) if dsip_runnning: result = searchLicenses(license_key=key, license_tag=tag) else: result = searchLicenses(license_key=key, license_tag=tag, state_dict=state_dict) if len(result) == 0: IO.printwarn('license(s) does not exist on this system') return 1 for license in result: license.deactivate() try: removeFromLicenseStore(license, state_dict=state_dict) except: license.activate() raise IO.printinfo('license(s) successfully deactivated') return 0 def cmdClear(args): if len(args) != 0: sys.exit(unknownCmd()) dsip_runnning = isDsiprouterRunning() if dsip_runnning: state_dict = None else: state_dict = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) if dsip_runnning: result = searchLicenses() else: result = searchLicenses(state_dict=state_dict) if len(result) == 0: IO.printwarn('no licenses exist on this system') return 0 for license in result: license.deactivate() try: removeFromLicenseStore(license, state_dict=state_dict) except: license.activate() raise IO.printinfo('all system licenses successfully deactivated') return 0 def cmdCheck(args): if len(args) != 1: sys.exit(unknownCmd()) if args[0][:4] == 'tag=': key = None tag = args[0][4:] else: key = args[0] tag = None if isDsiprouterRunning(): status = getLicenseStatus(license_key=key, license_tag=tag) else: state_dict = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) status = getLicenseStatus(license_key=key, license_tag=tag, state_dict=state_dict) if status == 0: IO.printwarn('license does not exist on this system') return 1 if status == 1: IO.printwarn('license is present but not active') return 1 if status == 2: IO.printwarn('license is present but associated with another machine') return 1 if status == 3: IO.printinfo('license is present and active') return 0 IO.printerr('unable to determine status of license') return 1 def unknownCmd(): IO.printerr(f'{sys.argv[0]}: Invalid arguments provided') return 1 def isDsiprouterRunning(): return os.waitstatus_to_exitcode(os.system('systemctl is-active -q dsiprouter')) == 0 def handleFatalException(ex_type, ex_value, ex_tb): IO.printerr('A fatal error occurred') IO.printerr(str(ex_value)) sys.exit(1) if __name__ == '__main__': if getattr(sys, 'gettrace', lambda: None)() is None: sys.excepthook = handleFatalException main() ================================================ FILE: gui/modules/api/licensemanager/functions.py ================================================ import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') from database import updateDsipSettingsTable from shared import updateConfig from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict from util.pyasync import mpexec from modules.api.licensemanager.classes import WoocommerceLicense, AES_CTR import settings # must be defined here or mpexec will fail to copy the func to child procs def __getLicense(license): return WoocommerceLicense(key_combo=license, decrypt=True) def quickLoadLicenses(keystore): # task for parallel processing (will send external requests on instantiation) return mpexec(__getLicense, ([x] for x in keystore.values())) def licenseDictToStateDict(keystore): """ Transform the license store into a lookup dict based on key :param keystore: the key store :type keystore: dict :return: keys formatted as a status lookup dict :rtype: dict """ return {lc.license_key: lc for lc in quickLoadLicenses(keystore)} def syncLicensesToGlobalState(): getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_license_store'] = licenseDictToStateDict(settings.DSIP_LICENSE_STORE) def getLicenseStatusFromStateDict(license_state, tag): """ Get license status given the license state dict and a license tag :param license_state: the license key store in global state :type license_state: dict :param tag: license tag to lookup :type tag: str :return: status of first license with that tag (valid licenses have priority) :rtype: int the license status corresponds to:: -1 == an unknown error occurred looking up the status 0 == no license present 1 == license present but not active 2 == license present but associated with another machine 3 == license present and valid """ res = [lc for lc in license_state.values() if tag in lc.tags] if len(res) == 0: return 0 lic = next((lc for lc in res if lc.valid), None) if lic is not None: return 3 lic = res[0] if not lic.machine_match: return 2 if not lic.active: return 1 return -1 def searchLicenses(license_key=None, key_combo=None, decrypt=False, license_tag=None, state_dict=None): """ Search licenses from the global state :param license_key: license key to lookup :type license_key: str|None :param key_combo: license key/machine ID to lookup :type key_combo: str|None :param decrypt: whether the key or key_combo provided needs decrypted :type decrypt: bool :param license_tag: license tag to lookup :type license_tag: str|None :param state_dict: the state dict to load the license from :type state_dict: dict|None :return: matching licenses :rtype: list[WoocommerceLicense] """ if state_dict is None: state_dict = getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_license_store'] if license_key is not None: if key_combo is not None: raise ValueError('license_key and key_combo are mutually exclusive') if isinstance(license_key, str): if len(license_key) == 0: raise ValueError('license_key must not be empty') if decrypt: license_key = AES_CTR.decrypt(license_key) elif isinstance(license_key, bytes): if len(license_key) == 0: raise ValueError('license_key must not be empty') if decrypt: license_key = AES_CTR.decrypt(license_key) else: license_key = license_key.decode('utf-8') else: raise ValueError('license_key must be a string or byte string') try: return [state_dict[license_key]] except KeyError: return [] if key_combo is not None: if isinstance(key_combo, str): if len(key_combo) == 0: raise ValueError('key_combo must not be empty') if decrypt: key_combo = AES_CTR.decrypt(key_combo.encode('utf-8')) elif isinstance(key_combo, bytes): if len(key_combo) == 0: raise ValueError('key_combo must not be empty') if decrypt: key_combo = AES_CTR.decrypt(key_combo) else: key_combo = key_combo.decode('utf-8') else: raise ValueError('key_combo must be a string or byte string') try: license_key = key_combo[:-WoocommerceLicense.MACHINE_ID_LEN] except IndexError: raise ValueError('key_combo is invalid') try: return [state_dict[license_key]] except KeyError: return [] if license_tag is not None: return [ lc for lc in state_dict.values() if license_tag in lc.tags ] return list(state_dict.values()) def getLicenseStatus(license_key=None, license_tag=None, state_dict=None): """ Get license status directly or filtered by license tag :param tag: license tag to lookup :type tag: str :return: status of first license with that tag (valid licenses have priority) :rtype: int :param license_key: license key to lookup :type license_key: str|None :param license_tag: license tag to lookup :type license_tag: str|None :param state_dict: the state dict to load the license from :type state_dict: dict|None :return: status of first license with that tag (valid licenses have priority) :rtype: int the license status corresponds to:: 0 == no license present 1 == license present but not valid 2 == license present but associated with another machine 3 == license present and valid """ res = searchLicenses(license_key=license_key, license_tag=license_tag, state_dict=state_dict) if len(res) == 0: return 0 lic = next((lc for lc in res if lc.valid), None) if lic is not None: return 3 lic = res[0] if not lic.machine_match: return 2 if not lic.active: return 1 raise Exception('could not determine status of license') def addToLicenseStore(license, state_dict=None): if state_dict is None: state_dict = getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_license_store'] license_store = settings.DSIP_LICENSE_STORE license_store[str(license.id)] = license.encrypt() state_dict[license.license_key] = license if settings.LOAD_SETTINGS_FROM == 'db': updateDsipSettingsTable({'DSIP_LICENSE_STORE': license_store}) updateConfig(settings, {'DSIP_LICENSE_STORE': license_store}, hot_reload=True) def removeFromLicenseStore(license, state_dict=None): if state_dict is None: state_dict = getSharedMemoryDict(STATE_SHMEM_NAME)['dsip_license_store'] license_store = settings.DSIP_LICENSE_STORE license_store.pop(str(license.id), None) state_dict.pop(license.license_key, None) if settings.LOAD_SETTINGS_FROM == 'db': updateDsipSettingsTable({'DSIP_LICENSE_STORE': license_store}) updateConfig(settings, {'DSIP_LICENSE_STORE': license_store}, hot_reload=True) ================================================ FILE: gui/modules/api/licensemanager/routes.py ================================================ import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') from flask import Blueprint, jsonify, request from werkzeug import exceptions as http_exceptions from shared import debugEndpoint, debugException, StatusCodes, getRequestData, updateConfig from modules.api.api_functions import showApiError, api_security from modules.api.licensemanager.classes import WoocommerceLicense, WoocommerceError from modules.api.licensemanager.functions import searchLicenses, addToLicenseStore, removeFromLicenseStore from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict import settings license_manager = Blueprint('licensing', '__name__') #================================================================================= # api standard response format #================================================================================= # { # "error": str, # "msg": str, # "kamreload": bool, # "data": list # } #================================================================================= # error: error if one occurred (db|http|server|other) or empty string # msg: response message (human readable) # kamreload: whether kamailio settings need reloaded or not # data: data returned, if any #================================================================================= # TODO: standardize response payloads using new createApiResponse() # marked for implementation in v0.76 #================================================================================= def showWoocommerceError(ex): debugException(ex) payload = { 'error': 'woocommerce', 'msg': str(ex), 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': [] } return jsonify(payload), ex.response.status_code def validateRequestArgs(data, allowed_args=dict(), required_args=set(), strict_mode=False, extra_checks=dict()): # whitelist of extra params allowed if strict mode is disabled # typically used with clientside-app-specific args that we ignore in our serverside code # for example; the '_' request arg is used by jquery for enabling/disabling caching if not strict_mode: ignored_args = {'_'} else: ignored_args = set() # validate required args (by key) for arg in required_args: if arg not in data.keys(): raise http_exceptions.BadRequest("Missing required request argument: {}".format(arg)) # validate whitelisted args (key -> value dict) for k, v in data.items(): # is the arg provided in the ignore list if k in ignored_args: continue # is the arg provided in the whitelist if k not in allowed_args.keys(): raise http_exceptions.BadRequest("Invalid request argument provided: {}".format(k)) # is the arg provided the right type if not type(v) == allowed_args[k]: # in strict mode we do not attempt casting if strict_mode: raise http_exceptions.BadRequest("Invalid request argument '{}' wrong type".format(k)) # try casting to correct type in case the query arg was parsed as the wrong type # this typically only happens with query args in the url (they are usually set to str by Flask) try: data[k] = allowed_args[k](v) except ValueError: raise http_exceptions.BadRequest("Invalid request argument '{}' wrong type".format(k)) # does this argument have extra constraints to follow? try: if not extra_checks[k](v): raise http_exceptions.BadRequest("Invalid request argument '{}' failed constraint checks".format(k)) except KeyError: pass @license_manager.route('/api/v1/licensing/validate', methods=['GET']) @api_security def validateLicense(): """ Validate a License ================= Request Arguments ================= .. code-block:: text license_key=<string> key_encrypted=<bool> ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ <bool> ] } """ # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() data = request.args.to_dict() # sanity checks (whitelist of acceptable args in the request body) validateRequestArgs(data, allowed_args={'license_key': str, 'key_encrypted': bool}, required_args={'license_key'}) # retrieve the license if data.get('key_encrypted', False): matches = searchLicenses(key_combo=data['license_key'], decrypt=True) else: matches = searchLicenses(data['license_key']) if len(matches) == 0: response_payload['data'].append(False) response_payload['msg'] = 'license does not exist' return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND lc = matches[0] if not lc.active: response_payload['data'].append(False) response_payload['msg'] = 'license has not been activated' return jsonify(response_payload), StatusCodes.HTTP_OK if not lc.machine_match: response_payload['data'].append(False) response_payload['msg'] = 'license is not registered to this node' return jsonify(response_payload), StatusCodes.HTTP_OK return jsonify(response_payload), StatusCodes.HTTP_OK except WoocommerceError as ex: return showWoocommerceError(ex) except Exception as ex: return showApiError(ex) @license_manager.route('/api/v1/licensing/retrieve', methods=['GET']) @api_security def retrieveLicense(): """ Retrieve details about a License ================= Request Arguments ================= .. code-block:: text license_key=<string> key_encrypted=<bool> ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ { license_key: <string> type: <string> expires: <string> active: <bool> valid: <bool> } ] } """ # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() data = request.args.to_dict() # sanity checks (whitelist of acceptable args in the request body) validateRequestArgs(data, allowed_args={'license_key': str, 'key_encrypted': bool}, required_args={'license_key'}) if data.get('key_encrypted', False): matches = searchLicenses(key_combo=data['license_key'], decrypt=True) else: matches = searchLicenses(data['license_key']) if len(matches) == 0: response_payload['data'].append({}) response_payload['msg'] = 'license does not exist' return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND response_payload['data'].append(dict(matches[0])) response_payload['msg'] = 'successfully retrieved license' return jsonify(response_payload), StatusCodes.HTTP_OK except WoocommerceError as ex: return showWoocommerceError(ex) except Exception as ex: return showApiError(ex) @license_manager.route('/api/v1/licensing/list', methods=['GET']) @api_security def listLicenses(): """ Retrieve details about all License's associated to this node ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ { license_key: <string> type: <string> expires: <string> active: <bool> valid: <bool> }, ... ] } """ # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() response_payload['data'] = [dict(lc) for lc in searchLicenses()] response_payload['msg'] = 'successfully retrieved licenses' return jsonify(response_payload), StatusCodes.HTTP_OK except WoocommerceError as ex: return showWoocommerceError(ex) except Exception as ex: return showApiError(ex) @license_manager.route('/api/v1/licensing/activate', methods=['PUT']) @api_security def activateLicense(): """ Activate a License =============== Request Payload =============== .. code-block:: json { license_key: <string>, key_encrypted: <bool> } ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ { license_key: <string> type: <string> expires: <string> active: <bool> valid: <bool> } ] } """ # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() data = getRequestData() # sanity checks (whitelist of acceptable args in the request body) validateRequestArgs(data, allowed_args={'license_key': str, 'key_encrypted': bool}, required_args={'license_key'}) if data.get('key_encrypted', False): args = dict(key_combo=data['license_key'], decrypt=True) else: args = dict(license_key=data['license_key']) matches = searchLicenses(**args) if len(matches) == 0: lc = WoocommerceLicense(**args) else: lc = matches[0] lc.activate() addToLicenseStore(lc) response_payload['data'].append(dict(lc)) response_payload['msg'] = 'activation succeeded' return jsonify(response_payload), StatusCodes.HTTP_OK except WoocommerceError as ex: return showWoocommerceError(ex) except Exception as ex: return showApiError(ex) @license_manager.route('/api/v1/licensing/deactivate', methods=['PUT']) @api_security def deactivateLicense(): """ Deactivate a License =============== Request Payload =============== .. code-block:: json { license_key: <string>, key_encrypted: <bool> } ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [] } """ # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() data = getRequestData() # sanity checks (whitelist of acceptable args in the request body) validateRequestArgs(data, allowed_args={'license_key': str, 'key_encrypted': bool}, required_args={'license_key'}) if data.get('key_encrypted', False): args = dict(key_combo=data['license_key'], decrypt=True) else: args = dict(license_key=data['license_key']) matches = searchLicenses(**args) if len(matches) == 0: response_payload['error'] = 'other' response_payload['msg'] = 'submitted key does not exist on this system' return jsonify(response_payload), StatusCodes.HTTP_NOT_FOUND else: lc = matches[0] lc.deactivate() removeFromLicenseStore(lc) response_payload['msg'] = 'deactivation succeeded' return jsonify(response_payload), StatusCodes.HTTP_OK except WoocommerceError as ex: return showWoocommerceError(ex) except Exception as ex: return showApiError(ex) ================================================ FILE: gui/modules/api/mediaserver/plugin/fusion/interface.py ================================================ # FusionPBX Plugin for the Media Server api import psycopg2 import psycopg2.extras import json import uuid # Defining a dialplan object, which allows you to define how the # dialplan within a domain behaves class cos_dialplan(): name = None dialplan_xml = None def __init__(self): self.dialplan_xml = [] def add(self,data): dialplan_entry = {} dialplan_entry['name'] = data['name'] dialplan_entry['number'] = data['number'] dialplan_entry['continue'] = data['continue'] dialplan_entry['order'] = data['order'] dialplan_entry['description'] = data['description'] dialplan_entry['enabled'] = data['enabled'] dialplan_entry['hostname'] = data['hostname'] dialplan_entry['logic'] = data['logic'] self.dialplan_xml.append(dialplan_entry) class cos(): domain_uuid="" db = "" dialplan_list = [] domain = None def __init__(self, domain,data=None): if domain.db: self.db = domain.db self.domain_uuid = domain.domain_id self.domain = domain # Load up some default CoS definitions # The default class of service will use the global dialplan within FUSIONPBX # In other words no changes to the Domain dialplan manager cos_default = cos_dialplan() cos_default.name="default" cos_default.dialplan_xml = None self.dialplan_list.append(cos_default) # This class of service will disable inbound caller id cos_standard = cos_dialplan() cos_standard.name="standard" entry = {} entry['name'] = "block_caller_id" entry['number'] = "" entry['continue'] = "true" entry['order'] = 700 entry['enabled'] = "true" entry['description'] = "Block inbound caller id" entry['hostname'] = "" entry['logic'] = "<extension name=\"local_extension\" continue=\"true\" uuid=\"fbba5244-7453-4390-8976-2c307140529g\"> \ <condition field=\"${user_exists}\" expression=\"true\"> \ <action application=\"export\" data=\"sip_cid_type=none\"/> \ <action application=\"export\" data=\"origination_caller_id_name=Blocked\"/> \ <action application=\"export\" data=\"origination_caller_id_number=0000000000\"/> \ </condition> \ </extension>" cos_standard.add(entry) self.dialplan_list.append(cos_standard) if data is not None and 'settings' in data: domain_settings = data['settings'] cos_settings = cos_dialplan() cos_settings.name="domain_settings" entry = {} entry['name'] = "domain_settings" entry['number'] = "" entry['continue'] = "true" entry['order'] = 20 entry['enabled'] = "true" entry['description'] = "Domain Settings" entry['hostname'] = "" entry['logic'] = "<extension name=\"domain_settings\" continue=\"true\" uuid=\"af2cd91f-1db2-4742-b8c2-ef519ba6e8b5\"> \ <condition field=\"\" expression=\"\"> \ <action application=\"set\" data=\"default_language={}\"/> \ <action application=\"set\" data=\"default_dialect={}\"/> \ <action application=\"set\" data=\"default_voice={}\"/> \ </condition> \ </extension>".format(domain_settings['default_language'],domain_settings['default_dialect'],domain_settings['default_voice']) cos_settings.add(entry) self.dialplan_list.append(cos_settings) def create(self, name): psycopg2.extras.register_uuid() app_uuid = uuid.uuid4() cur = self.db.cursor() for cos_dp in self.dialplan_list: print("{},{}".format(cos_dp.name,name)) if cos_dp.name == name: if cos_dp.dialplan_xml == None: continue for xml in cos_dp.dialplan_xml: dialplan_uuid = uuid.uuid4() query = "insert into v_dialplans (domain_uuid,dialplan_uuid,app_uuid,dialplan_context, \ dialplan_name,dialplan_number,dialplan_continue,dialplan_order,dialplan_enabled, \ dialplan_description, hostname, dialplan_xml) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" values = [self.domain_uuid,dialplan_uuid,app_uuid,self.domain.name, \ xml['name'], xml['number'], xml['continue'], \ xml['order'], xml['enabled'],xml['description'], \ xml['hostname'],xml['logic']] cur.execute(query,values) self.db.commit() cur.close() def delete(self): psycopg2.extras.register_uuid() cur = self.db.cursor() cur.execute("""delete from v_dialplans where domain_uuid = %s""",(self.domain_uuid)) self.db.commit() cur.close() return True class mediaserver(): hostname='' port='' auth_type='' username='' password='' dbname="fusionpbx" db='' def __init__(self, config): self.hostname =config.hostname self.port = config.port self.auth_type = config.auth_type self.username = config.username self.password = config.password self.dbname = config.dbname def testConnection(self): print("testConnection") pass def getConnection(self): try: db = psycopg2.connect(host=self.hostname,port=self.port,user=self.username,password=self.password,dbname=self.dbname) if db is not None: print("Connection to FusionPBX: {} database was successful".format(self.hostname)) return db except Exception as ex: raise def closeConnection(self): db.close() class domain(): domain_id="" name = "" enabled = "" description = "" cos = "" # Class of Service db = None def toJSON(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) class domains(): mediaserver='' db='' domain_list = [] def __init__(self, mediaserver): self.mediaserver = mediaserver self.db = self.mediaserver.getConnection() def create(self,data): #Todo: Check if the domain exists before creating it # call it in any place of your program # before working with UUID objects in PostgreSQL psycopg2.extras.register_uuid() domain_uuid = uuid.uuid4() cur = self.db.cursor() # Check for Duplicate domains cur.execute("""select domain_name from v_domains where domain_name = %s""",(data['name'],)) rows = cur.fetchall() if len(rows) > 0: raise Exception ("The domain already exists") return # Create the new domain cur.execute("""insert into v_domains (domain_uuid,domain_name,domain_enabled,domain_description) \ values (%s,%s,%s,%s)""",(domain_uuid,data['name'],data['enabled'],data['description'])) self.db.commit() d = domain() d.domain_id = domain_uuid d.name = data['name'] d.enabled = data['enabled'] d.description = data['description'] d.db = self.db d.cos = data['cos'] cur.close() return d def read(self,domain_id=None): cur = self.db.cursor() query = "select domain_uuid,domain_name,domain_enabled,domain_description from v_domains" values = [] if domain_id: query = query + " where domain_uuid = %s" values.append(domain_id) cur.execute(query,(values)) rows = cur.fetchall() if rows is not None: for row in rows: d = {} d['domain_uuid'] = row[0] d['domain_id'] = row[0] d['name']= row[1] d['enabled'] = row[2] d['description'] = row[3] self.domain_list.append(d) return self.domain_list def update(self,data): cur = self.db.cursor() # Convert UUID string to UUID type domain_uuid=data['domain_id'] # Get the current data for the domain from the database current_data = self.read(domain_uuid) # Update the current data with the new data if len(current_data) >= 1: current_data[0]['name'] = data['name'] current_data[0]['enabled'] = data['enabled'] current_data[0]['description'] = data['description'] cur.execute("""update v_domains set domain_name= %s,domain_enabled = %s,domain_description = %s where domain_uuid= %s""", \ (current_data[0]['name'],current_data[0]['enabled'],current_data[0]['description'],domain_uuid)) rows = cur.rowcount self.db.commit() cur.close() return True def delete(self,domain_id): #Todo: Check if the domain exists before creating it # call it in any place of your program # before working with UUID objects in PostgreSQL psycopg2.extras.register_uuid() domain_uuid = uuid.uuid4() cur = self.db.cursor() # Delete from domains cur.execute("""delete from v_domains where domain_uuid = %s""",(domain_id,)) # Delete the inbound dialplan for the domain cur.execute("""delete from v_dialplans where domain_uuid = %s""",(domain_id,)) self.db.commit() cur.close() return True def getExtensions(): pass class extension(): extension_id="" domain_id="" account_code="" extension="" password="" outbound_caller_number="" outbound_caller_name="" vm_enabled=True vm_password="" vm_notify_email="" enabled=False call_timeout=30 def __init__(self): pass def toJSON(self): return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) class extensions(): mediaserver=None domain=None domain_name='' domain_uuid='' db='' extension_list = [] def __init__(self, mediaserver, domain ,extension=None): self.mediaserver = mediaserver self.db = self.mediaserver.getConnection() self.domain = domain self.domain_uuid = self.domain['domain_id'] self.domain_name = self.domain['name'] def create(self,data): psycopg2.extras.register_uuid() extension_uuid = uuid.uuid4() voicemail_uuid = uuid.uuid4() cur = self.db.cursor() cur.execute("""insert into v_extensions (extension_uuid,domain_uuid,extension,password, \ user_context,call_timeout,enabled,outbound_caller_id_number, \ outbound_caller_id_name,accountcode) \ values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)""", \ (extension_uuid, self.domain_uuid, data.extension, \ data.password, self.domain['name'], data.call_timeout, data.enabled, \ data.outbound_caller_number,data.outbound_caller_name,data.account_code)) # Create voicemail box query = "insert into v_voicemails (domain_uuid,voicemail_uuid,voicemail_id, \ voicemail_password,greeting_id,voicemail_alternate_greet_id, \ voicemail_mail_to,voicemail_sms_to,voicemail_transcription_enabled,voicemail_attach_file, \ voicemail_file,voicemail_local_after_email,voicemail_enabled,voicemail_description, \ voicemail_name_base64,voicemail_tutorial) \ values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) " values = [self.domain_uuid,voicemail_uuid, \ data.extension,data.vm_password,None,None,data.vm_notify_email,None,None,None,'attach',"true", data.vm_enabled,None,None,None] cur.execute(query,values) self.db.commit() cur.close() def read(self, extension_id=None): cur = self.db.cursor() query = "select extension_uuid,domain_uuid,extension,password,user_context,call_timeout::integer as call_timeout, \ enabled,outbound_caller_id_number,outbound_caller_id_name, accountcode from v_extensions where domain_uuid = %s" values = [self.domain_uuid] if extension_id: query = query + " and extension_uuid = %s" values.append(extension_id) cur.execute(query,(values)) rows = cur.fetchall() if rows is not None: for row in rows: d = {} d['extensions_id'] = row[0] d['domain_uuid']= row[1] d['extension'] = row[2] #d['password'] = row[3] d['user_context'] = row[4] d['call_timeout'] = row[5] d['enabled'] = row[6] d['outbound_caller_number'] = row[7] d['outbound_caller_name'] = row[8] d['account_code'] = row[9] self.extension_list.append(d) return self.extension_list def update(self,data): psycopg2.extras.register_uuid() cur = self.db.cursor() query="update v_extensions set " values=[] db_data = {} #Map canaical format to database format db_data['domain_uuid'] = data['domain_id'] db_data['extension'] = data['extension'] db_data['password'] = data['password'] db_data['enabled'] = data['enabled'] db_data['accountcode'] = data['account_code'] db_data['outbound_caller_id_number'] = data['outbound_caller_number'] db_data['outbound_caller_id_name'] = data['outbound_caller_name'] db_data['call_timeout'] = data['call_timeout'] for element in db_data: # Don't process Voice Mail Attributes if "vm_" in element: continue query = query + "{} = %s,".format(element) values.append(db_data[element]) # Remove the last comma if query[len(query)-1] == ',': query = query[:-1] query = query + " where extension_uuid = '{}'".format(self.getExtensionID(db_data['extension'])) print(query) print(values) cur.execute(query,(values)) self.db.commit() cur.close() def getExtensionID(self,extension): cur = self.db.cursor() query = "select extension_uuid from v_extensions where domain_uuid = %s and extension = %s" values = [self.domain_uuid,extension] cur.execute(query,(values)) rows = cur.fetchall() if rows is not None: for row in rows: return row[0] cur.close() def delete(self,extension): #Todo: Check if the domain exists before creating it # call it in any place of your program # before working with UUID objects in PostgreSQL psycopg2.extras.register_uuid() domain_uuid = uuid.uuid4() cur = self.db.cursor() cur.execute("""delete from v_extensions where domain_uuid = %s and extension = %s""",(self.domain_uuid,extension)) cur.execute("""delete from v_voicemails where domain_uuid = %s and voicemail_id = %s""",(self.domain_uuid,extension)) self.db.commit() cur.close() return True ================================================ FILE: gui/modules/api/mediaserver/plugin/fusionpbx ================================================ ================================================ FILE: gui/modules/api/mediaserver/routes.py ================================================ import importlib.util, os from flask import Blueprint, jsonify from database import startSession, DummySession, dSIPMultiDomainMapping from shared import debugEndpoint,StatusCodes, getRequestData from modules.api.api_functions import showApiError, api_security from werkzeug import exceptions as http_exceptions from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict import settings # TODO: standardize response payloads using new createApiResponse() # marked for implementation in v0.74 class config(): hostname='' port='' username='' password='' dbname='' type='' auth_type='' plugin_type='' plugin=None def __init__(self,config_id): db = DummySession() db = startSession() try: print("The config_id is {}".format(config_id)) domainMapping = db.query(dSIPMultiDomainMapping).filter(dSIPMultiDomainMapping.pbx_id == config_id).first() if domainMapping is None: raise Exception("Configuration doesn't exist") else: print("***In domain mapping***") self.hostname=domainMapping.db_host #self.port=domainMapping.port if "port" in domainMapping else "5432" self.port="5432" self.username=domainMapping.db_username self.password=domainMapping.db_password self.dbname="fusionpbx" #response_payload['msg'] = 'Domain Configuration Exists' if domainMapping.type == int(dSIPMultiDomainMapping.FLAGS.TYPE_FUSIONPBX.value): self.plugin_type = FLAGS.FUSIONPBX_PLUGIN elif domainMapping.type == dSIPMultiDomainMapping.FLAGS.TYPE_FUSIONPBX_CLUSTER.value: self.plugin_type = FLAGS.FUSIONPBX_PLUGIN elif domainMapping.type == dSIPMultiDomainMapping.FLAGS.TYPE_FREEPBX.value: self.plugin_type = FLAGS.FREEPBX_PLUGIN; raise Exception("FreePBX Plugin is not supported yet") else: raise Exception("PBX plugin for config #{} can not be found".format(config_id)) # Import plugin # Returns the Base directory of this file base_dir = os.path.dirname(__file__) # Use the Base Dir to specify the location of the plugin required for this domain spec = importlib.util.spec_from_file_location("plugin.{}".format(self.plugin_type), "{}/plugin/{}/interface.py".format(base_dir,self.plugin_type)) self.plugin = importlib.util.module_from_spec(spec) if spec.loader.exec_module(self.plugin): print("***Plugin was loaded***") return except Exception as ex: raise ex finally: db.close() def getPlugin(self): if self.plugin: return self.plugin else: raise Exception("The plugin could not be loaded") class FLAGS(): FUSIONPBX_PLUGIN = "fusion" FREEPBX_PLUGIN = "freepbx" mediaserver = Blueprint('mediaserver','__name__') @mediaserver.route('/api/v1/mediaserver/domain/',methods=['GET']) @mediaserver.route('/api/v1/mediaserver/domain/<string:config_id>',methods=['GET']) @mediaserver.route('/api/v1/mediaserver/domain/<string:config_id>/<string:domain_id>',methods=['GET']) @api_security def getDomains(config_id=None,domain_id=None): """ List all of the domains on a PBX\n If the PBX only contains a single domain then it will return the hostname or ip address of the system. If the PBX is multi-tenant then a list of all domains will be returned =============== Request Payload =============== .. code-block:: json {} ================ Response Payload ================ .. code-block:: json { error: <string>, msg: <string>, kamreload: <bool>, data: [ domains: [ { domain_id: <int>, name: <string>, enabled: <string>, description: <string> } ] ] } """ # Determine which plug-in to use # Currently we only support FusionPBX # Check if Configuration ID exists response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() if config_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) # Use plugin to get list of domains by calling plugin.<pbxtype>.getDomain() domain_list = domains.read(domain_id) response_payload['msg'] = '{} domains were found'.format(len(domain_list)) response_payload['data'].append(domain_list) else: raise Exception("The configuration id must be provided") return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/domain',methods=['POST']) @api_security def postDomains(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"name": str, "enabled": bool, "description": str, "config_id": int, "cos": str, "settings": dict} # ensure requred args are provided REQUIRED_ARGS = {'name','config_id'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) config_id = data['config_id'] cos = data['cos'] if 'cos' in data else None domain_settings = data['settings'] if 'settings' in data else None # Create instance of Media Server Class if config_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain = domains.create(data) #Generate Close of Service if cos: cos_object = plugin.cos(domain,data) cos_object.create(cos) if domain_settings: cos_object.create("domain_settings") response_payload['data'] = {"domain_id": domain.domain_id} return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/domain',methods=['PUT']) @api_security def putDomains(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"name": str, "enabled": bool, "description": str, "config_id": int, "domain_id": str} # ensure requred args are provided REQUIRED_ARGS = {'domain_id','config_id'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) config_id = data['config_id'] domain_id = data['domain_id'] # Create instance of Media Server Class if config_id != None and domain_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain_id = domains.update(data) response_payload['msg'] = "Success" return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/domain',methods=['DELETE']) @api_security def deleteDomains(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"domain_id": str, "config_id": int} # ensure requred args are provided REQUIRED_ARGS = {'domain_id','config_id'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) config_id = data['config_id'] domain_id = data['domain_id'] # Create instance of Media Server Class if config_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) # Delete the domain if domains.delete(domain_id): response_payload['msg'] = "Success" return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/extension',methods=['POST']) @api_security def postExtensions(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"domain_id": str, "account_code": str, "extension": str, "password": str, \ "outbound_caller_number": str, "outbound_caller_name": str, "vm_enabled": bool, \ "vm_password": int, "vm_notify_email": str, "enabled": bool, "call_timeout": int, \ "config_id": int} # ensure requred args are provided REQUIRED_ARGS = {'domain_id','extension','password', 'enabled','config_id'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() print(data) # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) # Create instance of Media Server Class config_id = data['config_id'] domain_id = data['domain_id'] if config_id != None and domain_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain = domains.read(domain_id) print(domain_id) print(domain[0]['domain_id']) extensions = plugin.extensions(mediaserver,domain[0]) ext = plugin.extension() ext.domain_id=data['domain_id'] ext.extension=data['extension'] ext.password=data['password'] ext.enabled=data['enabled'] ext.config_id=data['config_id'] ext.outbound_caller_number=data['outbound_caller_number'] ext.outbound_caller_name=data['outbound_caller_name'] ext.vm_enabled=data['vm_enabled'] ext.vm_password=data['vm_password'] ext.vm_notify_email=data['vm_notify_email'] ext.account_code=data['account_code'] ext.call_timeout=data['call_timeout'] extensions.create(ext) return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/extension',methods=['PUT']) @api_security def putExtensions(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"domain_id": str, "account_code": str, "extension": str, "password": str, \ "outbound_caller_number": str, "outbound_caller_name": str, "vm_enabled": bool, \ "vm_password": int, "vm_notify_email": str, "enabled": bool, "call_timeout": int, \ "config_id": int} # ensure requred args are provided REQUIRED_ARGS = {'domain_id','extension','password', 'enabled','config_id'} # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() print(data) # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) # Create instance of Media Server Class config_id = data['config_id'] domain_id = data['domain_id'] if config_id != None and domain_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain = domains.read(domain_id) print(domain_id) print(domain[0]['domain_id']) extensions = plugin.extensions(mediaserver,domain[0]) extensions.update(data) return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/extension/<string:config_id>/<string:domain_id>',methods=['GET']) @mediaserver.route('/api/v1/mediaserver/extension/<string:config_id>/<string:domain_id>/<string:extension_id>',methods=['GET']) @api_security def getExtensions(config_id=None,domain_id=None,extension_id=None): """ List all of the domains on a PBX\n If the PBX only contains a single domain then it will return the hostname or ip address of the system. If the PBX is multi-tenant then a list of all domains will be returned =============== Request Payload =============== .. code-block:: json {} ================ Response Payload ================ .. code-block:: json { "data": [ [ { "call_timeout": null, "domain_uuid": "51f66016-c2d5-4bd8-8117-29c8fc8ffa17", "enabled": "true", "extensions_id": "ae3cb4b8-f467-4a13-9bb8-9296226c1887", "number": "504", "user_context": "restaurant.detroitpbx.com" } ] } """ # Determine which plug-in to use # Currently we only support FusionPBX # Check if Configuration ID exists response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if settings.DEBUG: debugEndpoint() if config_id != None and domain_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain = domains.read(domain_id) extensions = plugin.extensions(mediaserver,domain[0]) extension_list = extensions.read(extension_id) response_payload['msg'] = '{} extensions were found'.format(len(extension_list)) response_payload['data'].append(extension_list) else: raise Exception("The configuration id and the domain_id must be provided") return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) @mediaserver.route('/api/v1/mediaserver/extension',methods=['DELETE']) @api_security def deleteExtensions(): # use a whitelist to avoid possible buffer overflow vulns or crashes VALID_REQUEST_DATA_ARGS = {"domain_id": str, "config_id": int, "extension": str} # ensure requred args are provided REQUIRED_ARGS = VALID_REQUEST_DATA_ARGS # defaults.. keep data returned separate from returned metadata response_payload = {'error': '', 'msg': '', 'kamreload': getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'], 'data': []} try: if (settings.DEBUG): debugEndpoint() # get request data data = getRequestData() # sanity checks for k, v in data.items(): if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request data argument '{}' not recognized".format(k)) if not type(v) == VALID_REQUEST_DATA_ARGS[k]: raise http_exceptions.BadRequest("Request data argument '{}' not valid".format(k)) for k in REQUIRED_ARGS: if k not in VALID_REQUEST_DATA_ARGS.keys(): raise http_exceptions.BadRequest("Request argument '{}' is required".format(k)) config_id = data['config_id'] domain_id = data['domain_id'] extension = data['extension'] # Create instance of Media Server Class if config_id != None: config_info = config(config_id) plugin = config_info.getPlugin() # Create instance of Media Server Class if plugin: mediaserver = plugin.mediaserver(config_info) if mediaserver: domains = plugin.domains(mediaserver) domain = domains.read(domain_id) if domain: extensions = plugin.extensions(mediaserver,domain[0]) if extensions.delete(extension): response_payload['msg'] = "Success" return jsonify(response_payload), StatusCodes.HTTP_OK except Exception as ex: return showApiError(ex) ================================================ FILE: gui/modules/api/sample_api.py ================================================ from flask import Blueprint from modules.api.api_functions import api_security new_api = Blueprint('new_api','__name__') # Sample route. Replace new_api and new_entity with the name of the api # and the name of the entity that it will manipulate @new_api.route('/api/v1/new_api/new_entity') @api_security def getEntity(): result = "{ \ domain_id: 1012 \ name: AprilandMackCo, \ enabled: true, \ description: 'April and Mack Co', \ config_id: 64 \ }" return result ================================================ FILE: gui/modules/cdr/cdrs.sql ================================================ -- MySQL dump 10.14 Distrib 5.5.52-MariaDB, for Linux (x86_64) -- -- Host: localhost Database: kamailio -- ------------------------------------------------------ -- Server version 5.5.52-MariaDB /*!40101 SET @OLD_CHARACTER_SET_CLIENT = @@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS = @@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION = @@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE = @@TIME_ZONE */; /*!40103 SET TIME_ZONE = '+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS = @@UNIQUE_CHECKS, UNIQUE_CHECKS = 0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS = @@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS = 0 */; /*!40101 SET @OLD_SQL_MODE = @@SQL_MODE, SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES = @@SQL_NOTES, SQL_NOTES = 0 */; -- -- Table structure for table `acc` -- DROP TABLE IF EXISTS `acc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc` ( `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(128) NOT NULL DEFAULT '', `to_tag` varchar(128) NOT NULL DEFAULT '', `callid` varchar(255) NOT NULL DEFAULT '', `sip_code` char(3) NOT NULL DEFAULT '', `sip_reason` varchar(255) NOT NULL DEFAULT '', `time` datetime NOT NULL DEFAULT NOW(), `src_ip` varchar(64) NOT NULL DEFAULT '', `dst_ouser` varchar(128) NOT NULL DEFAULT '', `dst_user` varchar(128) NOT NULL DEFAULT '', `dst_domain` varchar(255) NOT NULL DEFAULT '', `src_user` varchar(128) NOT NULL DEFAULT '', `src_domain` varchar(255) NOT NULL DEFAULT '', `cdr_id` int(10) UNSIGNED NOT NULL DEFAULT '0', `calltype` varchar(20) DEFAULT NULL, `src_gwgroupid` varchar(10) NOT NULL DEFAULT '', `dst_gwgroupid` varchar(10) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `acc_callid` (`callid`) ); /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `cdrs` -- DROP TABLE IF EXISTS `cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cdrs` ( `cdr_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, `src_username` varchar(128) NOT NULL DEFAULT '', `src_domain` varchar(255) NOT NULL DEFAULT '', `dst_username` varchar(128) NOT NULL DEFAULT '', `dst_domain` varchar(255) NOT NULL DEFAULT '', `dst_ousername` varchar(128) NOT NULL DEFAULT '', `call_start_time` datetime NOT NULL, `duration` int(10) UNSIGNED NOT NULL DEFAULT '0', `sip_call_id` varchar(255) NOT NULL DEFAULT '', `sip_from_tag` varchar(128) NOT NULL DEFAULT '', `sip_to_tag` varchar(128) NOT NULL DEFAULT '', `src_ip` varchar(64) NOT NULL DEFAULT '', `cost` int(11) NOT NULL DEFAULT '0', `rated` int(11) NOT NULL DEFAULT '0', `created` datetime NOT NULL DEFAULT NOW(), `calltype` varchar(20) DEFAULT NULL, `fraud` bool NOT NULL DEFAULT '0', `src_gwgroupid` varchar(10) NOT NULL DEFAULT '', `dst_gwgroupid` varchar(10) NOT NULL DEFAULT '', PRIMARY KEY (`cdr_id`) ); /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping routines for database 'kamailio' -- /*!50003 DROP PROCEDURE IF EXISTS `kamailio_cdrs` */; /*!50003 SET @saved_cs_client = @@character_set_client */; /*!50003 SET @saved_cs_results = @@character_set_results */; /*!50003 SET @saved_col_connection = @@collation_connection */; /*!50003 SET character_set_client = utf8 */; /*!50003 SET character_set_results = utf8 */; /*!50003 SET collation_connection = utf8_general_ci */; /*!50003 SET @saved_sql_mode = @@sql_mode */; /*!50003 SET sql_mode = '' */; DELIMITER ;; CREATE PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done int DEFAULT 0; DECLARE bye_record int DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_callid,v_from_tag, v_to_tag,v_src_ip,v_calltype varchar(255); DECLARE v_src_gwgroupid, v_dst_gwgroupid int(11); DECLARE v_inv_time, v_bye_time datetime; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, time, callid, from_tag, to_tag, src_ip, calltype, src_gwgroupid, dst_gwgroupid FROM acc WHERE method = 'INVITE' AND cdr_id = '0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip, v_calltype, v_src_gwgroupid, v_dst_gwgroupid; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method = 'BYE' AND callid = v_callid AND ((from_tag = v_from_tag AND to_tag = v_to_tag) OR (from_tag = v_to_tag AND to_tag = v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username, src_domain, dst_username, dst_domain, call_start_time, duration, sip_call_id, sip_from_tag, sip_to_tag, src_ip, created, calltype, src_gwgroupid, dst_gwgroupid) VALUES (v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, UNIX_TIMESTAMP(v_bye_time) - UNIX_TIMESTAMP(v_inv_time), v_callid, v_from_tag, v_to_tag, v_src_ip, NOW(), v_calltype, v_src_gwgroupid, v_dst_gwgroupid); UPDATE acc SET cdr_id=last_insert_id() WHERE callid = v_callid AND from_tag = v_from_tag AND to_tag = v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */; /*!50003 SET character_set_client = @saved_cs_client */; /*!50003 SET character_set_results = @saved_cs_results */; /*!50003 SET collation_connection = @saved_col_connection */; /*!50003 DROP PROCEDURE IF EXISTS `kamailio_rating` */; /*!50003 SET @saved_cs_client = @@character_set_client */; /*!50003 SET @saved_cs_results = @@character_set_results */; /*!50003 SET @saved_col_connection = @@collation_connection */; /*!50003 SET character_set_client = utf8 */; /*!50003 SET character_set_results = utf8 */; /*!50003 SET collation_connection = utf8_general_ci */; /*!50003 SET @saved_sql_mode = @@sql_mode */; /*!50003 SET sql_mode = '' */; DELIMITER ;; CREATE PROCEDURE `kamailio_rating`(`rgroup` varchar(64)) BEGIN DECLARE done, rate_record, vx_cost int DEFAULT 0; DECLARE v_cdr_id bigint DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit int DEFAULT 0; DECLARE v_dst_username varchar(255); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated = 0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group = rgroup AND v_dst_username LIKE concat(prefix, '%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration / v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id = v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */; /*!50003 SET character_set_client = @saved_cs_client */; /*!50003 SET character_set_results = @saved_cs_results */; /*!50003 SET collation_connection = @saved_col_connection */; /*!40103 SET TIME_ZONE = @OLD_TIME_ZONE */; /*!40101 SET SQL_MODE = @OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS = @OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS = @OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT = @OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS = @OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION = @OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES = @OLD_SQL_NOTES */; -- Dump completed on 2017-10-07 11:57:33 ================================================ FILE: gui/modules/cdr/cron_functions.py ================================================ from datetime import datetime from shared import debugException from database import startSession, DummySession, dSIPCDRInfo from modules.api.api_routes import generateCDRS def sendCdrReport(gwgroupid): db = DummySession() try: db = startSession() now = datetime.now() cdr_info = db.query(dSIPCDRInfo).filter(dSIPCDRInfo.gwgroupid == gwgroupid).first() generateCDRS(gwgroupid=gwgroupid, report_type='csv', send_email=True, dtfilter=cdr_info.last_sent, run_standalone=True) cdr_info.last_sent = now db.commit() except Exception as ex: debugException(ex) db.rollback() db.flush() finally: db.close() ================================================ FILE: gui/modules/cdr/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { # Check to see if the acc table or cdr tables are in use MERGE_DATA=0 acc_row_count=$(withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from acc limit 10" 2> /dev/null) if [ ${acc_row_count:-0} -gt 0 ]; then MERGE_DATA=1 fi if [ ${MERGE_DATA} -eq 1 ]; then printwarn "The accounting table (acc) in Kamailio already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/cdr/cdrs.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table --no-create-info \ --insert-ignore dsip_lcr ) | withRootDBConn --db="$KAM_DB_NAME" mysql else # Replace the CDR tables and add some Kamailio stored procedures printwarn "Adding/Replacing the tables needed for CDR's within dSIPRouter..." withRootDBConn --db="$KAM_DB_NAME" mysql -sN <${DSIP_PROJECT_DIR}/gui/modules/cdr/cdrs.sql fi } function install { installSQL enableKamailioConfigAttrib 'WITH_CDRS' ${DSIP_KAMAILIO_CONFIG_FILE} printdbg "CDR module installed" } function uninstall { disableKamailioConfigAttrib 'WITH_CDRS' ${DSIP_KAMAILIO_CONFIG_FILE} printdbg "CDR module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/certificates/certificates.sql ================================================ DROP TABLE IF EXISTS `dsip_certificates`; CREATE TABLE IF NOT EXISTS `dsip_certificates` ( `id` INT NOT NULL AUTO_INCREMENT, `domain` VARCHAR(128) NULL, `type` VARCHAR(45) NULL, `email` VARCHAR(128) NULL, `cert` BLOB NULL, `key` BLOB NULL, PRIMARY KEY (`id`) ); ================================================ FILE: gui/modules/certificates/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { # Check to see if the acc table or cdr tables are in use MERGE_DATA=0 count=$(withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from dsip_certificates limit 10" 2> /dev/null) if [ ${count:-0} -gt 0 ]; then MERGE_DATA=1 fi if [ ${MERGE_DATA} -eq 1 ]; then printwarn "The table already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/certificates/certificates.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table --no-create-info \ --insert-ignore dsip_certificates ) | withRootDBConn --db="$KAM_DB_NAME" mysql else # Replace the api tables printwarn "Adding/Replacing the tables needed for Certificates module within dSIPRouter..." withRootDBConn --db="$KAM_DB_NAME" mysql -sN <${DSIP_PROJECT_DIR}/gui/modules/certificates/certificates.sql fi } function install { installSQL printdbg "Certificates module installed" } function uninstall { printdbg "Certificates module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/custom_routing/custom_routing.sql ================================================ -- MySQL dump 10.13 Distrib 5.5.59, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: kamailio -- ------------------------------------------------------ -- Server version 5.5.59-0+deb8u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `dr_custom_rules` -- DROP TABLE IF EXISTS `dr_custom_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_custom_rules` ( `dr_ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `locality` varchar(64) NOT NULL DEFAULT '', `ppm` decimal(10,2) NOT NULL DEFAULT '0.00', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`dr_ruleid`), CONSTRAINT `dr_custom_rules_ibfk_1` FOREIGN KEY (`dr_ruleid`) REFERENCES `dr_rules` (`ruleid`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `locale_lookup` -- DROP TABLE IF EXISTS `locale_lookup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `locale_lookup` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `locale` varchar(64) NOT NULL DEFAULT '', `fprefix` varchar(64) NOT NULL DEFAULT '0', `tprefix` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-03-31 0:10:33 ================================================ FILE: gui/modules/custom_routing/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { local TABLES=(dr_custom_rules locale_lookup) printwarn "Adding/Replacing the tables needed for Custom Routing within dSIPRouter..." # Check to see if table exists withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from ${TABLES[0]} limit 1" > /dev/null 2>&1 if [ $? -eq 0 ]; then printwarn "The dSIPRouter tables ${TABLES[@]} already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/custom_routing/custom_routing.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table --no-create-info \ --insert-ignore ${TABLES[@]}; ) | withRootDBConn --db="$KAM_DB_NAME" mysql else echo -e "Installing schema for custom routing" withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/gui/modules/custom_routing/custom_routing.sql fi } function install { installSQL printdbg "Custom Routing module installed" } function uninstall { printdbg "Custom Routing module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/dnid_enrichment/dnid_enrichment.sql ================================================ DROP TABLE IF EXISTS dsip_dnid_enrich_lnp; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE dsip_dnid_enrich_lnp ( id int(10) unsigned NOT NULL AUTO_INCREMENT, dnid varchar(64) NOT NULL, country_code varchar(64) NOT NULL DEFAULT '', routing_number varchar(64) NOT NULL DEFAULT '', description varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (id) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS dsip_dnid_lnp_mapping; DROP VIEW IF EXISTS dsip_dnid_lnp_mapping; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE VIEW dsip_dnid_lnp_mapping AS SELECT dnid, CONCAT(country_code, routing_number) AS prefix, '0' AS key_type, '0' AS value_type FROM dsip_dnid_enrich_lnp; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: gui/modules/dnid_enrichment/install.sh ================================================ # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { MERGE_DATA=0 count=$(withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from dsip_dnid_enrich_lnp limit 1" 2> /dev/null) if [ ${count:-0} -gt 0 ]; then MERGE_DATA=1 fi if [ ${MERGE_DATA} -eq 1 ]; then printwarn "The table already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/dnid_enrichment/dnid_enrichment.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table --no-create-info \ --insert-ignore dsip_dnid_enrich_lnp ) | withRootDBConn --db="$KAM_DB_NAME" mysql else # Replace the api tables printwarn "Adding/Replacing the tables needed for DNID LNP Enrichment module within dSIPRouter..." withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/gui/modules/dnid_enrichment/dnid_enrichment.sql fi } function install { installSQL enableKamailioConfigAttrib 'WITH_DNID_LNP_ENRICHMENT' ${DSIP_KAMAILIO_CONFIG_FILE} printdbg "DNID LNP Enrichment module installed" return 0 } function uninstall { disableKamailioConfigAttrib 'WITH_DNID_LNP_ENRICHMENT' ${DSIP_KAMAILIO_CONFIG_FILE} return 0 } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/domain/__init__.py ================================================ ================================================ FILE: gui/modules/domain/domain_mapping.sql ================================================ -- MySQL dump 10.16 Distrib 10.1.26-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: kamailio -- ------------------------------------------------------ -- Server version 10.1.26-MariaDB-0+deb9u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `dsip_multidomain_mapping` -- -- TODO: db_password should be encrypted DROP TABLE IF EXISTS `dsip_multidomain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_multidomain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `db_host` varchar(255) NOT NULL, `db_username` varchar(255) NOT NULL, `db_password` varchar(255) NOT NULL, `domain_list` varchar(255) NOT NULL DEFAULT '', `domain_list_hash` varchar(255) NOT NULL DEFAULT '', `attr_list` varchar(255) NOT NULL DEFAULT '', `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', `lastsync` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `syncstatus` tinyint(1) NOT NULL DEFAULT '0', `syncerror` varchar(200) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `dsip_domain_mapping` -- DROP TABLE IF EXISTS `dsip_domain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_domain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `domain_id` int(10) NOT NULL, `attr_list` varchar(255) NOT NULL, `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2018-08-28 22:04:51 ================================================ FILE: gui/modules/domain/domain_routes.py ================================================ import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import re from flask import request, Blueprint, render_template, redirect, session, url_for from sqlalchemy import exc as sql_exceptions from sqlalchemy.sql import text from werkzeug import exceptions as http_exceptions from modules.api.licensemanager.classes import WoocommerceError from modules.api.licensemanager.functions import getLicenseStatus from database import startSession, DummySession, Domain, DomainAttrs, Dispatcher, Gateways, Address from modules.api.api_routes import addEndpointGroups from shared import debugException, debugEndpoint, showError, strFieldsToDict, stripDictVals from util.ipc import STATE_SHMEM_NAME, getSharedMemoryDict import settings domains = Blueprint('domains', __name__) # Gateway to IP - A gateway can be a carrier or PBX def gatewayIdToIP(pbx_id, db): gw = db.query(Gateways).filter(Gateways.gwid == pbx_id).first() if gw is not None: return gw.address def addDomain(domain, authtype, pbxs, notes, db): # Create the domain because we need the domain id PBXDomain = Domain(domain=domain, did=domain) db.add(PBXDomain) db.flush() # Check if list of PBX's if pbxs: pbx_list = re.split(' |,', pbxs) else: pbx_list = [] # If list is found if len(pbx_list) > 1 and authtype == "passthru": pbx_id = pbx_list[0] else: # Else Single value was submitted pbx_id = pbxs # Implement Passthru authentication to the first PBX on the list. # because Passthru Registration only works against one PBX if authtype == "passthru": PBXDomainAttr1 = DomainAttrs(did=domain, name='pbx_list', value=pbx_id) PBXDomainAttr2 = DomainAttrs(did=domain, name='pbx_type', value="0") PBXDomainAttr3 = DomainAttrs(did=domain, name='created_by', value="0") PBXDomainAttr4 = DomainAttrs(did=domain, name='domain_auth', value=authtype) PBXDomainAttr5 = DomainAttrs(did=domain, name='description', value="notes:{}".format(notes)) PBXDomainAttr6 = DomainAttrs(did=domain, name='pbx_ip', value=gatewayIdToIP(pbx_id, db)) db.add(PBXDomainAttr1) db.add(PBXDomainAttr2) db.add(PBXDomainAttr3) db.add(PBXDomainAttr4) db.add(PBXDomainAttr5) db.add(PBXDomainAttr6) # MSTeams Support elif authtype == "msteams": # Set of MS Teams Proxies msteams_endpoint_uris = ['{}:5061;transport=tls'.format(endpoint) for endpoint in settings.MSTEAMS_DNS_ENDPOINTS] # Attributes to specify that the domain was created manually PBXDomainAttr1 = DomainAttrs(did=domain, name='pbx_list', value="{}".format(",".join(msteams_endpoint_uris))) PBXDomainAttr2 = DomainAttrs(did=domain, name='pbx_type', value="3") PBXDomainAttr3 = DomainAttrs(did=domain, name='created_by', value="0") PBXDomainAttr4 = DomainAttrs(did=domain, name='domain_auth', value=authtype) PBXDomainAttr5 = DomainAttrs(did=domain, name='description', value="notes:{}".format(notes)) # Serial folking will be used to forward registration info to multiple PBX's PBXDomainAttr6 = DomainAttrs(did=domain, name='dispatcher_alg_reg', value="4") PBXDomainAttr7 = DomainAttrs(did=domain, name='dispatcher_alg_in', value="4") # Create entry in dispatcher and set dispatcher_set_id in domain_attrs PBXDomainAttr8 = DomainAttrs(did=domain, name='dispatcher_set_id', value=PBXDomain.id) # Use the default MS Teams SIP Proxy List if one isn't defined print("pbx list {}".format(pbx_list)) if len(pbx_list) == 0 or pbx_list[0] == '': # Logic to handle when dSIPRouter is behind NAT (aka servernat is enabled) if settings.EXTERNAL_IP_ADDR != settings.INTERNAL_IP_ADDR: socket_addr = settings.INTERNAL_IP_ADDR else: socket_addr = settings.EXTERNAL_IP_ADDR for hostname, sipuri in zip(settings.MSTEAMS_DNS_ENDPOINTS,msteams_endpoint_uris): dispatcher = Dispatcher( setid=PBXDomain.id, destination=sipuri, attrs=f'socket=tls:{socket_addr}:5061;ping_from=sip:{domain}', flags=Dispatcher.FLAGS['KEEP_ALIVE'], name=hostname ) db.add(dispatcher) db.add(PBXDomainAttr1) db.add(PBXDomainAttr2) db.add(PBXDomainAttr3) db.add(PBXDomainAttr4) db.add(PBXDomainAttr5) db.add(PBXDomainAttr6) db.add(PBXDomainAttr7) db.add(PBXDomainAttr8) # Check if the MSTeams IP(s) that send us OPTION messages is in the address table for endpoint_ip in settings.MSTEAMS_IP_ENDPOINTS: address_query = db.query(Address).filter(Address.ip_addr == endpoint_ip).first() if address_query is None: Addr = Address("msteams-sbc", endpoint_ip, 32, settings.FLT_MSTEAMS, gwgroup=0) db.add(Addr) # Add Endpoint group to enable Inbound Mapping endpointGroup = {"name": domain, "endpoints": None} endpoints = [] for hostname in settings.MSTEAMS_DNS_ENDPOINTS: address_query = db.query(Address).filter(Address.ip_addr == hostname).first() if address_query is None: Addr = Address("msteams-sbc", hostname, 32, settings.FLT_MSTEAMS, gwgroup=0) db.add(Addr) hostname="{}:{};{}".format(hostname,"5061","transport=tls") endpoints.append({"host": hostname, "description": "msteams_endpoint", "maintmode": False, 'keepalive': '1'}) endpointGroup['endpoints'] = endpoints addEndpointGroups(endpointGroup, "msteams", domain) # Implement external authentication to either Realtime DB or Local Subscriber table else: # Attributes to specify that the domain was created manually PBXDomainAttr1 = DomainAttrs(did=domain, name='pbx_list', value=str(pbx_list)) PBXDomainAttr2 = DomainAttrs(did=domain, name='pbx_type', value="0") PBXDomainAttr3 = DomainAttrs(did=domain, name='created_by', value="0") PBXDomainAttr4 = DomainAttrs(did=domain, name='domain_auth', value=authtype) PBXDomainAttr5 = DomainAttrs(did=domain, name='description', value="notes:{}".format(notes)) # Serial folking will be used to forward registration info to multiple PBX's PBXDomainAttr6 = DomainAttrs(did=domain, name='dispatcher_alg_reg', value="8") PBXDomainAttr7 = DomainAttrs(did=domain, name='dispatcher_alg_in', value="4") # Create entry in dispatcher and set dispatcher_set_id in domain_attrs PBXDomainAttr8 = DomainAttrs(did=domain, name='dispatcher_set_id', value=PBXDomain.id) for pbx_id in pbx_list: dispatcher = Dispatcher( setid=PBXDomain.id, destination=gatewayIdToIP(pbx_id, db), gwid=pbx_id ) db.add(dispatcher) db.add(PBXDomainAttr1) db.add(PBXDomainAttr2) db.add(PBXDomainAttr3) db.add(PBXDomainAttr4) db.add(PBXDomainAttr5) db.add(PBXDomainAttr6) db.add(PBXDomainAttr7) db.add(PBXDomainAttr8) @domains.route("/domains/msteams/<int:id>", methods=['GET']) def configureMSTeams(id): db = DummySession() try: if not session.get('logged_in'): return redirect(url_for('index')) if (settings.DEBUG): debugEndpoint() license_status = getLicenseStatus(license_tag='DSIP_MSTEAMS') if license_status == 0: return render_template('license_required.html', msg=None) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') db = startSession() domain_query = db.query(Domain).filter(Domain.id == id) domain = domain_query.first() return render_template('msteams.html', domainid=id, domain=domain) except WoocommerceError as ex: return render_template('license_required.html', msg=str(ex)) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex, log_ex=True, print_ex=True, showstack=True) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @domains.route("/domains", methods=['GET']) def displayDomains(): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() if not session.get('logged_in'): return render_template('index.html', version=settings.VERSION) # sql1 = "select domain.id,domain.domain,dsip_domain_mapping.type,dsip_domain_mapping.pbx_id,dr_gateways.description from domain left join dsip_domain_mapping on domain.id = dsip_domain_mapping.domain_id left join dr_gateways on dsip_domain_mapping.pbx_id = dr_gateways.gwid;" sql1 = text("SELECT DISTINCT domain.did AS domain, domain.id, value as type FROM domain_attrs JOIN domain on domain.did = domain_attrs.did WHERE name='pbx_type'") res = db.execute(sql1) sql2 = text(""" SELECT distinct domain_attrs.did, pbx_list, domain_auth, creator, description FROM domain_attrs JOIN ( SELECT did,value AS pbx_list FROM domain_attrs WHERE name='pbx_list' ) t1 ON t1.did=domain_attrs.did JOIN ( SELECT did,value AS description FROM domain_attrs WHERE name='description' ) t2 ON t2.did=domain_attrs.did JOIN ( SELECT did,value AS domain_auth FROM domain_attrs WHERE name='domain_auth' ) t3 ON t3.did=domain_attrs.did JOIN ( SELECT did,description AS creator FROM domain_attrs LEFT JOIN dr_gw_lists ON domain_attrs.value = dr_gw_lists.id WHERE name='created_by') t4 ON t4.did=domain_attrs.did """) res2 = db.execute(sql2) pbx_lookup = {} for row in res2.mappings(): notes = strFieldsToDict(row["description"])["notes"] or '' if row["creator"] is not None: name = strFieldsToDict(row["creator"])["name"] or '' else: name = "Manually Created" pbx_lookup[row["did"]] = { 'pbx_list': str(row["pbx_list"].strip('[]')).replace("'", "").replace(",", ", "), 'domain_auth': row["domain_auth"], 'name': name, 'notes': notes } license_status = getLicenseStatus(license_tag='DSIP_MSTEAMS') if license_status == 0: return render_template('domains.html', rows=res, pbxlookup=pbx_lookup, hc=False) if license_status == 1: return render_template('license_required.html', msg='license is not valid, ensure your license is still active') if license_status == 2: return render_template('license_required.html', msg='license is associated with another machine, re-associate it with this machine first') return render_template('domains.html', rows=res, pbxlookup=pbx_lookup, hc=True) except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex, log_ex=True, print_ex=True, showstack=True) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @domains.route("/domains", methods=['POST']) def addUpdateDomain(): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() if not session.get('logged_in'): return render_template('index.html') form = stripDictVals(request.form.to_dict()) domain_id = form['domain_id'] if len(form['domain_id']) > 0 else '' domainlist = form['domainlist'] if len(form['domainlist']) > 0 else '' authtype = form['authtype'] if len(form['authtype']) > 0 else '' pbxs = request.form['pbx_list'] if len(form['pbx_list']) > 0 else '' notes = request.form['notes'] if len(form['notes']) > 0 else '' # Adding if len(domain_id) <= 0: domains = [domain.strip() for domain in domainlist.split(",")] for domain in domains: addDomain(domain, authtype, pbxs, notes, db) # Updating else: # remove old entries and add new ones domain_query = db.query(Domain).filter(Domain.id == domain_id) domain = domain_query.first() if domain is not None: db.query(DomainAttrs).filter(DomainAttrs.did == domain.did).delete( synchronize_session=False ) db.query(Dispatcher).filter(Dispatcher.setid == domain.id).delete( synchronize_session=False ) domain_query.delete(synchronize_session=False) db.flush() addDomain(domainlist.split(",")[0].strip(), authtype, pbxs, notes, db) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayDomains() except sql_exceptions.SQLAlchemyError as ex: debugException(ex, log_ex=True, print_ex=True, showstack=False) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex, log_ex=True, print_ex=True, showstack=False) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() @domains.route("/domainsdelete", methods=['POST']) def deleteDomain(): db = DummySession() try: if (settings.DEBUG): debugEndpoint() db = startSession() if not session.get('logged_in'): return render_template('index.html', version=settings.VERSION) form = stripDictVals(request.form.to_dict()) domainid = form['domain_id'] if 'domain_id' in form else '' domainname = form['domain_name'] if 'domain_name' in form else '' dispatcherEntry = db.query(Dispatcher).filter(Dispatcher.setid == domainid) domainAttrs = db.query(DomainAttrs).filter(DomainAttrs.did == domainname) domainEntry = db.query(Domain).filter(Domain.did == domainname) dispatcherEntry.delete(synchronize_session=False) domainAttrs.delete(synchronize_session=False) domainEntry.delete(synchronize_session=False) db.commit() getSharedMemoryDict(STATE_SHMEM_NAME)['kam_reload_required'] = True return displayDomains() except sql_exceptions.SQLAlchemyError as ex: debugException(ex) error = "db" db.rollback() db.flush() return showError(type=error) except http_exceptions.HTTPException as ex: debugException(ex) error = "http" db.rollback() db.flush() return showError(type=error) except Exception as ex: debugException(ex) error = "server" db.rollback() db.flush() return showError(type=error) finally: db.close() ================================================ FILE: gui/modules/domain/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { local TABLES=(dsip_multidomain_mapping dsip_domain_mapping) printwarn "Adding/Replacing the tables needed for Domain Mapping within dSIPRouter..." # Check to see if table exists withRootDBConn --db="$KAM_DB_NAME" mysql -sN -e "select count(*) from ${TABLES[0]} limit 1" >/dev/null 2>&1 if [ $? -eq 0 ]; then printwarn "The dSIPRouter tables ${TABLES[@]} already exists. Merging table data" ( cat ${DSIP_PROJECT_DIR}/gui/modules/domain/domain_mapping.sql; withRootDBConn --db="$KAM_DB_NAME" mysqldump --single-transaction --skip-triggers --skip-add-drop-table --no-create-info \ --insert-ignore ${TABLES[@]}; ) | withRootDBConn --db="$KAM_DB_NAME" mysql else echo -e "Installing schema for Domain Mapping" withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/gui/modules/domain/domain_mapping.sql fi } function install { installSQL printdbg "Domain module installed" } function uninstall { printdbg "Domain module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/flowroute/__init__.py ================================================ import requests import settings # TODO: automate route setup and ip auth config in flowroute class Numbers(): """ Contains methods for accessing the flowroute Numbers api """ def __init__(self): self.auth = (settings.FLOWROUTE_ACCESS_KEY, settings.FLOWROUTE_SECRET_KEY) self.api_url = settings.FLOWROUTE_API_ROOT_URL + "/numbers" def __del__(self): self.auth = None self.api_url = None def getNumbers(self, starts_with=None, contains=None, ends_with=None, limit=1000000, offset=None): """ Get flowroute DID's associated with accnt .. seealso:: `flowroute list numbers <https://developer.flowroute.com/api/numbers/v2.0/list-account-phone-numbers/>`_ :param starts_with: match numbers starting with.. :param contains: match numbers containing.. :param ends_with: match numbers ending with.. :param limit: limit of matched numbers :param offset: offsets list of numbers returned :return: list(*str) """ payload = { 'starts_with': starts_with, 'contains': contains, 'ends_with': ends_with, 'limit': limit, 'offset': offset } resp = requests.get(self.api_url, auth=self.auth, params=payload) resp.raise_for_status() return [num['attributes']['value'] for num in resp.json()['data']] ================================================ FILE: gui/modules/frauddetection/fraud.py ================================================ import sys from pyspark.sql import SQLContext from pyspark.sql import SparkSession from pyspark.ml.linalg import DenseVector from pyspark.sql import functions as F import pyspark.sql.types as T spark = SparkSession \ .builder \ .appName("Python Spark SQL basic example") \ .config("spark.driver.extraClassPath", "/usr/share/java/mysql-connector-java.jar") \ .config("spark.executor.extraClassPath", "/usr/share/java/mysql-connector-java.jar") \ .config("spark.pyspark.python","/usr/bin/python3.6") \ .getOrCreate() sc = spark.sparkContext sqlContext = SQLContext(sc) url = "jdbc:mysql://localhost:3306/kamailio?user=kamailio;password=kamailiorw" df = sqlContext \ .read \ .format("jdbc") \ .option("url", url) \ .option("dbtable", "cdrs") \ .option("user","kamailio") \ .option("password", "kamailiorw") \ .load() df.printSchema() df1 = df.select("fraud","src_username","dst_username","call_start_time") df1.show() def removeTechPrefix(col): techprefix,dst_username = col.split("*") return dst_username my_udf = F.UserDefinedFunction(removeTechPrefix, T.StringType()) df2=df1.withColumn("dst_username",my_udf(df1.dst_username)) df2=df2.withColumn("call_start_time",F.hour(df2.call_start_time)) df2.show() input_data = df2.rdd.map(lambda x: (x[0],DenseVector(x[1:]))) df3 = spark.createDataFrame(input_data, ["label", "features"]) df3.show() ================================================ FILE: gui/modules/frauddetection/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=0 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function installSQL { echo "" } function install { installSQL printdbg "Fraud Detection module installed" } function uninstall { printdbg "Fraud Detection module uninstalled" } function main { if [[ ${ENABLED} -eq 1 ]]; then install && exit 0 || exit 1 elif [[ ${ENABLED} -eq -1 ]]; then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/fusionpbx/dsiprouter-provisioner.conf ================================================ upstream fusionpbx { server fusionpbx5.dsiprouter.net:443; } # handle the https requests server { # by default we listen on all interfaces listen 80; listen [::]:80; server_name _; #ssl_certificate /etc/dsiprouter/certs/dsiprouter-cert.pem; #ssl_certificate_key /etc/dsiprouter/certs/dsiprouter-key.pem; location /provision/ { proxy_pass http://fusionpbx; proxy_redirect off; proxy_set_header Host $host; proxy_next_upstream error timeout http_404 http_403 http_500 http_502 http_503 http_504 non_idempotent; } location / { error_page 404 /404.html; } location /images/ { alias /etc/nginx/html/images/; } # enable the access log for debugging # it doesn't log data between nginx and the upstream FusionPBX servers # it only logs data between the phone and nginx #access_log /var/log/nginx/dsiprouter-provisioner-access.log; } ================================================ FILE: gui/modules/fusionpbx/dsiprouter-provisioner.tpl ================================================ upstream fusionpbx { ##SERVERLIST## } # handle the https requests server { # by default we listen on all interfaces listen 443 ssl http2 so_keepalive=on; listen [::]:443 ssl http2 so_keepalive=on; server_name _; ssl_certificate /etc/dsiprouter/certs/dsiprouter-cert.pem; ssl_certificate_key /etc/dsiprouter/certs/dsiprouter-key.pem; location ~^(\/app\/provision\/|\/provision\/) { proxy_pass https://fusionpbx; proxy_redirect off; proxy_set_header Host $host; proxy_next_upstream error timeout http_404 http_403 http_500 http_502 http_503 http_504 non_idempotent; } location / { error_page 404 /404.html; } location /images/ { alias /etc/nginx/html/images/; } # enable the access log for debugging # it doesn't log data between nginx and the upstream FusionPBX servers # it only logs data between the phone and nginx #access_log /var/log/nginx/dsiprouter-provisioner-access.log; } ================================================ FILE: gui/modules/fusionpbx/dsiprouter.nginx ================================================ upstream fusionpbx { server fusionpbx5.dsiprouter.net:443; } server { listen 80; listen 443 ssl; ssl_certificate /etc/ssl/certs/cert_combined.crt; ssl_certificate_key /etc/ssl/certs/cert.key; location /provision/ { proxy_pass https://fusionpbx; proxy_redirect off; proxy_next_upstream error timeout http_404 http_403 http_500 http_502 http_503 http_504 non_idempotent; } location / { error_page 404 /404.html; } location /images/ { alias /etc/nginx/html/images/; } } ================================================ FILE: gui/modules/fusionpbx/dsiprouter.nginx.tpl ================================================ upstream fusionpbx { ##SERVERLIST## } server { listen 80; listen 443 ssl; ssl_certificate /etc/ssl/certs/cert_combined.crt; ssl_certificate_key /etc/ssl/certs/cert.key; location /provision/ { proxy_pass https://fusionpbx; proxy_redirect off; proxy_next_upstream error timeout http_404 http_403 http_500 http_502 http_503 http_504 non_idempotent; } location / { error_page 404 /404.html; } location /images/ { alias /etc/nginx/html/images/; } } ================================================ FILE: gui/modules/fusionpbx/fusionpbx_sync_functions.py ================================================ import os, psycopg2, hashlib, MySQLdb, shutil from util.security import AES_CTR from util.networking import safeUriToHost, hostToIP from modules.api.kamailio.functions import sendJsonRpcCmd # TODO: error handling here is pretty bad, we need to establish connection from main func and pass conn/cursors to sub funcs # I implemented an exmaple in sync_needed() of proper connection / cursor handling, we need to move that to the entry func # i.e. run_sync() should utilize the proper handling of the connections/cursors and pass them to sub functions FUSIONPBX_SYNC_LOCK = '/run/dsiprouter/fusionsync.lock' # Obtain a set of FusionPBX systems that contains domains that Kamailio will route traffic to. def get_sources(db): # Dictionary object to hold the set of source FusionPBX systems sources = {} # Kamailio Database Parameters kam_hostname = db['hostname'] kam_username = db['username'] kam_password = db['password'] kam_database = db['database'] try: db = MySQLdb.connect(host=kam_hostname, user=kam_username, passwd=kam_password, db=kam_database) c = db.cursor() c.execute( """select pbx_id,address as pbx_host,db_host,db_username,db_password,domain_list,domain_list_hash,attr_list,dsip_multidomain_mapping.type from dsip_multidomain_mapping join dr_gw_lists on dsip_multidomain_mapping.pbx_id=dr_gw_lists.id join dr_gateways on dr_gateways.gwid = dr_gw_lists.gwlist where enabled=1""") results = c.fetchall() db.close() for row in results: # Store the PBX_ID as the key and the entire row as the value sources[row[1]] = row except Exception as e: print(str(e)) return sources # Will remove all of the fusionpbx domain data so that it can be rebuilt def drop_fusionpbx_domains(source, dest): # PBX Domain Mapping Parameters pbx_domain_list = source[5] pbx_attr_list = source[6] # Kamailio Database Parameters kam_hostname = dest['hostname'] kam_username = dest['username'] kam_password = dest['password'] kam_database = dest['database'] pbx_domain_list = list(map(int, filter(None, pbx_domain_list.split(",")))) pbx_attr_list = list(map(int, filter(None, pbx_attr_list.split(",")))) kam_conn = None kam_curs = None try: kam_conn = MySQLdb.connect(host=kam_hostname, user=kam_username, passwd=kam_password, db=kam_database) kam_curs = kam_conn.cursor() if len(pbx_domain_list) > 0: kam_curs.execute("""DELETE FROM domain WHERE id IN({})""".format(pbx_domain_list)) if len(pbx_attr_list) > 0: kam_curs.execute("""DELETE FROM domain_attrs WHERE id IN({})""".format(pbx_attr_list)) kam_conn.commit() except Exception as ex: error = str(ex) try: kam_conn.rollback() kam_curs.execute("update dsip_multidomain_mapping set syncstatus=4, lastsync=NOW(),syncerror='{}'".format(error)) kam_conn.commit() except: pass raise ex finally: if kam_curs is not None: kam_curs.close() if kam_conn is not None: kam_conn.close() def sync_db(source, dest): # FusionPBX Database Parameters pbx_id = source[0] pbx_host = source[1] if ':' in source[2]: fpbx_hostname = source[2].split(':')[0] fpbx_port = source[2].split(':')[1] else: fpbx_hostname = source[2] fpbx_port = 5432 fpbx_username = source[3] fpbx_password = source[4] pbx_domain_list = source[5] pbx_attr_list = source[6] pbx_type = source[8] fpbx_database = 'fusionpbx' # Kamailio Database Parameters kam_hostname = dest['hostname'] kam_username = dest['username'] kam_password = dest['password'] kam_database = dest['database'] domain_id_list = [] attr_id_list = [] fpbx_conn = None fpbx_curs = None kam_conn = None kam_curs = None try: # Get a connection to Kamailio Server DB kam_conn = MySQLdb.connect(host=kam_hostname, user=kam_username, passwd=kam_password, db=kam_database) kam_curs = kam_conn.cursor() # Delete existing domain for the pbx pbx_domain_list_str = ''.join(str(e) for e in pbx_domain_list) if len(pbx_domain_list_str) > 0: query = "delete from domain where id in ({})".format(pbx_domain_list_str) kam_curs.execute(query) pbx_domain_list = '' # Trying connecting to PostgresSQL database using a Trust releationship first fpbx_conn = psycopg2.connect(dbname=fpbx_database, user=fpbx_username, host=fpbx_hostname, port=fpbx_port, password=fpbx_password) if fpbx_conn is not None: print("Connection to FusionPBX:{} database was successful".format(fpbx_hostname)) fpbx_curs = fpbx_conn.cursor() fpbx_curs.execute("""select domain_name from v_domains where domain_enabled='true' and domain_name <> %s""",[hostToIP(fpbx_hostname)]) rows = fpbx_curs.fetchall() if rows is not None: counter = 0 domain_name_str = "" for row in rows: kam_curs.execute("""insert ignore into domain (id,domain,did,last_modified) values (null,%s,%s,NOW())""", (row[0], row[0])) if kam_curs.rowcount > 0: kam_curs.execute( """SELECT AUTO_INCREMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN('domain') ORDER BY FIND_IN_SET(TABLE_NAME, 'domain')""") rows_left = kam_curs.fetchall() domain_id_list.append(str(rows_left[0][0] - 1)) # Delete all domain_attrs for the domain first kam_curs.execute("""delete from domain_attrs where did=%s""", [row[0]]) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'pbx_ip',2,%s,NOW())""", (row[0], pbx_host)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'pbx_type',2,%s,NOW())""", (row[0], pbx_type)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'created_by',2,%s,NOW())""", (row[0], pbx_id)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'dispatcher_set_id',2,%s,NOW())""", (row[0], pbx_id)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'dispatcher_reg_alg',2,%s,NOW())""", (row[0], 4)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'domain_auth',2,%s,NOW())""", (row[0], 'passthru')) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'pbx_list',2,%s,NOW())""", (row[0], pbx_id)) kam_curs.execute( """insert ignore into domain_attrs (id,did,name,type,value,last_modified) values (null,%s,'description',2,%s,NOW())""", (row[0], 'notes:')) counter = counter + 1 domain_name_str += row[0] # Convert to a string seperated by commas domain_id_list = ','.join(domain_id_list) if not pbx_domain_list: # if empty string then this is the first set of domains pbx_domain_list = domain_id_list else: # adding to an existing list of domains pbx_domain_list = pbx_domain_list + "," + domain_id_list print("[sync_db] String of domains: {}".format(domain_name_str)) # Create Hash of the string domain_name_str_hash = hashlib.md5(domain_name_str.encode('utf-8')).hexdigest() print("[sync_db] Hashed String of domains: {}".format(domain_name_str_hash)) kam_curs.execute( """update dsip_multidomain_mapping set domain_list=%s, domain_list_hash=%s,syncstatus=1, lastsync=NOW(),syncerror='' where pbx_id=%s""", (pbx_domain_list, domain_name_str_hash, pbx_id)) kam_conn.commit() except Exception as ex: error = str(ex) try: kam_conn.rollback() kam_curs.execute("update dsip_multidomain_mapping set syncstatus=4, lastsync=NOW(),syncerror='{}'".format(error)) kam_conn.commit() except: pass raise ex finally: if fpbx_conn is not None: fpbx_conn.close() if fpbx_curs is not None: fpbx_curs.close() if kam_curs is not None: kam_curs.close() if kam_conn is not None: kam_conn.close() def reloadkam(): try: sendJsonRpcCmd('127.0.0.1', 'domain.reload') return True except: return False def update_nginx(sources): print("Updating Nginx") # # Connect to docker # client = docker.from_env() # if client is not None: # print("Got handle to docker") # # try: # # If there isn't any FusionPBX sources then just shutdown the container # if len(sources) < 1: # containers = client.containers.list() # for container in containers: # if container.name == "dsiprouter-nginx": # # Stop the container # container.stop() # container.remove(force=True) # print("Stopped nginx container") # return # except Exception as e: # os.remove(FUSIONPBX_SYNC_LOCK) # print(e) # Create the Nginx file try: serverList = "" for source in sources: serverList += "server " + safeUriToHost(str(source)) + ":443;\n" script_dir = os.path.dirname(os.path.abspath(__file__)) # Build a config for the native Nginx instance input = open(script_dir + "/dsiprouter-provisioner.tpl") try: output = open("/etc/nginx/sites-enabled/dsiprouter-provisioner.conf", "x") except FileExistsError: output = open("/etc/nginx/sites-enabled/dsiprouter-provisioner.conf", "w") finally: output.write(input.read().replace("##SERVERLIST##", serverList)) output.close() input.close() # Restart Nginx os.system('systemctl reload nginx') except Exception as e: os.remove(FUSIONPBX_SYNC_LOCK) print(e) # Depricating the use of docker containers - logic will be removed during the next release # # Check if dsiprouter-nginx is running. If so, reload nginx # # containers = client.containers.list() # print("past container list") # for container in containers: # if container.name == "dsiprouter-nginx": # # Execute a command to reload nginx # container.exec_run("nginx -s reload") # print("Reloaded nginx") # return # # # Start the container if one is not running # try: # print("trying to create a container") # host_volume_path = script_dir + "/dsiprouter.nginx" # html_volume_path = script_dir + "/html" # cert_volume_path = script_dir + "/certs" # # host_volume_path = script_dir # print(host_volume_path) # # remove the container with a name of dsiprouter-nginx to avoid conflicts if it already exists # containerList = client.containers.list('dsiprouter-nginx') # containerFound = False # for c in containerList: # if c.name == "dsiprouter-nginx": # containerFound = True # if containerFound: # print("dsiprouter-nginx found...about to remove it and recreate") # container = client.containers.get('dsiprouter-nginx') # container.remove() # client.containers.run(image='nginx:latest', # name="dsiprouter-nginx", # ports={'80/tcp': '80/tcp', '443/tcp': '443/tcp'}, # volumes={ # host_volume_path: {'bind': '/etc/nginx/conf.d/default.conf', 'mode': 'rw'}, # html_volume_path: {'bind': '/etc/nginx/html', 'mode': 'rw'}, # cert_volume_path: {'bind': '/etc/ssl/certs', 'mode': 'rw'} # }, # detach=True) # print("created a container") # except Exception as e: # os.remove(FUSIONPBX_SYNC_LOCK) # print(str(e)) def sync_needed(source, dest): # FusionPBX Database Parameters pbx_id = source[0] pbx_host = source[1] if ':' in source[2]: fpbx_hostname = source[2].split(':')[0] fpbx_port = source[2].split(':')[1] else: fpbx_hostname = source[2] fpbx_port = 5432 fpbx_username = source[3] fpbx_password = source[4] pbx_domain_list = source[5] pbx_domain_list_hash = source[6] pbx_attr_list = source[7] pbx_type = source[8] fpbx_database = 'fusionpbx' # Kamailio Database Parameters kam_hostname = dest['hostname'] kam_username = dest['username'] kam_password = dest['password'] kam_database = dest['database'] domain_id_list = [] attr_id_list = [] need_sync = True fpbx_conn = None fpbx_curs = None kam_conn = None kam_curs = None # Trying connecting to the databases try: # Get a connection to Kamailio Server DB kam_conn = MySQLdb.connect(host=kam_hostname, user=kam_username, passwd=kam_password, db=kam_database) kam_curs = kam_conn.cursor() if kam_curs is not None: print("[sync_needed] Connection to Kamailio DB: {} database was successful".format(kam_hostname)) # Get a connection to the FusionPBX Server fpbx_conn = psycopg2.connect(dbname=fpbx_database, user=fpbx_username, host=fpbx_hostname, port=fpbx_port, password=fpbx_password) if fpbx_conn is not None: print("[sync_needed] Connection to FusionPBX DB: {} database was successful".format(fpbx_hostname)) fpbx_curs = fpbx_conn.cursor() fpbx_curs.execute("""select domain_name from v_domains where domain_enabled='true' and domain_name <> %s""",[hostToIP(fpbx_hostname)]) rows = fpbx_curs.fetchall() if rows is not None: domain_name_str = "" # Build a string that contains all of the domains for row in rows: domain_name_str += row[0] print("[sync_needed] String of domains: {}".format(domain_name_str)) # Create Hash of the string domain_name_str_hash = hashlib.md5(domain_name_str.encode('utf-8')).hexdigest() print("[sync_needed] Hashed String of domains: {}".format(domain_name_str_hash)) if domain_name_str_hash == pbx_domain_list_hash: # Sync not needed. Will update the syncstatus=2 to denote a domain change was not detected kam_curs.execute("""update dsip_multidomain_mapping set syncstatus=2, lastsync=NOW()""") kam_conn.commit() need_sync = False else: # No domains yet, so no need to sync kam_curs.execute("""update dsip_multidomain_mapping set syncstatus=3, lastsync=NOW()""") kam_conn.commit() need_sync = False return need_sync except Exception as e: error = str(e) print(error) try: kam_conn.rollback() kam_curs.execute("update dsip_multidomain_mapping set syncstatus=4, lastsync=NOW(),syncerror='{}'".format()) kam_conn.commit() except: pass finally: if fpbx_conn is not None: fpbx_conn.close() if fpbx_curs is not None: fpbx_curs.close() if kam_curs is not None: kam_curs.close() if kam_conn is not None: kam_conn.close() def run_sync(settings): try: # Set the system where sync'd data will be stored. # The Kamailio DB in our case # If already running - don't run if os.path.exists(FUSIONPBX_SYNC_LOCK): print("Already running") return else: f = open(FUSIONPBX_SYNC_LOCK, "w+") f.close() # need to decrypt password if encrypted if isinstance(settings.KAM_DB_PASS, bytes): kam_password = AES_CTR.decrypt(settings.KAM_DB_PASS) else: kam_password = settings.KAM_DB_PASS dest = {} dest['hostname'] = settings.KAM_DB_HOST dest['username'] = settings.KAM_DB_USER dest['password'] = kam_password dest['database'] = settings.KAM_DB_NAME # Get the list of FusionPBX's that needs to be sync'd sources = get_sources(dest) # Loop thru each FusionPBX system and start the sync for key in sources: if sync_needed(sources[key], dest): #drop_fusionpbx_domains(sources[key], dest) sync_db(sources[key], dest) else: print("[run_sync] No changes - no sync needed for source: {}".format(sources[key][1])) # Reload Kamailio reloadkam() # Update Nginx configuration file for HTTP Provisioning and start docker container if we have FusionPBX systems # update_nginx(sources[key]) if sources is not None and len(sources) > 0: sources = list(sources.keys()) update_nginx(sources) except Exception as e: print(str(e)) finally: # Remove lock file os.remove(FUSIONPBX_SYNC_LOCK) ================================================ FILE: gui/modules/fusionpbx/html/images/placeholder.txt ================================================ ================================================ FILE: gui/modules/fusionpbx/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # ENABLED=1 --> install, ENABLED=0 --> do nothing, ENABLED=-1 uninstall ENABLED=1 # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # TODO: replace docker workflow here by simply adding to default dsiprouter nginx configs function install { local FUSIONPBX_DIR="${DSIP_PROJECT_DIR}/gui/modules/fusionpbx" case "$DISTRO" in debian|ubuntu) apt-get install -y apt-transport-https ca-certificates software-properties-common gnupg lsb-release local DEB_ARCH=$(dpkg --print-architecture) local DISTRO_CODENAME=$(lsb_release -cs) mkdir -p /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/${DISTRO}/gpg | gpg --dearmor --yes -o /etc/apt/keyrings/docker.gpg mkdir -p /etc/apt/sources.list.d (cat <<EOF deb [arch=${DEB_ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${DISTRO} ${DISTRO_CODENAME} stable #deb-src [arch=${DEB_ARCH} signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/${DISTRO} ${DISTRO_CODENAME} stable EOF ) >/etc/apt/sources.list.d/docker.list mkdir -p /etc/apt/preferences.d (cat <<'EOF' Package: * Pin: origin download.docker.com Pin-Priority: 1000 EOF ) >/etc/apt/preferences.d/docker.pref apt-get update -y apt-get install -y docker-ce if (( $? != 0 )); then printerr "Failed installing Docker" return 1 fi ;; amzn) yum install -y ca-certificates yum-utils device-mapper-persistent-data lvm2 amazon-linux-extras enable -y docker >/dev/null yum clean -y metadata yum install -y docker if (( $? != 0 )); then printerr "Failed installing Docker" return 1 fi ;; rhel|almalinux|rocky) dnf install -y dnf-utils device-mapper-persistent-data lvm2 ca-certificates # CA_CERT_DIR=$(dirname $(find / -name '*ca-bundle.crt')) # cp -f ${CA_CERT_DIR}/ca-bundle.crt ${CA_CERT_DIR}/ca-bundle.bak # curl http://curl.haxx.se/ca/cacert.pem -o ${CA_CERT_DIR}/ca-bundle.crt # update-ca-trust force-enable # update-ca-trust extract dnf remove -y docker\* # docker.io does not provide support for x86_64 on rhel/alma/rocky # instead we use the centos repo docker.io provides (binary compatible) dnf config-manager -y --add-repo https://download.docker.com/linux/centos/docker-ce.repo dnf config-manager -y --enable docker-ce-stable dnf install -y docker-ce if (( $? != 0 )); then printerr "Failed installing Docker" return 1 fi ;; *) printerr "Failed installing Docker, OS Distro not supported" return 1 ;; esac systemctl enable docker.service systemctl start docker firewall-cmd --permanent --zone=public --add-port=80/tcp firewall-cmd --permanent --zone=public --add-port=443/tcp firewall-cmd --reload # Install Nginx container docker create nginx #docker run --name docker-nginx -p 80:80 -v ${FUSIONPBX_DIR}/dsiprouter.nginx:/etc/nginx/conf.d/default.conf -d nginx # Install a default self signed certificate for spinning up NGINX mkdir -p ${DSIP_CERTS_DIR}/fusionpbx/ && openssl req -new -newkey rsa:4096 -days 3650 -nodes -x509 \ -subj "/C=US/ST=MI/L=Detroit/O=dopensource.com/CN=dSIPRouter" \ -keyout ${DSIP_CERTS_DIR}/fusionpbx/cert.key \ -out ${DSIP_CERTS_DIR}/fusionpbx/cert_combined.crt cronRemove -u dsiprouter 'dsiprouter_cron.py fusionpbx' cronAppend -u dsiprouter "*/1 * * * * ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter_cron.py fusionpbx sync" # Change to dsiprouter group so that the FusionPBX provisioning configuation can be written chown root:dsiprouter /etc/nginx/sites-enabled chmod 775 /etc/nginx/sites-enabled printdbg "FusionPBX module installed" return 0 } function uninstall { # Forcefully stop all docker containers and remove them docker ps -a -q > /dev/null if [ $? == 1 ]; then docker rm -f $(docker ps -a -q) > /dev/null printdbg "Stopped and removed all docker containers" else printwarn "No docker containers to remove" fi case "$DISTRO" in debian|ubuntu) # Can't remove packages because it removes python3-pip package #apt-get remove -y \ #apt-transport-https \ #ca-certificates #software-properties-common # Remove Docker Engine apt-get remove -y docker-ce # remove the docker repo rm -f /etc/apt/keyrings/docker.gpg /etc/apt/sources.list.d/docker.list /etc/apt/preferences.d/docker.pref apt-get update -y ;; amzn) yum remove -y docker amazon-linux-extras disable -y docker >/dev/null yum clean -y metadata ;; rhel|almalinux|rocky) #yum remove -y ca-certificates #yum remove -y device-mapper-persistent-data lvm2 yum remove -y docker-ce # Remove the repos rm -f /etc/yum.repos.d/docker-ce* yum clean all ;; esac firewall-cmd --permanent --zone=public --remove-port=80/tcp firewall-cmd --permanent --zone=public --remove-port=443/tcp firewall-cmd --reload rm -rf ${DSIP_CERTS_DIR}/fusionpbx/ cronRemove -u dsiprouter 'dsiprouter_cron.py fusionpbx' printdbg "FusionPBX module uninstalled" return 0 } function main { if (( ${ENABLED} == 1 )); then install && exit 0 || exit 1 elif (( ${ENABLED} == -1 )); then uninstall && exit 0 || exit 1 else exit 0 fi } main ================================================ FILE: gui/modules/upgrade/__init__.py ================================================ import re, requests import settings class UpdateUtils(): @staticmethod def get_repo_version_list(): headers = { 'Accept': 'application/vnd.github+json' } params = { "per_page": 100 } r = requests.get(settings.GIT_RELEASE_URL, params=params, headers=headers) return r.json() @staticmethod def get_latest_version(): latest = { 'tag_name': '', 'ver_num': 0 } for rel in UpdateUtils.get_repo_version_list(): tag_name = rel['tag_name'] ver_num = float(re.sub(r'^v([0-9]+\.[0-9]+).*?$', r'\1', tag_name, flags=re.MULTILINE)) if ver_num > latest['ver_num']: latest = { 'tag_name': tag_name, 'ver_num': ver_num } return latest ================================================ FILE: gui/requirements.txt ================================================ UltraDict acme ansi2html bjoern bson cron_descriptor cryptography dnspython docker docutils<0.17,>=0.12 Flask~=2.2.0 Flask_WTF itsdangerous==2.0.1 Jinja2==3.1.3 josepy myst-parser mysqlclient pem psycopg2 psycopg2_binary pycryptodome pygtail PyMySQL pyOpenSSL python-ldap==3.4.4 python_crontab pytz recommonmark requests sphinx sphinxcontrib-httpdomain sphinx-rtd-theme piccolo_theme SQLAlchemy~=2.0 twilio Werkzeug~=2.0 ================================================ FILE: gui/settings.py ================================================ ################ Database-Backed Settings ################ # settings in this section are synced with the DB # dSIPRouter settings # dSIPRouter will need to be restarted for any changes to take effect - except settings that can be hot reloaded # for more information on hot reloading and shared memory via IPC see shared.updateConfig() and dsiprouter.syncSettings() DSIP_ID = None DSIP_CLUSTER_ID = 1 DSIP_CLUSTER_SYNC = False DSIP_PROTO = 'https' DSIP_PORT = '5000' DSIP_USERNAME = 'admin' DSIP_PASSWORD = 'admin' DSIP_API_TOKEN = 'admin' DSIP_API_PROTO = 'https' DSIP_API_PORT = 5000 DSIP_PRIV_KEY = '/etc/dsiprouter/privkey' DSIP_PID_FILE = '/run/dsiprouter/dsiprouter.pid' DSIP_UNIX_SOCK = '/run/dsiprouter/dsiprouter.sock' DSIP_IPC_SOCK = '/run/dsiprouter/ipc.sock' DSIP_IPC_PASS = 'admin' # dsiprouter logging settings # syslog level and facility values based on: # <http://www.nightmare.com/squirl/python-ext/misc/syslog.py> DSIP_LOG_LEVEL = 3 DSIP_LOG_FACILITY = 18 # dSIPRouter SSL settings # ssl key / cert are absolute paths # email for re-certification must match certs DSIP_SSL_KEY = '/etc/dsiprouter/certs/dsiprouter-key.pem' DSIP_SSL_CERT = '/etc/dsiprouter/certs/dsiprouter-cert.pem' DSIP_SSL_CA = '/etc/dsiprouter/certs/ca-list.pem' DSIP_SSL_EMAIL = 'admin@sbc4.customers.dsiprouter.net' DSIP_CERTS_DIR = '/etc/dsiprouter/certs' # dSIPRouter internal settings VERSION = '0.78' DEBUG = False # '' (default) = handle inbound with domain mapping from endpoints, inbound from carriers and outbound to carriers # 'outbound' = act as an outbound proxy only (no domain routing) # 'inout' = inbound from carriers and outbound to carriers only (no domain routing) ROLE = '' GUI_INACTIVE_TIMEOUT = 20 # MySQL settings for kamailio # Database cluster #KAM_DB_HOST = ['64.129.84.11','64.129.84.12','50.237.20.11','50.237.20.12'] # Single Host KAM_DB_HOST = 'localhost' # Database Engine Driver to connect with (leave empty for default) # supported drivers: mysqldb | pymysql # see sqlalchemy docs for more info: <https://docs.sqlalchemy.org/en/latest/core/engines.html> KAM_DB_DRIVER = '' KAM_DB_TYPE = 'mysql' KAM_DB_PORT = '3306' KAM_DB_NAME = 'kamailio' KAM_DB_USER = 'kamailio' KAM_DB_PASS = 'kamailiorw' KAM_KAMCMD_PATH = '/usr/sbin/kamcmd' KAM_CFG_PATH = '/etc/kamailio/kamailio.cfg' KAM_TLSCFG_PATH = '/etc/kamailio/tls.cfg' RTP_CFG_PATH = '/etc/rtpengine/rtpengine.conf' # These constants shouldn't be modified # FLT_CARRIER/FLT_PBX/FLT_MSTEAMS: type in dr_gateway table # FLT_OUTBOUND/FLT_INBOUND: groupid in dr_rules table # FLT_LCR_MIN/FLT_FWD_MIN: range of groupid in dr_rules table FLT_CARRIER = 8 FLT_PBX = 9 FLT_MSTEAMS = 17 FLT_OUTBOUND = 8000 FLT_INBOUND = 9000 FLT_LCR_MIN = 10000 FLT_FWD_MIN = 20000 # The domain used to create user accounts for PBX and Endpoint registrations DEFAULT_AUTH_DOMAIN = 'sip.dsiprouter.org' # Teleblock Settings TELEBLOCK_GW_ENABLED = 0 TELEBLOCK_GW_IP = '62.34.24.22' TELEBLOCK_GW_PORT = '5066' TELEBLOCK_MEDIA_IP = '' TELEBLOCK_MEDIA_PORT = '' # Flowroute API Settings # TODO: encrypt/decrypt these creds instead of storing in plaintext FLOWROUTE_ACCESS_KEY = '' FLOWROUTE_SECRET_KEY = '' FLOWROUTE_API_ROOT_URL = 'https://api.flowroute.com/v2' # Homer settings HOMER_ID = None HOMER_HEP_HOST = '' HOMER_HEP_PORT = 9060 # Network Settings # possible network modes: # 0: (full-auto) dynamically update all network settings, updated on startup # 1: (manual) user sets all network settings manually, interfaces are ignored # 2: (dmz) internal/external IP/subnet resolved from public/private interfaces, all other settings as in mode 0 NETWORK_MODE = 0 IPV6_ENABLED = False # example: 192.168.0.1 INTERNAL_IP_ADDR = '' # example: 192.168.0.1/24 INTERNAL_IP_NET = '' # example: 2604:a880:400:d0::2048:3001 INTERNAL_IP6_ADDR = '' # example: 2604:a880:400:d0::2048:3001/64 INTERNAL_IP6_NET = '' # example: sip.dsiprouter.org INTERNAL_FQDN = '' # example: 1.1.1.1 EXTERNAL_IP_ADDR = '' # example: 2604:a880:400:d0::2048:3001 EXTERNAL_IP6_ADDR = '' # example: sip.dsiprouter.org EXTERNAL_FQDN = '' # example: eth0 PUBLIC_IFACE = '' # example: eth1 PRIVATE_IFACE = '' # upload folder for files UPLOAD_FOLDER = '/tmp' # email server config MAIL_SERVER = 'smtp.gmail.com' MAIL_PORT = 587 MAIL_USE_TLS = True MAIL_USERNAME = '' MAIL_PASSWORD = '' MAIL_ASCII_ATTACHMENTS = False MAIL_DEFAULT_SENDER = 'dSIPRouter <donotreply@sip.dsiprouter.org>' MAIL_DEFAULT_SUBJECT = 'dSIPRouter System Notification' # dSIPRouter licenses associated with this node # stored as hash_str: key_combo DSIP_LICENSE_STORE = {} # rtpengine settings RTPENGINE_URI = 'udp:localhost:7722' ################# End DB-Backed Settings ################# ################# Local-Only Settings #################### # settings in this section are not stored on the DB # the key used by the flask session manager DSIP_SESSION_KEY = None # the fqdn / ip address used by uac/nathelper modules when contacting other servers UAC_REG_ADDR = '' # Cloud Platform # The cloud platform the dSIPRouter is installed on # The installer will update this # '' = other or bare metal install # AWS = Amazon Web Services, GCP = Google Cloud Platform, AZURE = Microsoft Azure, DO = Digital Ocean, VULTR = Vultr Cloud CLOUD_PLATFORM = '' # backup settings BACKUP_FOLDER = '/var/backups/dsiprouter' # TransNexus Settings # TODO: marked for review, these settings should be synced across cluster in the DB TRANSNEXUS_AUTHSERVICE_ENABLED = 0 TRANSNEXUS_AUTHSERVICE_HOST = 'outbound.sip.clearip.com:5060' TRANSNEXUS_VERIFYSERVICE_ENABLED = 0 TRANSNEXUS_VERIFYSERVICE_HOST = 'inbound.sip.clearip.com:5060' # STIR/SHAKEN Settings # TODO: marked for review, these settings should be synced across cluster in the DB STIR_SHAKEN_ENABLED = 0 STIR_SHAKEN_PREFIX_A = '' STIR_SHAKEN_PREFIX_B = '' STIR_SHAKEN_PREFIX_C = '' STIR_SHAKEN_PREFIX_INVALID = '' STIR_SHAKEN_BLOCK_INVALID = 0 STIR_SHAKEN_CERT_URL = '' STIR_SHAKEN_KEY_PATH = '' # where the project was installed DSIP_PROJECT_DIR = '/opt/dsiprouter' # where the dsip docs are served from DSIP_DOCS_DIR = '/opt/dsiprouter/docs/build/html' # dr_routing prefix matching supported characters # refer to: <https://kamailio.org/docs/modules/5.1.x/modules/drouting.html#idp26708356> DID_PREFIX_ALLOWED_CHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '#', '*'} # micosoft teams settings # pick one of the MSTEAMS_DNS_ENDPOINTS = ["sip.pstnhub.microsoft.com","sip2.pstnhub.microsoft.com","sip3.pstnhub.microsoft.com"] #MSTEAMS_DNS_ENDPOINTS = ["sip.pstnhub.dod.teams.microsoft.us","sip.pstnhub.gov.teams.microsoft.us"] MSTEAMS_IP_ENDPOINTS = ["52.114.148.0","52.114.132.46","52.114.75.24","52.114.76.76","52.114.7.24","52.114.14.70","52.114.32.169"] #MSTEAMS_IP_ENDPOINTS = ["52.127.64.33","52.127.88.59","52.127.64.34","52.127.92.64"] # kamailio jsonrpc settings KAM_JSONRPC_ROOTPATH = '/api/kamailio' KAM_JSONRPC_VERSION = '2.0' KAM_JSONRPC_ID = 1 KAM_JSONRPC_TIMEOUT = 15 KAM_JSONRPC_RETRYIVAL = 1.0 # root DB credentials ROOT_DB_HOST = 'localhost' ROOT_DB_PORT = '' ROOT_DB_USER = 'root' ROOT_DB_PASS = '' ROOT_DB_NAME = 'mysql' # Where to sync settings from # file - load from setting.py file # db - load from dsip_settings table LOAD_SETTINGS_FROM = 'file' # where upgrades will be pulled from GIT_REPO_URL = 'https://github.com/dOpensource/dsiprouter.git' GIT_RELEASE_URL = 'https://api.github.com/repos/dOpensource/dsiprouter/releases' # auth modules # a dictionary of authentication modules to load and their corresponding settings # example for ldap module: # AUTH_MODULES = {"ldap": {"LDAP_HOST":"ldap://ldap.dopensource.com", "USER_SEARCH_BASE":"ou=People,dc=dopensource,dc=com", "GROUP_SEARCH_BASE":"dc=dopensource,dc=com", "GROUP_MEMBER_ATTRIBUTE":"memberUid", "REQUIRED_GROUP":"support", "USER_ATTRIBUTE":"uid"}} AUTH_MODULES = {} ############### End Local-Only Settings ################## ================================================ FILE: gui/shared.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import os, re, json, socket, logging, traceback, inspect, ssl from calendar import monthrange from importlib import reload from flask import request, render_template, make_response from werkzeug.utils import escape from werkzeug.urls import iri_to_uri import settings def isCertValid(hostname, externalip, port=5061): """ Returns true if the hostname has a valid cert""" result = {"tls_cert_valid": False, "tls_cert_details": "", "tls_error": ""} try: context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH, cafile=settings.DSIP_SSL_CA) context.load_cert_chain(certfile=settings.DSIP_SSL_CERT, keyfile=settings.DSIP_SSL_KEY) conn = context.wrap_socket(socket.socket(socket.AF_INET), server_hostname=hostname) conn.connect((externalip, port)) # print("SSL established. Peer: {}".format(conn.getpeercert())) cert = conn.getpeercert() result['tls_cert_details'] = cert #ssl.match_hostname(cert, hostname) result['tls_cert_valid'] = True return result except Exception as ex: result['tls_error'] = str(ex) # Return valid even if the cert can't be validated. We just want to validate # the ability to connect using the cert. return result def objToDict(obj): """ converts an arbitrary object to dict """ if isinstance(obj, dict): return obj vals = [] for attr in dir(obj): if attr.startswith('__'): continue val = getattr(obj, attr) if inspect.ismethod(val) or inspect.isfunction(val) or inspect.isbuiltin(val): continue vals.append((attr, val)) return dict(vals) def rowToDict(row): """ converts sqlalchemy row object to python dict does not recurse through relationships tries table data, then _asdict() method, then objToDict() """ d = {} if hasattr(row, '__table__'): for column in row.__table__.columns: d[column.name] = str(getattr(row, column.name)) elif hasattr(row, '_asdict'): d = row._asdict() elif hasattr(row, '__dict__'): d = row.__dict__ d.pop('_sa_instance_state', None) else: d = objToDict(row) return d # TODO: we are letting these functions error out until the DB relations are refactored to support these delimiters def strFieldsToDict(fields_str, delims=(',', ':')): return dict( item.split(delims[1], maxsplit=1) for item in fields_str.split(delims[0]) ) def dictToStrFields(fields_dict, delims=(',', ':')): return delims[0].join( f'{k}{delims[1]}{v}' for k, v in fields_dict.items() ) def updateConfig(config_obj, field_dict, hot_reload=False): """ Update a python config module :param config_obj: :param field_dict: :return: """ config_file = "<no filepath available>" try: config_file = config_obj.__file__ with open(config_file, 'r+') as config: config_str = config.read() for key, val in field_dict.items(): regex = r"^(?!#)(?:" + re.escape(key) + \ r")[ \t]*=[ \t]*(?:\w+\(.*\)[ \t\v]*$|[\w\d\.]+[ \t]*$|\{.*\}|\[.*\][ \t]*$|\(.*\)[ \t]*$|b?\"\"\".*\"\"\"[ \t]*$|b?'''.*'''[ \v]*$|b?\".*\"[ \t]*$|b?'.*')" replace_str = "{} = {}".format(key, repr(val)) config_str = re.sub(regex, replace_str, config_str, flags=re.MULTILINE) config.seek(0) config.write(config_str) config.truncate() if hot_reload: globals()[config_obj.__name__] = reload(config_obj) except: IO.logerr('Problem updating the {0} configuration file'.format(config_file)) def stripDictVals(d): for key, val in d.items(): if isinstance(val, str): d[key] = val.strip() elif isinstance(val, int): d[key] = int(str(val).strip()) return d def getCustomRoutes(): """ Return custom kamailio routes from config file """ custom_routes = [] with open(settings.KAM_CFG_PATH, 'rb') as kamcfg_file: kamcfg_bytes = kamcfg_file.read() regex = rb'''CUSTOM_ROUTING_START.*CUSTOM_ROUTING_END''' custom_routes_bytes = re.search(regex, kamcfg_bytes, flags=re.MULTILINE | re.DOTALL).group(0) regex = rb'''^route\[(\w+)\]''' matches = re.finditer(regex, custom_routes_bytes, flags=re.MULTILINE) for matchnum, match in enumerate(matches): if len(match.groups()) > 0: custom_routes.append(match.group(1).decode('utf-8')) return custom_routes def monthdelta(dt, delta): """ Return dt with a delta month change (neg or pos) """ m, y = (dt.month + delta) % 12, dt.year + (dt.month + delta - 1) // 12 if m == 0: m = 12 d = min(dt.day, monthrange(y, m)[1]) return dt.replace(day=d, month=m, year=y) # modified method from Python cookbook, #475186 def supportsColor(stream): """ Return True if terminal supports ASCII color codes """ if not hasattr(stream, "isatty") or not stream.isatty(): # auto color only on TTYs return False try: import curses curses.setupterm() return curses.tigetnum("colors") > 2 except: # guess false in case of error return False class IO(): """ Contains static methods for handling i/o operations """ if supportsColor(sys.stdout): @staticmethod def printerr(message): print('\x1b[1;31m' + str(message).strip() + '\x1b[0m') @staticmethod def printinfo(message): print('\x1b[1;32m' + str(message).strip() + '\x1b[0m') @staticmethod def printwarn(message): print('\x1b[1;33m' + str(message).strip() + '\x1b[0m') @staticmethod def printdbg(message): print('\x1b[1;34m' + str(message).strip() + '\x1b[0m') @staticmethod def printbold(message): print('\x1b[1;37m' + str(message).strip() + '\x1b[0m') @staticmethod def logcrit(message): logging.getLogger().log(logging.CRITICAL, '\x1b[1;31m' + str(message).strip() + '\x1b[0m') @staticmethod def logerr(message): logging.getLogger().log(logging.ERROR, '\x1b[1;31m' + str(message).strip() + '\x1b[0m') @staticmethod def loginfo(message): logging.getLogger().log(logging.INFO, '\x1b[1;32m' + str(message).strip() + '\x1b[0m') @staticmethod def logwarn(message): logging.getLogger().log(logging.WARNING, '\x1b[1;33m' + str(message).strip() + '\x1b[0m') @staticmethod def logdbg(message): logging.getLogger().log(logging.DEBUG, '\x1b[1;34m' + str(message).strip() + '\x1b[0m') @staticmethod def lognolvl(message): logging.getLogger().log(logging.NOTSET, '\x1b[1;37m' + str(message).strip() + '\x1b[0m') else: @staticmethod def printerr(message): print(str(message).strip()) @staticmethod def printinfo(message): print(str(message).strip()) @staticmethod def printwarn(message): print(str(message).strip()) @staticmethod def printdbg(message): print(str(message).strip()) @staticmethod def printbold(message): print(str(message).strip()) @staticmethod def logcrit(message): logging.getLogger().log(logging.CRITICAL, str(message).strip()) @staticmethod def logerr(message): logging.getLogger().log(logging.ERROR, str(message).strip()) @staticmethod def loginfo(message): logging.getLogger().log(logging.INFO, str(message).strip()) @staticmethod def logwarn(message): logging.getLogger().log(logging.WARNING, str(message).strip()) @staticmethod def logdbg(message): logging.getLogger().log(logging.DEBUG, str(message).strip()) @staticmethod def lognolvl(message): logging.getLogger().log(logging.NOTSET, str(message).strip()) def debugException(ex=None, log_ex=True, print_ex=True, showstack=True): """ Debugging of an exception: print and/or log frame and/or stacktrace :param ex: The exception object :param log_ex: True | False :param print_ex: True | False :param showstack: True | False """ # get basic info and the stack exc_type, exc_value, exc_tb = sys.exc_info() text = "((( EXCEPTION )))\n[CLASS]: {}\n[VALUE]: {}\n".format(exc_type, exc_value) # get detailed exception info if ex is None: ex = exc_value for k, v in vars(ex).items(): text += "[{}]: {}\n".format(k.upper(), str(v)) # determine how far we trace it back tb_list = None if showstack: tb_list = traceback.extract_tb(exc_tb) else: tb_list = traceback.extract_tb(exc_tb, limit=1) # ensure a backtrace exists first if tb_list is not None and len(tb_list) > 0: text += "((( BACKTRACE )))\n" for tb_info in tb_list: filename, linenum, funcname, source = tb_info if funcname != '<module>': funcname = funcname + '()' text += "[FILE]: {}\n[LINE NUM]: {}\n[FUNCTION]: {}\n[SOURCE]: {}\n".format(filename, linenum, funcname, source) if log_ex: IO.logerr(text) if print_ex: IO.printerr(text) def debugEndpoint(log_out=True, print_out=True, **kwargs): """ Debug an endpoint\n Must be run within request context :param log_out: True | False :param print_out: True | False :param kwargs: Any args to print / log (<key=value> key word pairs) """ calling_chain = [] frame = sys._getframe().f_back if sys._getframe().f_back is not None else sys._getframe() # parent module if hasattr(frame.f_code, 'co_filename'): calling_chain.append(os.path.abspath(frame.f_code.co_filename)) # parent class if 'self' in frame.f_locals: calling_chain.append(frame.f_locals["self"].__class__) else: for k, v in frame.f_globals.items(): if not k.startswith('__') and frame.f_code.co_name in dir(v): calling_chain.append(k) break # parent func if frame.f_code.co_name != '<module>': calling_chain.append(frame.f_code.co_name) text = "((( [DEBUG ENDPOINT]: {} )))\n".format(' -> '.join(calling_chain)) text += '\n'.join(( '{}: {}'.format('accept_charsets', str(request.accept_charsets).strip()), '{}: {}'.format('accept_encodings', str(request.accept_encodings).strip()), '{}: {}'.format('accept_languages', str(request.accept_languages).strip()), '{}: {}'.format('accept_mimetypes', str(request.accept_mimetypes).strip()), '{}: {}'.format('access_control_request_headers', str(request.access_control_request_headers).strip()), '{}: {}'.format('access_control_request_method', str(request.access_control_request_method).strip()), '{}: {}'.format('access_route', str(request.access_route).strip()), '{}: {}'.format('args', str(request.args).strip()), '{}: {}'.format('authorization', str(request.authorization).strip()), '{}: {}'.format('base_url', str(request.base_url).strip()), '{}: {}'.format('blueprint', str(request.blueprint).strip()), '{}: {}'.format('cache_control', str(request.cache_control).strip()), '{}: {}'.format('charset', str(request.charset).strip()), '{}: {}'.format('content_encoding', str(request.content_encoding).strip()), '{}: {}'.format('content_length', str(request.content_length).strip()), '{}: {}'.format('content_md5', str(request.content_md5).strip()), '{}: {}'.format('content_type', str(request.content_type).strip()), '{}: {}'.format('cookies', str(request.cookies).strip()), '{}: {}'.format('files', str(request.files).strip()), '{}: {}'.format('form', str(request.form).strip()), '{}: {}'.format('headers', str(request.headers).strip()), '{}: {}'.format('json', str(request.get_json(force=True, silent=True)).strip()), '{}: {}'.format('method', str(request.method).strip()), '{}: {}'.format('query_string', str(request.query_string).strip()), '{}: {}'.format('referrer', str(request.referrer).strip()), '{}: {}'.format('remote_addr', str(request.remote_addr).strip()), '{}: {}'.format('remote_user', str(request.remote_user).strip()), '{}: {}'.format('url', str(request.url).strip()), '{}: {}'.format('user_agent', str(request.user_agent).strip()), '{}: {}'.format('values', str(request.values).strip()), '{}: {}'.format('view_args', str(request.view_args).strip()), )) if len(kwargs) > 0: for k, v in sorted(kwargs): text += "{}: {}\n".format(k, str(v).strip()) if log_out: IO.logdbg(text) if print_out: IO.printdbg(text) def allowed_file(filename, ALLOWED_EXTENSIONS={'csv', 'txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'}): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def showError(type="", code=None, msg=None): code = int(code) if code is not None else StatusCodes.HTTP_INTERNAL_SERVER_ERROR return render_template('error.html', type=type, msg=msg), code def redirectCustom(location, *render_args, code=302, response_cb=None, force_redirect=False): """ ======= Summary ======= Combines functionality of :func:`werkzeug.utils.redirect` with :func:`flask.templating.render_template` to allow for virtual redirection to an endpoint with any HTTP status code.\n ========= Use Cases ========= 1. render template at a custom url (doesn't have to be an endpoint) def index(): return redirectCustom('http://localhost:5000/notfound', render_template('not_found.html'), code=404) def index(): html = '<h1>Are you lost {{ username }}?</h1>' return redirectCustom(url_for('notfound'), render_template_string(html), username='john', code=404) 2. customizing the response before sending to the client def index(): def addAuthHeaders(response): response.headers['Access-Control-Allow-Origin' = '*' response.headers['Access-Control-Allow-Headers'] = 'origin, x-requested-with, content-type' response.headers['Access-Control-Allow-Methods'] = 'PUT, GET, POST, DELETE, OPTIONS' return redirectCustom(url_for('login'), render_template('login_custom.html'), code=403, response_cb=addAuthHeaders) 3. redirecting with a custom HTTP status code (normally restricted to 300's) def index(): return redirectCustom(url_for('http://localhost:5000/error'), showError()) 4. forcing redirection when client normally would ignore location header def index(): return redirectCustom(url_for(showError), showError(), code=500, force_redirect=True) :note: the endpoint logic is only executed when a the view function is passed in render_args :param location: the location the response should virtually redirect to :param render_args: the return value from a view / template rendering function :param code: the return HTTP status code, defaults to 302 like a normal redirect :param response_cb: callback function taking :class:`werkzeug.wrappers.Response` object as arg and returning edited response :param force_redirect: whether to force redirection on client, only needed when client ignores location header :return: client viewable :class:`werkzeug.wrappers.Response` object """ display_location = escape(location) if isinstance(location, str): # Safe conversion as stated in :func:`werkzeug.utils.redirect` location = iri_to_uri(location, safe_conversion=True) # create a response object, from rendered data (accepts None) response = make_response(*render_args) # if no render_args given fill response with default redirect html if len(render_args) <= 0: response.response = '<!DOCTYPE HTML">\n' \ '<html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0; URL={location}">' \ '<title>Redirecting</title></head>\n' \ '<body><script type="text/javascript">window.location.href={location};</script></body></html>' if force_redirect else \ '<body><h1>Redirecting...</h1>\n' \ '<p>You should be redirected automatically to target URL: ' \ '<a href="{location}">{display_location}</a>. If not click the link.</p></body></html>' \ .format(location=escape(location), display_location=display_location) response.mimetype = 'text/html' # customize response if needed if response_cb is not None: response = response_cb(response) # override return code if set from render args # if len(render_args) == 3: response.status = code # change response location response.headers['Location'] = location return response def getRequestData(): """ Get data from request formatted as dict\n Must be run within request context ----- The following data formats are supported: - multipart forms - url encoded forms - JSON body :return: request data :rtype: dict """ content_type = str.lower(request.headers.get('Content-Type', '')) if 'multipart/form-data' in content_type: data = request.form.to_dict(flat=False) elif 'application/x-www-form-urlencoded' in content_type: data = request.form.to_dict(flat=False) elif 'application/json' in content_type: data = request.get_json() else: data = request.get_json(force=True, silent=True) # fix data if client is sloppy (http_async_client) if request.headers.get('User-Agent') == 'http_async_client': data = json.loads(list(data)[0]) return data class StatusCodes(): """ Namespace for descriptive status codes, for code readability -- HTTP Status Codes -- Original Idea From: `flask_api <https://www.flaskapi.org/>`_ :ref RFC2616: http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html :ref RFC6585: http://tools.ietf.org/html/rfc6585 ... examples of possible future sections ... -- MYSQL Error Codes -- -- SERVER Status Codes -- -- KAMAILIO Error Codes -- -- RTPENGINE Error Codes -- """ HTTP_CONTINUE = 100 HTTP_SWITCHING_PROTOCOLS = 101 HTTP_OK = 200 HTTP_CREATED = 201 HTTP_ACCEPTED = 202 HTTP_NON_AUTHORITATIVE_INFORMATION = 203 HTTP_NO_CONTENT = 204 HTTP_RESET_CONTENT = 205 HTTP_PARTIAL_CONTENT = 206 HTTP_MULTI_STATUS = 207 HTTP_MULTIPLE_CHOICES = 300 HTTP_MOVED_PERMANENTLY = 301 HTTP_FOUND = 302 HTTP_SEE_OTHER = 303 HTTP_NOT_MODIFIED = 304 HTTP_USE_PROXY = 305 HTTP_RESERVED = 306 HTTP_TEMPORARY_REDIRECT = 307 HTTP_PERMANENT_REDIRECT = 308 HTTP_BAD_REQUEST = 400 HTTP_UNAUTHORIZED = 401 HTTP_PAYMENT_REQUIRED = 402 HTTP_FORBIDDEN = 403 HTTP_NOT_FOUND = 404 HTTP_METHOD_NOT_ALLOWED = 405 HTTP_NOT_ACCEPTABLE = 406 HTTP_PROXY_AUTHENTICATION_REQUIRED = 407 HTTP_REQUEST_TIMEOUT = 408 HTTP_CONFLICT = 409 HTTP_GONE = 410 HTTP_LENGTH_REQUIRED = 411 HTTP_PRECONDITION_FAILED = 412 HTTP_REQUEST_ENTITY_TOO_LARGE = 413 HTTP_REQUEST_URI_TOO_LONG = 414 HTTP_UNSUPPORTED_MEDIA_TYPE = 415 HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416 HTTP_EXPECTATION_FAILED = 417 HTTP_PRECONDITION_REQUIRED = 428 HTTP_TOO_MANY_REQUESTS = 429 HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431 HTTP_CONNECTION_CLOSED_WITHOUT_RESPONSE = 444 HTTP_INTERNAL_SERVER_ERROR = 500 HTTP_NOT_IMPLEMENTED = 501 HTTP_BAD_GATEWAY = 502 HTTP_SERVICE_UNAVAILABLE = 503 HTTP_GATEWAY_TIMEOUT = 504 HTTP_HTTP_VERSION_NOT_SUPPORTED = 505 HTTP_LOOP_DETECTED = 508 HTTP_NOT_EXTENDED = 510 HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511 ================================================ FILE: gui/static/css/bootstrap-theme.css ================================================ /*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ .btn-default, .btn-primary, .btn-success, .btn-info, .btn-warning, .btn-danger { text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); } .btn-default:active, .btn-primary:active, .btn-success:active, .btn-info:active, .btn-warning:active, .btn-danger:active, .btn-default.active, .btn-primary.active, .btn-success.active, .btn-info.active, .btn-warning.active, .btn-danger.active { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); } .btn-default.disabled, .btn-primary.disabled, .btn-success.disabled, .btn-info.disabled, .btn-warning.disabled, .btn-danger.disabled, .btn-default[disabled], .btn-primary[disabled], .btn-success[disabled], .btn-info[disabled], .btn-warning[disabled], .btn-danger[disabled], fieldset[disabled] .btn-default, fieldset[disabled] .btn-primary, fieldset[disabled] .btn-success, fieldset[disabled] .btn-info, fieldset[disabled] .btn-warning, fieldset[disabled] .btn-danger { -webkit-box-shadow: none; box-shadow: none; } .btn-default .badge, .btn-primary .badge, .btn-success .badge, .btn-info .badge, .btn-warning .badge, .btn-danger .badge { text-shadow: none; } .btn:active, .btn.active { background-image: none; } .btn-default { text-shadow: 0 1px 0 #fff; background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #dbdbdb; border-color: #ccc; } .btn-default:hover, .btn-default:focus { background-color: #e0e0e0; background-position: 0 -15px; } .btn-default:active, .btn-default.active { background-color: #e0e0e0; border-color: #dbdbdb; } .btn-default.disabled, .btn-default[disabled], fieldset[disabled] .btn-default, .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, .btn-default.disabled:focus, .btn-default[disabled]:focus, fieldset[disabled] .btn-default:focus, .btn-default.disabled.focus, .btn-default[disabled].focus, fieldset[disabled] .btn-default.focus, .btn-default.disabled:active, .btn-default[disabled]:active, fieldset[disabled] .btn-default:active, .btn-default.disabled.active, .btn-default[disabled].active, fieldset[disabled] .btn-default.active { background-color: #e0e0e0; background-image: none; } .btn-primary { background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #245580; } .btn-primary:hover, .btn-primary:focus { background-color: #265a88; background-position: 0 -15px; } .btn-primary:active, .btn-primary.active { background-color: #265a88; border-color: #245580; } .btn-primary.disabled, .btn-primary[disabled], fieldset[disabled] .btn-primary, .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled.focus, .btn-primary[disabled].focus, fieldset[disabled] .btn-primary.focus, .btn-primary.disabled:active, .btn-primary[disabled]:active, fieldset[disabled] .btn-primary:active, .btn-primary.disabled.active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary.active { background-color: #265a88; background-image: none; } .btn-success { background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #3e8f3e; } .btn-success:hover, .btn-success:focus { background-color: #419641; background-position: 0 -15px; } .btn-success:active, .btn-success.active { background-color: #419641; border-color: #3e8f3e; } .btn-success.disabled, .btn-success[disabled], fieldset[disabled] .btn-success, .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled.focus, .btn-success[disabled].focus, fieldset[disabled] .btn-success.focus, .btn-success.disabled:active, .btn-success[disabled]:active, fieldset[disabled] .btn-success:active, .btn-success.disabled.active, .btn-success[disabled].active, fieldset[disabled] .btn-success.active { background-color: #419641; background-image: none; } .btn-info { background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #28a4c9; } .btn-info:hover, .btn-info:focus { background-color: #2aabd2; background-position: 0 -15px; } .btn-info:active, .btn-info.active { background-color: #2aabd2; border-color: #28a4c9; } .btn-info.disabled, .btn-info[disabled], fieldset[disabled] .btn-info, .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, .btn-info.disabled:focus, .btn-info[disabled]:focus, fieldset[disabled] .btn-info:focus, .btn-info.disabled.focus, .btn-info[disabled].focus, fieldset[disabled] .btn-info.focus, .btn-info.disabled:active, .btn-info[disabled]:active, fieldset[disabled] .btn-info:active, .btn-info.disabled.active, .btn-info[disabled].active, fieldset[disabled] .btn-info.active { background-color: #2aabd2; background-image: none; } .btn-warning { background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #e38d13; } .btn-warning:hover, .btn-warning:focus { background-color: #eb9316; background-position: 0 -15px; } .btn-warning:active, .btn-warning.active { background-color: #eb9316; border-color: #e38d13; } .btn-warning.disabled, .btn-warning[disabled], fieldset[disabled] .btn-warning, .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, .btn-warning.disabled:focus, .btn-warning[disabled]:focus, fieldset[disabled] .btn-warning:focus, .btn-warning.disabled.focus, .btn-warning[disabled].focus, fieldset[disabled] .btn-warning.focus, .btn-warning.disabled:active, .btn-warning[disabled]:active, fieldset[disabled] .btn-warning:active, .btn-warning.disabled.active, .btn-warning[disabled].active, fieldset[disabled] .btn-warning.active { background-color: #eb9316; background-image: none; } .btn-danger { background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-color: #b92c28; } .btn-danger:hover, .btn-danger:focus { background-color: #c12e2a; background-position: 0 -15px; } .btn-danger:active, .btn-danger.active { background-color: #c12e2a; border-color: #b92c28; } .btn-danger.disabled, .btn-danger[disabled], fieldset[disabled] .btn-danger, .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, .btn-danger.disabled:focus, .btn-danger[disabled]:focus, fieldset[disabled] .btn-danger:focus, .btn-danger.disabled.focus, .btn-danger[disabled].focus, fieldset[disabled] .btn-danger.focus, .btn-danger.disabled:active, .btn-danger[disabled]:active, fieldset[disabled] .btn-danger:active, .btn-danger.disabled.active, .btn-danger[disabled].active, fieldset[disabled] .btn-danger.active { background-color: #c12e2a; background-image: none; } .thumbnail, .img-thumbnail { -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); box-shadow: 0 1px 2px rgba(0, 0, 0, .075); } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { background-color: #e8e8e8; background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); background-repeat: repeat-x; } .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { background-color: #2e6da4; background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); background-repeat: repeat-x; } .navbar-default { background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); } .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .active > a { background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); background-repeat: repeat-x; -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); } .navbar-brand, .navbar-nav > li > a { text-shadow: 0 1px 0 rgba(255, 255, 255, .25); } .navbar-inverse { background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); background-repeat: repeat-x; border-radius: 4px; } .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .active > a { background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); background-repeat: repeat-x; -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); } .navbar-inverse .navbar-brand, .navbar-inverse .navbar-nav > li > a { text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); } .navbar-static-top, .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; } @media (max-width: 767px) { .navbar .navbar-nav .open .dropdown-menu > .active > a, .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { color: #fff; background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); background-repeat: repeat-x; } } .alert { text-shadow: 0 1px 0 rgba(255, 255, 255, .2); -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); } .alert-success { background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); background-repeat: repeat-x; border-color: #b2dba1; } .alert-info { background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); background-repeat: repeat-x; border-color: #9acfea; } .alert-warning { background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); background-repeat: repeat-x; border-color: #f5e79e; } .alert-danger { background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); background-repeat: repeat-x; border-color: #dca7a7; } .progress { background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); background-repeat: repeat-x; } .progress-bar { background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); background-repeat: repeat-x; } .progress-bar-success { background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); background-repeat: repeat-x; } .progress-bar-info { background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); background-repeat: repeat-x; } .progress-bar-warning { background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); background-repeat: repeat-x; } .progress-bar-danger { background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); background-repeat: repeat-x; } .progress-bar-striped { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .list-group { border-radius: 4px; -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); box-shadow: 0 1px 2px rgba(0, 0, 0, .075); } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { text-shadow: 0 -1px 0 #286090; background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); background-repeat: repeat-x; border-color: #2b669a; } .list-group-item.active .badge, .list-group-item.active:hover .badge, .list-group-item.active:focus .badge { text-shadow: none; } .panel { -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); box-shadow: 0 1px 2px rgba(0, 0, 0, .05); } .panel-default > .panel-heading { background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); background-repeat: repeat-x; } .panel-primary > .panel-heading { background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); background-repeat: repeat-x; } .panel-success > .panel-heading { background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); background-repeat: repeat-x; } .panel-info > .panel-heading { background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); background-repeat: repeat-x; } .panel-warning > .panel-heading { background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); background-repeat: repeat-x; } .panel-danger > .panel-heading { background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); background-repeat: repeat-x; } .well { background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); background-repeat: repeat-x; border-color: #dcdcdc; -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); } /*# sourceMappingURL=bootstrap-theme.css.map */ ================================================ FILE: gui/static/css/bootstrap-toggle.css ================================================ /*! ======================================================================== * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 * http://www.bootstraptoggle.com * ======================================================================== * Copyright 2014 Min Hur, The New York Times Company * Licensed under MIT * ======================================================================== */ .checkbox label .toggle, .checkbox-inline .toggle { margin-left: -20px; margin-right: 5px; } .toggle { position: relative; overflow: hidden; } .toggle input[type="checkbox"] { display: none; } .toggle-group { position: absolute; width: 200%; top: 0; bottom: 0; left: 0; transition: left 0.35s; -webkit-transition: left 0.35s; -moz-user-select: none; -webkit-user-select: none; } .toggle.off .toggle-group { left: -100%; } .toggle-on { position: absolute; top: 0; bottom: 0; left: 0; right: 50%; margin: 0; border: 0; border-radius: 0; } .toggle-off { position: absolute; top: 0; bottom: 0; left: 50%; right: 0; margin: 0; border: 0; border-radius: 0; } .toggle-handle { position: relative; margin: 0 auto; padding-top: 0px; padding-bottom: 0px; height: 100%; width: 0px; border-width: 0 1px; } .toggle.btn { min-width: 59px; min-height: 34px; } .toggle-on.btn { padding-right: 24px; } .toggle-off.btn { padding-left: 24px; } .toggle.btn-lg { min-width: 79px; min-height: 45px; } .toggle-on.btn-lg { padding-right: 31px; } .toggle-off.btn-lg { padding-left: 31px; } .toggle-handle.btn-lg { width: 40px; } .toggle.btn-sm { min-width: 50px; min-height: 30px;} .toggle-on.btn-sm { padding-right: 20px; } .toggle-off.btn-sm { padding-left: 20px; } .toggle.btn-xs { min-width: 35px; min-height: 22px;} .toggle-on.btn-xs { padding-right: 12px; } .toggle-off.btn-xs { padding-left: 12px; } ================================================ FILE: gui/static/css/bootstrap.css ================================================ /*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ /*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ html { font-family: sans-serif; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } body { margin: 0; } article, aside, details, figcaption, figure, footer, header, hgroup, main, menu, nav, section, summary { display: block; } audio, canvas, progress, video { display: inline-block; vertical-align: baseline; } audio:not([controls]) { display: none; height: 0; } [hidden], template { display: none; } a { background-color: transparent; } a:active, a:hover { outline: 0; } abbr[title] { border-bottom: 1px dotted; } b, strong { font-weight: bold; } dfn { font-style: italic; } h1 { margin: .67em 0; font-size: 2em; } mark { color: #000; background: #ff0; } small { font-size: 80%; } sub, sup { position: relative; font-size: 75%; line-height: 0; vertical-align: baseline; } sup { top: -.5em; } sub { bottom: -.25em; } img { border: 0; } svg:not(:root) { overflow: hidden; } figure { margin: 1em 40px; } hr { height: 0; -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; } pre { overflow: auto; } code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } button, input, optgroup, select, textarea { margin: 0; font: inherit; color: inherit; } button { overflow: visible; } button, select { text-transform: none; } button, html input[type="button"], input[type="reset"], input[type="submit"] { -webkit-appearance: button; cursor: pointer; } button[disabled], html input[disabled] { cursor: default; } button::-moz-focus-inner, input::-moz-focus-inner { padding: 0; border: 0; } input { line-height: normal; } input[type="checkbox"], input[type="radio"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; padding: 0; } input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { height: auto; } input[type="search"] { -webkit-box-sizing: content-box; -moz-box-sizing: content-box; box-sizing: content-box; -webkit-appearance: textfield; } input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } fieldset { padding: .35em .625em .75em; margin: 0 2px; border: 1px solid #c0c0c0; } legend { padding: 0; border: 0; } textarea { overflow: auto; } optgroup { font-weight: bold; } table { border-spacing: 0; border-collapse: collapse; } td, th { padding: 0; } /*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ @media print { *, *:before, *:after { color: #000 !important; text-shadow: none !important; background: transparent !important; -webkit-box-shadow: none !important; box-shadow: none !important; } a, a:visited { text-decoration: underline; } a[href]:after { content: " (" attr(href) ")"; } abbr[title]:after { content: " (" attr(title) ")"; } a[href^="#"]:after, a[href^="javascript:"]:after { content: ""; } pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } thead { display: table-header-group; } tr, img { page-break-inside: avoid; } img { max-width: 100% !important; } p, h2, h3 { orphans: 3; widows: 3; } h2, h3 { page-break-after: avoid; } .navbar { display: none; } .btn > .caret, .dropup > .btn > .caret { border-top-color: #000 !important; } .label { border: 1px solid #000; } .table { border-collapse: collapse !important; } .table td, .table th { background-color: #fff !important; } .table-bordered th, .table-bordered td { border: 1px solid #ddd !important; } } @font-face { font-family: 'Glyphicons Halflings'; src: url('../fonts/glyphicons-halflings-regular.eot'); src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } .glyphicon { position: relative; top: 1px; display: inline-block; font-family: 'Glyphicons Halflings'; font-style: normal; font-weight: normal; line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .glyphicon-asterisk:before { content: "\002a"; } .glyphicon-plus:before { content: "\002b"; } .glyphicon-euro:before, .glyphicon-eur:before { content: "\20ac"; } .glyphicon-minus:before { content: "\2212"; } .glyphicon-cloud:before { content: "\2601"; } .glyphicon-envelope:before { content: "\2709"; } .glyphicon-pencil:before { content: "\270f"; } .glyphicon-glass:before { content: "\e001"; } .glyphicon-music:before { content: "\e002"; } .glyphicon-search:before { content: "\e003"; } .glyphicon-heart:before { content: "\e005"; } .glyphicon-star:before { content: "\e006"; } .glyphicon-star-empty:before { content: "\e007"; } .glyphicon-user:before { content: "\e008"; } .glyphicon-film:before { content: "\e009"; } .glyphicon-th-large:before { content: "\e010"; } .glyphicon-th:before { content: "\e011"; } .glyphicon-th-list:before { content: "\e012"; } .glyphicon-ok:before { content: "\e013"; } .glyphicon-remove:before { content: "\e014"; } .glyphicon-zoom-in:before { content: "\e015"; } .glyphicon-zoom-out:before { content: "\e016"; } .glyphicon-off:before { content: "\e017"; } .glyphicon-signal:before { content: "\e018"; } .glyphicon-cog:before { content: "\e019"; } .glyphicon-trash:before { content: "\e020"; } .glyphicon-home:before { content: "\e021"; } .glyphicon-file:before { content: "\e022"; } .glyphicon-time:before { content: "\e023"; } .glyphicon-road:before { content: "\e024"; } .glyphicon-download-alt:before { content: "\e025"; } .glyphicon-download:before { content: "\e026"; } .glyphicon-upload:before { content: "\e027"; } .glyphicon-inbox:before { content: "\e028"; } .glyphicon-play-circle:before { content: "\e029"; } .glyphicon-repeat:before { content: "\e030"; } .glyphicon-refresh:before { content: "\e031"; } .glyphicon-list-alt:before { content: "\e032"; } .glyphicon-lock:before { content: "\e033"; } .glyphicon-flag:before { content: "\e034"; } .glyphicon-headphones:before { content: "\e035"; } .glyphicon-volume-off:before { content: "\e036"; } .glyphicon-volume-down:before { content: "\e037"; } .glyphicon-volume-up:before { content: "\e038"; } .glyphicon-qrcode:before { content: "\e039"; } .glyphicon-barcode:before { content: "\e040"; } .glyphicon-tag:before { content: "\e041"; } .glyphicon-tags:before { content: "\e042"; } .glyphicon-book:before { content: "\e043"; } .glyphicon-bookmark:before { content: "\e044"; } .glyphicon-print:before { content: "\e045"; } .glyphicon-camera:before { content: "\e046"; } .glyphicon-font:before { content: "\e047"; } .glyphicon-bold:before { content: "\e048"; } .glyphicon-italic:before { content: "\e049"; } .glyphicon-text-height:before { content: "\e050"; } .glyphicon-text-width:before { content: "\e051"; } .glyphicon-align-left:before { content: "\e052"; } .glyphicon-align-center:before { content: "\e053"; } .glyphicon-align-right:before { content: "\e054"; } .glyphicon-align-justify:before { content: "\e055"; } .glyphicon-list:before { content: "\e056"; } .glyphicon-indent-left:before { content: "\e057"; } .glyphicon-indent-right:before { content: "\e058"; } .glyphicon-facetime-video:before { content: "\e059"; } .glyphicon-picture:before { content: "\e060"; } .glyphicon-map-marker:before { content: "\e062"; } .glyphicon-adjust:before { content: "\e063"; } .glyphicon-tint:before { content: "\e064"; } .glyphicon-edit:before { content: "\e065"; } .glyphicon-share:before { content: "\e066"; } .glyphicon-check:before { content: "\e067"; } .glyphicon-move:before { content: "\e068"; } .glyphicon-step-backward:before { content: "\e069"; } .glyphicon-fast-backward:before { content: "\e070"; } .glyphicon-backward:before { content: "\e071"; } .glyphicon-play:before { content: "\e072"; } .glyphicon-pause:before { content: "\e073"; } .glyphicon-stop:before { content: "\e074"; } .glyphicon-forward:before { content: "\e075"; } .glyphicon-fast-forward:before { content: "\e076"; } .glyphicon-step-forward:before { content: "\e077"; } .glyphicon-eject:before { content: "\e078"; } .glyphicon-chevron-left:before { content: "\e079"; } .glyphicon-chevron-right:before { content: "\e080"; } .glyphicon-plus-sign:before { content: "\e081"; } .glyphicon-minus-sign:before { content: "\e082"; } .glyphicon-remove-sign:before { content: "\e083"; } .glyphicon-ok-sign:before { content: "\e084"; } .glyphicon-question-sign:before { content: "\e085"; } .glyphicon-info-sign:before { content: "\e086"; } .glyphicon-screenshot:before { content: "\e087"; } .glyphicon-remove-circle:before { content: "\e088"; } .glyphicon-ok-circle:before { content: "\e089"; } .glyphicon-ban-circle:before { content: "\e090"; } .glyphicon-arrow-left:before { content: "\e091"; } .glyphicon-arrow-right:before { content: "\e092"; } .glyphicon-arrow-up:before { content: "\e093"; } .glyphicon-arrow-down:before { content: "\e094"; } .glyphicon-share-alt:before { content: "\e095"; } .glyphicon-resize-full:before { content: "\e096"; } .glyphicon-resize-small:before { content: "\e097"; } .glyphicon-exclamation-sign:before { content: "\e101"; } .glyphicon-gift:before { content: "\e102"; } .glyphicon-leaf:before { content: "\e103"; } .glyphicon-fire:before { content: "\e104"; } .glyphicon-eye-open:before { content: "\e105"; } .glyphicon-eye-close:before { content: "\e106"; } .glyphicon-warning-sign:before { content: "\e107"; } .glyphicon-plane:before { content: "\e108"; } .glyphicon-calendar:before { content: "\e109"; } .glyphicon-random:before { content: "\e110"; } .glyphicon-comment:before { content: "\e111"; } .glyphicon-magnet:before { content: "\e112"; } .glyphicon-chevron-up:before { content: "\e113"; } .glyphicon-chevron-down:before { content: "\e114"; } .glyphicon-retweet:before { content: "\e115"; } .glyphicon-shopping-cart:before { content: "\e116"; } .glyphicon-folder-close:before { content: "\e117"; } .glyphicon-folder-open:before { content: "\e118"; } .glyphicon-resize-vertical:before { content: "\e119"; } .glyphicon-resize-horizontal:before { content: "\e120"; } .glyphicon-hdd:before { content: "\e121"; } .glyphicon-bullhorn:before { content: "\e122"; } .glyphicon-bell:before { content: "\e123"; } .glyphicon-certificate:before { content: "\e124"; } .glyphicon-thumbs-up:before { content: "\e125"; } .glyphicon-thumbs-down:before { content: "\e126"; } .glyphicon-hand-right:before { content: "\e127"; } .glyphicon-hand-left:before { content: "\e128"; } .glyphicon-hand-up:before { content: "\e129"; } .glyphicon-hand-down:before { content: "\e130"; } .glyphicon-circle-arrow-right:before { content: "\e131"; } .glyphicon-circle-arrow-left:before { content: "\e132"; } .glyphicon-circle-arrow-up:before { content: "\e133"; } .glyphicon-circle-arrow-down:before { content: "\e134"; } .glyphicon-globe:before { content: "\e135"; } .glyphicon-wrench:before { content: "\e136"; } .glyphicon-tasks:before { content: "\e137"; } .glyphicon-filter:before { content: "\e138"; } .glyphicon-briefcase:before { content: "\e139"; } .glyphicon-fullscreen:before { content: "\e140"; } .glyphicon-dashboard:before { content: "\e141"; } .glyphicon-paperclip:before { content: "\e142"; } .glyphicon-heart-empty:before { content: "\e143"; } .glyphicon-link:before { content: "\e144"; } .glyphicon-phone:before { content: "\e145"; } .glyphicon-pushpin:before { content: "\e146"; } .glyphicon-usd:before { content: "\e148"; } .glyphicon-gbp:before { content: "\e149"; } .glyphicon-sort:before { content: "\e150"; } .glyphicon-sort-by-alphabet:before { content: "\e151"; } .glyphicon-sort-by-alphabet-alt:before { content: "\e152"; } .glyphicon-sort-by-order:before { content: "\e153"; } .glyphicon-sort-by-order-alt:before { content: "\e154"; } .glyphicon-sort-by-attributes:before { content: "\e155"; } .glyphicon-sort-by-attributes-alt:before { content: "\e156"; } .glyphicon-unchecked:before { content: "\e157"; } .glyphicon-expand:before { content: "\e158"; } .glyphicon-collapse-down:before { content: "\e159"; } .glyphicon-collapse-up:before { content: "\e160"; } .glyphicon-log-in:before { content: "\e161"; } .glyphicon-flash:before { content: "\e162"; } .glyphicon-log-out:before { content: "\e163"; } .glyphicon-new-window:before { content: "\e164"; } .glyphicon-record:before { content: "\e165"; } .glyphicon-save:before { content: "\e166"; } .glyphicon-open:before { content: "\e167"; } .glyphicon-saved:before { content: "\e168"; } .glyphicon-import:before { content: "\e169"; } .glyphicon-export:before { content: "\e170"; } .glyphicon-send:before { content: "\e171"; } .glyphicon-floppy-disk:before { content: "\e172"; } .glyphicon-floppy-saved:before { content: "\e173"; } .glyphicon-floppy-remove:before { content: "\e174"; } .glyphicon-floppy-save:before { content: "\e175"; } .glyphicon-floppy-open:before { content: "\e176"; } .glyphicon-credit-card:before { content: "\e177"; } .glyphicon-transfer:before { content: "\e178"; } .glyphicon-cutlery:before { content: "\e179"; } .glyphicon-header:before { content: "\e180"; } .glyphicon-compressed:before { content: "\e181"; } .glyphicon-earphone:before { content: "\e182"; } .glyphicon-phone-alt:before { content: "\e183"; } .glyphicon-tower:before { content: "\e184"; } .glyphicon-stats:before { content: "\e185"; } .glyphicon-sd-video:before { content: "\e186"; } .glyphicon-hd-video:before { content: "\e187"; } .glyphicon-subtitles:before { content: "\e188"; } .glyphicon-sound-stereo:before { content: "\e189"; } .glyphicon-sound-dolby:before { content: "\e190"; } .glyphicon-sound-5-1:before { content: "\e191"; } .glyphicon-sound-6-1:before { content: "\e192"; } .glyphicon-sound-7-1:before { content: "\e193"; } .glyphicon-copyright-mark:before { content: "\e194"; } .glyphicon-registration-mark:before { content: "\e195"; } .glyphicon-cloud-download:before { content: "\e197"; } .glyphicon-cloud-upload:before { content: "\e198"; } .glyphicon-tree-conifer:before { content: "\e199"; } .glyphicon-tree-deciduous:before { content: "\e200"; } .glyphicon-cd:before { content: "\e201"; } .glyphicon-save-file:before { content: "\e202"; } .glyphicon-open-file:before { content: "\e203"; } .glyphicon-level-up:before { content: "\e204"; } .glyphicon-copy:before { content: "\e205"; } .glyphicon-paste:before { content: "\e206"; } .glyphicon-alert:before { content: "\e209"; } .glyphicon-equalizer:before { content: "\e210"; } .glyphicon-king:before { content: "\e211"; } .glyphicon-queen:before { content: "\e212"; } .glyphicon-pawn:before { content: "\e213"; } .glyphicon-bishop:before { content: "\e214"; } .glyphicon-knight:before { content: "\e215"; } .glyphicon-baby-formula:before { content: "\e216"; } .glyphicon-tent:before { content: "\26fa"; } .glyphicon-blackboard:before { content: "\e218"; } .glyphicon-bed:before { content: "\e219"; } .glyphicon-apple:before { content: "\f8ff"; } .glyphicon-erase:before { content: "\e221"; } .glyphicon-hourglass:before { content: "\231b"; } .glyphicon-lamp:before { content: "\e223"; } .glyphicon-duplicate:before { content: "\e224"; } .glyphicon-piggy-bank:before { content: "\e225"; } .glyphicon-scissors:before { content: "\e226"; } .glyphicon-bitcoin:before { content: "\e227"; } .glyphicon-btc:before { content: "\e227"; } .glyphicon-xbt:before { content: "\e227"; } .glyphicon-yen:before { content: "\00a5"; } .glyphicon-jpy:before { content: "\00a5"; } .glyphicon-ruble:before { content: "\20bd"; } .glyphicon-rub:before { content: "\20bd"; } .glyphicon-scale:before { content: "\e230"; } .glyphicon-ice-lolly:before { content: "\e231"; } .glyphicon-ice-lolly-tasted:before { content: "\e232"; } .glyphicon-education:before { content: "\e233"; } .glyphicon-option-horizontal:before { content: "\e234"; } .glyphicon-option-vertical:before { content: "\e235"; } .glyphicon-menu-hamburger:before { content: "\e236"; } .glyphicon-modal-window:before { content: "\e237"; } .glyphicon-oil:before { content: "\e238"; } .glyphicon-grain:before { content: "\e239"; } .glyphicon-sunglasses:before { content: "\e240"; } .glyphicon-text-size:before { content: "\e241"; } .glyphicon-text-color:before { content: "\e242"; } .glyphicon-text-background:before { content: "\e243"; } .glyphicon-object-align-top:before { content: "\e244"; } .glyphicon-object-align-bottom:before { content: "\e245"; } .glyphicon-object-align-horizontal:before { content: "\e246"; } .glyphicon-object-align-left:before { content: "\e247"; } .glyphicon-object-align-vertical:before { content: "\e248"; } .glyphicon-object-align-right:before { content: "\e249"; } .glyphicon-triangle-right:before { content: "\e250"; } .glyphicon-triangle-left:before { content: "\e251"; } .glyphicon-triangle-bottom:before { content: "\e252"; } .glyphicon-triangle-top:before { content: "\e253"; } .glyphicon-console:before { content: "\e254"; } .glyphicon-superscript:before { content: "\e255"; } .glyphicon-subscript:before { content: "\e256"; } .glyphicon-menu-left:before { content: "\e257"; } .glyphicon-menu-right:before { content: "\e258"; } .glyphicon-menu-down:before { content: "\e259"; } .glyphicon-menu-up:before { content: "\e260"; } * { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } *:before, *:after { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { font-size: 10px; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } body { font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; line-height: 1.42857143; color: #333; background-color: #fff; } input, button, select, textarea { font-family: inherit; font-size: inherit; line-height: inherit; } a { color: #337ab7; text-decoration: none; } a:hover, a:focus { color: #23527c; text-decoration: underline; } a:focus { outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } figure { margin: 0; } img { vertical-align: middle; } .img-responsive, .thumbnail > img, .thumbnail a > img, .carousel-inner > .item > img, .carousel-inner > .item > a > img { display: block; max-width: 100%; height: auto; } .img-rounded { border-radius: 6px; } .img-thumbnail { display: inline-block; max-width: 100%; height: auto; padding: 4px; line-height: 1.42857143; background-color: #fff; border: 1px solid #ddd; border-radius: 4px; -webkit-transition: all .2s ease-in-out; -o-transition: all .2s ease-in-out; transition: all .2s ease-in-out; } .img-circle { border-radius: 50%; } hr { margin-top: 20px; margin-bottom: 20px; border: 0; border-top: 1px solid #eee; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); border: 0; } .sr-only-focusable:active, .sr-only-focusable:focus { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; } [role="button"] { cursor: pointer; } h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; } h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small, h1 .small, h2 .small, h3 .small, h4 .small, h5 .small, h6 .small, .h1 .small, .h2 .small, .h3 .small, .h4 .small, .h5 .small, .h6 .small { font-weight: normal; line-height: 1; color: #777; } h1, .h1, h2, .h2, h3, .h3 { margin-top: 20px; margin-bottom: 10px; } h1 small, .h1 small, h2 small, .h2 small, h3 small, .h3 small, h1 .small, .h1 .small, h2 .small, .h2 .small, h3 .small, .h3 .small { font-size: 65%; } h4, .h4, h5, .h5, h6, .h6 { margin-top: 10px; margin-bottom: 10px; } h4 small, .h4 small, h5 small, .h5 small, h6 small, .h6 small, h4 .small, .h4 .small, h5 .small, .h5 .small, h6 .small, .h6 .small { font-size: 75%; } h1, .h1 { font-size: 36px; } h2, .h2 { font-size: 30px; } h3, .h3 { font-size: 24px; } h4, .h4 { font-size: 18px; } h5, .h5 { font-size: 14px; } h6, .h6 { font-size: 12px; } p { margin: 0 0 10px; } .lead { margin-bottom: 20px; font-size: 16px; font-weight: 300; line-height: 1.4; } @media (min-width: 768px) { .lead { font-size: 21px; } } small, .small { font-size: 85%; } mark, .mark { padding: .2em; background-color: #fcf8e3; } .text-left { text-align: left; } .text-right { text-align: right; } .text-center { text-align: center; } .text-justify { text-align: justify; } .text-nowrap { white-space: nowrap; } .text-lowercase { text-transform: lowercase; } .text-uppercase { text-transform: uppercase; } .text-capitalize { text-transform: capitalize; } .text-muted { color: #777; } .text-primary { color: #337ab7; } a.text-primary:hover, a.text-primary:focus { color: #286090; } .text-success { color: #3c763d; } a.text-success:hover, a.text-success:focus { color: #2b542c; } .text-info { color: #31708f; } a.text-info:hover, a.text-info:focus { color: #245269; } .text-warning { color: #8a6d3b; } a.text-warning:hover, a.text-warning:focus { color: #66512c; } .text-danger { color: #a94442; } a.text-danger:hover, a.text-danger:focus { color: #843534; } .bg-primary { color: #fff; background-color: #337ab7; } a.bg-primary:hover, a.bg-primary:focus { background-color: #286090; } .bg-success { background-color: #dff0d8; } a.bg-success:hover, a.bg-success:focus { background-color: #c1e2b3; } .bg-info { background-color: #d9edf7; } a.bg-info:hover, a.bg-info:focus { background-color: #afd9ee; } .bg-warning { background-color: #fcf8e3; } a.bg-warning:hover, a.bg-warning:focus { background-color: #f7ecb5; } .bg-danger { background-color: #f2dede; } a.bg-danger:hover, a.bg-danger:focus { background-color: #e4b9b9; } .page-header { padding-bottom: 9px; margin: 40px 0 20px; border-bottom: 1px solid #eee; } ul, ol { margin-top: 0; margin-bottom: 10px; } ul ul, ol ul, ul ol, ol ol { margin-bottom: 0; } .list-unstyled { padding-left: 0; list-style: none; } .list-inline { padding-left: 0; margin-left: -5px; list-style: none; } .list-inline > li { display: inline-block; padding-right: 5px; padding-left: 5px; } dl { margin-top: 0; margin-bottom: 20px; } dt, dd { line-height: 1.42857143; } dt { font-weight: bold; } dd { margin-left: 0; } @media (min-width: 768px) { .dl-horizontal dt { float: left; width: 160px; overflow: hidden; clear: left; text-align: right; text-overflow: ellipsis; white-space: nowrap; } .dl-horizontal dd { margin-left: 180px; } } abbr[title], abbr[data-original-title] { cursor: help; border-bottom: 1px dotted #777; } .initialism { font-size: 90%; text-transform: uppercase; } blockquote { padding: 10px 20px; margin: 0 0 20px; font-size: 17.5px; border-left: 5px solid #eee; } blockquote p:last-child, blockquote ul:last-child, blockquote ol:last-child { margin-bottom: 0; } blockquote footer, blockquote small, blockquote .small { display: block; font-size: 80%; line-height: 1.42857143; color: #777; } blockquote footer:before, blockquote small:before, blockquote .small:before { content: '\2014 \00A0'; } .blockquote-reverse, blockquote.pull-right { padding-right: 15px; padding-left: 0; text-align: right; border-right: 5px solid #eee; border-left: 0; } .blockquote-reverse footer:before, blockquote.pull-right footer:before, .blockquote-reverse small:before, blockquote.pull-right small:before, .blockquote-reverse .small:before, blockquote.pull-right .small:before { content: ''; } .blockquote-reverse footer:after, blockquote.pull-right footer:after, .blockquote-reverse small:after, blockquote.pull-right small:after, .blockquote-reverse .small:after, blockquote.pull-right .small:after { content: '\00A0 \2014'; } address { margin-bottom: 20px; font-style: normal; line-height: 1.42857143; } code, kbd, pre, samp { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } code { padding: 2px 4px; font-size: 90%; color: #c7254e; background-color: #f9f2f4; border-radius: 4px; } kbd { padding: 2px 4px; font-size: 90%; color: #fff; background-color: #333; border-radius: 3px; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); } kbd kbd { padding: 0; font-size: 100%; font-weight: bold; -webkit-box-shadow: none; box-shadow: none; } pre { display: block; padding: 9.5px; margin: 0 0 10px; font-size: 13px; line-height: 1.42857143; color: #333; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #ccc; border-radius: 4px; } pre code { padding: 0; font-size: inherit; color: inherit; white-space: pre-wrap; background-color: transparent; border-radius: 0; } .pre-scrollable { max-height: 340px; overflow-y: scroll; } .container { padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } @media (min-width: 768px) { .container { width: 750px; } } @media (min-width: 992px) { .container { width: 970px; } } @media (min-width: 1200px) { .container { width: 1170px; } } .container-fluid { padding-right: 15px; padding-left: 15px; margin-right: auto; margin-left: auto; } .row { margin-right: -15px; margin-left: -15px; } .col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { position: relative; min-height: 1px; padding-right: 15px; padding-left: 15px; } .col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { float: left; } .col-xs-12 { width: 100%; } .col-xs-11 { width: 91.66666667%; } .col-xs-10 { width: 83.33333333%; } .col-xs-9 { width: 75%; } .col-xs-8 { width: 66.66666667%; } .col-xs-7 { width: 58.33333333%; } .col-xs-6 { width: 50%; } .col-xs-5 { width: 41.66666667%; } .col-xs-4 { width: 33.33333333%; } .col-xs-3 { width: 25%; } .col-xs-2 { width: 16.66666667%; } .col-xs-1 { width: 8.33333333%; } .col-xs-pull-12 { right: 100%; } .col-xs-pull-11 { right: 91.66666667%; } .col-xs-pull-10 { right: 83.33333333%; } .col-xs-pull-9 { right: 75%; } .col-xs-pull-8 { right: 66.66666667%; } .col-xs-pull-7 { right: 58.33333333%; } .col-xs-pull-6 { right: 50%; } .col-xs-pull-5 { right: 41.66666667%; } .col-xs-pull-4 { right: 33.33333333%; } .col-xs-pull-3 { right: 25%; } .col-xs-pull-2 { right: 16.66666667%; } .col-xs-pull-1 { right: 8.33333333%; } .col-xs-pull-0 { right: auto; } .col-xs-push-12 { left: 100%; } .col-xs-push-11 { left: 91.66666667%; } .col-xs-push-10 { left: 83.33333333%; } .col-xs-push-9 { left: 75%; } .col-xs-push-8 { left: 66.66666667%; } .col-xs-push-7 { left: 58.33333333%; } .col-xs-push-6 { left: 50%; } .col-xs-push-5 { left: 41.66666667%; } .col-xs-push-4 { left: 33.33333333%; } .col-xs-push-3 { left: 25%; } .col-xs-push-2 { left: 16.66666667%; } .col-xs-push-1 { left: 8.33333333%; } .col-xs-push-0 { left: auto; } .col-xs-offset-12 { margin-left: 100%; } .col-xs-offset-11 { margin-left: 91.66666667%; } .col-xs-offset-10 { margin-left: 83.33333333%; } .col-xs-offset-9 { margin-left: 75%; } .col-xs-offset-8 { margin-left: 66.66666667%; } .col-xs-offset-7 { margin-left: 58.33333333%; } .col-xs-offset-6 { margin-left: 50%; } .col-xs-offset-5 { margin-left: 41.66666667%; } .col-xs-offset-4 { margin-left: 33.33333333%; } .col-xs-offset-3 { margin-left: 25%; } .col-xs-offset-2 { margin-left: 16.66666667%; } .col-xs-offset-1 { margin-left: 8.33333333%; } .col-xs-offset-0 { margin-left: 0; } @media (min-width: 768px) { .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { float: left; } .col-sm-12 { width: 100%; } .col-sm-11 { width: 91.66666667%; } .col-sm-10 { width: 83.33333333%; } .col-sm-9 { width: 75%; } .col-sm-8 { width: 66.66666667%; } .col-sm-7 { width: 58.33333333%; } .col-sm-6 { width: 50%; } .col-sm-5 { width: 41.66666667%; } .col-sm-4 { width: 33.33333333%; } .col-sm-3 { width: 25%; } .col-sm-2 { width: 16.66666667%; } .col-sm-1 { width: 8.33333333%; } .col-sm-pull-12 { right: 100%; } .col-sm-pull-11 { right: 91.66666667%; } .col-sm-pull-10 { right: 83.33333333%; } .col-sm-pull-9 { right: 75%; } .col-sm-pull-8 { right: 66.66666667%; } .col-sm-pull-7 { right: 58.33333333%; } .col-sm-pull-6 { right: 50%; } .col-sm-pull-5 { right: 41.66666667%; } .col-sm-pull-4 { right: 33.33333333%; } .col-sm-pull-3 { right: 25%; } .col-sm-pull-2 { right: 16.66666667%; } .col-sm-pull-1 { right: 8.33333333%; } .col-sm-pull-0 { right: auto; } .col-sm-push-12 { left: 100%; } .col-sm-push-11 { left: 91.66666667%; } .col-sm-push-10 { left: 83.33333333%; } .col-sm-push-9 { left: 75%; } .col-sm-push-8 { left: 66.66666667%; } .col-sm-push-7 { left: 58.33333333%; } .col-sm-push-6 { left: 50%; } .col-sm-push-5 { left: 41.66666667%; } .col-sm-push-4 { left: 33.33333333%; } .col-sm-push-3 { left: 25%; } .col-sm-push-2 { left: 16.66666667%; } .col-sm-push-1 { left: 8.33333333%; } .col-sm-push-0 { left: auto; } .col-sm-offset-12 { margin-left: 100%; } .col-sm-offset-11 { margin-left: 91.66666667%; } .col-sm-offset-10 { margin-left: 83.33333333%; } .col-sm-offset-9 { margin-left: 75%; } .col-sm-offset-8 { margin-left: 66.66666667%; } .col-sm-offset-7 { margin-left: 58.33333333%; } .col-sm-offset-6 { margin-left: 50%; } .col-sm-offset-5 { margin-left: 41.66666667%; } .col-sm-offset-4 { margin-left: 33.33333333%; } .col-sm-offset-3 { margin-left: 25%; } .col-sm-offset-2 { margin-left: 16.66666667%; } .col-sm-offset-1 { margin-left: 8.33333333%; } .col-sm-offset-0 { margin-left: 0; } } @media (min-width: 992px) { .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { float: left; } .col-md-12 { width: 100%; } .col-md-11 { width: 91.66666667%; } .col-md-10 { width: 83.33333333%; } .col-md-9 { width: 75%; } .col-md-8 { width: 66.66666667%; } .col-md-7 { width: 58.33333333%; } .col-md-6 { width: 50%; } .col-md-5 { width: 41.66666667%; } .col-md-4 { width: 33.33333333%; } .col-md-3 { width: 25%; } .col-md-2 { width: 16.66666667%; } .col-md-1 { width: 8.33333333%; } .col-md-pull-12 { right: 100%; } .col-md-pull-11 { right: 91.66666667%; } .col-md-pull-10 { right: 83.33333333%; } .col-md-pull-9 { right: 75%; } .col-md-pull-8 { right: 66.66666667%; } .col-md-pull-7 { right: 58.33333333%; } .col-md-pull-6 { right: 50%; } .col-md-pull-5 { right: 41.66666667%; } .col-md-pull-4 { right: 33.33333333%; } .col-md-pull-3 { right: 25%; } .col-md-pull-2 { right: 16.66666667%; } .col-md-pull-1 { right: 8.33333333%; } .col-md-pull-0 { right: auto; } .col-md-push-12 { left: 100%; } .col-md-push-11 { left: 91.66666667%; } .col-md-push-10 { left: 83.33333333%; } .col-md-push-9 { left: 75%; } .col-md-push-8 { left: 66.66666667%; } .col-md-push-7 { left: 58.33333333%; } .col-md-push-6 { left: 50%; } .col-md-push-5 { left: 41.66666667%; } .col-md-push-4 { left: 33.33333333%; } .col-md-push-3 { left: 25%; } .col-md-push-2 { left: 16.66666667%; } .col-md-push-1 { left: 8.33333333%; } .col-md-push-0 { left: auto; } .col-md-offset-12 { margin-left: 100%; } .col-md-offset-11 { margin-left: 91.66666667%; } .col-md-offset-10 { margin-left: 83.33333333%; } .col-md-offset-9 { margin-left: 75%; } .col-md-offset-8 { margin-left: 66.66666667%; } .col-md-offset-7 { margin-left: 58.33333333%; } .col-md-offset-6 { margin-left: 50%; } .col-md-offset-5 { margin-left: 41.66666667%; } .col-md-offset-4 { margin-left: 33.33333333%; } .col-md-offset-3 { margin-left: 25%; } .col-md-offset-2 { margin-left: 16.66666667%; } .col-md-offset-1 { margin-left: 8.33333333%; } .col-md-offset-0 { margin-left: 0; } } @media (min-width: 1200px) { .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { float: left; } .col-lg-12 { width: 100%; } .col-lg-11 { width: 91.66666667%; } .col-lg-10 { width: 83.33333333%; } .col-lg-9 { width: 75%; } .col-lg-8 { width: 66.66666667%; } .col-lg-7 { width: 58.33333333%; } .col-lg-6 { width: 50%; } .col-lg-5 { width: 41.66666667%; } .col-lg-4 { width: 33.33333333%; } .col-lg-3 { width: 25%; } .col-lg-2 { width: 16.66666667%; } .col-lg-1 { width: 8.33333333%; } .col-lg-pull-12 { right: 100%; } .col-lg-pull-11 { right: 91.66666667%; } .col-lg-pull-10 { right: 83.33333333%; } .col-lg-pull-9 { right: 75%; } .col-lg-pull-8 { right: 66.66666667%; } .col-lg-pull-7 { right: 58.33333333%; } .col-lg-pull-6 { right: 50%; } .col-lg-pull-5 { right: 41.66666667%; } .col-lg-pull-4 { right: 33.33333333%; } .col-lg-pull-3 { right: 25%; } .col-lg-pull-2 { right: 16.66666667%; } .col-lg-pull-1 { right: 8.33333333%; } .col-lg-pull-0 { right: auto; } .col-lg-push-12 { left: 100%; } .col-lg-push-11 { left: 91.66666667%; } .col-lg-push-10 { left: 83.33333333%; } .col-lg-push-9 { left: 75%; } .col-lg-push-8 { left: 66.66666667%; } .col-lg-push-7 { left: 58.33333333%; } .col-lg-push-6 { left: 50%; } .col-lg-push-5 { left: 41.66666667%; } .col-lg-push-4 { left: 33.33333333%; } .col-lg-push-3 { left: 25%; } .col-lg-push-2 { left: 16.66666667%; } .col-lg-push-1 { left: 8.33333333%; } .col-lg-push-0 { left: auto; } .col-lg-offset-12 { margin-left: 100%; } .col-lg-offset-11 { margin-left: 91.66666667%; } .col-lg-offset-10 { margin-left: 83.33333333%; } .col-lg-offset-9 { margin-left: 75%; } .col-lg-offset-8 { margin-left: 66.66666667%; } .col-lg-offset-7 { margin-left: 58.33333333%; } .col-lg-offset-6 { margin-left: 50%; } .col-lg-offset-5 { margin-left: 41.66666667%; } .col-lg-offset-4 { margin-left: 33.33333333%; } .col-lg-offset-3 { margin-left: 25%; } .col-lg-offset-2 { margin-left: 16.66666667%; } .col-lg-offset-1 { margin-left: 8.33333333%; } .col-lg-offset-0 { margin-left: 0; } } table { background-color: transparent; } caption { padding-top: 8px; padding-bottom: 8px; color: #777; text-align: left; } th { text-align: left; } .table { width: 100%; max-width: 100%; margin-bottom: 20px; } .table > thead > tr > th, .table > tbody > tr > th, .table > tfoot > tr > th, .table > thead > tr > td, .table > tbody > tr > td, .table > tfoot > tr > td { padding: 8px; line-height: 1.42857143; vertical-align: top; border-top: 1px solid #ddd; } .table > thead > tr > th { vertical-align: bottom; border-bottom: 2px solid #ddd; } .table > caption + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > th, .table > thead:first-child > tr:first-child > th, .table > caption + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > td, .table > thead:first-child > tr:first-child > td { border-top: 0; } .table > tbody + tbody { border-top: 2px solid #ddd; } .table .table { background-color: #fff; } .table-condensed > thead > tr > th, .table-condensed > tbody > tr > th, .table-condensed > tfoot > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > td { padding: 5px; } .table-bordered { border: 1px solid #ddd; } .table-bordered > thead > tr > th, .table-bordered > tbody > tr > th, .table-bordered > tfoot > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > td { border: 1px solid #ddd; } .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { border-bottom-width: 2px; } .table-striped > tbody > tr:nth-of-type(odd) { background-color: #f9f9f9; } .table-hover > tbody > tr:hover { background-color: #f5f5f5; } table col[class*="col-"] { position: static; display: table-column; float: none; } table td[class*="col-"], table th[class*="col-"] { position: static; display: table-cell; float: none; } .table > thead > tr > td.active, .table > tbody > tr > td.active, .table > tfoot > tr > td.active, .table > thead > tr > th.active, .table > tbody > tr > th.active, .table > tfoot > tr > th.active, .table > thead > tr.active > td, .table > tbody > tr.active > td, .table > tfoot > tr.active > td, .table > thead > tr.active > th, .table > tbody > tr.active > th, .table > tfoot > tr.active > th { background-color: #f5f5f5; } .table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover, .table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr.active:hover > th { background-color: #e8e8e8; } .table > thead > tr > td.success, .table > tbody > tr > td.success, .table > tfoot > tr > td.success, .table > thead > tr > th.success, .table > tbody > tr > th.success, .table > tfoot > tr > th.success, .table > thead > tr.success > td, .table > tbody > tr.success > td, .table > tfoot > tr.success > td, .table > thead > tr.success > th, .table > tbody > tr.success > th, .table > tfoot > tr.success > th { background-color: #dff0d8; } .table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover, .table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr.success:hover > th { background-color: #d0e9c6; } .table > thead > tr > td.info, .table > tbody > tr > td.info, .table > tfoot > tr > td.info, .table > thead > tr > th.info, .table > tbody > tr > th.info, .table > tfoot > tr > th.info, .table > thead > tr.info > td, .table > tbody > tr.info > td, .table > tfoot > tr.info > td, .table > thead > tr.info > th, .table > tbody > tr.info > th, .table > tfoot > tr.info > th { background-color: #d9edf7; } .table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover, .table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr.info:hover > th { background-color: #c4e3f3; } .table > thead > tr > td.warning, .table > tbody > tr > td.warning, .table > tfoot > tr > td.warning, .table > thead > tr > th.warning, .table > tbody > tr > th.warning, .table > tfoot > tr > th.warning, .table > thead > tr.warning > td, .table > tbody > tr.warning > td, .table > tfoot > tr.warning > td, .table > thead > tr.warning > th, .table > tbody > tr.warning > th, .table > tfoot > tr.warning > th { background-color: #fcf8e3; } .table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover, .table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr.warning:hover > th { background-color: #faf2cc; } .table > thead > tr > td.danger, .table > tbody > tr > td.danger, .table > tfoot > tr > td.danger, .table > thead > tr > th.danger, .table > tbody > tr > th.danger, .table > tfoot > tr > th.danger, .table > thead > tr.danger > td, .table > tbody > tr.danger > td, .table > tfoot > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr.danger > th, .table > tfoot > tr.danger > th { background-color: #f2dede; } .table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover, .table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr.danger:hover > th { background-color: #ebcccc; } .table-responsive { min-height: .01%; overflow-x: auto; } @media screen and (max-width: 767px) { .table-responsive { width: 100%; margin-bottom: 15px; overflow-y: hidden; -ms-overflow-style: -ms-autohiding-scrollbar; border: 1px solid #ddd; } .table-responsive > .table { margin-bottom: 0; } .table-responsive > .table > thead > tr > th, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tfoot > tr > td { white-space: nowrap; } .table-responsive > .table-bordered { border: 0; } .table-responsive > .table-bordered > thead > tr > th:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child { border-left: 0; } .table-responsive > .table-bordered > thead > tr > th:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > th, .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > td { border-bottom: 0; } } fieldset { min-width: 0; padding: 0; margin: 0; border: 0; } legend { display: block; width: 100%; padding: 0; margin-bottom: 20px; font-size: 21px; line-height: inherit; color: #333; border: 0; border-bottom: 1px solid #e5e5e5; } label { display: inline-block; max-width: 100%; margin-bottom: 5px; font-weight: bold; } input[type="search"] { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } input[type="radio"], input[type="checkbox"] { margin: 4px 0 0; margin-top: 1px \9; line-height: normal; } input[type="file"] { display: block; } input[type="range"] { display: block; width: 100%; } select[multiple], select[size] { height: auto; } input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } output { display: block; padding-top: 7px; font-size: 14px; line-height: 1.42857143; color: #555; } .form-control { display: block; width: 100%; height: 34px; padding: 6px 12px; font-size: 14px; line-height: 1.42857143; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; } .form-control:focus { border-color: #66afe9; outline: 0; -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); } .form-control::-moz-placeholder { color: #999; opacity: 1; } .form-control:-ms-input-placeholder { color: #999; } .form-control::-webkit-input-placeholder { color: #999; } .form-control::-ms-expand { background-color: transparent; border: 0; } .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { background-color: #eee; opacity: 1; } .form-control[disabled], fieldset[disabled] .form-control { cursor: not-allowed; } textarea.form-control { height: auto; } input[type="search"] { -webkit-appearance: none; } @media screen and (-webkit-min-device-pixel-ratio: 0) { input[type="date"].form-control, input[type="time"].form-control, input[type="datetime-local"].form-control, input[type="month"].form-control { line-height: 34px; } input[type="date"].input-sm, input[type="time"].input-sm, input[type="datetime-local"].input-sm, input[type="month"].input-sm, .input-group-sm input[type="date"], .input-group-sm input[type="time"], .input-group-sm input[type="datetime-local"], .input-group-sm input[type="month"] { line-height: 30px; } input[type="date"].input-lg, input[type="time"].input-lg, input[type="datetime-local"].input-lg, input[type="month"].input-lg, .input-group-lg input[type="date"], .input-group-lg input[type="time"], .input-group-lg input[type="datetime-local"], .input-group-lg input[type="month"] { line-height: 46px; } } .form-group { margin-bottom: 15px; } .radio, .checkbox { position: relative; display: block; margin-top: 10px; margin-bottom: 10px; } .radio label, .checkbox label { min-height: 20px; padding-left: 20px; margin-bottom: 0; font-weight: normal; cursor: pointer; } .radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { position: absolute; margin-top: 4px \9; margin-left: -20px; } .radio + .radio, .checkbox + .checkbox { margin-top: -5px; } .radio-inline, .checkbox-inline { position: relative; display: inline-block; padding-left: 20px; margin-bottom: 0; font-weight: normal; vertical-align: middle; cursor: pointer; } .radio-inline + .radio-inline, .checkbox-inline + .checkbox-inline { margin-top: 0; margin-left: 10px; } input[type="radio"][disabled], input[type="checkbox"][disabled], input[type="radio"].disabled, input[type="checkbox"].disabled, fieldset[disabled] input[type="radio"], fieldset[disabled] input[type="checkbox"] { cursor: not-allowed; } .radio-inline.disabled, .checkbox-inline.disabled, fieldset[disabled] .radio-inline, fieldset[disabled] .checkbox-inline { cursor: not-allowed; } .radio.disabled label, .checkbox.disabled label, fieldset[disabled] .radio label, fieldset[disabled] .checkbox label { cursor: not-allowed; } .form-control-static { min-height: 34px; padding-top: 7px; padding-bottom: 7px; margin-bottom: 0; } .form-control-static.input-lg, .form-control-static.input-sm { padding-right: 0; padding-left: 0; } .input-sm { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-sm { height: 30px; line-height: 30px; } textarea.input-sm, select[multiple].input-sm { height: auto; } .form-group-sm .form-control { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .form-group-sm select.form-control { height: 30px; line-height: 30px; } .form-group-sm textarea.form-control, .form-group-sm select[multiple].form-control { height: auto; } .form-group-sm .form-control-static { height: 30px; min-height: 32px; padding: 6px 10px; font-size: 12px; line-height: 1.5; } .input-lg { height: 46px; padding: 10px 16px; font-size: 18px; line-height: 1.3333333; border-radius: 6px; } select.input-lg { height: 46px; line-height: 46px; } textarea.input-lg, select[multiple].input-lg { height: auto; } .form-group-lg .form-control { height: 46px; padding: 10px 16px; font-size: 18px; line-height: 1.3333333; border-radius: 6px; } .form-group-lg select.form-control { height: 46px; line-height: 46px; } .form-group-lg textarea.form-control, .form-group-lg select[multiple].form-control { height: auto; } .form-group-lg .form-control-static { height: 46px; min-height: 38px; padding: 11px 16px; font-size: 18px; line-height: 1.3333333; } .has-feedback { position: relative; } .has-feedback .form-control { padding-right: 42.5px; } .form-control-feedback { position: absolute; top: 0; right: 0; z-index: 2; display: block; width: 34px; height: 34px; line-height: 34px; text-align: center; pointer-events: none; } .input-lg + .form-control-feedback, .input-group-lg + .form-control-feedback, .form-group-lg .form-control + .form-control-feedback { width: 46px; height: 46px; line-height: 46px; } .input-sm + .form-control-feedback, .input-group-sm + .form-control-feedback, .form-group-sm .form-control + .form-control-feedback { width: 30px; height: 30px; line-height: 30px; } .has-success .help-block, .has-success .control-label, .has-success .radio, .has-success .checkbox, .has-success .radio-inline, .has-success .checkbox-inline, .has-success.radio label, .has-success.checkbox label, .has-success.radio-inline label, .has-success.checkbox-inline label { color: #3c763d; } .has-success .form-control { border-color: #3c763d; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } .has-success .form-control:focus { border-color: #2b542c; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; } .has-success .input-group-addon { color: #3c763d; background-color: #dff0d8; border-color: #3c763d; } .has-success .form-control-feedback { color: #3c763d; } .has-warning .help-block, .has-warning .control-label, .has-warning .radio, .has-warning .checkbox, .has-warning .radio-inline, .has-warning .checkbox-inline, .has-warning.radio label, .has-warning.checkbox label, .has-warning.radio-inline label, .has-warning.checkbox-inline label { color: #8a6d3b; } .has-warning .form-control { border-color: #8a6d3b; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } .has-warning .form-control:focus { border-color: #66512c; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; } .has-warning .input-group-addon { color: #8a6d3b; background-color: #fcf8e3; border-color: #8a6d3b; } .has-warning .form-control-feedback { color: #8a6d3b; } .has-error .help-block, .has-error .control-label, .has-error .radio, .has-error .checkbox, .has-error .radio-inline, .has-error .checkbox-inline, .has-error.radio label, .has-error.checkbox label, .has-error.radio-inline label, .has-error.checkbox-inline label { color: #a94442; } .has-error .form-control { border-color: #a94442; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); } .has-error .form-control:focus { border-color: #843534; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; } .has-error .input-group-addon { color: #a94442; background-color: #f2dede; border-color: #a94442; } .has-error .form-control-feedback { color: #a94442; } .has-feedback label ~ .form-control-feedback { top: 25px; } .has-feedback label.sr-only ~ .form-control-feedback { top: 0; } .help-block { display: block; margin-top: 5px; margin-bottom: 10px; color: #737373; } @media (min-width: 768px) { .form-inline .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .form-inline .form-control { display: inline-block; width: auto; vertical-align: middle; } .form-inline .form-control-static { display: inline-block; } .form-inline .input-group { display: inline-table; vertical-align: middle; } .form-inline .input-group .input-group-addon, .form-inline .input-group .input-group-btn, .form-inline .input-group .form-control { width: auto; } .form-inline .input-group > .form-control { width: 100%; } .form-inline .control-label { margin-bottom: 0; vertical-align: middle; } .form-inline .radio, .form-inline .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; vertical-align: middle; } .form-inline .radio label, .form-inline .checkbox label { padding-left: 0; } .form-inline .radio input[type="radio"], .form-inline .checkbox input[type="checkbox"] { position: relative; margin-left: 0; } .form-inline .has-feedback .form-control-feedback { top: 0; } } .form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline { padding-top: 7px; margin-top: 0; margin-bottom: 0; } .form-horizontal .radio, .form-horizontal .checkbox { min-height: 27px; } .form-horizontal .form-group { margin-right: -15px; margin-left: -15px; } @media (min-width: 768px) { .form-horizontal .control-label { padding-top: 7px; margin-bottom: 0; text-align: right; } } .form-horizontal .has-feedback .form-control-feedback { right: 15px; } @media (min-width: 768px) { .form-horizontal .form-group-lg .control-label { padding-top: 11px; font-size: 18px; } } @media (min-width: 768px) { .form-horizontal .form-group-sm .control-label { padding-top: 6px; font-size: 12px; } } .btn { display: inline-block; padding: 6px 12px; margin-bottom: 0; font-size: 14px; font-weight: normal; line-height: 1.42857143; text-align: center; white-space: nowrap; vertical-align: middle; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-image: none; border: 1px solid transparent; border-radius: 4px; } .btn:focus, .btn:active:focus, .btn.active:focus, .btn.focus, .btn:active.focus, .btn.active.focus { outline: 5px auto -webkit-focus-ring-color; outline-offset: -2px; } .btn:hover, .btn:focus, .btn.focus { color: #333; text-decoration: none; } .btn:active, .btn.active { background-image: none; outline: 0; -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); } .btn.disabled, .btn[disabled], fieldset[disabled] .btn { cursor: not-allowed; filter: alpha(opacity=65); -webkit-box-shadow: none; box-shadow: none; opacity: .65; } a.btn.disabled, fieldset[disabled] a.btn { pointer-events: none; } .btn-default { color: #333; background-color: #fff; border-color: #ccc; } .btn-default:focus, .btn-default.focus { color: #333; background-color: #e6e6e6; border-color: #8c8c8c; } .btn-default:hover { color: #333; background-color: #e6e6e6; border-color: #adadad; } .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { color: #333; background-color: #e6e6e6; border-color: #adadad; } .btn-default:active:hover, .btn-default.active:hover, .open > .dropdown-toggle.btn-default:hover, .btn-default:active:focus, .btn-default.active:focus, .open > .dropdown-toggle.btn-default:focus, .btn-default:active.focus, .btn-default.active.focus, .open > .dropdown-toggle.btn-default.focus { color: #333; background-color: #d4d4d4; border-color: #8c8c8c; } .btn-default:active, .btn-default.active, .open > .dropdown-toggle.btn-default { background-image: none; } .btn-default.disabled:hover, .btn-default[disabled]:hover, fieldset[disabled] .btn-default:hover, .btn-default.disabled:focus, .btn-default[disabled]:focus, fieldset[disabled] .btn-default:focus, .btn-default.disabled.focus, .btn-default[disabled].focus, fieldset[disabled] .btn-default.focus { background-color: #fff; border-color: #ccc; } .btn-default .badge { color: #fff; background-color: #333; } .btn-primary { color: #fff; background-color: #337ab7; border-color: #2e6da4; } .btn-primary:focus, .btn-primary.focus { color: #fff; background-color: #286090; border-color: #122b40; } .btn-primary:hover { color: #fff; background-color: #286090; border-color: #204d74; } .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { color: #fff; background-color: #286090; border-color: #204d74; } .btn-primary:active:hover, .btn-primary.active:hover, .open > .dropdown-toggle.btn-primary:hover, .btn-primary:active:focus, .btn-primary.active:focus, .open > .dropdown-toggle.btn-primary:focus, .btn-primary:active.focus, .btn-primary.active.focus, .open > .dropdown-toggle.btn-primary.focus { color: #fff; background-color: #204d74; border-color: #122b40; } .btn-primary:active, .btn-primary.active, .open > .dropdown-toggle.btn-primary { background-image: none; } .btn-primary.disabled:hover, .btn-primary[disabled]:hover, fieldset[disabled] .btn-primary:hover, .btn-primary.disabled:focus, .btn-primary[disabled]:focus, fieldset[disabled] .btn-primary:focus, .btn-primary.disabled.focus, .btn-primary[disabled].focus, fieldset[disabled] .btn-primary.focus { background-color: #337ab7; border-color: #2e6da4; } .btn-primary .badge { color: #337ab7; background-color: #fff; } .btn-success { color: #fff; background-color: #5cb85c; border-color: #4cae4c; } .btn-success:focus, .btn-success.focus { color: #fff; background-color: #449d44; border-color: #255625; } .btn-success:hover { color: #fff; background-color: #449d44; border-color: #398439; } .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { color: #fff; background-color: #449d44; border-color: #398439; } .btn-success:active:hover, .btn-success.active:hover, .open > .dropdown-toggle.btn-success:hover, .btn-success:active:focus, .btn-success.active:focus, .open > .dropdown-toggle.btn-success:focus, .btn-success:active.focus, .btn-success.active.focus, .open > .dropdown-toggle.btn-success.focus { color: #fff; background-color: #398439; border-color: #255625; } .btn-success:active, .btn-success.active, .open > .dropdown-toggle.btn-success { background-image: none; } .btn-success.disabled:hover, .btn-success[disabled]:hover, fieldset[disabled] .btn-success:hover, .btn-success.disabled:focus, .btn-success[disabled]:focus, fieldset[disabled] .btn-success:focus, .btn-success.disabled.focus, .btn-success[disabled].focus, fieldset[disabled] .btn-success.focus { background-color: #5cb85c; border-color: #4cae4c; } .btn-success .badge { color: #5cb85c; background-color: #fff; } .btn-info { color: #fff; background-color: #5bc0de; border-color: #46b8da; } .btn-info:focus, .btn-info.focus { color: #fff; background-color: #31b0d5; border-color: #1b6d85; } .btn-info:hover { color: #fff; background-color: #31b0d5; border-color: #269abc; } .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { color: #fff; background-color: #31b0d5; border-color: #269abc; } .btn-info:active:hover, .btn-info.active:hover, .open > .dropdown-toggle.btn-info:hover, .btn-info:active:focus, .btn-info.active:focus, .open > .dropdown-toggle.btn-info:focus, .btn-info:active.focus, .btn-info.active.focus, .open > .dropdown-toggle.btn-info.focus { color: #fff; background-color: #269abc; border-color: #1b6d85; } .btn-info:active, .btn-info.active, .open > .dropdown-toggle.btn-info { background-image: none; } .btn-info.disabled:hover, .btn-info[disabled]:hover, fieldset[disabled] .btn-info:hover, .btn-info.disabled:focus, .btn-info[disabled]:focus, fieldset[disabled] .btn-info:focus, .btn-info.disabled.focus, .btn-info[disabled].focus, fieldset[disabled] .btn-info.focus { background-color: #5bc0de; border-color: #46b8da; } .btn-info .badge { color: #5bc0de; background-color: #fff; } .btn-warning { color: #fff; background-color: #f0ad4e; border-color: #eea236; } .btn-warning:focus, .btn-warning.focus { color: #fff; background-color: #ec971f; border-color: #985f0d; } .btn-warning:hover { color: #fff; background-color: #ec971f; border-color: #d58512; } .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { color: #fff; background-color: #ec971f; border-color: #d58512; } .btn-warning:active:hover, .btn-warning.active:hover, .open > .dropdown-toggle.btn-warning:hover, .btn-warning:active:focus, .btn-warning.active:focus, .open > .dropdown-toggle.btn-warning:focus, .btn-warning:active.focus, .btn-warning.active.focus, .open > .dropdown-toggle.btn-warning.focus { color: #fff; background-color: #d58512; border-color: #985f0d; } .btn-warning:active, .btn-warning.active, .open > .dropdown-toggle.btn-warning { background-image: none; } .btn-warning.disabled:hover, .btn-warning[disabled]:hover, fieldset[disabled] .btn-warning:hover, .btn-warning.disabled:focus, .btn-warning[disabled]:focus, fieldset[disabled] .btn-warning:focus, .btn-warning.disabled.focus, .btn-warning[disabled].focus, fieldset[disabled] .btn-warning.focus { background-color: #f0ad4e; border-color: #eea236; } .btn-warning .badge { color: #f0ad4e; background-color: #fff; } .btn-danger { color: #fff; background-color: #d9534f; border-color: #d43f3a; } .btn-danger:focus, .btn-danger.focus { color: #fff; background-color: #c9302c; border-color: #761c19; } .btn-danger:hover { color: #fff; background-color: #c9302c; border-color: #ac2925; } .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { color: #fff; background-color: #c9302c; border-color: #ac2925; } .btn-danger:active:hover, .btn-danger.active:hover, .open > .dropdown-toggle.btn-danger:hover, .btn-danger:active:focus, .btn-danger.active:focus, .open > .dropdown-toggle.btn-danger:focus, .btn-danger:active.focus, .btn-danger.active.focus, .open > .dropdown-toggle.btn-danger.focus { color: #fff; background-color: #ac2925; border-color: #761c19; } .btn-danger:active, .btn-danger.active, .open > .dropdown-toggle.btn-danger { background-image: none; } .btn-danger.disabled:hover, .btn-danger[disabled]:hover, fieldset[disabled] .btn-danger:hover, .btn-danger.disabled:focus, .btn-danger[disabled]:focus, fieldset[disabled] .btn-danger:focus, .btn-danger.disabled.focus, .btn-danger[disabled].focus, fieldset[disabled] .btn-danger.focus { background-color: #d9534f; border-color: #d43f3a; } .btn-danger .badge { color: #d9534f; background-color: #fff; } .btn-link { font-weight: normal; color: #337ab7; border-radius: 0; } .btn-link, .btn-link:active, .btn-link.active, .btn-link[disabled], fieldset[disabled] .btn-link { background-color: transparent; -webkit-box-shadow: none; box-shadow: none; } .btn-link, .btn-link:hover, .btn-link:focus, .btn-link:active { border-color: transparent; } .btn-link:hover, .btn-link:focus { color: #23527c; text-decoration: underline; background-color: transparent; } .btn-link[disabled]:hover, fieldset[disabled] .btn-link:hover, .btn-link[disabled]:focus, fieldset[disabled] .btn-link:focus { color: #777; text-decoration: none; } .btn-lg, .btn-group-lg > .btn { padding: 10px 16px; font-size: 18px; line-height: 1.3333333; border-radius: 6px; } .btn-sm, .btn-group-sm > .btn { padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-xs, .btn-group-xs > .btn { padding: 1px 5px; font-size: 12px; line-height: 1.5; border-radius: 3px; } .btn-block { display: block; width: 100%; } .btn-block + .btn-block { margin-top: 5px; } input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { width: 100%; } .fade { opacity: 0; -webkit-transition: opacity .15s linear; -o-transition: opacity .15s linear; transition: opacity .15s linear; } .fade.in { opacity: 1; } .collapse { display: none; } .collapse.in { display: block; } tr.collapse.in { display: table-row; } tbody.collapse.in { display: table-row-group; } .collapsing { position: relative; height: 0; overflow: hidden; -webkit-transition-timing-function: ease; -o-transition-timing-function: ease; transition-timing-function: ease; -webkit-transition-duration: .35s; -o-transition-duration: .35s; transition-duration: .35s; -webkit-transition-property: height, visibility; -o-transition-property: height, visibility; transition-property: height, visibility; } .caret { display: inline-block; width: 0; height: 0; margin-left: 2px; vertical-align: middle; border-top: 4px dashed; border-top: 4px solid \9; border-right: 4px solid transparent; border-left: 4px solid transparent; } .dropup, .dropdown { position: relative; } .dropdown-toggle:focus { outline: 0; } .dropdown-menu { position: absolute; top: 100%; left: 0; z-index: 1000; display: none; float: left; min-width: 160px; padding: 5px 0; margin: 2px 0 0; font-size: 14px; text-align: left; list-style: none; background-color: #fff; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, .15); border-radius: 4px; -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); box-shadow: 0 6px 12px rgba(0, 0, 0, .175); } .dropdown-menu.pull-right { right: 0; left: auto; } .dropdown-menu .divider { height: 1px; margin: 9px 0; overflow: hidden; background-color: #e5e5e5; } .dropdown-menu > li > a { display: block; padding: 3px 20px; clear: both; font-weight: normal; line-height: 1.42857143; color: #333; white-space: nowrap; } .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { color: #262626; text-decoration: none; background-color: #f5f5f5; } .dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { color: #fff; text-decoration: none; background-color: #337ab7; outline: 0; } .dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { color: #777; } .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { text-decoration: none; cursor: not-allowed; background-color: transparent; background-image: none; filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); } .open > .dropdown-menu { display: block; } .open > a { outline: 0; } .dropdown-menu-right { right: 0; left: auto; } .dropdown-menu-left { right: auto; left: 0; } .dropdown-header { display: block; padding: 3px 20px; font-size: 12px; line-height: 1.42857143; color: #777; white-space: nowrap; } .dropdown-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 990; } .pull-right > .dropdown-menu { right: 0; left: auto; } .dropup .caret, .navbar-fixed-bottom .dropdown .caret { content: ""; border-top: 0; border-bottom: 4px dashed; border-bottom: 4px solid \9; } .dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu { top: auto; bottom: 100%; margin-bottom: 2px; } @media (min-width: 768px) { .navbar-right .dropdown-menu { right: 0; left: auto; } .navbar-right .dropdown-menu-left { right: auto; left: 0; } } .btn-group, .btn-group-vertical { position: relative; display: inline-block; vertical-align: middle; } .btn-group > .btn, .btn-group-vertical > .btn { position: relative; float: left; } .btn-group > .btn:hover, .btn-group-vertical > .btn:hover, .btn-group > .btn:focus, .btn-group-vertical > .btn:focus, .btn-group > .btn:active, .btn-group-vertical > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn.active { z-index: 2; } .btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { margin-left: -1px; } .btn-toolbar { margin-left: -5px; } .btn-toolbar .btn, .btn-toolbar .btn-group, .btn-toolbar .input-group { float: left; } .btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group { margin-left: 5px; } .btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { border-radius: 0; } .btn-group > .btn:first-child { margin-left: 0; } .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { border-top-left-radius: 0; border-bottom-left-radius: 0; } .btn-group > .btn-group { float: left; } .btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { border-top-right-radius: 0; border-bottom-right-radius: 0; } .btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { border-top-left-radius: 0; border-bottom-left-radius: 0; } .btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { outline: 0; } .btn-group > .btn + .dropdown-toggle { padding-right: 8px; padding-left: 8px; } .btn-group > .btn-lg + .dropdown-toggle { padding-right: 12px; padding-left: 12px; } .btn-group.open .dropdown-toggle { -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); } .btn-group.open .dropdown-toggle.btn-link { -webkit-box-shadow: none; box-shadow: none; } .btn .caret { margin-left: 0; } .btn-lg .caret { border-width: 5px 5px 0; border-bottom-width: 0; } .dropup .btn-lg .caret { border-width: 0 5px 5px; } .btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn { display: block; float: none; width: 100%; max-width: 100%; } .btn-group-vertical > .btn-group > .btn { float: none; } .btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { margin-top: -1px; margin-left: 0; } .btn-group-vertical > .btn:not(:first-child):not(:last-child) { border-radius: 0; } .btn-group-vertical > .btn:first-child:not(:last-child) { border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn:last-child:not(:first-child) { border-top-left-radius: 0; border-top-right-radius: 0; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } .btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { border-radius: 0; } .btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { border-top-left-radius: 0; border-top-right-radius: 0; } .btn-group-justified { display: table; width: 100%; table-layout: fixed; border-collapse: separate; } .btn-group-justified > .btn, .btn-group-justified > .btn-group { display: table-cell; float: none; width: 1%; } .btn-group-justified > .btn-group .btn { width: 100%; } .btn-group-justified > .btn-group .dropdown-menu { left: auto; } [data-toggle="buttons"] > .btn input[type="radio"], [data-toggle="buttons"] > .btn-group > .btn input[type="radio"], [data-toggle="buttons"] > .btn input[type="checkbox"], [data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { position: absolute; clip: rect(0, 0, 0, 0); pointer-events: none; } .input-group { position: relative; display: table; border-collapse: separate; } .input-group[class*="col-"] { float: none; padding-right: 0; padding-left: 0; } .input-group .form-control { position: relative; z-index: 2; float: left; width: 100%; margin-bottom: 0; } .input-group .form-control:focus { z-index: 3; } .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { height: 46px; padding: 10px 16px; font-size: 18px; line-height: 1.3333333; border-radius: 6px; } select.input-group-lg > .form-control, select.input-group-lg > .input-group-addon, select.input-group-lg > .input-group-btn > .btn { height: 46px; line-height: 46px; } textarea.input-group-lg > .form-control, textarea.input-group-lg > .input-group-addon, textarea.input-group-lg > .input-group-btn > .btn, select[multiple].input-group-lg > .form-control, select[multiple].input-group-lg > .input-group-addon, select[multiple].input-group-lg > .input-group-btn > .btn { height: auto; } .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { height: 30px; padding: 5px 10px; font-size: 12px; line-height: 1.5; border-radius: 3px; } select.input-group-sm > .form-control, select.input-group-sm > .input-group-addon, select.input-group-sm > .input-group-btn > .btn { height: 30px; line-height: 30px; } textarea.input-group-sm > .form-control, textarea.input-group-sm > .input-group-addon, textarea.input-group-sm > .input-group-btn > .btn, select[multiple].input-group-sm > .form-control, select[multiple].input-group-sm > .input-group-addon, select[multiple].input-group-sm > .input-group-btn > .btn { height: auto; } .input-group-addon, .input-group-btn, .input-group .form-control { display: table-cell; } .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child), .input-group .form-control:not(:first-child):not(:last-child) { border-radius: 0; } .input-group-addon, .input-group-btn { width: 1%; white-space: nowrap; vertical-align: middle; } .input-group-addon { padding: 6px 12px; font-size: 14px; font-weight: normal; line-height: 1; color: #555; text-align: center; background-color: #eee; border: 1px solid #ccc; border-radius: 4px; } .input-group-addon.input-sm { padding: 5px 10px; font-size: 12px; border-radius: 3px; } .input-group-addon.input-lg { padding: 10px 16px; font-size: 18px; border-radius: 6px; } .input-group-addon input[type="radio"], .input-group-addon input[type="checkbox"] { margin-top: 0; } .input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { border-top-right-radius: 0; border-bottom-right-radius: 0; } .input-group-addon:first-child { border-right: 0; } .input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { border-top-left-radius: 0; border-bottom-left-radius: 0; } .input-group-addon:last-child { border-left: 0; } .input-group-btn { position: relative; font-size: 0; white-space: nowrap; } .input-group-btn > .btn { position: relative; } .input-group-btn > .btn + .btn { margin-left: -1px; } .input-group-btn > .btn:hover, .input-group-btn > .btn:focus, .input-group-btn > .btn:active { z-index: 2; } .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group { margin-right: -1px; } .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group { z-index: 2; margin-left: -1px; } .nav { padding-left: 0; margin-bottom: 0; list-style: none; } .nav > li { position: relative; display: block; } .nav > li > a { position: relative; display: block; padding: 10px 15px; } .nav > li > a:hover, .nav > li > a:focus { text-decoration: none; background-color: #eee; } .nav > li.disabled > a { color: #777; } .nav > li.disabled > a:hover, .nav > li.disabled > a:focus { color: #777; text-decoration: none; cursor: not-allowed; background-color: transparent; } .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { background-color: #eee; border-color: #337ab7; } .nav .nav-divider { height: 1px; margin: 9px 0; overflow: hidden; background-color: #e5e5e5; } .nav > li > a > img { max-width: none; } .nav-tabs { border-bottom: 1px solid #ddd; } .nav-tabs > li { float: left; margin-bottom: -1px; } .nav-tabs > li > a { margin-right: 2px; line-height: 1.42857143; border: 1px solid transparent; border-radius: 4px 4px 0 0; } .nav-tabs > li > a:hover { border-color: #eee #eee #ddd; } .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { color: #555; cursor: default; background-color: #fff; border: 1px solid #ddd; border-bottom-color: transparent; } .nav-tabs.nav-justified { width: 100%; border-bottom: 0; } .nav-tabs.nav-justified > li { float: none; } .nav-tabs.nav-justified > li > a { margin-bottom: 5px; text-align: center; } .nav-tabs.nav-justified > .dropdown .dropdown-menu { top: auto; left: auto; } @media (min-width: 768px) { .nav-tabs.nav-justified > li { display: table-cell; width: 1%; } .nav-tabs.nav-justified > li > a { margin-bottom: 0; } } .nav-tabs.nav-justified > li > a { margin-right: 0; border-radius: 4px; } .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { border: 1px solid #ddd; } @media (min-width: 768px) { .nav-tabs.nav-justified > li > a { border-bottom: 1px solid #ddd; border-radius: 4px 4px 0 0; } .nav-tabs.nav-justified > .active > a, .nav-tabs.nav-justified > .active > a:hover, .nav-tabs.nav-justified > .active > a:focus { border-bottom-color: #fff; } } .nav-pills > li { float: left; } .nav-pills > li > a { border-radius: 4px; } .nav-pills > li + li { margin-left: 2px; } .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { color: #fff; background-color: #337ab7; } .nav-stacked > li { float: none; } .nav-stacked > li + li { margin-top: 2px; margin-left: 0; } .nav-justified { width: 100%; } .nav-justified > li { float: none; } .nav-justified > li > a { margin-bottom: 5px; text-align: center; } .nav-justified > .dropdown .dropdown-menu { top: auto; left: auto; } @media (min-width: 768px) { .nav-justified > li { display: table-cell; width: 1%; } .nav-justified > li > a { margin-bottom: 0; } } .nav-tabs-justified { border-bottom: 0; } .nav-tabs-justified > li > a { margin-right: 0; border-radius: 4px; } .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { border: 1px solid #ddd; } @media (min-width: 768px) { .nav-tabs-justified > li > a { border-bottom: 1px solid #ddd; border-radius: 4px 4px 0 0; } .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > a:focus { border-bottom-color: #fff; } } .tab-content > .tab-pane { display: none; } .tab-content > .active { display: block; } .nav-tabs .dropdown-menu { margin-top: -1px; border-top-left-radius: 0; border-top-right-radius: 0; } .navbar { position: relative; min-height: 50px; margin-bottom: 20px; border: 1px solid transparent; } @media (min-width: 768px) { .navbar { border-radius: 4px; } } @media (min-width: 768px) { .navbar-header { float: left; } } .navbar-collapse { padding-right: 15px; padding-left: 15px; overflow-x: visible; -webkit-overflow-scrolling: touch; border-top: 1px solid transparent; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); } .navbar-collapse.in { overflow-y: auto; } @media (min-width: 768px) { .navbar-collapse { width: auto; border-top: 0; -webkit-box-shadow: none; box-shadow: none; } .navbar-collapse.collapse { display: block !important; height: auto !important; padding-bottom: 0; overflow: visible !important; } .navbar-collapse.in { overflow-y: visible; } .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { padding-right: 0; padding-left: 0; } } .navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { max-height: 340px; } @media (max-device-width: 480px) and (orientation: landscape) { .navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { max-height: 200px; } } .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-collapse { margin-right: -15px; margin-left: -15px; } @media (min-width: 768px) { .container > .navbar-header, .container-fluid > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-collapse { margin-right: 0; margin-left: 0; } } .navbar-static-top { z-index: 1000; border-width: 0 0 1px; } @media (min-width: 768px) { .navbar-static-top { border-radius: 0; } } .navbar-fixed-top, .navbar-fixed-bottom { position: fixed; right: 0; left: 0; z-index: 1030; } @media (min-width: 768px) { .navbar-fixed-top, .navbar-fixed-bottom { border-radius: 0; } } .navbar-fixed-top { top: 0; border-width: 0 0 1px; } .navbar-fixed-bottom { bottom: 0; margin-bottom: 0; border-width: 1px 0 0; } .navbar-brand { float: left; height: 50px; padding: 15px 15px; font-size: 18px; line-height: 20px; } .navbar-brand:hover, .navbar-brand:focus { text-decoration: none; } .navbar-brand > img { display: block; } @media (min-width: 768px) { .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { margin-left: -15px; } } .navbar-toggle { position: relative; float: right; padding: 9px 10px; margin-top: 8px; margin-right: 15px; margin-bottom: 8px; background-color: transparent; background-image: none; border: 1px solid transparent; border-radius: 4px; } .navbar-toggle:focus { outline: 0; } .navbar-toggle .icon-bar { display: block; width: 22px; height: 2px; border-radius: 1px; } .navbar-toggle .icon-bar + .icon-bar { margin-top: 4px; } @media (min-width: 768px) { .navbar-toggle { display: none; } } .navbar-nav { margin: 7.5px -15px; } .navbar-nav > li > a { padding-top: 10px; padding-bottom: 10px; line-height: 20px; } @media (max-width: 767px) { .navbar-nav .open .dropdown-menu { position: static; float: none; width: auto; margin-top: 0; background-color: transparent; border: 0; -webkit-box-shadow: none; box-shadow: none; } .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header { padding: 5px 15px 5px 25px; } .navbar-nav .open .dropdown-menu > li > a { line-height: 20px; } .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-nav .open .dropdown-menu > li > a:focus { background-image: none; } } @media (min-width: 768px) { .navbar-nav { float: left; margin: 0; } .navbar-nav > li { float: left; } .navbar-nav > li > a { padding-top: 15px; padding-bottom: 15px; } } .navbar-form { padding: 10px 15px; margin-top: 8px; margin-right: -15px; margin-bottom: 8px; margin-left: -15px; border-top: 1px solid transparent; border-bottom: 1px solid transparent; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); } @media (min-width: 768px) { .navbar-form .form-group { display: inline-block; margin-bottom: 0; vertical-align: middle; } .navbar-form .form-control { display: inline-block; width: auto; vertical-align: middle; } .navbar-form .form-control-static { display: inline-block; } .navbar-form .input-group { display: inline-table; vertical-align: middle; } .navbar-form .input-group .input-group-addon, .navbar-form .input-group .input-group-btn, .navbar-form .input-group .form-control { width: auto; } .navbar-form .input-group > .form-control { width: 100%; } .navbar-form .control-label { margin-bottom: 0; vertical-align: middle; } .navbar-form .radio, .navbar-form .checkbox { display: inline-block; margin-top: 0; margin-bottom: 0; vertical-align: middle; } .navbar-form .radio label, .navbar-form .checkbox label { padding-left: 0; } .navbar-form .radio input[type="radio"], .navbar-form .checkbox input[type="checkbox"] { position: relative; margin-left: 0; } .navbar-form .has-feedback .form-control-feedback { top: 0; } } @media (max-width: 767px) { .navbar-form .form-group { margin-bottom: 5px; } .navbar-form .form-group:last-child { margin-bottom: 0; } } @media (min-width: 768px) { .navbar-form { width: auto; padding-top: 0; padding-bottom: 0; margin-right: 0; margin-left: 0; border: 0; -webkit-box-shadow: none; box-shadow: none; } } .navbar-nav > li > .dropdown-menu { margin-top: 0; border-top-left-radius: 0; border-top-right-radius: 0; } .navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { margin-bottom: 0; border-top-left-radius: 4px; border-top-right-radius: 4px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; } .navbar-btn { margin-top: 8px; margin-bottom: 8px; } .navbar-btn.btn-sm { margin-top: 10px; margin-bottom: 10px; } .navbar-btn.btn-xs { margin-top: 14px; margin-bottom: 14px; } .navbar-text { margin-top: 15px; margin-bottom: 15px; } @media (min-width: 768px) { .navbar-text { float: left; margin-right: 15px; margin-left: 15px; } } @media (min-width: 768px) { .navbar-left { float: left !important; } .navbar-right { float: right !important; margin-right: -15px; } .navbar-right ~ .navbar-right { margin-right: 0; } } .navbar-default { background-color: #f8f8f8; border-color: #e7e7e7; } .navbar-default .navbar-brand { color: #777; } .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { color: #5e5e5e; background-color: transparent; } .navbar-default .navbar-text { color: #777; } .navbar-default .navbar-nav > li > a { color: #777; } .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { color: #333; background-color: transparent; } .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { color: #555; background-color: #e7e7e7; } .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, .navbar-default .navbar-nav > .disabled > a:focus { color: #ccc; background-color: transparent; } .navbar-default .navbar-toggle { border-color: #ddd; } .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { background-color: #ddd; } .navbar-default .navbar-toggle .icon-bar { background-color: #888; } .navbar-default .navbar-collapse, .navbar-default .navbar-form { border-color: #e7e7e7; } .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { color: #555; background-color: #e7e7e7; } @media (max-width: 767px) { .navbar-default .navbar-nav .open .dropdown-menu > li > a { color: #777; } .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { color: #333; background-color: transparent; } .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { color: #555; background-color: #e7e7e7; } .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #ccc; background-color: transparent; } } .navbar-default .navbar-link { color: #777; } .navbar-default .navbar-link:hover { color: #333; } .navbar-default .btn-link { color: #777; } .navbar-default .btn-link:hover, .navbar-default .btn-link:focus { color: #333; } .navbar-default .btn-link[disabled]:hover, fieldset[disabled] .navbar-default .btn-link:hover, .navbar-default .btn-link[disabled]:focus, fieldset[disabled] .navbar-default .btn-link:focus { color: #ccc; } .navbar-inverse { background-color: #222; border-color: #080808; } .navbar-inverse .navbar-brand { color: #9d9d9d; } .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { color: #fff; background-color: transparent; } .navbar-inverse .navbar-text { color: #9d9d9d; } .navbar-inverse .navbar-nav > li > a { color: #9d9d9d; } .navbar-inverse .navbar-nav > li > a:hover, .navbar-inverse .navbar-nav > li > a:focus { color: #fff; background-color: transparent; } .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { color: #fff; background-color: #080808; } .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:hover, .navbar-inverse .navbar-nav > .disabled > a:focus { color: #444; background-color: transparent; } .navbar-inverse .navbar-toggle { border-color: #333; } .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { background-color: #333; } .navbar-inverse .navbar-toggle .icon-bar { background-color: #fff; } .navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { border-color: #101010; } .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { color: #fff; background-color: #080808; } @media (max-width: 767px) { .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { border-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu .divider { background-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { color: #9d9d9d; } .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { color: #fff; background-color: transparent; } .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { color: #fff; background-color: #080808; } .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { color: #444; background-color: transparent; } } .navbar-inverse .navbar-link { color: #9d9d9d; } .navbar-inverse .navbar-link:hover { color: #fff; } .navbar-inverse .btn-link { color: #9d9d9d; } .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link:focus { color: #fff; } .navbar-inverse .btn-link[disabled]:hover, fieldset[disabled] .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link[disabled]:focus, fieldset[disabled] .navbar-inverse .btn-link:focus { color: #444; } .breadcrumb { padding: 8px 15px; margin-bottom: 20px; list-style: none; background-color: #f5f5f5; border-radius: 4px; } .breadcrumb > li { display: inline-block; } .breadcrumb > li + li:before { padding: 0 5px; color: #ccc; content: "/\00a0"; } .breadcrumb > .active { color: #777; } .pagination { display: inline-block; padding-left: 0; margin: 20px 0; border-radius: 4px; } .pagination > li { display: inline; } .pagination > li > a, .pagination > li > span { position: relative; float: left; padding: 6px 12px; margin-left: -1px; line-height: 1.42857143; color: #337ab7; text-decoration: none; background-color: #fff; border: 1px solid #ddd; } .pagination > li:first-child > a, .pagination > li:first-child > span { margin-left: 0; border-top-left-radius: 4px; border-bottom-left-radius: 4px; } .pagination > li:last-child > a, .pagination > li:last-child > span { border-top-right-radius: 4px; border-bottom-right-radius: 4px; } .pagination > li > a:hover, .pagination > li > span:hover, .pagination > li > a:focus, .pagination > li > span:focus { z-index: 2; color: #23527c; background-color: #eee; border-color: #ddd; } .pagination > .active > a, .pagination > .active > span, .pagination > .active > a:hover, .pagination > .active > span:hover, .pagination > .active > a:focus, .pagination > .active > span:focus { z-index: 3; color: #fff; cursor: default; background-color: #337ab7; border-color: #337ab7; } .pagination > .disabled > span, .pagination > .disabled > span:hover, .pagination > .disabled > span:focus, .pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { color: #777; cursor: not-allowed; background-color: #fff; border-color: #ddd; } .pagination-lg > li > a, .pagination-lg > li > span { padding: 10px 16px; font-size: 18px; line-height: 1.3333333; } .pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { border-top-left-radius: 6px; border-bottom-left-radius: 6px; } .pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span { border-top-right-radius: 6px; border-bottom-right-radius: 6px; } .pagination-sm > li > a, .pagination-sm > li > span { padding: 5px 10px; font-size: 12px; line-height: 1.5; } .pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { border-top-left-radius: 3px; border-bottom-left-radius: 3px; } .pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span { border-top-right-radius: 3px; border-bottom-right-radius: 3px; } .pager { padding-left: 0; margin: 20px 0; text-align: center; list-style: none; } .pager li { display: inline; } .pager li > a, .pager li > span { display: inline-block; padding: 5px 14px; background-color: #fff; border: 1px solid #ddd; border-radius: 15px; } .pager li > a:hover, .pager li > a:focus { text-decoration: none; background-color: #eee; } .pager .next > a, .pager .next > span { float: right; } .pager .previous > a, .pager .previous > span { float: left; } .pager .disabled > a, .pager .disabled > a:hover, .pager .disabled > a:focus, .pager .disabled > span { color: #777; cursor: not-allowed; background-color: #fff; } .label { display: inline; padding: .2em .6em .3em; font-size: 75%; font-weight: bold; line-height: 1; color: #fff; text-align: center; white-space: nowrap; vertical-align: baseline; border-radius: .25em; } a.label:hover, a.label:focus { color: #fff; text-decoration: none; cursor: pointer; } .label:empty { display: none; } .btn .label { position: relative; top: -1px; } .label-default { background-color: #777; } .label-default[href]:hover, .label-default[href]:focus { background-color: #5e5e5e; } .label-primary { background-color: #337ab7; } .label-primary[href]:hover, .label-primary[href]:focus { background-color: #286090; } .label-success { background-color: #5cb85c; } .label-success[href]:hover, .label-success[href]:focus { background-color: #449d44; } .label-info { background-color: #5bc0de; } .label-info[href]:hover, .label-info[href]:focus { background-color: #31b0d5; } .label-warning { background-color: #f0ad4e; } .label-warning[href]:hover, .label-warning[href]:focus { background-color: #ec971f; } .label-danger { background-color: #d9534f; } .label-danger[href]:hover, .label-danger[href]:focus { background-color: #c9302c; } .badge { display: inline-block; min-width: 10px; padding: 3px 7px; font-size: 12px; font-weight: bold; line-height: 1; color: #fff; text-align: center; white-space: nowrap; vertical-align: middle; background-color: #777; border-radius: 10px; } .badge:empty { display: none; } .btn .badge { position: relative; top: -1px; } .btn-xs .badge, .btn-group-xs > .btn .badge { top: 0; padding: 1px 5px; } a.badge:hover, a.badge:focus { color: #fff; text-decoration: none; cursor: pointer; } .list-group-item.active > .badge, .nav-pills > .active > a > .badge { color: #337ab7; background-color: #fff; } .list-group-item > .badge { float: right; } .list-group-item > .badge + .badge { margin-right: 5px; } .nav-pills > li > a > .badge { margin-left: 3px; } .jumbotron { padding-top: 30px; padding-bottom: 30px; margin-bottom: 30px; color: inherit; background-color: #eee; } .jumbotron h1, .jumbotron .h1 { color: inherit; } .jumbotron p { margin-bottom: 15px; font-size: 21px; font-weight: 200; } .jumbotron > hr { border-top-color: #d5d5d5; } .container .jumbotron, .container-fluid .jumbotron { padding-right: 15px; padding-left: 15px; border-radius: 6px; } .jumbotron .container { max-width: 100%; } @media screen and (min-width: 768px) { .jumbotron { padding-top: 48px; padding-bottom: 48px; } .container .jumbotron, .container-fluid .jumbotron { padding-right: 60px; padding-left: 60px; } .jumbotron h1, .jumbotron .h1 { font-size: 63px; } } .thumbnail { display: block; padding: 4px; margin-bottom: 20px; line-height: 1.42857143; background-color: #fff; border: 1px solid #ddd; border-radius: 4px; -webkit-transition: border .2s ease-in-out; -o-transition: border .2s ease-in-out; transition: border .2s ease-in-out; } .thumbnail > img, .thumbnail a > img { margin-right: auto; margin-left: auto; } a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active { border-color: #337ab7; } .thumbnail .caption { padding: 9px; color: #333; } .alert { padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; } .alert h4 { margin-top: 0; color: inherit; } .alert .alert-link { font-weight: bold; } .alert > p, .alert > ul { margin-bottom: 0; } .alert > p + p { margin-top: 5px; } .alert-dismissable, .alert-dismissible { padding-right: 35px; } .alert-dismissable .close, .alert-dismissible .close { position: relative; top: -2px; right: -21px; color: inherit; } .alert-success { color: #3c763d; background-color: #dff0d8; border-color: #d6e9c6; } .alert-success hr { border-top-color: #c9e2b3; } .alert-success .alert-link { color: #2b542c; } .alert-info { color: #31708f; background-color: #d9edf7; border-color: #bce8f1; } .alert-info hr { border-top-color: #a6e1ec; } .alert-info .alert-link { color: #245269; } .alert-warning { color: #8a6d3b; background-color: #fcf8e3; border-color: #faebcc; } .alert-warning hr { border-top-color: #f7e1b5; } .alert-warning .alert-link { color: #66512c; } .alert-danger { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .alert-danger hr { border-top-color: #e4b9c0; } .alert-danger .alert-link { color: #843534; } @-webkit-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @-o-keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } @keyframes progress-bar-stripes { from { background-position: 40px 0; } to { background-position: 0 0; } } .progress { height: 20px; margin-bottom: 20px; overflow: hidden; background-color: #f5f5f5; border-radius: 4px; -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); } .progress-bar { float: left; width: 0; height: 100%; font-size: 12px; line-height: 20px; color: #fff; text-align: center; background-color: #337ab7; -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); -webkit-transition: width .6s ease; -o-transition: width .6s ease; transition: width .6s ease; } .progress-striped .progress-bar, .progress-bar-striped { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); -webkit-background-size: 40px 40px; background-size: 40px 40px; } .progress.active .progress-bar, .progress-bar.active { -webkit-animation: progress-bar-stripes 2s linear infinite; -o-animation: progress-bar-stripes 2s linear infinite; animation: progress-bar-stripes 2s linear infinite; } .progress-bar-success { background-color: #5cb85c; } .progress-striped .progress-bar-success { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-info { background-color: #5bc0de; } .progress-striped .progress-bar-info { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-warning { background-color: #f0ad4e; } .progress-striped .progress-bar-warning { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .progress-bar-danger { background-color: #d9534f; } .progress-striped .progress-bar-danger { background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); } .media { margin-top: 15px; } .media:first-child { margin-top: 0; } .media, .media-body { overflow: hidden; zoom: 1; } .media-body { width: 10000px; } .media-object { display: block; } .media-object.img-thumbnail { max-width: none; } .media-right, .media > .pull-right { padding-left: 10px; } .media-left, .media > .pull-left { padding-right: 10px; } .media-left, .media-right, .media-body { display: table-cell; vertical-align: top; } .media-middle { vertical-align: middle; } .media-bottom { vertical-align: bottom; } .media-heading { margin-top: 0; margin-bottom: 5px; } .media-list { padding-left: 0; list-style: none; } .list-group { padding-left: 0; margin-bottom: 20px; } .list-group-item { position: relative; display: block; padding: 10px 15px; margin-bottom: -1px; background-color: #fff; border: 1px solid #ddd; } .list-group-item:first-child { border-top-left-radius: 4px; border-top-right-radius: 4px; } .list-group-item:last-child { margin-bottom: 0; border-bottom-right-radius: 4px; border-bottom-left-radius: 4px; } a.list-group-item, button.list-group-item { color: #555; } a.list-group-item .list-group-item-heading, button.list-group-item .list-group-item-heading { color: #333; } a.list-group-item:hover, button.list-group-item:hover, a.list-group-item:focus, button.list-group-item:focus { color: #555; text-decoration: none; background-color: #f5f5f5; } button.list-group-item { width: 100%; text-align: left; } .list-group-item.disabled, .list-group-item.disabled:hover, .list-group-item.disabled:focus { color: #777; cursor: not-allowed; background-color: #eee; } .list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading { color: inherit; } .list-group-item.disabled .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text { color: #777; } .list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { z-index: 2; color: #fff; background-color: #337ab7; border-color: #337ab7; } .list-group-item.active .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading, .list-group-item.active .list-group-item-heading > small, .list-group-item.active:hover .list-group-item-heading > small, .list-group-item.active:focus .list-group-item-heading > small, .list-group-item.active .list-group-item-heading > .small, .list-group-item.active:hover .list-group-item-heading > .small, .list-group-item.active:focus .list-group-item-heading > .small { color: inherit; } .list-group-item.active .list-group-item-text, .list-group-item.active:hover .list-group-item-text, .list-group-item.active:focus .list-group-item-text { color: #c7ddef; } .list-group-item-success { color: #3c763d; background-color: #dff0d8; } a.list-group-item-success, button.list-group-item-success { color: #3c763d; } a.list-group-item-success .list-group-item-heading, button.list-group-item-success .list-group-item-heading { color: inherit; } a.list-group-item-success:hover, button.list-group-item-success:hover, a.list-group-item-success:focus, button.list-group-item-success:focus { color: #3c763d; background-color: #d0e9c6; } a.list-group-item-success.active, button.list-group-item-success.active, a.list-group-item-success.active:hover, button.list-group-item-success.active:hover, a.list-group-item-success.active:focus, button.list-group-item-success.active:focus { color: #fff; background-color: #3c763d; border-color: #3c763d; } .list-group-item-info { color: #31708f; background-color: #d9edf7; } a.list-group-item-info, button.list-group-item-info { color: #31708f; } a.list-group-item-info .list-group-item-heading, button.list-group-item-info .list-group-item-heading { color: inherit; } a.list-group-item-info:hover, button.list-group-item-info:hover, a.list-group-item-info:focus, button.list-group-item-info:focus { color: #31708f; background-color: #c4e3f3; } a.list-group-item-info.active, button.list-group-item-info.active, a.list-group-item-info.active:hover, button.list-group-item-info.active:hover, a.list-group-item-info.active:focus, button.list-group-item-info.active:focus { color: #fff; background-color: #31708f; border-color: #31708f; } .list-group-item-warning { color: #8a6d3b; background-color: #fcf8e3; } a.list-group-item-warning, button.list-group-item-warning { color: #8a6d3b; } a.list-group-item-warning .list-group-item-heading, button.list-group-item-warning .list-group-item-heading { color: inherit; } a.list-group-item-warning:hover, button.list-group-item-warning:hover, a.list-group-item-warning:focus, button.list-group-item-warning:focus { color: #8a6d3b; background-color: #faf2cc; } a.list-group-item-warning.active, button.list-group-item-warning.active, a.list-group-item-warning.active:hover, button.list-group-item-warning.active:hover, a.list-group-item-warning.active:focus, button.list-group-item-warning.active:focus { color: #fff; background-color: #8a6d3b; border-color: #8a6d3b; } .list-group-item-danger { color: #a94442; background-color: #f2dede; } a.list-group-item-danger, button.list-group-item-danger { color: #a94442; } a.list-group-item-danger .list-group-item-heading, button.list-group-item-danger .list-group-item-heading { color: inherit; } a.list-group-item-danger:hover, button.list-group-item-danger:hover, a.list-group-item-danger:focus, button.list-group-item-danger:focus { color: #a94442; background-color: #ebcccc; } a.list-group-item-danger.active, button.list-group-item-danger.active, a.list-group-item-danger.active:hover, button.list-group-item-danger.active:hover, a.list-group-item-danger.active:focus, button.list-group-item-danger.active:focus { color: #fff; background-color: #a94442; border-color: #a94442; } .list-group-item-heading { margin-top: 0; margin-bottom: 5px; } .list-group-item-text { margin-bottom: 0; line-height: 1.3; } .panel { margin-bottom: 20px; background-color: #fff; border: 1px solid transparent; border-radius: 4px; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); box-shadow: 0 1px 1px rgba(0, 0, 0, .05); } .panel-body { padding: 15px; } .panel-heading { padding: 10px 15px; border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel-heading > .dropdown .dropdown-toggle { color: inherit; } .panel-title { margin-top: 0; margin-bottom: 0; font-size: 16px; color: inherit; } .panel-title > a, .panel-title > small, .panel-title > .small, .panel-title > small > a, .panel-title > .small > a { color: inherit; } .panel-footer { padding: 10px 15px; background-color: #f5f5f5; border-top: 1px solid #ddd; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel > .list-group, .panel > .panel-collapse > .list-group { margin-bottom: 0; } .panel > .list-group .list-group-item, .panel > .panel-collapse > .list-group .list-group-item { border-width: 1px 0; border-radius: 0; } .panel > .list-group:first-child .list-group-item:first-child, .panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { border-top: 0; border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel > .list-group:last-child .list-group-item:last-child, .panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { border-bottom: 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel > .panel-heading + .panel-collapse > .list-group .list-group-item:first-child { border-top-left-radius: 0; border-top-right-radius: 0; } .panel-heading + .list-group .list-group-item:first-child { border-top-width: 0; } .list-group + .panel-footer { border-top-width: 0; } .panel > .table, .panel > .table-responsive > .table, .panel > .panel-collapse > .table { margin-bottom: 0; } .panel > .table caption, .panel > .table-responsive > .table caption, .panel > .panel-collapse > .table caption { padding-right: 15px; padding-left: 15px; } .panel > .table:first-child, .panel > .table-responsive:first-child > .table:first-child { border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel > .table:first-child > thead:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { border-top-left-radius: 3px; border-top-right-radius: 3px; } .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { border-top-left-radius: 3px; } .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { border-top-right-radius: 3px; } .panel > .table:last-child, .panel > .table-responsive:last-child > .table:last-child { border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel > .table:last-child > tbody:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; } .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { border-bottom-left-radius: 3px; } .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { border-bottom-right-radius: 3px; } .panel > .panel-body + .table, .panel > .panel-body + .table-responsive, .panel > .table + .panel-body, .panel > .table-responsive + .panel-body { border-top: 1px solid #ddd; } .panel > .table > tbody:first-child > tr:first-child th, .panel > .table > tbody:first-child > tr:first-child td { border-top: 0; } .panel > .table-bordered, .panel > .table-responsive > .table-bordered { border: 0; } .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { border-left: 0; } .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { border-right: 0; } .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { border-bottom: 0; } .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { border-bottom: 0; } .panel > .table-responsive { margin-bottom: 0; border: 0; } .panel-group { margin-bottom: 20px; } .panel-group .panel { margin-bottom: 0; border-radius: 4px; } .panel-group .panel + .panel { margin-top: 5px; } .panel-group .panel-heading { border-bottom: 0; } .panel-group .panel-heading + .panel-collapse > .panel-body, .panel-group .panel-heading + .panel-collapse > .list-group { border-top: 1px solid #ddd; } .panel-group .panel-footer { border-top: 0; } .panel-group .panel-footer + .panel-collapse .panel-body { border-bottom: 1px solid #ddd; } .panel-default { border-color: #ddd; } .panel-default > .panel-heading { color: #333; background-color: #f5f5f5; border-color: #ddd; } .panel-default > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ddd; } .panel-default > .panel-heading .badge { color: #f5f5f5; background-color: #333; } .panel-default > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ddd; } .panel-primary { border-color: #337ab7; } .panel-primary > .panel-heading { color: #fff; background-color: #337ab7; border-color: #337ab7; } .panel-primary > .panel-heading + .panel-collapse > .panel-body { border-top-color: #337ab7; } .panel-primary > .panel-heading .badge { color: #337ab7; background-color: #fff; } .panel-primary > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #337ab7; } .panel-success { border-color: #d6e9c6; } .panel-success > .panel-heading { color: #3c763d; background-color: #dff0d8; border-color: #d6e9c6; } .panel-success > .panel-heading + .panel-collapse > .panel-body { border-top-color: #d6e9c6; } .panel-success > .panel-heading .badge { color: #dff0d8; background-color: #3c763d; } .panel-success > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #d6e9c6; } .panel-info { border-color: #bce8f1; } .panel-info > .panel-heading { color: #31708f; background-color: #d9edf7; border-color: #bce8f1; } .panel-info > .panel-heading + .panel-collapse > .panel-body { border-top-color: #bce8f1; } .panel-info > .panel-heading .badge { color: #d9edf7; background-color: #31708f; } .panel-info > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #bce8f1; } .panel-warning { border-color: #faebcc; } .panel-warning > .panel-heading { color: #8a6d3b; background-color: #fcf8e3; border-color: #faebcc; } .panel-warning > .panel-heading + .panel-collapse > .panel-body { border-top-color: #faebcc; } .panel-warning > .panel-heading .badge { color: #fcf8e3; background-color: #8a6d3b; } .panel-warning > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #faebcc; } .panel-danger { border-color: #ebccd1; } .panel-danger > .panel-heading { color: #a94442; background-color: #f2dede; border-color: #ebccd1; } .panel-danger > .panel-heading + .panel-collapse > .panel-body { border-top-color: #ebccd1; } .panel-danger > .panel-heading .badge { color: #f2dede; background-color: #a94442; } .panel-danger > .panel-footer + .panel-collapse > .panel-body { border-bottom-color: #ebccd1; } .embed-responsive { position: relative; display: block; height: 0; padding: 0; overflow: hidden; } .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object, .embed-responsive video { position: absolute; top: 0; bottom: 0; left: 0; width: 100%; height: 100%; border: 0; } .embed-responsive-16by9 { padding-bottom: 56.25%; } .embed-responsive-4by3 { padding-bottom: 75%; } .well { min-height: 20px; padding: 19px; margin-bottom: 20px; background-color: #f5f5f5; border: 1px solid #e3e3e3; border-radius: 4px; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); } .well blockquote { border-color: #ddd; border-color: rgba(0, 0, 0, .15); } .well-lg { padding: 24px; border-radius: 6px; } .well-sm { padding: 9px; border-radius: 3px; } .close { float: right; font-size: 21px; font-weight: bold; line-height: 1; color: #000; text-shadow: 0 1px 0 #fff; filter: alpha(opacity=20); opacity: .2; } .close:hover, .close:focus { color: #000; text-decoration: none; cursor: pointer; filter: alpha(opacity=50); opacity: .5; } button.close { -webkit-appearance: none; padding: 0; cursor: pointer; background: transparent; border: 0; } .modal-open { overflow: hidden; } .modal { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1050; display: none; overflow: hidden; -webkit-overflow-scrolling: touch; outline: 0; } .modal.fade .modal-dialog { -webkit-transition: -webkit-transform .3s ease-out; -o-transition: -o-transform .3s ease-out; transition: transform .3s ease-out; -webkit-transform: translate(0, -25%); -ms-transform: translate(0, -25%); -o-transform: translate(0, -25%); transform: translate(0, -25%); } .modal.in .modal-dialog { -webkit-transform: translate(0, 0); -ms-transform: translate(0, 0); -o-transform: translate(0, 0); transform: translate(0, 0); } .modal-open .modal { overflow-x: hidden; overflow-y: auto; } .modal-dialog { position: relative; width: auto; margin: 10px; } .modal-content { position: relative; background-color: #fff; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid #999; border: 1px solid rgba(0, 0, 0, .2); border-radius: 6px; outline: 0; -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); box-shadow: 0 3px 9px rgba(0, 0, 0, .5); } .modal-backdrop { position: fixed; top: 0; right: 0; bottom: 0; left: 0; z-index: 1040; background-color: #000; } .modal-backdrop.fade { filter: alpha(opacity=0); opacity: 0; } .modal-backdrop.in { filter: alpha(opacity=50); opacity: .5; } .modal-header { padding: 15px; border-bottom: 1px solid #e5e5e5; } .modal-header .close { margin-top: -2px; } .modal-title { margin: 0; line-height: 1.42857143; } .modal-body { position: relative; padding: 15px; } .modal-footer { padding: 15px; text-align: right; border-top: 1px solid #e5e5e5; } .modal-footer .btn + .btn { margin-bottom: 0; margin-left: 5px; } .modal-footer .btn-group .btn + .btn { margin-left: -1px; } .modal-footer .btn-block + .btn-block { margin-left: 0; } .modal-scrollbar-measure { position: absolute; top: -9999px; width: 50px; height: 50px; overflow: scroll; } @media (min-width: 768px) { .modal-dialog { width: 600px; margin: 30px auto; } .modal-content { -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); box-shadow: 0 5px 15px rgba(0, 0, 0, .5); } .modal-sm { width: 300px; } } @media (min-width: 992px) { .modal-lg { width: 900px; } } .tooltip { position: absolute; z-index: 1070; display: block; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; font-style: normal; font-weight: normal; line-height: 1.42857143; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; word-wrap: normal; white-space: normal; filter: alpha(opacity=0); opacity: 0; line-break: auto; } .tooltip.in { filter: alpha(opacity=90); opacity: .9; } .tooltip.top { padding: 5px 0; margin-top: -3px; } .tooltip.right { padding: 0 5px; margin-left: 3px; } .tooltip.bottom { padding: 5px 0; margin-top: 3px; } .tooltip.left { padding: 0 5px; margin-left: -3px; } .tooltip-inner { max-width: 200px; padding: 3px 8px; color: #fff; text-align: center; background-color: #000; border-radius: 4px; } .tooltip-arrow { position: absolute; width: 0; height: 0; border-color: transparent; border-style: solid; } .tooltip.top .tooltip-arrow { bottom: 0; left: 50%; margin-left: -5px; border-width: 5px 5px 0; border-top-color: #000; } .tooltip.top-left .tooltip-arrow { right: 5px; bottom: 0; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #000; } .tooltip.top-right .tooltip-arrow { bottom: 0; left: 5px; margin-bottom: -5px; border-width: 5px 5px 0; border-top-color: #000; } .tooltip.right .tooltip-arrow { top: 50%; left: 0; margin-top: -5px; border-width: 5px 5px 5px 0; border-right-color: #000; } .tooltip.left .tooltip-arrow { top: 50%; right: 0; margin-top: -5px; border-width: 5px 0 5px 5px; border-left-color: #000; } .tooltip.bottom .tooltip-arrow { top: 0; left: 50%; margin-left: -5px; border-width: 0 5px 5px; border-bottom-color: #000; } .tooltip.bottom-left .tooltip-arrow { top: 0; right: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #000; } .tooltip.bottom-right .tooltip-arrow { top: 0; left: 5px; margin-top: -5px; border-width: 0 5px 5px; border-bottom-color: #000; } .popover { position: absolute; top: 0; left: 0; z-index: 1060; display: none; max-width: 276px; padding: 1px; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 14px; font-style: normal; font-weight: normal; line-height: 1.42857143; text-align: left; text-align: start; text-decoration: none; text-shadow: none; text-transform: none; letter-spacing: normal; word-break: normal; word-spacing: normal; word-wrap: normal; white-space: normal; background-color: #fff; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, .2); border-radius: 6px; -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); box-shadow: 0 5px 10px rgba(0, 0, 0, .2); line-break: auto; } .popover.top { margin-top: -10px; } .popover.right { margin-left: 10px; } .popover.bottom { margin-top: 10px; } .popover.left { margin-left: -10px; } .popover-title { padding: 8px 14px; margin: 0; font-size: 14px; background-color: #f7f7f7; border-bottom: 1px solid #ebebeb; border-radius: 5px 5px 0 0; } .popover-content { padding: 9px 14px; } .popover > .arrow, .popover > .arrow:after { position: absolute; display: block; width: 0; height: 0; border-color: transparent; border-style: solid; } .popover > .arrow { border-width: 11px; } .popover > .arrow:after { content: ""; border-width: 10px; } .popover.top > .arrow { bottom: -11px; left: 50%; margin-left: -11px; border-top-color: #999; border-top-color: rgba(0, 0, 0, .25); border-bottom-width: 0; } .popover.top > .arrow:after { bottom: 1px; margin-left: -10px; content: " "; border-top-color: #fff; border-bottom-width: 0; } .popover.right > .arrow { top: 50%; left: -11px; margin-top: -11px; border-right-color: #999; border-right-color: rgba(0, 0, 0, .25); border-left-width: 0; } .popover.right > .arrow:after { bottom: -10px; left: 1px; content: " "; border-right-color: #fff; border-left-width: 0; } .popover.bottom > .arrow { top: -11px; left: 50%; margin-left: -11px; border-top-width: 0; border-bottom-color: #999; border-bottom-color: rgba(0, 0, 0, .25); } .popover.bottom > .arrow:after { top: 1px; margin-left: -10px; content: " "; border-top-width: 0; border-bottom-color: #fff; } .popover.left > .arrow { top: 50%; right: -11px; margin-top: -11px; border-right-width: 0; border-left-color: #999; border-left-color: rgba(0, 0, 0, .25); } .popover.left > .arrow:after { right: 1px; bottom: -10px; content: " "; border-right-width: 0; border-left-color: #fff; } .carousel { position: relative; } .carousel-inner { position: relative; width: 100%; overflow: hidden; } .carousel-inner > .item { position: relative; display: none; -webkit-transition: .6s ease-in-out left; -o-transition: .6s ease-in-out left; transition: .6s ease-in-out left; } .carousel-inner > .item > img, .carousel-inner > .item > a > img { line-height: 1; } @media all and (transform-3d), (-webkit-transform-3d) { .carousel-inner > .item { -webkit-transition: -webkit-transform .6s ease-in-out; -o-transition: -o-transform .6s ease-in-out; transition: transform .6s ease-in-out; -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-perspective: 1000px; perspective: 1000px; } .carousel-inner > .item.next, .carousel-inner > .item.active.right { left: 0; -webkit-transform: translate3d(100%, 0, 0); transform: translate3d(100%, 0, 0); } .carousel-inner > .item.prev, .carousel-inner > .item.active.left { left: 0; -webkit-transform: translate3d(-100%, 0, 0); transform: translate3d(-100%, 0, 0); } .carousel-inner > .item.next.left, .carousel-inner > .item.prev.right, .carousel-inner > .item.active { left: 0; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } } .carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev { display: block; } .carousel-inner > .active { left: 0; } .carousel-inner > .next, .carousel-inner > .prev { position: absolute; top: 0; width: 100%; } .carousel-inner > .next { left: 100%; } .carousel-inner > .prev { left: -100%; } .carousel-inner > .next.left, .carousel-inner > .prev.right { left: 0; } .carousel-inner > .active.left { left: -100%; } .carousel-inner > .active.right { left: 100%; } .carousel-control { position: absolute; top: 0; bottom: 0; left: 0; width: 15%; font-size: 20px; color: #fff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, .6); background-color: rgba(0, 0, 0, 0); filter: alpha(opacity=50); opacity: .5; } .carousel-control.left { background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); background-repeat: repeat-x; } .carousel-control.right { right: 0; left: auto; background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); background-repeat: repeat-x; } .carousel-control:hover, .carousel-control:focus { color: #fff; text-decoration: none; filter: alpha(opacity=90); outline: 0; opacity: .9; } .carousel-control .icon-prev, .carousel-control .icon-next, .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right { position: absolute; top: 50%; z-index: 5; display: inline-block; margin-top: -10px; } .carousel-control .icon-prev, .carousel-control .glyphicon-chevron-left { left: 50%; margin-left: -10px; } .carousel-control .icon-next, .carousel-control .glyphicon-chevron-right { right: 50%; margin-right: -10px; } .carousel-control .icon-prev, .carousel-control .icon-next { width: 20px; height: 20px; font-family: serif; line-height: 1; } .carousel-control .icon-prev:before { content: '\2039'; } .carousel-control .icon-next:before { content: '\203a'; } .carousel-indicators { position: absolute; bottom: 10px; left: 50%; z-index: 15; width: 60%; padding-left: 0; margin-left: -30%; text-align: center; list-style: none; } .carousel-indicators li { display: inline-block; width: 10px; height: 10px; margin: 1px; text-indent: -999px; cursor: pointer; background-color: #000 \9; background-color: rgba(0, 0, 0, 0); border: 1px solid #fff; border-radius: 10px; } .carousel-indicators .active { width: 12px; height: 12px; margin: 0; background-color: #fff; } .carousel-caption { position: absolute; right: 15%; bottom: 20px; left: 15%; z-index: 10; padding-top: 20px; padding-bottom: 20px; color: #fff; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, .6); } .carousel-caption .btn { text-shadow: none; } @media screen and (min-width: 768px) { .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-prev, .carousel-control .icon-next { width: 30px; height: 30px; margin-top: -10px; font-size: 30px; } .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { margin-left: -10px; } .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { margin-right: -10px; } .carousel-caption { right: 20%; left: 20%; padding-bottom: 30px; } .carousel-indicators { bottom: 20px; } } .clearfix:before, .clearfix:after, .dl-horizontal dd:before, .dl-horizontal dd:after, .container:before, .container:after, .container-fluid:before, .container-fluid:after, .row:before, .row:after, .form-horizontal .form-group:before, .form-horizontal .form-group:after, .btn-toolbar:before, .btn-toolbar:after, .btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after, .nav:before, .nav:after, .navbar:before, .navbar:after, .navbar-header:before, .navbar-header:after, .navbar-collapse:before, .navbar-collapse:after, .pager:before, .pager:after, .panel-body:before, .panel-body:after, .modal-header:before, .modal-header:after, .modal-footer:before, .modal-footer:after { display: table; content: " "; } .clearfix:after, .dl-horizontal dd:after, .container:after, .container-fluid:after, .row:after, .form-horizontal .form-group:after, .btn-toolbar:after, .btn-group-vertical > .btn-group:after, .nav:after, .navbar:after, .navbar-header:after, .navbar-collapse:after, .pager:after, .panel-body:after, .modal-header:after, .modal-footer:after { clear: both; } .center-block { display: block; margin-right: auto; margin-left: auto; } .pull-right { float: right !important; } .pull-left { float: left !important; } .hide { display: none !important; } .show { display: block !important; } .invisible { visibility: hidden; } .text-hide { font: 0/0 a; color: transparent; text-shadow: none; background-color: transparent; border: 0; } .hidden { display: none !important; } .affix { position: fixed; } @-ms-viewport { width: device-width; } .visible-xs, .visible-sm, .visible-md, .visible-lg { display: none !important; } .visible-xs-block, .visible-xs-inline, .visible-xs-inline-block, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-lg-block, .visible-lg-inline, .visible-lg-inline-block { display: none !important; } @media (max-width: 767px) { .visible-xs { display: block !important; } table.visible-xs { display: table !important; } tr.visible-xs { display: table-row !important; } th.visible-xs, td.visible-xs { display: table-cell !important; } } @media (max-width: 767px) { .visible-xs-block { display: block !important; } } @media (max-width: 767px) { .visible-xs-inline { display: inline !important; } } @media (max-width: 767px) { .visible-xs-inline-block { display: inline-block !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm { display: block !important; } table.visible-sm { display: table !important; } tr.visible-sm { display: table-row !important; } th.visible-sm, td.visible-sm { display: table-cell !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-block { display: block !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-inline { display: inline !important; } } @media (min-width: 768px) and (max-width: 991px) { .visible-sm-inline-block { display: inline-block !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md { display: block !important; } table.visible-md { display: table !important; } tr.visible-md { display: table-row !important; } th.visible-md, td.visible-md { display: table-cell !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-block { display: block !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-inline { display: inline !important; } } @media (min-width: 992px) and (max-width: 1199px) { .visible-md-inline-block { display: inline-block !important; } } @media (min-width: 1200px) { .visible-lg { display: block !important; } table.visible-lg { display: table !important; } tr.visible-lg { display: table-row !important; } th.visible-lg, td.visible-lg { display: table-cell !important; } } @media (min-width: 1200px) { .visible-lg-block { display: block !important; } } @media (min-width: 1200px) { .visible-lg-inline { display: inline !important; } } @media (min-width: 1200px) { .visible-lg-inline-block { display: inline-block !important; } } @media (max-width: 767px) { .hidden-xs { display: none !important; } } @media (min-width: 768px) and (max-width: 991px) { .hidden-sm { display: none !important; } } @media (min-width: 992px) and (max-width: 1199px) { .hidden-md { display: none !important; } } @media (min-width: 1200px) { .hidden-lg { display: none !important; } } .visible-print { display: none !important; } @media print { .visible-print { display: block !important; } table.visible-print { display: table !important; } tr.visible-print { display: table-row !important; } th.visible-print, td.visible-print { display: table-cell !important; } } .visible-print-block { display: none !important; } @media print { .visible-print-block { display: block !important; } } .visible-print-inline { display: none !important; } @media print { .visible-print-inline { display: inline !important; } } .visible-print-inline-block { display: none !important; } @media print { .visible-print-inline-block { display: inline-block !important; } } @media print { .hidden-print { display: none !important; } } /*# sourceMappingURL=bootstrap.css.map */ ================================================ FILE: gui/static/css/carriergroups.css ================================================ .navbar { background: #FFFFFF; padding: 0; margin: 0; } .navbar > ul { list-style: none; padding: 0; margin: 0; } .navbar > ul > li { -webkit-transition: all 0.2s cubic-bezier(0.2, 0.6, 0.4, 1); -moz-transition: all 0.2s cubic-bezier(0.2, 0.6, 0.4, 1); -o-transition: all 0.2s cubic-bezier(0.2, 0.6, 0.4, 1); transition: all 0.2s cubic-bezier(0.2, 0.6, 0.4, 1); background: rgba(0, 0, 0, 0.05); margin: 0 2px; } .navbar > ul > li a { -webkit-transition: all 0.2s ease; -moz-transition: all 0.2s ease; -o-transition: all 0.2s ease; transition: all 0.2s ease; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; border: 2px solid transparent; background: rgba(255, 255, 255, 0.75); } .navbar > ul > li a:hover, .current-navlink.current-navlink:hover { border: 2px solid rgba(255, 129, 0, 0.8); color: #0fafff; } .current-navlink.current-navlink { border-top: 2px solid transparent; border-right: 2px solid transparent; border-bottom: 2px solid rgba(255, 129, 0, 1.0); border-left: 2px solid transparent; color: #3355ff; background: rgba(255, 255, 255, 1.0); } .spinner { width: 40px; height: 40px; clear: both; margin: 20px auto; } .spinner-circle { border: 4px rgba(0, 0, 0, 0.25) solid; border-top: 4px black solid; -webkit-border-radius: 50%; -moz-border-radius: 50%; border-radius: 50%; -webkit-animation: rotator .5s infinite linear; -moz-animation: rotator .5s infinite linear; -o-animation: rotator .5s infinite linear; animation: rotator .5s infinite linear; } @-webkit-keyframes rotator { from { -webkit-transform: rotate(0deg); } to { -webkit-transform: rotate(359deg); } } @-moz-keyframes rotator { from { -moz-transform: rotate(0deg); } to { -moz-transform: rotate(359deg); } } @-o-keyframes rotator { from { -o-transform: rotate(0deg); } to { -o-transform: rotate(359deg); } } @keyframes rotator { from { -webkit-transform: rotate(0deg); -moz-transform: rotate(0deg); -o-transform: rotate(0deg); transform: rotate(0deg); } to { -webkit-transform: rotate(359deg); -moz-transform: rotate(359deg); -o-transform: rotate(359deg); transform: rotate(359deg); } } .modal-dialog { width: fit-content; margin: 30px auto; min-width: 600px; } ================================================ FILE: gui/static/css/cdrs.css ================================================ #downloadCDR, #refreshCDR { left: .25em; cursor: pointer; } #cdrs_wrapper > div.btn-group, #cdrs_wrapper > div.btn-group-vertical { position: absolute; display: -ms-inline-flexbox; display: inline-flex; vertical-align: middle; } ================================================ FILE: gui/static/css/certificates.css ================================================ .loader { border: 16px solid #f3f3f3; /* Light grey */ border-top: 16px solid #3498db; /* Blue */ border-radius: 50%; width: 120px; height: 120px; position: relative; display: none; z-index: 10; animation: spin 2s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } ================================================ FILE: gui/static/css/combobox.css ================================================ /* * THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. * The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders. * * Original Version [combobox-1.1.css]: https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/css/combobox-1.1.css * Copyright © [2015] World Wide Web Consortium, (Massachusetts Institute of Technology, European Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights Reserved. This work is distributed under the W3C® Software License [1] 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. * http://www.w3.org/Consortium/Legal/copyright-software * * Changes made by the dOpenSource Team * Copyright © [2018] W3C®, dOpenSource */ .combobox-wrapper { position: relative; font-size: 16px; } .listbox, .grid { width: 100%; color: #555; background-color: #fff; background-image: none; border: 1px solid #ccc; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; list-style: none; margin: 0; padding: 0; position: absolute; top: 30px; z-index: 1; } .listbox .result { cursor: default; margin: 0; } .listbox .result:hover, .grid .result-row:hover { background: rgb(139, 189, 225); } .listbox .focused, .grid .focused { background: rgb(139, 189, 225); } .grid .focused-cell { outline-style: dotted; outline-color: green; } .combobox-wrapper input { padding-right: 30px; } .combobox-dropdown { position: absolute; padding: 0 0 2px; background-color: #fff; background-image: none; border: 1px solid #ccc; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; right: .5px; top: .5px; height: 32.5px; width: 32.5px; } .grid .result-row { padding: 2px; cursor: default; margin: 0; } .grid .result-cell { display: inline-block; cursor: default; margin: 0; padding: 0 5px; } .grid .result-cell:last-child { float: right; font-size: 12px; font-weight: 200; color: #333; line-height: 24px; } ================================================ FILE: gui/static/css/dashboard.css ================================================ .dashboard-metric { font-size: 80px; text-align: center; } .dashboard-metric-label { font-size: 35px; text-align: center; color: #ddd; } ================================================ FILE: gui/static/css/highlight/LICENSE ================================================ Copyright (c) 2006, Ivan Sagalaev All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of highlight.js nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: gui/static/css/highlight/codepen-embed.css ================================================ /* codepen.io Embed Theme Author: Justin Perry <http://github.com/ourmaninamsterdam> Original theme - https://github.com/chriskempson/tomorrow-theme */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: #222; color: #fff; } .hljs-comment, .hljs-quote { color: #777; } .hljs-variable, .hljs-template-variable, .hljs-tag, .hljs-regexp, .hljs-meta, .hljs-number, .hljs-built_in, .hljs-builtin-name, .hljs-literal, .hljs-params, .hljs-symbol, .hljs-bullet, .hljs-link, .hljs-deletion { color: #ab875d; } .hljs-section, .hljs-title, .hljs-name, .hljs-selector-id, .hljs-selector-class, .hljs-type, .hljs-attribute { color: #9b869b; } .hljs-string, .hljs-keyword, .hljs-selector-tag, .hljs-addition { color: #8f9c6c; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: gui/static/css/highlight/darcula.css ================================================ /* Darcula color scheme from the JetBrains family of IDEs */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: #2b2b2b; } .hljs { color: #bababa; } .hljs-strong, .hljs-emphasis { color: #a8a8a2; } .hljs-bullet, .hljs-quote, .hljs-link, .hljs-number, .hljs-regexp, .hljs-literal { color: #6896ba; } .hljs-code, .hljs-selector-class { color: #a6e22e; } .hljs-emphasis { font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-section, .hljs-attribute, .hljs-name, .hljs-variable { color: #cb7832; } .hljs-params { color: #b9b9b9; } .hljs-string { color: #6a8759; } .hljs-subst, .hljs-type, .hljs-built_in, .hljs-builtin-name, .hljs-symbol, .hljs-selector-id, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-template-tag, .hljs-template-variable, .hljs-addition { color: #e0c46c; } .hljs-comment, .hljs-deletion, .hljs-meta { color: #7f7f7f; } ================================================ FILE: gui/static/css/highlight/default.css ================================================ /* Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org> */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: #F0F0F0; } /* Base color: saturation 0; */ .hljs, .hljs-subst { color: #444; } .hljs-comment { color: #888888; } .hljs-keyword, .hljs-attribute, .hljs-selector-tag, .hljs-meta-keyword, .hljs-doctag, .hljs-name { font-weight: bold; } /* User color: hue: 0 */ .hljs-type, .hljs-string, .hljs-number, .hljs-selector-id, .hljs-selector-class, .hljs-quote, .hljs-template-tag, .hljs-deletion { color: #880000; } .hljs-title, .hljs-section { color: #880000; font-weight: bold; } .hljs-regexp, .hljs-symbol, .hljs-variable, .hljs-template-variable, .hljs-link, .hljs-selector-attr, .hljs-selector-pseudo { color: #BC6060; } /* Language color: hue: 90; */ .hljs-literal { color: #78A960; } .hljs-built_in, .hljs-bullet, .hljs-code, .hljs-addition { color: #397300; } /* Meta color: hue: 200 */ .hljs-meta { color: #1f7199; } .hljs-meta-string { color: #4d99bf; } /* Misc effects */ .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: gui/static/css/highlight/github-gist.css ================================================ /** * GitHub Gist Theme * Author : Louis Barranqueiro - https://github.com/LouisBarranqueiro */ .hljs { display: block; background: white; padding: 0.5em; color: #333333; overflow-x: auto; } .hljs-comment, .hljs-meta { color: #969896; } .hljs-string, .hljs-variable, .hljs-template-variable, .hljs-strong, .hljs-emphasis, .hljs-quote { color: #df5000; } .hljs-keyword, .hljs-selector-tag, .hljs-type { color: #a71d5d; } .hljs-literal, .hljs-symbol, .hljs-bullet, .hljs-attribute { color: #0086b3; } .hljs-section, .hljs-name { color: #63a35c; } .hljs-tag { color: #333333; } .hljs-title, .hljs-attr, .hljs-selector-id, .hljs-selector-class, .hljs-selector-attr, .hljs-selector-pseudo { color: #795da3; } .hljs-addition { color: #55a532; background-color: #eaffea; } .hljs-deletion { color: #bd2c00; background-color: #ffecec; } .hljs-link { text-decoration: underline; } ================================================ FILE: gui/static/css/highlight/github.css ================================================ /* github.com style (c) Vasily Polovnyov <vast@whiteants.net> */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hljs-comment, .hljs-quote { color: #998; font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-subst { color: #333; font-weight: bold; } .hljs-number, .hljs-literal, .hljs-variable, .hljs-template-variable, .hljs-tag .hljs-attr { color: #008080; } .hljs-string, .hljs-doctag { color: #d14; } .hljs-title, .hljs-section, .hljs-selector-id { color: #900; font-weight: bold; } .hljs-subst { font-weight: normal; } .hljs-type, .hljs-class .hljs-title { color: #458; font-weight: bold; } .hljs-tag, .hljs-name, .hljs-attribute { color: #000080; font-weight: normal; } .hljs-regexp, .hljs-link { color: #009926; } .hljs-symbol, .hljs-bullet { color: #990073; } .hljs-built_in, .hljs-builtin-name { color: #0086b3; } .hljs-meta { color: #999; font-weight: bold; } .hljs-deletion { background: #fdd; } .hljs-addition { background: #dfd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: gui/static/css/highlight/googlecode.css ================================================ /* Google Code style (c) Aahan Krish <geekpanth3r@gmail.com> */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: white; color: black; } .hljs-comment, .hljs-quote { color: #800; } .hljs-keyword, .hljs-selector-tag, .hljs-section, .hljs-title, .hljs-name { color: #008; } .hljs-variable, .hljs-template-variable { color: #660; } .hljs-string, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-regexp { color: #080; } .hljs-literal, .hljs-symbol, .hljs-bullet, .hljs-meta, .hljs-number, .hljs-link { color: #066; } .hljs-title, .hljs-doctag, .hljs-type, .hljs-attr, .hljs-built_in, .hljs-builtin-name, .hljs-params { color: #606; } .hljs-attribute, .hljs-subst { color: #000; } .hljs-formula { background-color: #eee; font-style: italic; } .hljs-selector-id, .hljs-selector-class { color: #9B703F } .hljs-addition { background-color: #baeeba; } .hljs-deletion { background-color: #ffc8bd; } .hljs-doctag, .hljs-strong { font-weight: bold; } .hljs-emphasis { font-style: italic; } ================================================ FILE: gui/static/css/highlight/idea.css ================================================ /* Intellij Idea-like styling (c) Vasily Polovnyov <vast@whiteants.net> */ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #000; background: #fff; } .hljs-subst, .hljs-title { font-weight: normal; color: #000; } .hljs-comment, .hljs-quote { color: #808080; font-style: italic; } .hljs-meta { color: #808000; } .hljs-tag { background: #efefef; } .hljs-section, .hljs-name, .hljs-literal, .hljs-keyword, .hljs-selector-tag, .hljs-type, .hljs-selector-id, .hljs-selector-class { font-weight: bold; color: #000080; } .hljs-attribute, .hljs-number, .hljs-regexp, .hljs-link { font-weight: bold; color: #0000ff; } .hljs-number, .hljs-regexp, .hljs-link { font-weight: normal; } .hljs-string { color: #008000; font-weight: bold; } .hljs-symbol, .hljs-bullet, .hljs-formula { color: #000; background: #d0eded; font-style: italic; } .hljs-doctag { text-decoration: underline; } .hljs-variable, .hljs-template-variable { color: #660e7a; } .hljs-addition { background: #baeeba; } .hljs-deletion { background: #ffc8bd; } .hljs-emphasis { font-style: italic; } .hljs-strong { font-weight: bold; } ================================================ FILE: gui/static/css/highlight/monokai-sublime.css ================================================ /* Monokai Sublime style. Derived from Monokai by noformnocontent http://nn.mit-license.org/ */ .hljs { display: block; overflow-x: auto; padding: 0.5em; background: #23241f; } .hljs, .hljs-tag, .hljs-subst { color: #f8f8f2; } .hljs-strong, .hljs-emphasis { color: #a8a8a2; } .hljs-bullet, .hljs-quote, .hljs-number, .hljs-regexp, .hljs-literal, .hljs-link { color: #ae81ff; } .hljs-code, .hljs-title, .hljs-section, .hljs-selector-class { color: #a6e22e; } .hljs-strong { font-weight: bold; } .hljs-emphasis { font-style: italic; } .hljs-keyword, .hljs-selector-tag, .hljs-name, .hljs-attr { color: #f92672; } .hljs-symbol, .hljs-attribute { color: #66d9ef; } .hljs-params, .hljs-class .hljs-title { color: #f8f8f2; } .hljs-string, .hljs-type, .hljs-built_in, .hljs-builtin-name, .hljs-selector-id, .hljs-selector-attr, .hljs-selector-pseudo, .hljs-addition, .hljs-variable, .hljs-template-variable { color: #e6db74; } .hljs-comment, .hljs-deletion, .hljs-meta { color: #75715e; } ================================================ FILE: gui/static/css/main.css ================================================ /* custom icons */ @font-face { font-family: 'icomoon'; src: url('/static/fonts/icomoon.eot?imwift'); src: url('/static/fonts/icomoon.eot?imwift#iefix') format('embedded-opentype'), url('/static/fonts/icomoon.ttf?imwift') format('truetype'), url('/static/fonts/icomoon.woff?imwift') format('woff'), url('/static/fonts/icomoon.svg?imwift#icomoon') format('svg'); font-weight: normal; font-style: normal; font-display: block; } [class^="icon-"], [class*=" icon-"] { /* use !important to prevent issues with browser extensions that change fonts */ font-family: 'icomoon' !important; speak: never; font-style: normal; font-weight: normal; font-variant: normal; text-transform: none; line-height: 1; /* Better Font Rendering =========== */ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .icon-phone_enabled:before { content: "\e90d"; } .icon-money:before { content: "\e900"; } .icon-load_balance:before { content: "\e111"; } .icon-fusionpbx_full:before { content: "\e901"; } .icon-gryphon:before { content: "\e902"; } .icon-fusionpbx:before { content: "\e903"; } .icon-asterisk:before { content: "\e904"; } .icon-kamailio:before { content: "\e905"; } .icon-flowroute:before { content: "\e906"; } .icon-freepbx:before { content: "\e907"; } .icon-zencommunication:before { content: "\e908"; } .icon-call_failfwd:before { content: "\e909"; } .icon-call_hardfwd:before { content: "\e90a"; } .icon-transnexus .path1:before { content: "\e90b"; color: rgb(0, 78, 168); } .icon-transnexus .path2:before { content: "\e90c"; margin-left: -1em; color: rgb(255, 255, 255); } .icon-circle-up:before { content: "\ea41"; } .icon-circle-down:before { content: "\ea43"; } .wrap { position: relative; } .wrap .nav-bar { -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; } .wrap .nav-bar .navbar-brand { color: #FFB266; padding: 0.5em 2em; } .wrap .nav-bar .navbar-brand > * { max-height: 100%; } .wrap .nav-bar .navbar-form { margin-top: 25px; } .wrap .nav-bar .navbar-right { float: right !important; } .wrap .nav-bar .navbar-right .btn { margin-top: 0.5em; } .wrap .nav-bar .nav .dropdown span.fa, .wrap .nav-bar .nav .dropdown span.caret { margin-right: 10px; } .wrap .nav-bar .nav .dropdown-menu { background: #222; left: auto; width: 200px; right: 0; } .wrap .nav-bar .nav .dropdown-menu > li > a { color: #ddd; padding: 8px; } .wrap .nav-bar .nav .dropdown-menu > li > a:hover { background: #3355ff; } .wrap .side-menu-link { left: 21em; } .wrap .burger { position: relative; width: 35px; height: 30px; left: 10px; top: 10px; z-index: 500000; } .wrap .burger .burger_inside { position: absolute; background-color: #222; width: 30px; height: 5px; left: 2.5px; -webkit-transition: all 0.5s ease-out; -moz-transition: all 0.5s ease-out; -o-transition: all 0.5s ease-out; transition: all 0.5s ease-out; } .wrap .burger #bgrOne { top: 0; } .wrap .burger #bgrTwo { top: 10px; } .wrap .burger #bgrThree { top: 20px; } .wrap #side-menu { position: absolute; z-index: -1; background: #404040; width: 22em; padding-top: 10px; padding-right: 20px; padding-left: 10px; float: left; display: block; left: 0; height: 2000px; } .wrap .content { position: absolute; right: 0; min-width: 400px; -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; } .wrap .content .top-bar { height: 45px; background: #ddd; border: 1px solid transparent; overflow: hidden; } .wrap .content .content-inner { padding: 0; margin: 0; background: #fff; display: block; position: absolute; height: 2000px; width: 100%; } .wrap .content .content-inner .content-header { border-bottom: 2px solid #dddddd; padding: 0 15px 15px 15px; margin: 15px 0 20px 0; } .wrap .content .content-inner .content-section { padding: 15px 0; margin: 15px 0; } .wrap ul.accordion { width: 100%; background: transparent; } .wrap ul.accordion .link { cursor: pointer; display: block; padding: 15px 15px 15px 42px; color: #9D9D9D; font-size: 14px; font-weight: 700; border-bottom: 1px solid #303030; position: relative; -webkit-transition: all 0.4s ease; -o-transition: all 0.4s ease; -moz-transition: all 0.4s ease; transition: all 0.4s ease; } .wrap ul.accordion li:not(open) :last-child .link { border-bottom: 0; } .wrap ul.accordion li i { position: absolute; top: 16px; left: 12px; font-size: 18px; color: #999; -webkit-transition: all 0.4s ease; -o-transition: all 0.4s ease; -moz-transition: all 0.4s ease; transition: all 0.4s ease; } .wrap ul.accordion li i.glyphicon-chevron-down { right: 12px; left: auto; font-size: 16px; padding: 5px; } .wrap ul.accordion li.open .link { color: #FF8100; } .wrap ul.accordion li.open i { color: #FF8100; } .wrap ul.accordion li.open i.glyphicon-chevron-down { -webkit-transform: rotate(180deg); -ms-transform: rotate(180deg); -o-transform: rotate(180deg); -moz-transform: rotate(180deg); transform: rotate(180deg); } .wrap ul.accordion ul.submenu { display: none; background: transparent; font-size: 14px; padding: 0; } .wrap ul.accordion ul.submenu li { border-bottom: 1px solid #4b4a5e; list-style: none; padding: 5px; } .wrap ul.accordion ul.submenu li a { display: block; text-decoration: none; color: #d9d9d9; padding: 12px; padding-left: 42px; -webkit-transition: all 0.25s ease; -o-transition: all 0.25s ease; -moz-transition: all 0.25s ease; transition: all 0.25s ease; } /* .wrap ul.accordion ul.submenu li a:hover { background: rgba(240, 128, 128, 0.8); color: #ffb266; } */ .panel-info > .panel-heading { color: #FF8100; background-color: #000000; border-color: #FF8100; } .panel-info { border-color: #FF8100; } .loginlogo { text-align: center; } .navlink a:link, .navlink a:visited, .navlink a:hover { color: #9d9d9d; } .a { color: #9d9d9d; text-decoration: none; } a.currentlink { color: #FF8100 !important; } a.navlink { color: #c4c4c5; } .label-toggle { margin-left: 10px; width: 100%; } .container.container { width: 100%; height: 100%; margin: auto; padding: 0; } .left-half { background-color: #f4f6f9; position: absolute; left: 0px; width: 60%; height: 100%; } .logon-image { height: 100%; max-width: 100%; max-height: 100%; margin: auto; } .right-half { background-color: #f4f6f9; position: absolute; right: 0px; width: 40%; height: 100%; } .sponsor-title { color: #5c9a4f; font-size: 14px; margin-left: 5px; margin-bottom: 5px; } .sponsor-group { width: 100%; text-align: left; margin-left: 10px } .sponsor { display: inline-block; margin-right: 10px; } .sponsor img { width: 120px; } .navBarButtons { float: right; } .table-centered th, .table-centered td { text-align: center; vertical-align: middle; } /* horizontal wrapper for block elements */ .wrapper-horizontal { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; flex-direction: row; } .wrapper-horizontal > div, .wrapper-horizontal > h1, .wrapper-horizontal > h2, .wrapper-horizontal > h3, .wrapper-horizontal > h4, .wrapper-horizontal > h5, .wrapper-horizontal > h6, .wrapper-horizontal > p, .wrapper-horizontal > form, .wrapper-horizontal > header, .wrapper-horizontal > footer, .wrapper-horizontal > section, .wrapper-horizontal > li, .wrapper-horizontal > ul, .wrapper-horizontal > ol, .wrapper-horizontal > table, .wrapper-horizontal > main, .wrapper-horizontal > nav, .wrapper-horizontal > pre, .wrapper-horizontal > noscript, .wrapper-horizontal > output, .wrapper-horizontal > section, .wrapper-horizontal > video { display: inline-block; } /* vertical wrapper for block elements */ .wrapper-vertical { display: -webkit-box; display: -moz-box; display: -ms-flexbox; display: -webkit-flex; display: flex; flex-direction: column; } .wrapper-vertical > div, .wrapper-vertical > h1, .wrapper-vertical > h2, .wrapper-vertical > h3, .wrapper-vertical > h4, .wrapper-vertical > h5, .wrapper-vertical > h6, .wrapper-vertical > p, .wrapper-vertical > form, .wrapper-vertical > header, .wrapper-vertical > footer, .wrapper-vertical > section, .wrapper-vertical > li, .wrapper-vertical > ul, .wrapper-vertical > ol, .wrapper-vertical > table, .wrapper-vertical > main, .wrapper-vertical > nav, .wrapper-vertical > pre, .wrapper-vertical > noscript, .wrapper-vertical > output, .wrapper-vertical > section, .wrapper-vertical > video { display: block; } .centered { margin: auto; align-items: center; justify-content: center; } .centered > * { text-align: center; } .edge-centered { margin: auto; align-items: center; justify-content: space-between; } .edge-centered > * { text-align: center; } .equal-centered { margin: auto; align-items: center; justify-content: space-evenly; } .equal-centered > * { text-align: center; } .children-align-inherit.children-align-inherit > * { text-align: inherit; } .hidden { display: none; visibility: hidden; -webkit-transition: opacity 3s ease-in-out; -moz-transition: opacity 3s ease-in-out; -ms-transition: opacity 3s ease-in-out; -o-transition: opacity 3s ease-in-out; transition: opacity 3s ease-in-out; } /* add to parent elem */ .resizable { overflow: hidden; } .resizable > * { overflow: auto; resize: both; } /* make the modal dialogs relatively larger */ .modal-dialog { width: 50vw; } /* fix bootstrap modals when stacking */ .modal-open.modal { overflow-x: hidden; overflow-y: auto; z-index: 9999; } .selected { background-color: #acbad4 !important; } .wrapper-fieldicon-right { position: relative; } .wrapper-fieldicon-right > span { position: absolute; bottom: 6px; right: 12px; cursor: pointer; line-height: inherit; top: inherit; } .submit-form-btn.submit-form-btn { width: 100%; } /* force dataTables to use full table width */ table.dataTable { width: 100% !important; } /* give dataTables header and footer some padding */ .dataTables_wrapper > div:first-child, .dataTables_wrapper > div:last-child { padding-right: 10px; padding-left: 10px; } #reloading_overlay { position: absolute; width: 100%; height: 100%; background-image: url("/static/images/spinner.svg"), -webkit-gradient(linear, left bottom, left top, from(rgba(74, 243, 245, 0.85)), to(rgba(34, 45, 55, 0.85))); background-image: url("/static/images/spinner.svg"), -o-linear-gradient(bottom, rgba(74, 243, 245, 0.85) 0%, rgba(34, 45, 55, 0.85) 100%); background-image: url("/static/images/spinner.svg"), linear-gradient(0deg, rgba(74, 243, 245, 0.85) 0%, rgba(34, 45, 55, 0.85) 100%); background-repeat: no-repeat; background-position-x: center; background-size: auto auto; -webkit-box-shadow: inset 2em 2em 2em -20px rgba(20, 20, 20, .1); box-shadow: inset 2em 2em 2em -20px rgba(20, 20, 20, .1); z-index: 1050; } /* iPhone 11 */ @media screen and (max-width: 552px) { .left-half { display: none; } .right-half { width: 100%; } .col-xs-4 { width: 70%; display: inline-block; } /*.wrap .content .content-inner .content-section { margin: 0 auto; }*/ } @media screen and (min-width: 769px) { .side-menu-link { display: none; } .wrap { position: relative; } .wrap .content { left: 20em; right: 0; } } @media screen and (max-width: 768px) { .wrap .nav-bar .navbar-brand { margin-top: 0; padding-left: 0; } .wrap .side-menu-link { display: inline-block; } .wrap #side-menu #qform { position: absolute; top: 10px; } .wrap .content { left: 0; } .wrap.active .content { left: 20em; } .wrap.active .content #bgrOne { top: 10px; -webkit-transform: rotate(225deg); -moz-transform: rotate(225deg); -ms-transform: rotate(225deg); -o-transform: rotate(225deg); transform: rotate(225deg); } .wrap.active .content #bgrTwo { opacity: 0; } .wrap.active .content #bgrThree { top: 10px; -webkit-transform: rotate(315deg); -moz-transform: rotate(315deg); -ms-transform: rotate(315deg); -o-transform: rotate(315deg); transform: rotate(315deg); } } #reload_kam, #reload_dsip { cursor: pointer; } ================================================ FILE: gui/static/css/msteams.css ================================================ /* The Configuration Panel */ .configurationpanel { height:auto; /* Specify a height */ width: 0; /* 0 width - change this with JavaScript */ /*position: fixed; Stay in place */ z-index: 1; /* Stay on top */ /*top: 0; left: 0; */ background-color: #111; /* Black*/ overflow-x: hidden; /* Disable horizontal scroll */ padding-top: 60px; /* Place content 60px from the top */ transition: 0.5s; /* 0.5 second transition effect to slide in the sidepanel */ } .tooltip-inner{ font-size: 10px; border:1px solid #e74c3c; background-color: red; height:100%; } ================================================ FILE: gui/static/css/titatoggle-dist.css ================================================ /******************************************************* Variables *******************************************************/ /******************************************************* Animation *******************************************************/ @keyframes popIn { 0% { transform: scale(1, 1); } 25% { transform: scale(1.2, 1); } 50% { transform: scale(1.4, 1); } 100% { transform: scale(1, 1); } } @keyframes popOut { 0% { transform: scale(1, 1); } 25% { transform: scale(1.2, 1); } 50% { transform: scale(1.4, 1); } 100% { transform: scale(1, 1); } } @keyframes splashIn { 0% { transform: scale(1); opacity: 1; } 25% { transform: scale(1.1); opacity: 0.8; } 50% { transform: scale(1.1); opacity: .9; } 100% { transform: scale(1); opacity: 1; } } @keyframes splashOut { 0% { transform: scale(1); opacity: 1; } 25% { transform: scale(1); opacity: 0.8; } 50% { transform: scale(1); opacity: .9; } 100% { transform: scale(0.5); opacity: 1; } } /******************************************************* Main Slider basics *******************************************************/ .checkbox-toggle { position: relative; } .checkbox-toggle input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-toggle input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-toggle input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-toggle input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-toggle input + span > h4 { display: inline; } .form-horizontal [class^='checkbox'] input + span:after { top: 7px; } /******************************************************* Main Slider *******************************************************/ .checkbox-slider { position: relative; } .checkbox-slider input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider input + span > h4 { display: inline; } .checkbox-slider input + span { padding-left: 40px; } .checkbox-slider input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider input:checked + span:before { transition: background 0.2s ease-in; } /******************************************************* Slider default *******************************************************/ .checkbox-slider--default { position: relative; } .checkbox-slider--default input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--default input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--default input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--default input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--default input + span > h4 { display: inline; } .checkbox-slider--default input + span { padding-left: 40px; } .checkbox-slider--default input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--default input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--default input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--default input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--default input + span:after { background: #FFF; border: solid transparent 1px; background-clip: content-box; } .checkbox-slider--default input:checked + span:after { background: #5cb85c; border: solid transparent 1px; background-clip: content-box; } /******************************************************* Slider default rounded *******************************************************/ .checkbox-slider--a-rounded { position: relative; } .checkbox-slider--a-rounded input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--a-rounded input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--a-rounded input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--a-rounded input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--a-rounded input + span > h4 { display: inline; } .checkbox-slider--a-rounded input + span { padding-left: 40px; } .checkbox-slider--a-rounded input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--a-rounded input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--a-rounded input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--a-rounded input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--a-rounded input + span:after { background: #FFF; border: solid transparent 1px; background-clip: content-box; } .checkbox-slider--a-rounded input:checked + span:after { background: #5cb85c; border: solid transparent 1px; background-clip: content-box; } .checkbox-slider--a-rounded input + span:after, .checkbox-slider--a-rounded input + span:before { border-radius: 4px; } .checkbox-slider--a-rounded input + span:after, .checkbox-slider--a-rounded input:checked + span:after { border: solid transparent 2px; background-clip: content-box; } /******************************************************* Slider default rounded Sizes *******************************************************/ .checkbox-slider--a-rounded.checkbox-slider-sm input + span:before, .checkbox-slider--a-rounded.checkbox-slider-sm input + span:after { border-radius: 3px; } .checkbox-slider--a-rounded.checkbox-slider-md input + span:before, .checkbox-slider--a-rounded.checkbox-slider-md input + span:after { border-radius: 4px; } .checkbox-slider--a-rounded.checkbox-slider-lg input + span:before, .checkbox-slider--a-rounded.checkbox-slider-lg input + span:after { border-radius: 6px; } /******************************************************* Slider A *******************************************************/ .checkbox-slider--a { position: relative; } .checkbox-slider--a input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--a input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--a input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--a input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--a input + span > h4 { display: inline; } .checkbox-slider--a input + span { padding-left: 40px; } .checkbox-slider--a input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--a input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--a input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--a input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--a input + span { padding-left: 60px; } .checkbox-slider--a input + span:before { content: ""; width: 60px; } .checkbox-slider--a input + span:after { width: 40px; font-size: 10px; color: #000; content: "Off"; border: solid transparent 1px; background-clip: content-box; } .checkbox-slider--a input:checked + span:after { content: "On"; color: #fff; background: #5cb85c; border: solid transparent 1px; background-clip: content-box; } /******************************************************* Slider A SIZES *******************************************************/ .checkbox-slider--a.checkbox-slider-sm input + span { padding-left: 30px; } .checkbox-slider--a.checkbox-slider-sm input + span:before { width: 30px; } .checkbox-slider--a.checkbox-slider-sm input + span:after { width: 20px; font-size: 5px; } .checkbox-slider--a.checkbox-slider-sm input:checked + span:after { margin-left: 10px; } .checkbox-slider--a.checkbox-slider-md input + span { padding-left: 90px; } .checkbox-slider--a.checkbox-slider-md input + span:before { width: 90px; } .checkbox-slider--a.checkbox-slider-md input + span:after { width: 60px; font-size: 15px; } .checkbox-slider--a.checkbox-slider-md input:checked + span:after { margin-left: 30px; } .checkbox-slider--a.checkbox-slider-lg input + span { padding-left: 120px; } .checkbox-slider--a.checkbox-slider-lg input + span:before { width: 120px; } .checkbox-slider--a.checkbox-slider-lg input + span:after { width: 80px; font-size: 20px; } .checkbox-slider--a.checkbox-slider-lg input:checked + span:after { margin-left: 40px; } /******************************************************* Slider B *******************************************************/ .checkbox-slider--b { position: relative; } .checkbox-slider--b input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--b input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--b input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--b input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--b input + span > h4 { display: inline; } .checkbox-slider--b input + span { padding-left: 40px; } .checkbox-slider--b input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--b input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--b input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--b input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--b input + span { padding-left: 40px; } .checkbox-slider--b input + span:before { border-radius: 20px; width: 40px; } .checkbox-slider--b input + span:after { background: #FFF; content: ""; width: 20px; border: solid transparent 2px; background-clip: padding-box; border-radius: 20px; } .checkbox-slider--b input:not(:checked) + span:after { animation: popOut ease-in 0.3s normal; } .checkbox-slider--b input:checked + span:after { content: ""; margin-left: 20px; border: solid transparent 2px; background-clip: padding-box; animation: popIn ease-in 0.3s normal; } .checkbox-slider--b input:checked + span:before { background: #5cb85c; } /******************************************************* Slider B Sizes *******************************************************/ .checkbox-slider--b.checkbox-slider-md input + span:before { border-radius: 30px; } .checkbox-slider--b.checkbox-slider-md input + span:after { border-radius: 30px; } .checkbox-slider--b.checkbox-slider-lg input + span:before { border-radius: 40px; } .checkbox-slider--b.checkbox-slider-lg input + span:after { border-radius: 40px; } /******************************************************* Slider B-flat *******************************************************/ .checkbox-slider--b-flat { position: relative; } .checkbox-slider--b-flat input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--b-flat input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--b-flat input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--b-flat input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--b-flat input + span > h4 { display: inline; } .checkbox-slider--b-flat input + span { padding-left: 40px; } .checkbox-slider--b-flat input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--b-flat input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--b-flat input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--b-flat input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--b-flat input + span { padding-left: 40px; } .checkbox-slider--b-flat input + span:before { border-radius: 20px; width: 40px; } .checkbox-slider--b-flat input + span:after { background: #FFF; content: ""; width: 20px; border: solid transparent 2px; background-clip: padding-box; border-radius: 20px; } .checkbox-slider--b-flat input:not(:checked) + span:after { animation: popOut ease-in 0.3s normal; } .checkbox-slider--b-flat input:checked + span:after { content: ""; margin-left: 20px; border: solid transparent 2px; background-clip: padding-box; animation: popIn ease-in 0.3s normal; } .checkbox-slider--b-flat input:checked + span:before { background: #5cb85c; } .checkbox-slider--b-flat.checkbox-slider-md input + span:before { border-radius: 30px; } .checkbox-slider--b-flat.checkbox-slider-md input + span:after { border-radius: 30px; } .checkbox-slider--b-flat.checkbox-slider-lg input + span:before { border-radius: 40px; } .checkbox-slider--b-flat.checkbox-slider-lg input + span:after { border-radius: 40px; } .checkbox-slider--b-flat input + span:before { box-shadow: none; } /******************************************************* Slider C *******************************************************/ .checkbox-slider--c { position: relative; } .checkbox-slider--c input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--c input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--c input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--c input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--c input + span > h4 { display: inline; } .checkbox-slider--c input + span { padding-left: 40px; } .checkbox-slider--c input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--c input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--c input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--c input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--c input + span { padding-left: 40px; } .checkbox-slider--c input + span:before { height: 2px !important; top: 10px; box-shadow: none; width: 40px; background: #555555; } .checkbox-slider--c input + span:after { box-shadow: none; width: 20px; border: solid #555555 2px; border-radius: 20px; } .checkbox-slider--c input:checked + span:after { background: #5cb85c; margin-left: 20px; border: solid #5cb85c 2px; animation: splashIn ease-in 0.3s normal; } .checkbox-slider--c input:checked + span:before { background: #5cb85c; } /******************************************************* Slider C Sizes *******************************************************/ .checkbox-slider--c.checkbox-slider-sm input + span:before { top: 4px; } .checkbox-slider--c.checkbox-slider-md input + span:before { top: 14px; } .checkbox-slider--c.checkbox-slider-md input + span:after { width: 30px; border-radius: 30px; } .checkbox-slider--c.checkbox-slider-lg input + span:before { top: 19px; } .checkbox-slider--c.checkbox-slider-lg input + span:after { width: 40px; border-radius: 40px; } .form-horizontal [class*='checkbox-slider--c'].checkbox-slider-sm input + span:before { top: 10px; } .form-horizontal [class*='checkbox-slider--c'].checkbox-slider-md input + span:before { top: 20px; } .form-horizontal [class*='checkbox-slider--c'].checkbox-slider-lg input + span:before { top: 25px; } /******************************************************* Slider C-weight *******************************************************/ .checkbox-slider--c-weight { position: relative; } .checkbox-slider--c-weight input[type="checkbox"] { display: block; position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: 0%; height: 0%; margin: 0 0; cursor: pointer; opacity: 0; } .checkbox-slider--c-weight input[type="checkbox"]:focus + *:before { outline: solid #66afe9 2px; } .checkbox-slider--c-weight input + span { cursor: pointer; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .checkbox-slider--c-weight input + span:before { position: absolute; left: 0px; display: inline-block; } .checkbox-slider--c-weight input + span > h4 { display: inline; } .checkbox-slider--c-weight input + span { padding-left: 40px; } .checkbox-slider--c-weight input + span:before { content: ""; height: 20px; width: 40px; background: rgba(100, 100, 100, 0.2); box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.8); transition: background 0.2s ease-out; } .checkbox-slider--c-weight input + span:after { width: 20px; height: 20px; position: absolute; left: 0px; top: 0; display: block; background: #FFF; transition: margin-left 0.1s ease-in-out; text-align: center; font-weight: bold; content: ""; } .checkbox-slider--c-weight input:checked + span:after { margin-left: 20px; content: ""; } .checkbox-slider--c-weight input:checked + span:before { transition: background 0.2s ease-in; } .checkbox-slider--c-weight input + span { padding-left: 40px; } .checkbox-slider--c-weight input + span:before { height: 2px !important; top: 10px; box-shadow: none; width: 40px; background: #555555; } .checkbox-slider--c-weight input + span:after { box-shadow: none; width: 20px; border: solid #555555 2px; border-radius: 20px; } .checkbox-slider--c-weight input:checked + span:after { background: #5cb85c; margin-left: 20px; border: solid #5cb85c 2px; animation: splashIn ease-in 0.3s normal; } .checkbox-slider--c-weight input:checked + span:before { background: #5cb85c; } .checkbox-slider--c-weight.checkbox-slider-sm input + span:before { top: 4px; } .checkbox-slider--c-weight.checkbox-slider-md input + span:before { top: 14px; } .checkbox-slider--c-weight.checkbox-slider-md input + span:after { width: 30px; border-radius: 30px; } .checkbox-slider--c-weight.checkbox-slider-lg input + span:before { top: 19px; } .checkbox-slider--c-weight.checkbox-slider-lg input + span:after { width: 40px; border-radius: 40px; } .checkbox-slider--c-weight input + span:before { height: 1px !important; } .checkbox-slider--c-weight input:checked + span:before { height: 2px !important; } .checkbox-slider--c-weight input:not(:checked) + span:after { transform: scale(0.7); left: -6px; } /****************************************************** State Disabled *******************************************************/ .checkbox-slider--default input:disabled + span:after { background: #777777; } .checkbox-slider--default input:disabled + span:before { box-shadow: 0 0 0 black; } .checkbox-slider--default input:disabled + span { color: #777777; } .checkbox-slider--a-rounded input:disabled + span:after, .checkbox-slider--a input:disabled + span:after { background: #777777; color: #FFF; } .checkbox-slider--a-rounded input:disabled + span:before, .checkbox-slider--a input:disabled + span:before { box-shadow: 0 0 0 black; } .checkbox-slider--a-rounded input:disabled + span, .checkbox-slider--a input:disabled + span { color: #777777; } .checkbox-slider--b-flat input:disabled + span:after, .checkbox-slider--b input:disabled + span:after { border: solid transparent 2px; border-radius: 40px; } .checkbox-slider--b-flat input:disabled + span:before, .checkbox-slider--b input:disabled + span:before { box-shadow: 0 0 0 black; } .checkbox-slider--b-flat input:disabled + span, .checkbox-slider--b input:disabled + span { color: #777777; } .checkbox-slider--b-flat input:disabled:checked + span:before, .checkbox-slider--b input:disabled:checked + span:before { background: #777777; } .checkbox-slider--c-weight input:disabled:checked + span:after, .checkbox-slider--c input:disabled:checked + span:after { background: #777777; } .checkbox-slider--c-weight input:disabled + span:after, .checkbox-slider--c input:disabled + span:after { border-color: #777777; } .checkbox-slider--c-weight input:disabled + span:before, .checkbox-slider--c input:disabled + span:before { background: #777777; } .checkbox-slider--c-weight input:disabled + span, .checkbox-slider--c input:disabled + span { color: #777777; } /******************************************************* Indicators *******************************************************/ input:checked + .indicator-primary { color: #337ab7; } input:checked + .indicator-success { color: #5cb85c; } input:checked + .indicator-info { color: #5bc0de; } input:checked + .indicator-warning { color: #f0ad4e; } input:checked + .indicator-danger { color: #d9534f; } /******************************************************* Sizes *******************************************************/ .checkbox-slider-sm { line-height: 10px; } .checkbox-slider-sm input + span { padding-left: 20px; } .checkbox-slider-sm input + span:before { width: 20px; } .checkbox-slider-sm input + span:after, .checkbox-slider-sm input + span:before { height: 10px; line-height: 10px; } .checkbox-slider-sm input + span:after { width: 10px; vertical-align: middle; } .checkbox-slider-sm input:checked + span:after { margin-left: 10px; } .checkbox-slider-md { line-height: 30px; } .checkbox-slider-md input + span { padding-left: 60px; } .checkbox-slider-md input + span:before { width: 60px; } .checkbox-slider-md input + span:after, .checkbox-slider-md input + span:before { height: 30px; line-height: 30px; } .checkbox-slider-md input + span:after { width: 30px; vertical-align: middle; } .checkbox-slider-md input:checked + span:after { margin-left: 30px; } .checkbox-slider-lg { line-height: 40px; } .checkbox-slider-lg input + span { padding-left: 80px; } .checkbox-slider-lg input + span:before { width: 80px; } .checkbox-slider-lg input + span:after, .checkbox-slider-lg input + span:before { height: 40px; line-height: 40px; } .checkbox-slider-lg input + span:after { width: 40px; vertical-align: middle; } .checkbox-slider-lg input:checked + span:after { margin-left: 40px; } /******************************************************* Variations *******************************************************/ .checkbox-slider-primary.checkbox-slider--default input:checked + span:after, .checkbox-slider-primary.checkbox-slider--a input:checked + span:after, .checkbox-slider-primary.checkbox-slider--a-rounded input:checked + span:after, .checkbox-slider-primary.checkbox-slider--c input:checked + span:after, .checkbox-slider-primary.checkbox-slider--c-weight input:checked + span:after { background: #337ab7; background-clip: content-box; } .checkbox-slider-primary.checkbox-slider--c input:checked + span:after, .checkbox-slider-primary.checkbox-slider--c-weight input:checked + span:after { border-color: #337ab7; } .checkbox-slider-primary.checkbox-slider--b input:checked + span:before, .checkbox-slider-primary.checkbox-slider--b-flat input:checked + span:before, .checkbox-slider-primary.checkbox-slider--c input:checked + span:before, .checkbox-slider-primary.checkbox-slider--c-weight input:checked + span:before { background: #337ab7; } .checkbox-slider-info.checkbox-slider--default input:checked + span:after, .checkbox-slider-info.checkbox-slider--a input:checked + span:after, .checkbox-slider-info.checkbox-slider--a-rounded input:checked + span:after, .checkbox-slider-info.checkbox-slider--c input:checked + span:after, .checkbox-slider-info.checkbox-slider--c-weight input:checked + span:after { background: #5bc0de; background-clip: content-box; } .checkbox-slider-info.checkbox-slider--c input:checked + span:after, .checkbox-slider-info.checkbox-slider--c-weight input:checked + span:after { border-color: #5bc0de; } .checkbox-slider-info.checkbox-slider--b input:checked + span:before, .checkbox-slider-info.checkbox-slider--b-flat input:checked + span:before, .checkbox-slider-info.checkbox-slider--c input:checked + span:before, .checkbox-slider-info.checkbox-slider--c-weight input:checked + span:before { background: #5bc0de; } .checkbox-slider-warning.checkbox-slider--default input:checked + span:after, .checkbox-slider-warning.checkbox-slider--a input:checked + span:after, .checkbox-slider-warning.checkbox-slider--a-rounded input:checked + span:after, .checkbox-slider-warning.checkbox-slider--c input:checked + span:after, .checkbox-slider-warning.checkbox-slider--c-weight input:checked + span:after { background: #f0ad4e; background-clip: content-box; } .checkbox-slider-warning.checkbox-slider--c input:checked + span:after, .checkbox-slider-warning.checkbox-slider--c-weight input:checked + span:after { border-color: #f0ad4e; } .checkbox-slider-warning.checkbox-slider--b input:checked + span:before, .checkbox-slider-warning.checkbox-slider--b-flat input:checked + span:before, .checkbox-slider-warning.checkbox-slider--c input:checked + span:before, .checkbox-slider-warning.checkbox-slider--c-weight input:checked + span:before { background: #f0ad4e; } .checkbox-slider-danger.checkbox-slider--default input:checked + span:after, .checkbox-slider-danger.checkbox-slider--a input:checked + span:after, .checkbox-slider-danger.checkbox-slider--a-rounded input:checked + span:after, .checkbox-slider-danger.checkbox-slider--c input:checked + span:after, .checkbox-slider-danger.checkbox-slider--c-weight input:checked + span:after { background: #d9534f; background-clip: content-box; } .checkbox-slider-danger.checkbox-slider--c input:checked + span:after, .checkbox-slider-danger.checkbox-slider--c-weight input:checked + span:after { border-color: #d9534f; } .checkbox-slider-danger.checkbox-slider--b input:checked + span:before, .checkbox-slider-danger.checkbox-slider--b-flat input:checked + span:before, .checkbox-slider-danger.checkbox-slider--c input:checked + span:before, .checkbox-slider-danger.checkbox-slider--c-weight input:checked + span:before { background: #d9534f; } ================================================ FILE: gui/static/fonts/icomoon.json ================================================ { "selection": [ { "order": 40, "prevSize": 28, "name": "money" }, { "order": 41, "prevSize": 20, "name": "load_balance" }, { "order": 42, "ligatures": "", "prevSize": 20, "name": "fusionpbx_full" }, { "order": 43, "ligatures": "", "prevSize": 20, "name": "gryphon" }, { "order": 44, "ligatures": "", "prevSize": 20, "name": "fusionpbx" }, { "order": 45, "ligatures": "", "prevSize": 20, "name": "asterisk" }, { "order": 46, "ligatures": "", "prevSize": 20, "name": "kamailio" }, { "order": 47, "ligatures": "", "prevSize": 20, "name": "flowroute" }, { "order": 48, "ligatures": "", "prevSize": 20, "name": "freepbx" }, { "order": 49, "ligatures": "", "prevSize": 20, "name": "zencommunication" }, { "order": 50, "name": "call_failfwd", "prevSize": 20 }, { "order": 51, "name": "call_hardfwd", "prevSize": 20 }, { "order": 52, "name": "transnexus", "prevSize": 20, "codes": [ 59658, 59659 ] }, { "order": 53, "ligatures": "", "prevSize": 20, "name": "circle-up" }, { "order": 54, "ligatures": "", "prevSize": 20, "name": "circle-down" } ], "metadata": { "name": "icomoon", "iconsHash": 510358743 }, "height": 1024, "prevSize": 32, "icons": [ { "paths": [ "M438.857 658.286h219.429v-54.857h-73.143v-256h-65.143l-84.571 78.286 44 45.714c13.714-12 22.286-18.286 31.429-32.571h1.143v164.571h-73.143v54.857zM731.429 512c0 104-62.857 237.714-182.857 237.714s-182.857-133.714-182.857-237.714 62.857-237.714 182.857-237.714 182.857 133.714 182.857 237.714zM1024 658.286v-292.571c-80.571 0-146.286-65.714-146.286-146.286h-658.286c0 80.571-65.714 146.286-146.286 146.286v292.571c80.571 0 146.286 65.714 146.286 146.286h658.286c0-80.571 65.714-146.286 146.286-146.286zM1097.143 182.857v658.286c0 20-16.571 36.571-36.571 36.571h-1024c-20 0-36.571-16.571-36.571-36.571v-658.286c0-20 16.571-36.571 36.571-36.571h1024c20 0 36.571 16.571 36.571 36.571z" ], "width": 1097, "tags": [ "money" ], "grid": 14 }, { "paths": [ "M870.4 614.4h-307.2v204.8h51.2v204.8h-204.8v-204.8h51.2v-204.8h-307.2v204.8h51.2v204.8h-204.8v-204.8h51.2v-204.8c0-56.554 45.846-102.4 102.4-102.4v0h307.2v-204.8h-102.4v-307.2h307.2v307.2h-102.4v204.8h307.2c56.554 0 102.4 45.846 102.4 102.4v0 204.8h51.2v204.8h-204.8v-204.8h51.2v-204.8z" ], "tags": [ "load_balance" ], "defaultCode": 57617, "grid": 20 }, { "paths": [ "M170.797 60.487c-133.263 40.003-208.245 79.971-208.245 109.95s-6.676 31.64 104.97-6.676c118.305-41.636 258.208-58.291 354.879-43.333 74.945 10.014 76.642 11.656 66.645 61.62-4.998 26.678-24.982 71.616-44.965 98.294-43.333 61.62-31.64 98.294 34.978 98.294 38.307 0 58.291-11.656 83.3-53.32 18.324-30.007 36.665-74.945 41.636-99.954 11.656-48.294 58.291-64.949 58.291-20.011 0 13.353-14.985 61.62-34.978 106.621-33.336 78.274-33.336 83.3-6.676 141.627 16.682 31.64 38.307 56.658 48.294 54.962 28.311-8.327 123.276-213.27 134.959-288.197 8.327-64.949 8.327-64.949-18.324 14.985-13.353 43.333-28.311 78.274-31.64 74.945-6.676-6.676-91.609-231.557-91.609-241.554 0-6.676 51.633 3.347 119.947 20.011 14.985 4.998 31.64 20.011 34.978 33.336 4.998 14.985 6.676 13.353 4.998-4.998-3.347-49.991-116.609-76.642-343.195-81.603-164.939-4.998-229.925 0-308.226 24.982zM878.913 173.785c0 26.678-20.011 98.294-43.333 163.243-23.349 63.316-43.333 129.934-44.965 144.956s-6.676 26.678-13.353 24.982c-4.998-1.669-30.007 21.653-56.658 51.633-36.665 43.333-49.991 49.991-68.287 34.978-20.011-14.985-21.653-14.985-14.985 4.998 14.985 38.307-40.003 51.633-86.629 20.011l-41.636-26.678 38.307 51.633c26.678 36.665 33.336 56.658 20.011 69.974-11.656 11.656-40.003-8.327-93.296-66.645-66.645-76.642-81.603-84.942-143.259-84.942-38.307 0-68.287 4.998-68.287 10.014 0 23.349 119.947 186.565 169.91 231.557 49.991 46.662 51.633 46.662 14.985 4.998-23.349-26.678-36.665-51.633-30.007-56.658 18.324-20.011 244.883 11.656 234.886 33.336-4.998 10.014 16.682-10.014 48.294-44.965 81.603-88.271 156.585-239.912 194.919-388.206 40.003-156.585 43.333-223.257 8.327-223.257-16.682 0-24.982 16.682-24.982 44.965zM404.060 595.3c13.353 21.653-8.327 21.653-41.636 0-21.653-13.353-21.653-16.682 3.347-16.682 14.985 0 33.336 6.676 38.307 16.682zM162.461 195.456c-24.982 8.327 6.676 10.014 74.945 4.998l116.609-8.327-53.32 54.962c-30.007 30.007-78.274 63.316-108.318 71.616-40.003 13.353-54.962 28.311-54.962 56.658-1.669 48.294-69.974 119.947-81.603 84.942-4.998-13.353-21.653-53.32-34.978-89.967l-24.982-66.645 10.014 58.291c4.998 31.64 38.307 121.634 76.642 198.248 49.991 104.97 96.625 169.91 186.565 259.904 128.301 129.934 174.936 163.243 208.245 143.259 13.353-10.014-14.985-48.294-94.956-128.301-129.934-129.934-226.586-298.212-184.923-324.89 13.353-8.327 64.949-14.985 116.609-14.985 108.318 0 136.601-30.007 86.629-88.271-26.678-28.311-44.965-31.64-141.627-23.349-86.629 4.998-113.28 1.669-113.28-14.985 0-23.349 98.294-56.658 169.91-56.658 31.64 0 53.32-14.985 78.274-56.658 20.011-30.007 34.978-59.987 34.978-66.645 0-14.985-216.599-10.014-266.563 6.676zM9.178 247.134c-16.682 8.327-30.007 24.982-30.007 36.665 0 13.353 4.998 14.985 13.353 1.669 6.676-10.014 26.678-26.678 44.965-36.665s26.678-18.324 16.682-18.324c-8.327 0-30.007 6.676-44.965 16.682zM1028.839 536.982v208.245h49.991c49.991 0 49.991-1.669 49.991-91.609v-91.609h74.945c68.287 0 74.945-3.347 74.945-41.636s-6.676-41.636-74.945-41.636c-63.316 0-74.945-4.998-74.945-33.336s11.656-33.336 74.945-33.336c68.287 0 74.945-3.347 74.945-41.636 0-40.003-3.347-41.636-124.972-41.636h-124.972v208.245zM2761.596 536.982v191.59h41.636c38.307 0 41.636-6.676 41.636-71.616 0-69.974 1.669-73.312 61.62-88.271 84.942-20.011 119.947-81.603 89.967-154.943-13.353-28.311-30.007-51.633-38.307-51.633-10.014 0-8.327 10.014 3.347 24.982 11.656 13.353 14.985 38.307 8.327 53.32-10.014 28.311-11.656 28.311-26.678 0-21.653-38.307-114.976-40.003-114.976-1.669 0 23.349-4.998 23.349-24.982 6.676-13.353-11.656-24.982-34.978-24.982-51.633 0-23.349 16.682-31.64 79.971-36.665l78.274-4.998-174.936-6.676v191.59zM2919.868 462.009c0 13.353-16.682 26.678-36.665 30.007-30.007 4.998-38.307-3.347-38.307-30.007s8.327-34.978 38.307-30.007c20.011 3.347 36.665 16.682 36.665 30.007zM3083.166 357.057c24.982 3.347 61.62 3.347 83.3 0 20.011-3.347 0-6.676-46.662-6.676-44.965 0-63.316 3.347-36.665 6.676zM3294.776 355.415c0 4.998 23.349 43.333 53.32 84.942 28.311 43.333 51.633 86.629 51.633 96.625s-23.349 53.32-51.633 96.625c-63.316 89.967-63.316 94.956-20.011 94.956 20.011 0 48.294-23.349 69.974-58.291 21.653-31.64 43.333-58.291 49.991-58.291s30.007 26.678 49.991 58.291c24.982 38.307 51.633 58.291 76.642 58.291 41.636 0 41.636 0-33.336-111.647-24.982-38.307-46.662-73.312-46.662-79.971s23.349-44.965 53.32-88.271c44.965-68.287 46.662-73.312 10.014-41.636-53.32 49.991-66.645 48.294-43.333-1.669 10.014-24.982 34.978-44.965 61.62-48.294l43.333-6.676-44.965-3.347c-33.336-1.669-51.633 11.656-79.971 56.658-20.011 31.64-41.636 58.291-48.294 58.291-4.998 0-24.982-26.678-41.636-58.291-23.349-41.636-43.333-58.291-71.616-58.291-20.011 0-38.307 4.998-38.307 10.014zM3373.086 382.066c10.014 11.656 21.653 34.978 26.678 49.991 14.985 41.636-30.007 20.011-63.316-30.007-21.653-33.336-21.653-40.003-1.669-40.003 11.656 0 30.007 10.014 38.307 20.011zM3246.454 390.366c26.678 44.965 8.327 68.287-34.978 41.636-44.965-28.311-91.609-24.982-118.305 8.327-21.653 24.982-24.982 21.653-33.336-21.653-6.676-26.678-11.656 31.64-13.353 129.934l-1.669 179.898h96.625c73.312 0 104.97-8.327 124.972-30.007 30.007-33.336 38.307-128.301 13.353-143.259-8.327-4.998-10.014-41.636-6.676-81.603 6.676-54.962 1.669-76.642-21.653-94.956-26.678-23.349-26.678-21.653-4.998 11.656zM3194.794 462.009c0 24.982-11.656 33.336-40.003 33.336-31.64 0-40.003-8.327-38.307-33.336 1.669-23.349 13.353-33.336 40.003-33.336s38.307 10.014 38.307 33.336zM3196.482 578.645c24.982 0 16.682 66.645-10.014 76.642-44.965 18.324-71.616-4.998-61.62-51.633 4.998-30.007 14.985-40.003 31.64-34.978 13.353 4.998 31.64 10.014 40.003 10.014zM1312.084 503.673c0 131.63 26.678 211.574 74.945 231.557 61.62 23.349 143.259 10.014 183.227-31.64 38.307-36.665 41.636-53.32 41.636-183.227 0-139.93 0-141.627-41.636-141.627s-41.636 1.669-41.636 133.263c0 113.28-4.998 134.959-31.64 149.918-41.636 21.653-44.965 21.653-74.945-10.014-20.011-18.324-26.678-59.987-26.678-149.918 0-119.947-1.669-123.276-41.636-123.276s-41.636 3.347-41.636 124.972zM1677.004 413.706c-53.32 53.32-36.665 113.28 48.294 169.91 38.307 26.678 69.974 53.32 69.974 61.62 0 28.311-56.658 33.336-86.629 4.998-26.678-24.982-33.336-24.982-54.962-3.347-23.349 21.653-21.653 30.007 8.327 61.62 49.991 53.32 136.601 49.991 181.594-6.676 20.011-24.982 34.978-54.962 34.978-68.287 0-23.349-49.991-76.642-118.305-123.276-49.991-34.978-30.007-71.616 28.311-44.965 31.64 14.985 44.965 14.985 61.62-1.669 14.985-16.682 11.656-26.678-20.011-53.32-49.991-41.636-109.95-40.003-153.256 3.347zM1895.209 561.991v183.227h83.3v-366.517h-83.3v183.227zM2100.151 410.368c-99.954 63.316-119.947 178.265-44.965 266.563 109.95 131.63 323.202 59.987 323.202-108.318 0-146.589-154.943-234.886-278.201-158.281zM2255.113 475.335c69.974 44.965 58.291 158.281-18.324 186.565-71.616 26.678-141.627-21.653-141.627-98.294s96.625-131.63 159.914-88.271zM2411.725 561.991v183.227h41.636c38.307 0 40.003-6.676 44.965-103.292l4.998-103.292 63.316 103.292c43.333 69.974 74.945 103.292 96.625 103.292 30.007 0 31.64-13.353 31.64-183.227s-1.669-183.227-33.336-183.227c-28.311 0-31.64 13.353-36.665 111.647l-4.998 111.647-73.312-111.647c-46.662-71.616-83.3-111.647-103.292-111.647-28.311 0-31.64 14.985-31.64 183.227z" ], "width": 3657, "tags": [ "fusionpbx_full" ], "defaultCode": 59648, "grid": 20 }, { "paths": [ "M130.768 87.178c75.416 34.595 298.897 85.795 375.005 85.795 31.135 0 31.135-0.692 37.362 93.405 13.146 213.103 112.778 371.546 251.157 401.297 26.984 5.535 26.984 7.611 4.843 25.6-26.292 20.757-32.519 35.978-22.832 58.811 4.843 10.378 8.303 22.141 8.995 26.292 1.384 20.065 11.070 24.216 31.135 13.838 22.832-11.762 45.665-13.146 51.892-3.459 2.076 4.151-2.076 6.919-9.686 6.919s-15.914 3.459-17.989 6.919c-2.768 4.151 4.151 6.919 15.222 6.919 20.065 0 50.508 16.605 50.508 27.676 0 2.768-11.070 2.768-23.524 0-13.146-2.768-25.6-4.151-26.984-2.768-1.384 2.076 7.611 13.838 21.449 27.676 13.146 13.146 22.141 25.6 20.065 27.676s-15.914-2.768-31.827-10.378c-15.222-8.303-35.978-14.53-46.357-14.53-21.449 0-78.876 25.6-78.876 35.286 0 3.459-4.843 6.227-9.686 6.227-15.222 0 0-38.054 22.141-55.351 16.605-13.838 17.297-14.53 4.151-24.216-6.919-5.535-18.681-8.303-24.908-6.227-14.53 4.151-59.503-17.989-79.568-40.13-20.757-22.141-29.751-49.124-29.751-88.562 0-31.827-0.692-33.903-24.216-41.514-32.519-10.378-87.178-61.578-107.935-99.632l-16.605-31.827h-42.205c-53.968 0-94.097-12.454-141.838-43.589-42.897-27.676-92.714-72.649-87.178-78.876 2.076-2.076 13.838 3.459 25.6 11.762 32.519 23.524 94.789 42.205 155.676 48.432 30.443 2.768 56.735 3.459 58.119 2.076 1.384-0.692-0.692-9.686-4.843-19.373-6.227-13.838-13.146-17.297-34.595-17.297-71.957 0-168.13-43.589-222.097-100.324-33.903-35.978-83.027-121.081-69.881-121.081 3.459 0 20.065 12.454 35.286 27.676 54.659 52.584 143.222 95.481 222.789 106.551 20.757 2.768 25.6 1.384 25.6-8.303 0-20.757-9.686-28.368-44.281-35.978-76.8-17.297-170.205-71.265-248.389-144.605l-33.211-30.443 2.076 352.173c2.076 348.022 2.076 352.865 17.297 380.541 23.524 43.589 45.665 67.114 86.486 89.254l36.67 20.757h747.243v-740.324l-19.373-39.438c-20.757-42.205-44.281-65.73-90.638-90.638-28.368-15.222-31.827-15.222-391.611-16.605l-363.243-1.384 37.362 17.297zM635.849 207.568c8.303 0 26.292-4.843 40.13-10.378 28.368-11.762 80.259-13.838 99.632-2.768 7.611 3.459 20.065 17.297 26.984 29.059 10.378 16.605 18.681 22.141 35.286 22.832 42.897 2.076 68.497 28.368 68.497 69.881 0 16.605-0.692 17.297-11.762 7.611-24.216-20.757-36.67-20.065-58.119 0.692-31.135 31.135-24.216 71.957 17.989 111.395 27.676 25.6 40.13 54.659 40.13 93.405 0 35.286-13.146 69.881-32.519 85.795-8.303 6.919-18.681 6.919-47.049 1.384-74.032-15.914-128-63.654-168.822-148.065-13.146-26.984-23.524-52.584-23.524-56.735 0-3.459 10.378-11.762 22.832-17.297 29.751-13.838 31.135-29.059 4.151-40.822-26.292-11.070-51.2-61.578-58.119-116.238-5.535-42.897 0-56.043 16.605-40.822 6.919 6.227 18.681 11.070 27.676 11.070zM214.486 528.605c8.995 4.151 21.449 16.605 26.984 28.368 5.535 11.070 15.914 31.827 22.832 45.665 21.449 42.897 15.914 64.346-26.292 112.086-24.908 26.984-28.368 42.205-15.914 69.881 7.611 16.605 45.665 40.822 55.351 35.286 2.076-2.076 6.227-21.449 8.995-44.281 6.919-60.886 44.973-132.843 87.87-166.746 43.589-34.595 61.578-40.13 70.573-23.524 3.459 7.611 24.908 31.827 47.741 53.968 22.141 22.832 40.13 41.514 40.13 42.897 0 0.692-10.378 4.151-22.141 6.919-32.519 6.919-54.659 23.524-65.038 49.124-13.838 32.519-12.454 36.67 16.605 37.362 40.13 0 84.411 20.757 107.243 49.124 19.373 24.216 22.141 46.357 3.459 27.676-4.843-4.843-13.146-8.303-18.681-8.303-8.303 0-8.303 2.076-2.076 8.303 4.843 4.843 8.303 12.454 8.303 17.989 0 6.919-2.768 6.919-15.222-1.384-8.303-6.227-20.065-11.070-25.6-11.070-7.611 0-7.611 2.076 1.384 12.454 17.989 19.373 14.53 22.141-12.454 11.070-17.297-6.919-52.584-9.686-125.924-9.686-116.238 0-144.605-5.535-178.508-35.286-47.741-42.205-49.124-104.476-2.768-154.984 14.53-15.914 26.984-33.211 26.984-38.054 0-14.53-21.449-34.595-35.978-34.595-13.146 0-33.211-35.978-44.281-78.184-4.151-19.373-4.151-19.373 22.141-15.222 15.222 2.768 34.595 8.303 44.281 13.146z" ], "width": 1038, "tags": [ "gryphon" ], "defaultCode": 59649, "grid": 20 }, { "paths": [ "M288.768 102.4c-116.736 28.672-225.28 83.968-212.992 112.64 8.192 22.528 22.528 20.48 81.92-4.096 90.112-36.864 333.824-57.344 372.736-30.72 24.576 18.432 22.528 34.816-14.336 108.544-26.624 51.2-36.864 96.256-26.624 106.496 36.864 36.864 96.256 2.048 135.168-81.92 63.488-135.168 100.352-112.64 40.96 26.624-26.624 65.536-26.624 79.872 2.048 137.216l32.768 61.44 38.912-49.152c45.056-57.344 108.544-253.952 94.208-290.816-6.144-12.288-12.288-4.096-12.288 20.48-4.096 77.824-40.96 53.248-55.296-32.768-10.24-67.584-22.528-81.92-73.728-92.16-83.968-16.384-317.44-12.288-403.456 8.192zM794.624 137.216c14.336 6.144 32.768 4.096 38.912-2.048 8.192-6.144-4.096-12.288-26.624-10.24-22.528 0-28.672 6.144-12.288 12.288zM888.832 260.096c-8.192 53.248-16.384 112.64-14.336 129.024 4.096 45.056-53.248 147.456-112.64 198.656-47.104 43.008-88.064 57.344-135.168 49.152-10.24-2.048-12.288 10.24-6.144 26.624 32.768 81.92-49.152 49.152-194.56-79.872-43.008-34.816-77.824-40.96-77.824-10.24 0 28.672 94.208 178.176 124.928 196.608 16.384 8.192 18.432 6.144 10.24-8.192-10.24-18.432 22.528-24.576 116.736-24.576 100.352 0 139.264-8.192 163.84-36.864 92.16-102.4 210.944-471.040 163.84-518.144-14.336-14.336-26.624 6.144-38.912 77.824zM387.072 223.232l49.152 12.288-65.536 57.344c-65.536 53.248-65.536 55.296-18.432 55.296 57.344 0 151.552-88.064 129.024-120.832-6.144-12.288-43.008-22.528-77.824-20.48-59.392 2.048-61.44 4.096-16.384 16.384zM262.144 239.616c14.336 6.144 32.768 4.096 38.912-2.048 8.192-6.144-4.096-12.288-26.624-10.24-22.528 0-28.672 6.144-12.288 12.288zM288.768 403.456c-26.624 6.144-65.536 30.72-86.016 53.248-32.768 38.912-40.96 40.96-59.392 14.336-38.912-57.344-20.48 2.048 32.768 104.448 86.016 169.984 315.392 395.264 358.4 352.256 10.24-10.24-16.384-47.104-69.632-92.16-92.16-75.776-221.184-288.768-192.512-315.392 8.192-10.24 55.296-16.384 102.4-18.432 100.352 0 126.976-26.624 81.92-75.776-32.768-36.864-69.632-40.96-167.936-22.528z" ], "tags": [ "fusionpbx" ], "defaultCode": 59650, "grid": 20 }, { "paths": [ "M668.374 102.904c-2.522 1.009-23.204 6.053-45.399 11.098-88.78 19.673-160.914 57.001-240.615 125.1-58.514 49.939-123.586 129.135-143.764 175.543-3.531 8.071-8.575 14.124-11.602 14.124-2.522 0-5.044-1.009-5.044-2.018 0-6.558 31.779-75.665 44.39-97.86 8.575-14.124 15.133-26.231 14.124-26.231-2.522 0-54.479 65.576-66.081 83.232-24.717 39.346-44.895 78.692-57.001 113.498-12.611 34.806-13.62 44.39-13.115 110.975 0 67.090 1.513 76.169 13.62 106.94 38.841 97.86 105.427 157.888 217.915 196.225 32.284 11.098 43.381 12.106 131.153 12.106 84.24 0 101.391-1.513 141.241-11.602 50.443-13.115 84.745-25.222 134.179-47.417l33.293-15.133 41.364 13.115c22.7 7.062 45.399 13.115 50.443 13.115 5.549 0 29.762 5.549 54.983 11.602l45.903 12.106-35.31-68.099c-19.168-37.833-34.806-71.63-34.806-75.161 0-4.035 5.044-12.106 11.602-18.16 33.797-32.788 85.249-105.931 113.498-162.428 74.152-148.303 53.974-300.642-53.47-404.556-31.275-29.762-49.939-39.346-26.231-13.62 24.717 27.239 38.841 48.426 50.443 75.665 16.142 36.824 11.602 35.815-15.133-5.044-61.036-90.798-130.144-123.586-269.872-127.117-41.364-1.009-77.683-1.009-80.709 0zM809.616 143.259c130.648 23.204 215.393 126.613 214.889 262.81 0 96.851-46.912 205.809-123.586 288.536l-28.753 31.275 27.239 41.868c30.266 45.903 38.841 61.541 38.841 68.603 0 4.54-28.753-4.54-95.338-31.275-18.16-7.062-36.319-13.115-41.364-13.115-4.54 0-27.239 7.567-50.948 17.151-23.204 9.584-67.090 23.204-97.356 30.77-49.939 12.611-60.532 13.62-120.56 11.098-73.143-3.027-120.56-15.133-172.012-44.39-114.002-64.063-164.445-190.171-126.108-314.262 23.204-75.161 55.992-128.126 120.56-192.694 124.091-123.586 298.625-183.614 454.495-156.374zM574.045 322.837l-1.513 80.709-32.788-21.186c-18.16-11.602-45.399-29.257-60.532-39.346s-30.77-19.673-34.301-22.195c-3.531-2.018-7.567-0.504-9.584 5.549-1.513 5.044-7.062 14.629-11.602 21.691s-14.124 23.204-21.691 36.319l-13.62 23.708 38.841 21.691c21.186 12.106 50.948 28.753 66.585 37.328 15.133 8.575 28.248 17.655 29.257 20.177s-9.080 10.593-22.195 17.655c-27.239 15.133-108.958 61.036-111.984 63.054-1.009 1.009 3.027 8.071 9.080 16.142 6.053 8.575 10.593 16.142 10.593 17.655 0 5.044 31.779 53.974 34.806 53.974 1.513 0 8.575-4.54 15.637-10.089s14.629-10.089 16.646-10.089c2.522 0 11.602-5.044 20.177-11.602 17.151-12.106 24.213-16.142 58.010-35.815l21.186-12.611v155.87h101.391l-1.513-76.169c-1.513-68.603-0.504-75.665 7.062-73.647 4.54 1.513 34.806 18.664 67.090 38.841 31.779 20.177 59.019 35.815 60.028 34.806s13.115-21.186 27.239-44.39l25.222-42.877-66.081-36.824c-36.319-20.177-68.099-38.841-70.116-40.859-3.531-3.531 60.532-42.877 118.037-73.143l19.168-9.584-9.080-13.62c-14.629-22.195-20.682-32.284-31.779-53.47-6.053-11.098-11.602-20.682-12.106-21.691-1.009-1.009-16.142 8.071-33.293 20.177-33.797 23.204-99.373 64.567-101.391 64.567-1.009 0-0.504-36.319 0-80.709l2.018-80.709h-101.896l-1.009 80.709z" ], "width": 1251, "tags": [ "asterisk" ], "defaultCode": 59651, "grid": 20 }, { "paths": [ "M1492.234 124.822c-152.161 5.636-351.326 56.836-588.74 151.322-48.562 19.425-109.954 45.325-109.235 46.164 0.36 0.24 7.674-1.799 16.547-4.556 28.897-8.993 33.814-10.432 80.697-23.981 47.483-13.669 112.472-29.737 170.867-42.447 149.044-32.255 294.010-47.723 430.464-45.924 56.236 0.839 86.213 2.998 125.062 9.233 56.236 8.993 98.683 26.739 125.662 52.399l9.353 8.873 1.319-4.556c3.477-12.47 5.036-26.499 4.437-40.528-1.079-24.821-7.554-39.929-24.701-57.075-25.060-25.060-61.992-38.010-129.019-45.444-21.104-2.278-68.946-4.916-82.735-4.556-3.237 0.12-16.787 0.6-29.977 1.079zM788.984 323.747c-0.959 0.6-1.199 1.199-0.6 1.199 0.719 0 2.038-0.6 2.998-1.199s1.319-1.199 0.6-1.199c-0.6 0-2.038 0.6-2.998 1.199zM1687.681 382.621c-74.582 4.916-125.182 76.5-106.237 150.243 2.758 10.312 11.031 28.778 16.787 37.171 14.629 21.223 39.449 38.73 64.989 45.804 14.029 3.837 40.289 5.276 54.677 2.878 38.49-6.115 71.344-30.096 87.891-64.15 7.074-14.509 10.312-25.66 11.991-41.967 2.998-27.339-4.197-56.476-19.665-79.498-6.355-9.353-21.343-24.461-30.576-30.696-22.183-14.988-49.641-21.823-79.858-19.785zM1715.74 420.871c36.571 8.873 60.433 40.768 60.433 80.937 0 37.89-21.343 68.107-55.517 78.659-12.71 3.957-31.655 3.957-44.365 0-22.183-6.835-38.37-20.984-48.682-42.687-10.672-22.063-10.312-53.238 0.719-75.781 16.187-32.974 51.68-49.641 87.412-41.128zM444.373 389.815c-1.799 4.317-19.065 44.126-37.291 85.613-3.837 8.513-9.473 21.463-12.59 28.778-3.118 7.194-15.228 34.893-26.859 61.392s-21.104 48.802-21.104 49.521c0 0.839 6.235 1.199 20.024 0.959l20.024-0.36 16.307-38.37c8.993-21.104 23.861-55.876 32.974-77.34 8.993-21.463 19.185-45.325 22.542-53.119 3.237-7.794 6.355-13.669 6.955-13.19 0.839 0.959 39.449 89.69 39.449 90.889 0 0.36-12.59 0.6-27.818 0.6h-27.938l-6.835 16.547c-3.837 9.113-6.955 16.907-6.955 17.386s18.945 0.959 42.207 1.079l42.207 0.36 6.715 15.588c3.717 8.513 9.113 21.104 11.871 27.818l5.156 12.35h41.847l-0.839-2.758c-0.36-1.439-1.439-3.957-2.278-5.636-1.679-3.237-21.104-47.603-95.326-217.99l-2.278-5.036h-38.25l-1.919 4.916zM976.877 424.708c-32.015 73.143-81.416 186.215-82.496 188.972l-1.079 2.638 20.624-0.24 20.504-0.36 8.393-19.785c7.434-17.626 30.576-72.304 58.874-138.612 5.276-12.59 10.192-23.022 10.792-23.142s4.197 6.955 8.034 15.708c3.717 8.753 12.35 28.658 19.065 44.126s12.47 28.897 12.83 29.617c0.48 1.199-5.516 1.559-27.459 1.559h-28.178l-7.194 17.027-7.074 17.147 85.133 1.199 11.631 27.578 11.511 27.578 21.104 0.36 21.223 0.24-1.079-2.638c-1.079-2.758-6.115-14.149-47.723-109.474-11.511-26.379-23.741-54.198-26.979-61.752-3.357-7.554-10.312-23.622-15.708-35.732l-9.593-21.823h-37.651l-17.506 39.809zM130.698 501.208v115.11h39.569v-66.548l17.506-18.346c9.593-9.952 17.986-18.226 18.466-18.226 0.959 0 11.271 13.43 66.908 86.572l12.59 16.547h23.861c18.825 0 23.741-0.36 23.022-1.559-0.839-1.319-43.646-57.555-81.776-107.556l-16.907-22.183 8.034-8.393c12.83-13.43 42.207-43.766 65.589-67.747l21.583-22.183-23.382-0.36c-12.83-0.12-23.861-0.12-24.701 0.24-1.199 0.48-25.18 25.42-87.412 91.249-11.871 12.47-21.943 22.902-22.422 23.142-0.6 0.24-0.959-25.54-0.959-57.195v-57.675h-39.569v115.11zM625.911 501.208v115.11h39.569l0.24-82.256 0.36-82.376 35.252 52.639c19.305 29.017 35.852 53.238 36.571 54.078 0.959 0.959 10.432-12.35 37.291-52.519 19.785-29.617 36.332-53.838 36.931-53.838 0.48 0 0.839 36.931 0.839 82.136v82.136h39.569v-230.22h-42.687l-2.998 4.437c-1.559 2.518-17.386 27.099-35.133 54.677l-32.255 50.121-3.717-5.636c-2.038-3.118-17.866-27.578-35.013-54.318l-31.415-48.682-21.703-0.36-21.703-0.24v115.11zM1176.281 501.208v115.11h39.569v-230.22h-39.569v115.11zM1278.201 501.208v115.11h161.874v-35.972h-121.105v-194.248h-40.768v115.11zM1485.639 501.208v115.11h39.569v-230.22h-39.569v115.11zM1133.714 708.766c-20.264 5.756-86.093 22.662-107.796 27.698-31.775 7.314-36.452 8.393-52.279 11.751-84.294 17.866-130.458 25.78-221.227 37.77-7.194 0.959-21.823 2.518-32.375 3.477-87.652 8.513-110.194 9.593-195.447 9.712-77.699 0-100.721-1.079-149.283-7.314-82.496-10.312-136.333-30.336-168.588-62.711l-9.712-9.593-2.518 10.432c-14.748 58.754 7.794 99.043 68.706 122.904 47.843 18.705 140.65 28.178 229.98 23.502 64.51-3.357 159.356-17.267 226.863-33.454 5.276-1.319 16.667-3.957 25.18-5.995 101.8-24.101 215.352-59.833 345.93-108.875 14.149-5.396 33.574-12.71 43.166-16.427 24.581-9.593 24.461-9.952-0.6-2.878z" ], "width": 1919, "tags": [ "kamailio" ], "defaultCode": 59652, "grid": 20 }, { "paths": [ "M253.2 236c-26 15.6-25.2 6.4-25.2 276.8 0 236 0.4 245.6 8 258 15.6 26 6.4 25.2 276.8 25.2 236 0 245.6-0.4 258-8 26-15.6 25.2-6.4 25.2-276s0.8-260.4-25.2-276c-12.4-7.6-22-8-258.8-8s-246.4 0.4-258.8 8zM566 320.4c8.8 2.4 24.4 8 35.2 12.4 16 6.8 18.8 10 18.8 19.2 0 10.8-0.8 10.8-27.2 12.8-70.8 5.2-126.4 63.6-131.6 138.4-2.8 36.4-12 58-30.4 68.8-28.8 17.6-70.4 13.2-93.2-9.6-18.4-18.4-23.6-40.8-18-80 10.4-75.6 57.6-133.2 127.6-157.2 30-10 90-12.8 118.8-4.8zM675.6 448c8.8 4.4 20 14.4 24.8 22.8 8 12.8 9.6 20 8.8 47.2-2 91.2-59.6 163.6-146 184-45.6 10.8-94 6.4-134-12-17.2-7.6-21.2-11.6-21.2-19.2 0-8.8 2-10 23.2-12 47.2-5.2 84.8-26.8 110-63.6 15.6-22.8 26.8-58.8 26.8-85.6 0-30 13.6-53.2 38-64 18-8 51.6-6.8 69.6 2.4zM456 328.8c-49.6 17.6-77.6 38.8-103.6 79.2-27.6 42.8-37.6 102.8-22.4 133.6 15.2 30.4 47.6 43.2 80 32 33.2-11.2 42-25.6 46.4-74.4 4-42 16.4-70.8 42.4-97.2 24.8-24.8 56.4-39.6 90-41.2 22-1.6 27.2-2.8 27.2-8.4 0-4.4-9.2-10.4-28.8-18.4-41.2-16-94-18.4-131.2-5.2zM536 328c0 2 3.2 4 7.2 4 10.4 0 44.8 12 44.8 16 0 1.6-8 4-18 4.8-32.4 4-90.8 40-83.6 52 1.6 2.8 1.2 3.2-1.6 1.6-6-3.6-22.4 22.8-30.8 48.8-4 12.4-6.4 29.2-5.2 42.4 0.4 12.4 0 22.4-1.2 22.4-1.6 0-4 4.4-5.2 9.6-2 8-11.6 18-37.2 38.4-6.8 5.6-28.4 4.8-36-1.2-18.4-14.4-33.2-30.8-33.2-36.4 0-3.6-1.6-6.4-4-6.4-2 0-4-10-4-22s1.6-22 3.2-22c2 0 5.6-8.8 8.4-20 6.4-25.2 29.6-61.2 54-83.2 20.8-19.2 60-39.6 83.6-43.2 8-1.6 14.8-4 14.8-6s10-3.6 22-3.6c12 0 22 1.6 22 4zM608 452.8c-24 12.4-32.4 28-36.4 70-4.4 43.2-16.8 70.8-43.6 97.2-24.8 25.2-54.8 39.6-89.6 42.8-35.6 3.2-34.4 11.2 3.6 26 54.4 20.8 104.4 18.8 158-7.2 62.4-29.6 103.6-94.8 104.8-164.4 0.4-34.8-12-56.4-38.4-67.2-20-8.4-38-7.6-58.4 2.8zM644 452c0 2 4 4 8.4 4 7.6 0 26.4 16.4 42.8 37.2 5.6 7.2 6.8 46.8 1.2 46.8-2 0-4.8 6-6 12.8-4.8 25.2-25.6 62-47.6 84.4-26 26.8-70 49.6-102 53.6-11.6 1.2-20.8 4-20.8 5.6 0 2-2.8 3.6-6 3.6s-6-2-6-4c0-2.4-5.2-4-12-4-6.4 0-24-4-38.8-8.8l-27.2-9.2 22-3.2c71.2-10 120.8-64.4 127.6-140 4-40.4 12.4-57.6 34.4-68.8 8.8-4.4 16.8-9.2 17.2-11.2 2-4.4 12.8-3.6 12.8 1.2z" ], "tags": [ "flowroute" ], "defaultCode": 59653, "grid": 20 }, { "paths": [ "M843.419 67.559c-2.266 0.283-9.631 2.266-16.288 4.391-6.657 1.983-12.889 3.682-13.88 3.541-0.991 0-1.558 0.283-0.991 0.708 0.85 0.85-10.622 6.657-12.464 6.232-0.708-0.142-0.85 0.142-0.425 0.85s-2.266 2.549-5.807 4.391c-6.373 2.974-6.657 3.116-17.279 0.85-14.588-3.116-19.97-4.957-19.97-6.515 0-0.85-0.567-1.133-1.133-0.708-1.558 0.85-13.172-2.124-12.464-3.258 0.708-0.991-4.249-1.275-20.395-1.133-20.395 0.142-22.661 0.283-22.661 1.558 0 0.567-1.275 0.85-2.833 0.425-1.841-0.425-2.549-0.142-1.841 0.85 0.567 0.85-0.283 1.133-2.266 0.567-1.841-0.425-2.833-0.425-2.408 0 1.275 1.275-6.373 4.815-8.073 3.682-0.708-0.425-0.991-0.142-0.567 0.567 0.567 0.85-1.133 2.408-3.541 3.399-7.082 2.974-19.97 16.996-24.786 27.052-5.665 11.755-8.215 26.768-6.657 39.090 0.708 5.24 1.133 10.622 1.133 11.897s0.567 1.983 1.275 1.558c0.85-0.567 1.133 0.708 0.708 2.833-0.425 2.266-0.142 3.399 0.708 2.833s1.133 0.283 0.708 2.124c-0.567 1.7-0.283 2.833 0.283 2.408 0.708-0.425 1.7 0.708 2.124 2.408 0.85 2.974 5.099 13.313 10.056 24.786 2.549 5.807 4.815 17.562 4.815 25.494 0 6.090-1.558 9.773-13.172 32.292-13.313 25.352-15.013 52.687-4.674 70.108 8.64 14.163 16.571 31.159 15.58 32.717-0.708 1.133-0.425 1.275 0.85 0.567 1.416-0.85 1.7-0.142 1.133 2.549-0.425 2.266-0.142 3.399 0.708 2.833s1.133 0.567 0.708 2.974c-0.425 1.983-0.142 3.682 0.425 3.682 1.983 0 1.416 24.077-0.708 31.867-2.549 9.631-7.082 18.837-12.322 25.211s-18.412 16.571-25.635 19.828c-2.974 1.275-7.507 3.399-10.198 4.674-2.691 1.133-7.082 2.266-9.773 2.408s-4.674 0.708-4.249 1.275c1.7 2.974-23.369 0.142-29.601-3.258-1.416-0.708-3.541-1.275-4.674-1.275-3.258 0-12.039-5.099-10.906-6.373 0.567-0.708 0.142-0.708-0.85-0.142-0.991 0.708-8.356-3.116-18.554-9.489-9.206-5.807-18.129-11.047-19.828-11.614-1.7-0.425-2.549-1.558-2.124-2.408 0.567-0.991 0.283-1.133-0.567-0.567-2.124 1.275-10.339-3.399-9.206-5.24 0.425-0.85 0.283-0.991-0.567-0.567-1.7 1.133-31.867-18.129-30.451-19.404 0.567-0.567 0.283-0.85-0.708-0.567-0.85 0.283-7.365-3.824-14.446-9.206-7.082-5.24-13.172-9.489-13.738-9.489-1.133 0-25.635-20.962-32.15-27.477l-5.524-5.807 0.85 5.665c0.708 4.249 0.567 5.099-0.567 3.541-1.275-1.7-1.416-0.708-1.133 3.824 0.283 3.258-0.142 5.524-0.85 5.099s-0.991 0.85-0.567 2.833c0.425 2.124 0.283 3.399-0.283 3.116s-1.7 1.841-2.691 4.815c-0.85 3.116-3.541 7.507-5.807 10.056-4.107 4.391-4.532 4.532-13.455 3.824-5.24-0.425-9.773-1.133-10.198-1.558-0.567-0.425-0.142-0.85 0.85-0.85s1.275-0.708 0.85-1.558c-0.567-0.85-1.983-1.133-3.116-0.708s-1.7 0.283-1.275-0.425c0.85-1.275-5.24-4.391-8.781-4.391s-9.064 4.107-7.931 5.949c0.567 0.85 0.283 1.133-0.567 0.567-0.708-0.425-2.833 0.85-4.532 2.974-1.983 2.549-2.549 2.974-1.558 0.991 2.266-4.532 11.189-11.897 14.588-11.897 1.7 0 6.94 2.266 11.897 4.957 11.614 6.515 17.279 6.657 22.944 0.142 6.657-7.507 11.189-30.593 11.614-58.211 0.283-23.794 6.798-48.863 18.837-72.091 5.807-11.331 11.472-17.987 21.245-25.352 15.296-11.472 14.163-34.7-2.266-46.88-5.665-4.107-25.352-5.099-34.417-1.558-10.198 3.824-11.047 6.090-16.854 45.747-1.275 7.931-3.541 17.562-5.099 21.103-6.373 14.871-19.404 25.635-28.893 23.936-2.691-0.567-11.897-4.391-20.82-8.498-18.554-8.781-24.219-14.022-40.082-36.966-5.382-7.79-12.18-16.996-15.155-20.395-3.824-4.391-6.232-9.206-8.356-16.288-1.558-5.524-3.824-13.597-5.099-17.846-5.524-19.687-22.378-32.575-42.206-32.575-20.395 0-34.558 20.253-26.202 37.391 3.258 6.798 19.262 22.661 21.528 21.245 0.708-0.425 0.991-0.283 0.85 0.425-0.567 1.558 11.614 16.996 23.228 29.459 7.931 8.64 9.914 11.897 12.322 19.97 3.966 12.747 5.382 25.919 4.391 37.957-0.991 11.472-2.974 13.738-15.296 17.279-14.305 4.249-23.794 2.974-58.777-7.79-23.086-6.94-29.743-11.047-37.391-22.661-7.223-11.047-12.605-14.871-24.361-17.987-15.013-3.824-27.618-0.425-36.683 9.914-8.073 9.206-7.79 20.253 0.708 33 4.532 6.798 6.94 8.923 16.004 13.313 12.889 6.515 21.103 9.206 36.258 11.755 21.386 3.824 45.322 15.58 80.022 39.232 11.472 7.931 13.455 9.914 16.713 16.854 3.399 7.082 3.682 9.348 3.541 25.494 0 9.773-0.85 20.395-1.7 23.653-3.824 12.889-18.129 26.344-45.181 42.348-8.781 5.099-16.713 10.339-17.704 11.614-2.266 2.691-2.266 14.871 0.142 21.953 4.674 14.022 15.438 19.687 29.884 16.004 19.262-4.957 32.292-15.296 46.314-36.683 15.296-23.228 31.584-41.357 45.039-50.421 6.657-4.532 22.095-11.472 25.211-11.472 1.275 0 3.258-1.133 4.674-2.549 1.416-1.275 3.116-2.408 3.824-2.408 0.85 0 1.7-0.85 2.124-1.983s0-1.558-0.991-0.991c-0.991 0.708-1.275 0.142-0.567-1.275 0.708-1.841 0.991-1.983 1.983-0.425s1.275 1.416 1.275-0.142c0-1.133 0.425-1.558 0.85-1.133 1.133 1.133-3.399 8.356-4.815 7.648-0.567-0.283-2.408 0.85-3.966 2.408l-3.116 2.974 15.863-0.425c16.004-0.425 25.494 0.425 24.361 2.266-0.283 0.567 0.85 1.133 2.691 1.133 5.665 0.425 16.288 5.099 15.296 6.798-0.425 0.85-0.425 1.133 0.283 0.567 0.991-0.991 7.082 1.983 31.584 15.58 3.966 2.124 6.94 4.532 6.373 5.24-0.708 0.567-0.425 0.708 0.425 0.283 0.708-0.425 6.94 2.833 13.738 7.223 18.412 12.039 22.236 14.446 23.653 14.446 2.549 0 18.271 11.047 17.279 12.039-0.567 0.425-0.283 0.708 0.425 0.425 1.558-0.425 21.386 11.331 27.76 16.429 1.983 1.7 5.382 4.249 7.507 5.807 2.266 1.558 6.94 5.099 10.622 7.931 3.541 2.691 7.507 5.665 8.781 6.373s9.773 7.931 18.837 16.146c16.713 15.013 35.266 35.691 35.266 39.232 0 0.991 0.425 1.558 0.708 1.133 1.558-1.416 11.897 16.996 10.481 18.554-0.567 0.567-0.283 0.567 0.708 0 1.133-0.567 2.124 0.425 2.691 2.974 0.567 2.124 3.116 8.64 5.807 14.305 2.549 5.807 4.249 11.047 3.824 11.897-0.567 0.85-0.283 1.133 0.425 0.708 1.841-1.133 3.824 5.524 2.408 7.79-0.567 0.85-0.425 1.133 0.425 0.708 0.85-0.567 2.266 1.841 3.258 5.099 1.133 3.258 2.408 6.515 2.974 7.365 0.567 0.708 2.974 8.073 5.524 16.288 4.249 13.88 12.039 31.442 18.695 42.206 1.558 2.549 2.549 4.957 2.124 5.524-0.425 0.425-1.983-1.416-3.399-4.249-1.416-2.691-5.099-9.914-8.356-16.004-3.116-6.090-7.507-16.996-9.773-23.936-4.249-13.455-9.064-25.211-9.206-22.378 0 0.85 0.708 3.399 1.558 5.665 0.85 2.124 3.399 9.773 5.524 16.996 4.249 13.88 17.279 40.082 28.185 56.086 3.541 5.24 8.498 12.747 10.764 16.429 15.438 24.502 39.657 49.146 63.026 64.018 17.704 11.331 26.060 15.438 43.198 21.245 7.79 2.691 14.446 5.24 14.871 5.665 0.708 0.991 10.481 3.824 16.854 5.099 1.983 0.283 3.824 0.85 4.391 1.133 0.425 0.283 8.498 1.275 18.129 2.124 22.378 2.124 58.494 0 73.224-4.532 5.524-1.7 10.198-3.541 10.622-4.107s2.974-1.558 5.665-2.408c13.030-3.966 26.91-9.348 33.992-13.313 28.468-15.863 55.378-38.382 69.966-58.636 3.966-5.524 11.189-14.871 16.004-20.962 9.489-11.614 15.155-20.678 26.060-41.498 6.657-12.747 9.064-19.687 4.815-14.022-1.133 1.558-2.124 2.266-2.124 1.7 0-1.133 6.090-16.146 8.498-20.678 0.567-1.275 1.558-4.249 2.124-6.798 0.425-2.549 1.558-4.249 2.266-3.824 0.708 0.567 0.85-0.283 0.283-1.558-0.567-1.558-0.283-2.124 0.567-1.558s1.558 0.142 1.416-0.85c-0.142-4.391 0.567-6.798 1.7-6.090 0.708 0.567 0.991-0.567 0.425-2.266-0.567-2.266-0.283-2.691 1.133-1.841 1.275 0.85 1.558 0.567 0.708-0.708-0.708-1.133 0.142-4.249 1.841-7.648 1.7-3.258 7.082-14.446 12.18-24.927 4.957-10.481 13.030-24.502 17.846-31.159 9.914-13.455 39.374-44.189 41.357-43.056 0.708 0.425 0.991 0.142 0.85-0.708-0.283-0.85 1.275-2.691 3.258-3.966 2.124-1.416 9.348-7.082 16.288-12.747 6.798-5.665 13.030-9.773 13.738-9.348 0.85 0.425 1.133 0.283 0.708-0.425-0.991-1.558 32.575-24.077 34.841-23.511 0.85 0.283 1.133 0 0.425-0.567-0.567-0.567 1.7-2.549 5.099-4.249s12.605-7.223 20.395-12.464c14.871-9.773 29.601-18.412 36.258-21.103 2.124-0.85 3.824-2.124 3.824-2.691s0.567-1.133 1.416-1.133c0.708 0 4.391-1.983 8.073-4.249 3.682-2.408 7.648-3.966 8.64-3.541 0.991 0.283 1.558 0.142 0.991-0.567-0.425-0.708 4.249-2.974 10.339-4.957 10.056-3.399 12.889-3.682 28.751-3.258l17.562 0.425-4.249-4.391c-2.266-2.408-4.815-4.107-5.524-3.682-0.708 0.283-0.85-0.142-0.283-0.991 1.133-1.841-6.232-17.421-7.931-16.429-0.567 0.425-0.708-0.425-0.142-1.7 0.567-1.416 0.283-2.124-0.425-1.558-0.708 0.425-1.416-0.425-1.416-1.983 0-3.966 1.416-1.983 7.931 11.472 6.515 13.313 9.348 16.996 12.464 16.996 1.133 0 7.365 2.266 13.88 5.099 18.271 7.79 38.099 27.76 57.503 57.928 3.541 5.382 10.622 13.88 15.721 18.837 9.914 9.489 19.404 14.588 32.434 17.421 7.507 1.558 8.498 1.416 14.871-1.7 8.923-4.532 14.588-14.73 14.588-26.627 0-4.391-0.85-8.923-1.841-10.198-0.991-1.133-8.073-5.949-15.863-10.481-18.554-11.047-28.043-17.987-37.249-27.335-9.489-9.914-12.039-17.987-12.18-38.524-0.283-25.069 3.966-33.284 23.511-45.747 42.065-27.052 56.653-33.85 81.155-37.957 17.704-2.974 38.807-12.18 45.181-19.828 14.588-17.137 12.747-34.7-4.674-44.897-5.524-3.258-8.215-3.824-17.137-3.824-8.781 0-12.039 0.708-18.695 3.966-7.931 3.966-9.348 5.099-18.554 17.987-6.94 9.489-14.022 13.455-34.275 19.687-35.691 10.906-46.172 12.322-60.477 8.073-5.382-1.558-9.206-3.682-11.614-6.657-3.258-3.824-3.682-5.524-3.682-15.438 0-12.18 2.833-29.318 6.232-37.249 1.983-4.957 34.7-43.339 45.464-53.395 10.481-9.773 13.455-15.155 13.455-24.077 0-6.657-0.708-9.064-4.107-13.738-7.507-10.339-11.897-12.464-25.635-12.464-14.446 0-20.112 2.549-30.168 13.313-7.931 8.498-9.914 12.605-13.738 29.176-2.833 12.464-4.815 16.429-11.472 24.361-2.124 2.408-9.064 12.18-15.438 21.67-6.373 9.348-14.588 19.687-18.271 22.803-8.781 7.648-35.833 20.253-43.481 20.395-7.79 0-16.146-5.949-22.52-16.146-6.232-9.773-6.798-12.18-11.189-40.79-2.124-13.88-4.532-24.786-5.949-27.335-3.541-5.949-11.189-8.498-25.069-8.498-14.305 0-19.687 2.408-26.060 11.897-8.498 12.464-7.082 27.477 3.399 36.966 19.262 17.279 24.077 24.361 32.717 49.146 6.232 17.704 8.356 29.884 10.198 57.786 0.85 13.597 2.408 28.61 3.399 33.284 1.133 4.674 1.841 8.781 1.416 9.064-0.283 0.283-0.567-0.142-0.567-1.133s-0.567-1.416-1.275-0.991c-0.567 0.425-0.85-0.85-0.425-2.974s0.142-3.399-0.708-2.833c-0.708 0.425-1.133-0.85-0.708-3.399 0.283-2.266 0-4.107-0.708-4.107s-0.991-2.266-0.708-5.099c0.425-3.116 0.142-4.815-0.708-4.391-0.708 0.425-1.133-1.133-0.991-3.682 0.283-2.549 0.142-3.258-0.283-1.7-1.558 5.382-28.751 30.309-32.009 29.318-0.708-0.142-0.991 0.142-0.567 0.85 0.283 0.567-2.691 3.258-6.657 5.949-4.107 2.691-11.189 7.79-15.863 11.331-17.987 13.738-51.979 34.558-53.82 32.859-0.708-0.567-0.708-0.283-0.142 0.708s-0.425 1.983-2.266 2.691c-1.841 0.567-5.807 2.549-8.923 4.674-3.116 1.983-6.94 4.391-8.498 5.382s-5.665 3.399-9.206 5.524c-3.399 2.124-7.082 3.824-8.073 3.824s-1.558 0.425-1.133 0.708c1.983 1.983-22.661 9.914-33.992 11.047-6.515 0.708-11.047 0.142-19.12-2.124-14.588-4.249-18.554-5.949-17.562-7.365 0.425-0.708 0.283-1.275-0.425-1.133-7.223 1.7-31.017-22.661-33.284-34.133-0.283-1.133-0.991-2.549-1.7-2.974-0.708-0.567-1.275-2.833-1.416-5.24 0-2.408-0.708-3.966-1.416-3.541-0.567 0.425-0.991-2.124-0.708-5.665s0.142-6.090-0.425-5.665c-0.708 0.283-1.133-2.124-1.133-5.524 0-4.107 0.425-5.382 1.275-4.107 0.708 1.133 0.991-0.708 0.425-4.815-0.425-3.824-0.142-6.515 0.567-5.949 0.708 0.425 1.416-1.558 1.7-4.391 0.142-2.974 0.85-5.382 1.558-5.665s0.991-1.558 0.708-2.833c-0.283-1.275 1.983-6.515 5.099-11.614 11.331-18.695 15.155-28.326 16.571-40.648 0.708-6.373 0.708-11.189 0.142-10.906-0.991 0.567-1.558-5.24-1.558-15.296 0-1.416-0.567-2.124-1.416-1.558s-1.133-0.567-0.708-2.974c0.425-2.124 0.425-3.399 0-2.974s-2.266-3.258-4.249-8.073c-1.983-4.674-5.099-10.906-6.94-13.738-7.365-10.764-9.489-17.279-9.489-29.035-0.142-7.79 0.425-11.472 1.7-11.897 0.991-0.283 1.275-1.133 0.85-1.983-0.567-0.708-0.283-2.833 0.567-4.391 0.85-1.7 3.541-8.215 6.090-14.446 2.408-6.232 5.099-11.897 5.665-12.605 0.708-0.85 1.275-2.833 1.275-4.674 0-1.7 0.85-3.966 1.841-4.957 1.133-0.991 1.7-2.974 1.275-4.532-0.425-1.416-0.142-2.408 0.567-1.983s1.416-2.691 1.558-7.082c0.283-4.532 1.133-8.356 2.124-9.064 1.416-0.85 1.416-1.133 0.142-1.133s-1.841-2.266-1.841-6.798c0-21.245-15.155-45.039-35.408-55.237-1.983-0.991-5.807-2.974-8.498-4.532-2.691-1.416-9.773-3.116-15.58-3.682-7.79-0.708-9.914-1.275-7.79-2.124 2.124-0.991 1.841-1.133-1.416-0.567-5.382 0.991-20.112 2.549-25.069 2.691-2.266 0.142-3.966 0.567-3.966 1.133 0 0.708-0.85 1.133-1.841 1.133-1.133 0-3.824 0.991-6.090 2.124-2.408 1.133-11.047 3.682-19.404 5.807-16.854 4.249-15.58 4.391-30.026-4.532-3.824-2.408-9.348-5.099-12.039-6.232-2.691-0.991-5.524-2.408-6.232-3.116-0.85-0.567-2.974-1.133-4.815-1.133-2.124 0-2.974-0.567-2.266-1.7 0.567-0.85 0.425-1.133-0.425-0.708-0.85 0.567-5.807 0-11.189-1.133-9.489-1.983-15.296-2.266-23.936-1.133zM723.315 80.164c-0.425 0.425-1.7 0.567-2.691 0.142-1.133-0.425-0.708-0.85 0.85-0.85 1.558-0.142 2.408 0.283 1.841 0.708zM734.079 80.305c-1.841 0.283-4.674 0.283-6.373 0-1.841-0.283-0.425-0.567 3.116-0.567s4.957 0.283 3.258 0.567zM979.103 80.305c-1.841 0.283-4.674 0.283-6.373 0-1.841-0.283-0.425-0.567 3.116-0.567s4.957 0.283 3.258 0.567zM986.751 80.164c-0.425 0.425-1.7 0.567-2.691 0.142-1.133-0.425-0.708-0.85 0.85-0.85 1.558-0.142 2.408 0.283 1.841 0.708zM730.538 82.288c9.773 0.142 16.854 0.85 16.429 1.558s0.283 1.133 1.558 0.85c1.558-0.283 2.833 0.708 3.258 2.408 1.416 5.949 0.283 20.678-1.416 19.545-1.133-0.567-1.7 1.7-1.275 4.249 0.142 0.708-0.425 0.85-1.133 0.283-1.841-0.991-4.249 3.541-3.399 6.373 0.567 1.7 0.425 1.7-0.708 0.142-1.7-2.266-3.824 0.425-2.266 2.974 0.567 0.85 0.425 1.133-0.425 0.708-1.983-1.275-10.764 9.914-9.489 12.18 0.567 0.85 0.425 1.275-0.142 0.567-0.708-0.567-2.408 0-3.824 1.416-2.833 2.549-3.541 5.382-0.708 3.824 0.85-0.567 1.416-0.708 0.991-0.142-0.425 0.425-1.275 0.708-1.841 0.708-1.7 0-9.489 7.648-9.489 9.206 0 0.708 1.133 0.425 2.549-0.567 2.124-1.841 2.266-1.7 0.425 0.425-0.991 1.416-2.408 2.124-2.974 1.841-1.416-0.85-12.039 11.331-12.039 13.88-0.142 0.991-0.85 1.558-1.841 1.416-1.983-0.425-7.507 3.966-6.657 5.382 0.425 0.567-0.425 0.85-1.841 0.708-1.275 0-2.266 0.708-1.983 1.7 0.142 1.133-0.283 1.558-0.991 1.133s-3.966 0.142-7.223 1.275c-7.507 2.691-14.588 2.691-19.545 0.142-4.391-2.266-10.906-9.206-11.047-11.897-0.142-0.85-0.283-2.974-0.283-4.674-0.142-1.558-0.708-2.549-1.275-2.266-0.708 0.425-1.133-3.399-1.133-8.498-0.142-5.099 0.283-8.073 0.708-6.657 0.708 1.841 1.275 0.567 1.983-4.107 0.567-3.682 0.708-7.223 0.283-7.931s0.142-1.558 1.275-2.124c1.133-0.425 2.124-1.7 2.124-2.691 0-2.833 3.966-10.339 6.373-12.322 2.974-2.549 2.691-4.957-0.283-2.691-1.416 0.991-0.991 0.142 0.85-1.841 1.841-2.124 3.824-3.541 4.391-3.258 1.133 0.708 8.923-6.94 8.64-8.64-0.142-0.567 0.708-1.275 1.7-1.416 3.541-0.425 9.489-5.099 8.498-6.657-0.425-0.708-0.283-0.991 0.567-0.567 1.7 0.991 16.429-3.824 15.296-4.957-0.425-0.425 0.708-0.425 2.549-0.142 1.983 0.425 3.966 0 4.532-0.85 0.425-0.85 1.416-1.275 1.983-0.991 0.567 0.425 8.781 0.85 18.271 0.991zM1003.463 83.28c-0.425 0.425 2.833 2.124 7.082 3.966 4.391 1.7 9.631 4.249 11.755 5.665 2.124 1.558 4.674 2.691 5.524 2.691 0.991 0 1.841 0.708 1.983 1.416 0.142 2.408 2.974 4.957 4.249 4.107 0.708-0.425 1.133 0 0.991 1.133-0.283 1.133 0.567 1.7 2.124 1.416 1.275-0.283 2.549 0.283 2.549 1.133 0.425 2.691 0.708 3.541 2.408 6.798 0.991 1.7 2.549 2.691 3.399 2.266 0.85-0.567 1.133-0.425 0.567 0.567-0.991 1.558 3.258 11.897 4.674 11.047 0.425-0.283 0.991 1.133 0.991 3.116 0.142 2.124 0.708 5.382 1.275 7.365 1.416 5.099 1.983 28.468 0.567 27.477-0.708-0.283-1.558 0.85-2.124 2.833-0.425 1.841-1.983 4.532-3.541 5.949-1.416 1.275-2.408 2.833-1.983 3.258 0.283 0.567-0.567 0.991-1.841 1.133s-5.099 0.708-8.356 1.133c-3.966 0.708-7.931 0.283-12.464-1.275-3.682-1.133-7.365-1.841-7.931-1.416-0.708 0.425-1.133 0-0.991-0.991 0.283-0.85-0.425-1.983-1.558-2.408-1.275-0.425-1.558 0-0.85 1.133 0.708 1.275 0.567 1.558-0.567 0.85-0.85-0.567-1.275-1.558-0.85-2.266 0.991-1.558-3.116-4.674-4.674-3.682-1.416 0.85-5.524-3.258-4.815-4.674 0.708-1.558-11.047-14.305-12.889-13.88-0.991 0.142-1.558-0.567-1.275-1.558 0.425-2.124-3.682-6.657-5.949-6.657-0.85-0.142-1.416-0.85-1.275-1.841 0.567-2.408-3.966-7.365-5.665-6.373-0.85 0.567-0.991 0.142-0.425-0.85 1.416-2.124-3.258-7.507-5.24-6.232-0.85 0.425-0.991 0.142-0.425-0.85 1.416-2.124-1.841-6.090-3.824-4.815-0.85 0.425-0.991 0.142-0.425-0.708 0.991-1.558-8.215-16.288-9.348-15.155-0.283 0.283-0.708-3.116-0.85-7.507-0.283-5.807 0.283-9.064 1.983-10.906 1.133-1.558 1.841-1.841 1.275-0.708-0.567 1.275-0.142 1.275 0.991-0.283 1.558-2.266 4.391-2.408 24.077-2.266 12.322 0.142 22.095 0.567 21.67 0.85zM873.587 132.568c5.665 1.275 9.914 2.549 9.631 3.116s1.133 1.133 3.116 1.133c4.532 0.142 21.953 10.056 20.82 11.755-0.283 0.708 0.425 1.133 1.841 0.991 1.275 0 2.266 0.567 2.124 1.416-0.142 0.991 0.708 1.558 2.124 1.416 1.275 0 2.266 0.567 2.124 1.416-0.142 0.991 0.708 1.558 2.124 1.416 1.275 0 2.266 0.567 2.124 1.416-0.142 0.991 0.708 1.558 2.124 1.416 1.275 0 2.266 0.708 1.983 1.7-0.142 1.133 0.283 1.558 1.133 0.991 1.841-1.133 11.755 6.090 10.622 7.79-0.425 0.708 0 1.7 0.85 2.266 1.133 0.567 1.275 0.425 0.708-0.567-1.841-3.116 0.991-1.7 7.082 3.399 3.399 2.691 6.090 5.665 5.807 6.657-0.142 0.991 0.708 1.558 1.983 1.275 1.275-0.142 3.116 0.708 4.107 1.841s1.275 2.124 0.708 2.124c-0.567 0-0.283 0.991 0.708 2.124s2.408 1.7 3.116 1.275c1.558-0.991 8.498 4.957 7.507 6.515-0.425 0.567 0.425 1.983 1.7 2.974 1.7 1.416 1.983 1.416 1.275 0.142-2.408-3.966 1.841-1.275 7.648 4.957 3.541 3.682 4.815 5.524 2.833 3.966l-3.541-2.833 2.549 3.116c1.558 1.7 3.399 2.833 4.391 2.408 0.85-0.283 6.94 4.957 13.597 11.755s9.206 9.914 5.665 6.798l-6.373-5.665 5.382 5.807c2.974 3.258 6.373 5.807 7.365 5.665 0.991 0 9.489 7.365 18.979 16.571 16.288 16.004 19.404 19.404 15.438 17.137-1.133-0.567-1.416-0.425-0.708 0.283s1.983 1.275 2.833 1.275c2.691 0 2.833 4.957 0.142 7.931-3.966 4.391-12.747 3.541-21.811-1.983-4.249-2.549-7.648-5.524-7.365-6.515 0.142-0.991-0.283-1.416-1.133-0.85-1.841 1.133-12.747-7.365-12.322-9.631 0.142-0.991-0.567-1.558-1.7-1.275-2.549 0.567-8.781-5.382-7.648-7.223 0.425-0.85 0.142-0.991-0.85-0.425-1.558 0.991-15.013-8.923-13.597-10.198 1.133-1.133-2.266-4.532-3.824-3.682-1.558 0.991-5.665-2.124-4.674-3.682 0.425-0.708 0-1.7-0.85-2.266-1.133-0.567-1.275-0.425-0.708 0.567 1.841 2.974-1.416 1.841-4.532-1.558-1.7-1.841-2.691-3.824-2.124-4.532 0.708-0.567 0.283-0.708-0.708-0.142-1.983 1.133-20.537-13.030-19.404-14.73 0.425-0.567-0.425-0.991-1.7-0.85-1.416 0-2.549-0.567-2.549-1.558 0-0.85-0.567-2.124-1.275-2.833s-0.85-0.425-0.283 0.567c1.983 3.399-1.275 1.841-8.356-3.966-3.966-3.258-6.94-6.515-6.657-7.223 0.142-0.708-0.283-0.991-0.991-0.567-1.7 1.133-12.322-6.090-11.755-7.931 0.142-0.708-0.283-0.991-0.991-0.567s-6.232-1.7-12.464-4.815c-17.137-8.923-31.442-13.030-47.872-13.738-17.987-0.991-27.618 1.275-47.447 11.189-8.215 4.107-15.58 7.365-16.571 7.365-0.991-0.142-1.558 0.283-1.416 0.708 0.283 0.991-23.936 19.97-25.352 19.97-0.425 0-0.283-0.85 0.283-1.841 0.567-0.85 0.567-1.275 0-0.708-0.567 0.425-0.85 1.133-0.567 1.558 0.425 0.708-20.395 22.944-21.67 22.944-1.983 0-5.382 3.966-4.674 5.24 1.133 1.7-9.348 12.605-11.047 11.614-0.708-0.425-0.991 0-0.85 0.991 0.283 0.991-0.283 1.841-1.275 1.983-0.85 0-2.124 0.567-2.833 1.275s-0.425 0.85 0.708 0.283c0.85-0.567 1.7-0.425 1.7 0.142 0 1.841-22.095 23.228-23.511 22.803-0.708-0.283-1.275 0-1.275 0.567 0 2.124-9.914 11.047-11.614 10.481-0.991-0.425-1.7 0-1.416 0.991 0.142 0.991-0.85 1.558-2.124 1.558-1.416-0.142-2.124 0.283-1.7 0.991 0.991 1.7-7.931 6.94-12.039 6.94-8.781 0-10.906-8.498-5.099-20.962 3.824-8.215 18.695-24.786 20.962-23.369 0.708 0.425 0.991 0.283 0.567-0.567-1.133-1.841 15.013-17.704 17.562-17.137 1.133 0.283 1.841-0.283 1.7-1.133s12.322-13.455 27.618-28.185c15.296-14.73 26.627-25.211 25.069-23.369l-2.833 3.541 3.116-2.691c1.841-1.416 2.833-3.258 2.408-3.966-0.567-0.85 1.841-4.249 5.24-7.648 5.807-5.949 9.773-8.215 7.507-4.532-0.708 1.275-0.425 1.275 1.275-0.142 1.275-0.991 2.124-2.408 1.7-2.974-0.991-1.558 2.833-4.391 5.099-3.966 0.991 0.283 1.841-0.283 1.983-0.991 0.142-2.833 2.408-5.099 4.391-4.391 1.133 0.425 1.841 0 1.558-0.85-0.425-2.266 16.996-12.889 21.67-13.030 2.124-0.142 3.682-0.567 3.399-0.991-0.708-1.275 16.713-5.382 24.927-5.949 10.764-0.708 28.468 0.567 39.374 2.691zM649.666 152.538c-0.425 1.416-0.708 0.567-0.708-1.7 0-2.408 0.283-3.399 0.708-2.549 0.283 0.991 0.283 2.974 0 4.249zM885.201 181.147c7.365 2.549 15.721 5.949 18.412 7.648 12.747 7.648 29.884 22.236 55.237 47.022 29.035 28.468 43.481 40.932 53.395 45.889 8.498 4.249 20.395 8.498 27.618 9.914 5.524 1.133 5.665 1.275 1.416 1.416-2.549 0.142-9.064-1.416-14.73-3.399-18.837-6.515-30.876-15.155-56.653-40.507-49.288-48.58-59.485-56.653-82.572-65.434-27.477-10.339-63.026-8.215-89.936 5.382-10.906 5.524-28.468 18.837-40.79 30.876-12.605 12.464-24.927 28.043-41.073 52.121-7.507 11.331-11.614 16.004-16.854 19.404-9.773 6.232-21.953 9.773-36.541 10.764-15.863 0.991-14.163 0 2.974-1.841 14.022-1.558 27.052-5.949 35.125-12.18 2.833-2.124 9.773-10.906 15.58-19.687 17.279-25.635 19.545-28.61 33.284-43.339 20.112-21.811 38.524-34.841 60.335-42.631 16.288-5.949 22.661-6.798 43.906-6.373 16.429 0.425 19.828 0.85 31.867 4.957zM676.010 180.864c-0.991 0.283-2.974 0.283-4.249 0-1.416-0.425-0.567-0.708 1.7-0.708 2.408 0 3.399 0.283 2.549 0.708zM1035.755 180.864c-0.991 0.283-2.974 0.283-4.249 0-1.416-0.425-0.567-0.708 1.7-0.708 2.408 0 3.399 0.283 2.549 0.708zM1048.077 295.586c0 0.567-2.833 7.507-6.232 15.296-11.614 26.060-16.854 54.953-16.854 92.769-0.142 42.49 8.356 84.413 24.502 121.095 5.099 11.614 15.155 43.056 17.704 55.237 3.258 15.438 4.249 43.764 2.266 60.194-2.408 19.687-11.897 56.794-13.88 54.67-0.425-0.283 0.567-4.674 2.266-9.631 15.863-48.297 14.163-96.593-5.24-146.872-16.996-44.472-21.245-58.494-26.060-87.245-4.391-26.344-4.391-70.674 0-94.893 3.399-19.12 8.215-34.841 15.155-49.854 2.549-5.524 4.249-10.481 3.824-10.906s0-0.85 0.85-0.85c0.991 0 1.7 0.425 1.7 0.991zM649.666 305.359c-0.425 0.425 1.841 4.107 5.099 8.073 10.481 12.889 26.627 48.438 26.485 58.211-0.142 1.7-2.266-3.399-4.815-11.189-6.515-20.112-15.721-37.674-25.069-48.58-4.391-4.957-5.099-7.365-2.408-7.365 0.85 0 1.275 0.425 0.708 0.85zM685.358 401.81c1.558 17.562 0.142 47.73-2.691 58.494-0.85 3.399-0.991 1.275-0.283-6.373 1.275-14.73 1.275-53.395 0-67.983-0.708-7.931-0.708-9.914 0.283-6.373 0.708 2.691 1.983 12.747 2.691 22.236zM1308.539 390.763c4.957 2.691 11.897 1.7 18.554-2.691 3.541-2.266 7.082-4.249 7.79-4.249 0.85 0.142-0.425 1.133-2.549 2.266s-3.541 2.691-3.116 3.258c0.283 0.708-0.425 1.133-1.841 0.991-1.275 0-2.266 0.567-2.124 1.558 0.283 1.133-0.567 1.416-1.983 0.85-1.275-0.567-1.983-0.283-1.416 0.425 0.567 0.991-1.841 1.133-6.373 0.567-5.665-0.708-8.073-1.983-11.331-5.524-2.124-2.408-3.966-5.24-3.966-6.232 0-0.85 1.275 0.283 2.833 2.833 1.416 2.408 3.966 5.24 5.524 5.949zM1345.363 384.389c2.124 1.133 4.815 3.258 5.949 4.674 1.841 2.408 1.7 2.408-1.133 0-1.7-1.558-3.824-2.266-4.815-1.558-1.133 0.708-1.416 0.425-0.567-0.85 0.85-1.416 0.567-1.7-1.275-0.991-1.275 0.567-1.983 0.425-1.558-0.283s-0.708-1.7-2.408-1.983c-2.266-0.425-2.549-0.708-0.708-0.85 1.416 0 4.249 0.708 6.515 1.841zM394.729 396.003c-0.425 0.425-1.7 0.567-2.691 0.142-1.133-0.425-0.708-0.85 0.85-0.85 1.558-0.142 2.408 0.283 1.841 0.708zM351.814 400.111c0.425 1.133 0 1.983-0.991 1.7-0.991-0.142-1.7 1.133-1.558 2.833 0 1.7-0.567 2.691-1.275 2.266-0.85-0.567-1.133 0-0.708 1.275 0.567 1.275 0 2.124-1.133 2.124-2.549 0-2.549-0.283 1.133-6.798 3.541-6.373 3.399-6.232 4.532-3.399zM680.825 471.918c-1.558 11.189-10.764 36.399-22.095 61.043-6.232 13.455-12.464 28.61-14.022 33.708-10.481 34.275-6.94 72.516 11.047 120.529 8.781 23.511 20.537 45.464 36.399 68.408 8.64 12.464 8.073 12.18-9.914-4.957-8.356-7.79-16.004-17.137-12.322-14.871 1.841 1.133 9.064 8.923 12.322 13.313 0.85 1.275 1.983 1.841 2.549 1.416 0.425-0.567-2.833-7.223-7.365-14.871-11.755-19.828-16.146-28.751-22.944-46.88-12.605-33.708-17.137-55.095-17.137-81.863 0.142-24.219 4.815-44.189 15.721-66.001 12.322-24.786 26.768-63.734 26.768-72.374 0-1.416 0.425-2.549 0.85-2.549 0.567 0 0.567 2.691 0.142 5.949zM1068.189 692.864c-0.991 0.991-1.7 1.133-1.7 0.283 0-1.983 1.7-3.682 2.691-2.691 0.283 0.425-0.142 1.558-0.991 2.408zM1059.691 709.152c-3.966 6.798-8.215 13.738-9.489 15.296-1.558 1.841-1.416 0.991 0.425-2.124 2.549-4.391 6.94-12.605 12.889-23.794 0.991-1.841 2.124-2.974 2.549-2.549 0.425 0.283-2.549 6.232-6.373 13.172zM1054.451 701.362c-0.567 2.549-4.107 11.189-7.79 19.12-3.824 8.073-6.515 14.588-5.949 14.588 0.425 0 2.408-1.7 4.249-3.966 2.266-2.691 2.124-1.983-0.85 2.549-4.815 7.365-11.472 14.73-12.464 13.88-0.283-0.425 3.824-9.348 9.348-19.97 5.382-10.764 10.622-21.953 11.472-25.069 2.124-6.798 3.258-7.507 1.983-1.133zM665.671 732.804c0 0.85-1.558-0.708-3.541-3.399s-3.541-5.099-3.541-5.524c0-1.416 6.94 7.507 7.082 8.923zM714.534 84.554c-8.64 1.133-19.262 4.815-26.627 9.064-9.206 5.382-21.386 18.129-25.919 27.052-10.481 20.678-9.773 45.464 1.416 52.97 5.099 3.258 18.554 1.983 25.919-2.408 7.79-4.532 18.979-15.58 32.009-31.726 6.232-7.79 11.897-14.446 12.464-14.871s4.532-6.090 8.923-12.747c5.524-8.356 7.931-13.313 7.931-16.571 0-5.099-3.966-9.631-9.064-10.622-4.391-0.85-21.67-0.991-27.052-0.142zM740.028 89.087c3.541 0.708 4.391 1.7 4.957 5.807 0.991 5.807 0.142 10.198-1.558 9.064-0.708-0.425-1.275 0-1.275 0.708 0 2.974-0.425 3.682-2.833 5.382-1.275 0.991-4.815 5.24-7.79 9.489-2.974 4.391-5.949 7.931-6.657 7.931-0.567 0-1.133 0.708-1.133 1.558 0 0.708 0.85 0.991 1.841 0.567 0.85-0.567 0.142 0.425-1.841 2.266-1.983 1.7-3.399 3.682-3.116 4.249 0.142 0.708-0.567 1.133-1.416 0.85-0.991-0.142-1.7 0.567-1.416 1.416 0.142 0.991-0.567 1.7-1.416 1.416-0.991-0.142-1.7 0.567-1.416 1.416 0.142 0.991-0.708 1.841-2.124 1.841-1.275 0-1.983 0.425-1.416 0.85 0.85 0.85-0.567 2.691-9.206 11.755-2.833 2.974-5.665 5.099-6.090 4.674s-0.708 0.283-0.708 1.7c0 1.275-0.567 2.124-1.275 1.558-0.85-0.425-1.983 0.425-2.833 1.7-0.85 1.416-1.558 2.124-1.558 1.416 0-0.567-2.124 0-4.532 1.275-2.549 1.275-6.515 2.408-8.781 2.266-2.266 0-5.24 0.425-6.515 1.133-4.532 2.408-11.755-10.198-11.614-20.537 0.142-7.507 2.549-18.837 4.674-21.386 1.558-1.983 1.558-2.266 0-1.416-1.133 0.85-0.991 0 0.567-2.549 1.416-1.983 1.841-3.682 1.275-3.682-0.708-0.142 0.283-1.133 2.266-2.266 2.549-1.558 2.833-1.983 0.708-1.416l-2.833 0.85 2.833-2.266c1.558-1.133 2.691-2.974 2.408-3.824-0.283-0.991 0-1.275 0.567-0.567 1.416 1.416 6.373-3.258 5.24-5.099-0.425-0.708-0.142-0.991 0.708-0.425 0.85 0.425 3.541-1.558 5.949-4.391 2.549-2.974 4.107-4.391 3.541-3.258-0.425 1.133 0.283 0.708 1.841-0.991s3.116-2.691 3.682-2.266c0.425 0.567 0.85 0 0.85-0.991s0.425-1.416 0.991-0.991c0.425 0.567 2.408 0 4.391-1.275 2.549-1.7 3.258-1.7 2.408-0.425-0.425 1.133-0.142 0.991 0.991-0.283s4.674-2.266 8.923-2.549c3.824-0.142 7.223-0.567 7.648-0.991 0.85-0.85 17.137-0.425 22.095 0.708zM963.381 85.546c-2.549 0.85-5.24 2.549-5.949 3.682-2.266 3.682-1.558 10.339 1.983 16.429 2.691 4.674 22.095 30.593 24.927 33.142 0.425 0.425 2.549 2.974 4.815 5.665 2.124 2.691 8.356 9.631 13.88 15.155 10.764 11.047 20.253 16.004 30.451 16.004 6.373 0 13.88-4.815 16.288-10.622 2.549-5.949 2.124-23.936-0.85-33.992-5.665-19.545-24.786-37.816-46.030-44.048-9.773-2.974-33.142-3.824-39.515-1.416zM1001.339 89.511c1.133 0.142 2.833 0.991 3.541 2.124 0.85 0.991 3.116 1.983 5.382 2.124s3.824 1.133 3.966 2.549c0 1.275 0.425 1.416 0.708 0.425 0.425-0.991 3.682 1.275 7.931 5.24 3.966 3.824 7.931 6.515 8.64 6.090 0.85-0.425 0.991-0.283 0.567 0.567-0.567 0.708 1.275 3.399 3.966 5.807 2.974 2.549 4.674 5.099 4.107 6.373-0.425 1.133 0.283 2.408 1.558 2.974 1.275 0.425 1.841 1.416 1.416 2.266-0.425 0.708-0.142 1.416 0.708 1.416 0.991 0 1.416 0.991 0.991 2.124-0.567 1.133-0.283 2.408 0.283 2.833 0.708 0.425 0.991 2.124 0.425 3.824-0.567 2.266-0.283 2.691 1.133 1.841 1.275-0.708 1.558-0.567 0.85 0.425-0.567 0.991-0.567 2.408 0 3.399 1.558 2.408 1.558 16.713 0 16.713-0.567 0-0.85 0.991-0.283 2.124 0.425 1.133 0.142 2.124-0.708 2.124s-1.133 0.567-0.708 1.275c0.425 0.708-0.142 1.7-1.416 2.124-1.133 0.425-1.7 1.558-1.133 2.408 0.567 1.133 0.425 1.275-0.567 0.708s-1.7-0.567-1.7 0.142c0 0.567-3.824 1.133-8.498 1.133s-8.498-0.425-8.498-0.85c0-0.708-4.957-2.691-6.798-2.549-0.142 0-4.391-3.966-9.206-8.923-4.815-4.815-9.348-8.64-9.914-8.498-0.708 0.283-0.85-0.142-0.567-0.708 0.425-0.708-1.558-3.682-4.249-6.798-2.833-3.116-4.249-4.391-3.116-2.833 1.841 2.549 1.841 2.691-0.425 0.85-1.416-0.991-2.266-2.408-1.841-2.974 0.425-0.708 0.142-1.558-0.708-2.124-0.85-0.425-4.957-5.807-9.348-11.755s-9.631-12.464-11.614-14.305c-2.124-1.841-2.833-2.833-1.7-2.266 1.416 0.567 1.275 0.142-0.283-1.133-1.416-1.133-2.124-2.408-1.558-2.833 0.425-0.567 0.142-0.991-0.85-0.991s-1.275-0.708-0.85-1.416c0.425-0.85 0.283-1.416-0.567-1.416s-0.85-1.558-0.142-4.107c0.567-2.266 0.85-4.532 0.567-4.957s-0.142-0.567 0.283-0.283c0.425 0.283 2.408 0 4.532-0.708s10.481-0.85 18.695-0.425c8.215 0.425 15.863 0.85 16.996 0.85zM862.256 132.709c0.991 0.425 2.266 0.283 2.691-0.142 0.567-0.425-0.283-0.85-1.841-0.708-1.558 0-1.983 0.425-0.85 0.85zM739.178 196.444c-1.841 2.266-1.7 2.408 0.567 0.567 1.275-0.991 2.408-2.124 2.408-2.408 0-1.133-1.133-0.425-2.974 1.841zM657.315 286.947c0 1.558 0.425 1.983 0.85 0.85 0.425-0.991 0.283-2.266-0.142-2.691-0.425-0.567-0.85 0.283-0.708 1.841zM328.303 427.304c0.991 0.425 2.266 0.283 2.691-0.142 0.567-0.425-0.283-0.85-1.841-0.708-1.558 0-1.983 0.425-0.85 0.85zM1376.381 427.304c0.991 0.425 2.266 0.283 2.691-0.142 0.567-0.425-0.283-0.85-1.841-0.708-1.558 0-1.983 0.425-0.85 0.85zM1162.799 702.070c-4.249 0.85-6.798 2.549-15.296 10.622-6.798 6.515-44.472 39.799-49.288 43.623-1.558 1.133-7.082 5.949-12.605 10.481-10.339 8.781-16.288 12.889-38.099 26.768-14.022 8.923-49.005 26.485-53.254 26.91-1.416 0.142-2.266 0.708-1.841 1.558 0.567 0.708-0.425 0.991-2.124 0.567-1.7-0.567-2.833-0.425-2.549 0.142 0.425 0.567-2.549 1.841-6.657 2.974-4.107 0.991-7.931 2.266-8.781 2.833-1.841 1.275-27.902 7.648-41.073 9.914-5.382 0.991-17.846 3.399-27.618 5.24-15.296 3.116-21.811 3.541-48.863 3.682-35.125 0.283-50.138-1.558-77.898-9.631-3.824-1.133-12.18-3.399-18.412-5.099-17.846-4.674-30.309-8.781-29.884-9.631 0.283-0.425-0.85-0.991-2.549-0.991-1.7-0.142-6.090-1.558-9.631-3.258-3.682-1.558-7.223-2.974-7.931-2.974-1.133 0-7.931-2.974-31.017-13.597-2.549-1.133-4.674-2.833-4.674-3.682s-0.425-1.133-0.991-0.708c-0.991 1.133-12.464-5.099-25.352-13.738-13.313-8.923-41.357-32.15-75.348-62.318-2.691-2.408-3.682-2.549-6.090-1.133-1.558 0.991-3.541 1.416-4.391 0.85s-1.133-0.425-0.708 0.425c1.133 1.841-2.833 3.682-4.957 2.266-0.85-0.425-1.133-0.142-0.567 0.708 0.425 0.85-0.567 1.841-2.266 2.408-8.498 2.408-31.726 14.163-40.79 20.537-1.983 1.416-8.923 5.665-15.58 9.631-26.768 16.004-52.829 42.348-57.078 57.928-0.85 3.258-1.841 5.665-2.408 5.382-1.133-0.708 0.85 23.511 2.266 26.91 1.7 4.249 8.64 9.631 15.58 12.18 3.399 1.133 5.949 2.549 5.524 2.974s0.85 0.425 2.833 0.142c1.983-0.425 3.541-0.142 3.541 0.567s1.983 0.991 4.391 0.708c2.549-0.425 4.107-0.142 3.682 0.708-0.425 0.708 1.416 0.991 4.249 0.708 2.833-0.425 4.674-0.142 4.249 0.567-0.283 0.708 4.532 1.416 10.906 1.7 6.232 0.142 16.854 1.275 23.511 2.408 6.657 0.991 15.863 2.408 20.537 2.974s8.923 1.275 9.631 1.416c0.567 0.142 2.974 0.283 5.382 0.425 2.266 0 3.966 0.708 3.541 1.275-0.425 0.708 1.275 0.85 3.682 0.567 3.258-0.567 4.107-0.283 3.258 1.133-0.708 1.275-0.567 1.558 0.567 0.85 2.408-1.558 10.339 0.425 9.206 2.266-0.567 0.991-0.283 1.133 0.567 0.567 2.408-1.416 11.755 0.567 10.622 2.408-0.425 0.85-0.142 0.991 0.708 0.425 2.124-1.275 41.215 11.897 55.52 18.554 23.794 11.189 42.631 21.103 41.781 21.953-0.425 0.425 0.142 0.708 1.133 0.708s6.373 2.833 12.039 6.515c5.524 3.541 11.897 7.507 14.022 8.498 4.249 2.266 21.245 5.24 29.318 5.24 3.116 0 5.099 0.708 5.099 1.7 0 1.133 0.425 1.275 1.133 0.425 0.567-0.85 11.472-1.416 27.193-1.558 30.168-0.142 56.653-2.124 97.726-7.082 16.004-1.983 30.451-3.258 32.15-2.974 1.841 0.283 2.833 0 2.266-0.85s1.275-1.133 5.24-0.567c3.399 0.425 5.382 0.283 4.391-0.283-2.124-1.416 11.755-3.258 16.854-2.266 2.974 0.708 3.116 0.567 0.708-0.425-2.124-0.991-0.708-1.558 5.807-2.266 4.674-0.567 9.773-0.567 11.331 0 2.266 0.708 2.408 0.567 0.567-0.85-1.7-1.275-1.133-1.558 3.116-1.133 2.833 0.283 4.957 0.142 4.532-0.425-0.85-1.558 14.446-3.258 16.571-1.983 0.991 0.708 1.416 0.567 0.85-0.283s3.966-2.124 11.472-3.116c13.455-1.841 25.352-3.966 44.897-8.356 29.035-6.515 33.992-7.365 35.833-6.657 1.133 0.425 1.416 0 0.85-0.85-0.708-1.133 0.425-1.416 3.399-0.991 2.408 0.283 4.107 0.142 3.824-0.425-0.708-0.85 2.549-1.841 29.035-7.648 5.807-1.275 18.554-4.532 28.185-7.223 9.631-2.549 17.987-4.391 18.695-4.107 0.567 0.425 1.275 0 1.558-0.85 0.283-0.991 2.549-1.7 4.957-1.841 2.408 0 4.107-0.425 3.824-0.708-0.991-0.991 15.155-5.949 16.429-5.099 0.85 0.567 1.416 0.142 1.416-0.567 0-0.85 0.991-1.558 2.124-1.558s9.489-2.549 18.695-5.807c9.206-3.116 18.979-6.373 21.67-7.223 2.691-0.708 5.665-1.983 6.657-2.833 0.991-0.708 2.549-0.85 3.399-0.283 0.991 0.567 1.275 0.425 0.85-0.425-0.85-1.416 6.515-3.966 9.348-3.399 0.708 0.142 0.85-0.283 0.425-0.991s2.549-2.266 6.657-3.682c8.781-3.116 20.537-8.781 29.318-14.305 3.541-2.266 7.223-3.966 8.073-3.966 0.991 0 1.841-0.567 1.841-1.133 0-0.708 3.399-3.399 7.507-5.949 8.073-5.382 14.163-11.472 19.97-20.112 3.966-5.949 4.957-15.58 2.266-22.52-2.974-7.931-21.103-23.653-32.15-27.902-2.124-0.85-4.957-2.266-6.090-3.258-3.966-3.541-35.125-18.695-45.606-22.236-2.833-0.991-4.815-2.408-4.391-3.116 0.425-0.85 0.142-0.991-0.708-0.567-1.7 1.133-6.232-0.283-5.382-1.7 0.425-0.567-0.567-0.991-2.124-1.133-1.416-0.142-6.373-1.841-10.764-3.824-10.764-4.532-30.734-11.614-38.382-13.597-3.258-0.85-5.665-2.124-5.24-2.833 0.567-0.85 0.708-1.275 0.425-1.275-0.283 0.142-1.983 0.567-3.966 0.85z" ], "width": 1700, "tags": [ "freepbx" ], "defaultCode": 59654, "grid": 20 }, { "paths": [ "M877.529 75.18c-59.625 59.625-60.922 62.218-53.144 125.732 5.185 36.294 2.592 86.846-5.185 114.066-7.777 25.924-9.073 47.959-3.889 47.959 27.22 0 129.62-130.916 143.878-180.172 16.851-63.514 12.962-159.433-6.481-165.914-7.777-2.592-41.478 23.332-75.18 58.329zM715.504 138.694c-9.073 28.516-15.554 62.218-15.554 72.587 0 25.924 53.144 138.694 66.106 138.694 15.554 0 37.59-77.772 37.59-130.916 0-36.294-10.37-60.922-36.294-90.734l-36.294-40.182-15.554 50.552zM67.403 352.567l3.889 42.775h107.585c58.329 0 106.289 1.296 106.289 3.889s-58.329 99.808-129.62 216.466c-71.291 116.658-129.62 219.058-129.62 226.835 0 9.073 60.922 12.962 198.319 10.37l197.023-3.889v-77.772l-248.871-12.962 133.509-217.762c73.884-119.251 134.805-220.354 134.805-222.947 0-3.889-85.549-6.481-189.246-6.481h-187.949l3.889 41.478zM581.995 399.23c-149.063 77.772-174.987 278.684-49.256 383.676 55.737 46.663 94.623 60.922 168.506 62.218 93.327 0 163.322-46.663 211.281-141.286 27.22-54.441 27.22-55.737-15.554-55.737-25.924 0-41.478 10.37-59.625 40.182-27.22 45.367-92.030 89.438-133.509 89.438-16.851 0-29.813-2.592-29.813-6.481s29.813-85.549 64.81-182.765c36.294-97.215 64.81-180.172 64.81-186.653 0-14.258-73.884-38.886-116.658-38.886-19.443 0-67.403 15.554-104.992 36.294zM705.134 469.225c-5.185 16.851-31.109 86.846-57.033 156.841l-47.959 128.324-33.701-40.182c-18.147-23.332-37.59-60.922-42.775-84.253-15.554-88.142 66.106-189.246 154.248-189.246 32.405 0 34.997 3.889 27.22 28.516zM976.041 372.010c-12.962 3.889-16.851 64.81-16.851 238.501v232.020l31.109 7.777c18.147 3.889 38.886 3.889 45.367-1.296 9.073-5.185 14.258-68.699 14.258-164.618v-155.544l44.071-44.071c58.329-58.329 119.251-60.922 178.876-6.481l42.775 37.59 6.481 167.21 6.481 165.914h64.81l3.889-169.803c2.592-158.137 1.296-171.099-25.924-212.577-64.81-95.919-187.949-133.509-282.572-84.253-32.405 16.851-37.59 16.851-44.071 0-6.481-19.443-37.59-23.332-68.699-10.37zM738.835 964.375c0 32.405 6.481 59.625 12.962 59.625 15.554 0 12.962-102.4-2.592-111.473-5.185-3.889-10.37 19.443-10.37 51.848zM1075.848 964.375c0 32.405 6.481 59.625 12.962 59.625 15.554 0 12.962-102.4-2.592-111.473-5.185-3.889-10.37 19.443-10.37 51.848zM995.484 935.858c-9.073 27.22 5.185 88.142 20.739 88.142 20.739 0 20.739-88.142 0-95.919-9.073-3.889-18.147 0-20.739 7.777zM16.851 942.339c-9.073 2.592-16.851 23.332-16.851 44.071 0 31.109 5.185 37.59 32.405 37.59 18.147 0 32.405-6.481 32.405-12.962 0-7.777-9.073-12.962-19.443-12.962s-19.443-9.073-19.443-19.443c0-10.37 9.073-19.443 19.443-19.443s19.443-6.481 19.443-12.962c0-12.962-20.739-15.554-47.959-3.889zM103.696 952.709c-22.035 25.924-10.37 63.514 20.739 68.699 38.886 5.185 62.218-27.22 44.071-60.922-16.851-32.405-42.775-34.997-64.81-7.777zM137.397 994.187c-12.962 12.962-24.628-6.481-12.962-23.332 7.777-14.258 10.37-14.258 15.554 0 2.592 9.073 1.296 19.443-2.592 23.332zM224.243 942.339c-19.443 6.481-23.332 81.661-5.185 81.661 6.481 0 15.554-12.962 18.147-29.813l5.185-28.516 2.592 28.516c2.592 42.775 37.59 37.59 44.071-5.185l5.185-36.294 2.592 34.997c0 20.739 7.777 36.294 15.554 36.294s11.666-18.147 9.073-42.775c-3.889-36.294-9.073-41.478-42.775-42.775-20.739-1.296-46.663 1.296-54.441 3.889zM370.714 942.339c-12.962 12.962-9.073 81.661 3.889 81.661 6.481 0 15.554-15.554 18.147-34.997l5.185-36.294 2.592 34.997c1.296 44.071 36.294 49.256 44.071 6.481l5.185-28.516 2.592 28.516c0 16.851 7.777 29.813 14.258 29.813 18.147 0 15.554-59.625-2.592-73.884-19.443-14.258-80.365-19.443-93.327-7.777zM639.028 942.339c-19.443 7.777-23.332 81.661-3.889 81.661 7.777 0 12.962-11.666 12.962-25.924s5.185-25.924 11.666-25.924c6.481 0 9.073 11.666 5.185 25.924-5.185 18.147 0 25.924 14.258 25.924 24.628 0 28.516-51.848 5.185-75.18-16.851-16.851-20.739-16.851-45.367-6.481zM803.646 952.709c-22.035 27.22-10.37 64.81 23.332 68.699 15.554 2.592 28.516-1.296 28.516-9.073s-9.073-14.258-19.443-14.258c-28.516 0-23.332-27.22 6.481-34.997 14.258-3.889 22.035-11.666 18.147-18.147-10.37-18.147-38.886-14.258-57.033 7.777zM902.157 944.932c-5.185 6.481 0 15.554 7.777 18.147 12.962 5.185 12.962 7.777 1.296 7.777-9.073 1.296-16.851 12.962-16.851 27.22 0 19.443 9.073 25.924 32.405 25.924 33.701 0 41.478-24.628 24.628-69.995-9.073-22.035-37.59-27.22-49.256-9.073zM1140.658 952.709c-23.332 28.516-10.37 64.81 24.628 64.81 41.478 0 63.514-36.294 41.478-63.514-22.035-25.924-45.367-25.924-66.106-1.296zM1174.359 994.187c-12.962 12.962-24.628-6.481-12.962-23.332 7.777-14.258 10.37-14.258 15.554 0 2.592 9.073 1.296 19.443-2.592 23.332zM1261.205 942.339c-19.443 6.481-23.332 81.661-5.185 81.661 6.481 0 15.554-12.962 18.147-29.813l5.185-28.516 2.592 28.516c1.296 40.182 40.182 38.886 40.182 0 0-16.851-6.481-36.294-15.554-45.367-16.851-16.851-20.739-16.851-45.367-6.481zM1363.605 948.82c-10.37 10.37-11.666 19.443-1.296 32.405 10.37 11.666 10.37 16.851 0 16.851-7.777 0-14.258 5.185-14.258 12.962 0 6.481 14.258 12.962 32.405 12.962 23.332 0 32.405-6.481 32.405-24.628 0-14.258-7.777-28.516-15.554-31.109-12.962-5.185-12.962-7.777 0-7.777 9.073-1.296 12.962-7.777 9.073-14.258-10.37-16.851-23.332-16.851-42.775 2.592zM505.519 964.375c0 38.886 16.851 59.625 46.663 59.625 25.924 0 31.109-6.481 31.109-38.886 0-45.367-22.035-51.848-29.813-10.37l-5.185 29.813-2.592-29.813c-1.296-31.109-40.182-41.478-40.182-10.37z" ], "width": 1413, "tags": [ "zencommunication" ], "defaultCode": 59655, "grid": 20 }, { "paths": [ "M697.217 202.5l-185.217 185.821-97.737-98.341-98.341-97.737h146.606l2.413-30.166 1.81-30.166h-250.375v250.375l57.315-1.207 1.81-74.811 1.81-74.207 234.689 234.689 412.666-412.666-17.496-18.703c-9.653-9.653-18.703-18.1-20.513-18.1s-86.877 83.258-189.441 185.217z", "M436.586 518.034c-127.903 14.479-246.152 60.935-349.922 136.349-56.711 41.628-80.241 64.555-85.068 82.654-3.62 15.686 1.81 22.926 65.158 86.273 42.835 42.835 73.604 68.778 80.844 68.778s29.562-13.273 50.075-30.166c20.513-16.29 50.075-37.406 65.761-46.455 51.282-28.959 49.472-25.339 49.472-108.597v-74.811l19.91-8.446c75.414-31.372 243.739-32.579 355.352-3.017l22.926 6.637v74.811c0 84.464 1.207 87.48 47.662 111.613 13.273 6.637 38.613 24.132 56.711 38.613 42.232 34.389 55.504 41.025 73.001 36.199 7.843-1.81 41.628-31.372 75.414-65.158 70.587-72.397 74.207-83.258 39.215-115.837-93.514-88.687-219.606-152.638-352.335-179.787-57.315-11.463-159.879-16.29-214.176-9.653z" ], "tags": [ "call_failfwd" ], "defaultCode": 59656, "grid": 20 }, { "paths": [ "M807.374 108.322v75.484h-203.479v210.7l203.479-3.938v157.533l127.995-127.995c70.234-70.234 127.995-130.62 127.995-133.247 0-6.563-244.176-254.022-250.739-254.022-3.283 0-5.252 34.131-5.252 75.484z", "M54.499 148.361c-16.409 17.067-17.067 18.378-13.128 86.643 11.815 213.325 101.741 414.179 254.677 565.805 154.908 154.25 351.166 241.551 568.432 254.022 64.982 3.938 66.951 3.283 83.36-13.128 17.067-17.067 17.067-17.722 17.067-125.37 0-139.155 1.313-137.185-98.458-147.687-38.070-3.938-85.987-11.159-106.99-17.067-21.005-5.252-44.635-9.846-52.512-9.846-9.19 0-36.758 22.317-78.111 63.669l-64.327 63.669-21.005-11.815c-129.965-74.172-230.392-174.599-304.564-303.906l-11.815-21.661 63.669-64.327c68.921-70.89 70.234-73.515 53.823-128.651-5.252-17.722-13.128-64.982-17.067-105.021-11.159-104.365-8.532-102.396-147.687-102.396-107.648 0-108.304 0-125.37 17.067z" ], "tags": [ "call_hardfwd" ], "defaultCode": 59657, "grid": 20 }, { "paths": [ "M128 0h768c70.692 0 128 57.308 128 128v768c0 70.692-57.308 128-128 128h-768c-70.692 0-128-57.308-128-128v-768c0-70.692 57.308-128 128-128z", "M494.413 102.4l-366.413 432.358 194.854 397.030 133.248-159.411-25.28-51.494-96.448 115.379-144.192-293.798 290.765-343.078 49.485 108.749 37.478-44.224zM597.018 327.91l-37.466 44.211 57.37 126.118-141.478 169.254 25.28 51.507 177.587-212.442zM712.115 102.4l-366.426 432.358 194.854 397.030 355.456-425.242zM698.637 199.386l135.987 298.854-282.56 338.022-144.192-293.798 290.765-343.078z" ], "attrs": [ { "fill": "rgb(0, 78, 168)" }, { "fill": "rgb(255, 255, 255)" } ], "isMulticolor": true, "colorPermutations": { "2552552551781681": [ { "f": 0 }, { "f": 1 } ] }, "tags": [ "transnexus" ], "defaultCode": 59658, "grid": 20 }, { "paths": [ "M0 512c0 282.77 229.23 512 512 512s512-229.23 512-512-229.23-512-512-512-512 229.23-512 512zM928 512c0 229.75-186.25 416-416 416s-416-186.25-416-416 186.25-416 416-416 416 186.25 416 416zM706.744 669.256l90.512-90.512-285.256-285.254-285.254 285.256 90.508 90.508 194.746-194.744z" ], "tags": [ "circle-up" ], "defaultCode": 59969, "grid": 20 }, { "paths": [ "M1024 512c0-282.77-229.23-512-512-512s-512 229.23-512 512 229.23 512 512 512 512-229.23 512-512zM96 512c0-229.75 186.25-416 416-416s416 186.25 416 416-186.25 416-416 416-416-186.25-416-416zM317.256 354.744l-90.512 90.512 285.256 285.254 285.254-285.256-90.508-90.508-194.746 194.744z" ], "tags": [ "circle-down" ], "defaultCode": 59971, "grid": 20 } ], "colorThemes": [ [ [ 0, 78, 168, 1 ], [ 255, 255, 255, 1 ] ] ], "colorThemeIdx": 0, "preferences": { "showGlyphs": true, "showQuickUse": true, "showQuickUse2": true, "showSVGs": true, "fontPref": { "prefix": "icon-", "metadata": { "fontFamily": "icomoon" }, "metrics": { "emSize": 1024, "baseline": 6.25, "whitespace": 50 }, "embed": false }, "imagePref": { "prefix": "icon-", "png": true, "useClassSelector": true, "color": 0, "bgColor": 16777215, "classSelector": ".icon" }, "historySize": 50, "showCodes": true, "gridSize": 16 }, "IcoMoonType": "icon-set" } ================================================ FILE: gui/static/js/backupandrestore.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof reloadKamRequired === "undefined") { throw new Error("reloadKamRequired() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } /* global variables */ var loading_spinner = $('#reloading_overlay'); /** * Show a spinner while loading * @param isLoading {boolean} */ function changeLoadingState(isLoading) { if (isLoading) { loading_spinner.removeClass('hidden'); } else { loading_spinner.addClass('hidden'); } } function downloadResponse(response, filename) { var a = document.createElement("a"); var fp = new Blob([response], {type: 'text/plain'}); var url = window.URL.createObjectURL(fp); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } /** * Get the current date / time formatted as "YYYYmmdd-HHMM" * @returns {string} */ function getFormattedDateTime() { var d = new Date(); return d.getFullYear().toString() + (d.getMonth() + 1).toString().padStart(2, '0') + d.getDate().toString().padStart(2, '0') + '-' + d.getHours().toString().padStart(2, '0') + d.getMinutes().toString().padStart(2, '0'); } $('#start-Backup').click(function() { $.ajax({ url: API_BASE_URL + 'backupandrestore/backup', type: 'GET', cache: false, processData: false, accepts: { json: "application/json", text: "application/sql" }, beforeSend: function(xhr, settings) { changeLoadingState(true); }, success: function(response, text_status, xhr) { showNotification("Database backup complete"); var fname = getFormattedDateTime() + '.sql'; downloadResponse(response, fname); }, error: function(xhr, text_status, error_msg) { var string = JSON.stringify(xhr.responseJSON); var json_object = JSON.parse(string); showNotification(json_object.msg, true); }, complete: function(xhr, text_status) { changeLoadingState(false); }, }); }); $('#restore-backup').submit(function(event) { event.preventDefault(); var formData = new FormData($(this)[0]); $.ajax({ url: API_BASE_URL + 'backupandrestore/restore', type: 'POST', data: formData, cache: false, contentType: false, processData: false, beforeSend: function(xhr, settings) { changeLoadingState(true); }, success: function(response, text_status, xhr) { showNotification("Database restore complete"); reloadKamRequired(true); }, error: function(xhr, text_status, error_msg) { var string = JSON.stringify(xhr.responseJSON); var json_object = JSON.parse(string); showNotification(json_object.msg, true); }, complete: function(xhr, text_status) { changeLoadingState(false); }, }); return false; }); })(window, document); ================================================ FILE: gui/static/js/bootstrap-toggle.js ================================================ /*! ======================================================================== * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 * http://www.bootstraptoggle.com * ======================================================================== * Copyright 2014 Min Hur, The New York Times Company * Licensed under MIT * ======================================================================== */ +function ($) { 'use strict'; // TOGGLE PUBLIC CLASS DEFINITION // ============================== var Toggle = function (element, options) { this.$element = $(element) this.options = $.extend({}, this.defaults(), options) this.render() } Toggle.VERSION = '2.2.0' Toggle.DEFAULTS = { on: 'On', off: 'Off', onstyle: 'primary', offstyle: 'default', size: 'normal', style: '', width: null, height: null } Toggle.prototype.defaults = function() { return { on: this.$element.attr('data-on') || Toggle.DEFAULTS.on, off: this.$element.attr('data-off') || Toggle.DEFAULTS.off, onstyle: this.$element.attr('data-onstyle') || Toggle.DEFAULTS.onstyle, offstyle: this.$element.attr('data-offstyle') || Toggle.DEFAULTS.offstyle, size: this.$element.attr('data-size') || Toggle.DEFAULTS.size, style: this.$element.attr('data-style') || Toggle.DEFAULTS.style, width: this.$element.attr('data-width') || Toggle.DEFAULTS.width, height: this.$element.attr('data-height') || Toggle.DEFAULTS.height } } Toggle.prototype.render = function () { this._onstyle = 'btn-' + this.options.onstyle this._offstyle = 'btn-' + this.options.offstyle var size = this.options.size === 'large' ? 'btn-lg' : this.options.size === 'small' ? 'btn-sm' : this.options.size === 'mini' ? 'btn-xs' : '' var $toggleOn = $('<label class="btn">').html(this.options.on) .addClass(this._onstyle + ' ' + size) var $toggleOff = $('<label class="btn">').html(this.options.off) .addClass(this._offstyle + ' ' + size + ' active') var $toggleHandle = $('<span class="toggle-handle btn btn-default">') .addClass(size) var $toggleGroup = $('<div class="toggle-group">') .append($toggleOn, $toggleOff, $toggleHandle) var $toggle = $('<div class="toggle btn" data-toggle="toggle">') .addClass( this.$element.prop('checked') ? this._onstyle : this._offstyle+' off' ) .addClass(size).addClass(this.options.style) this.$element.wrap($toggle) $.extend(this, { $toggle: this.$element.parent(), $toggleOn: $toggleOn, $toggleOff: $toggleOff, $toggleGroup: $toggleGroup }) this.$toggle.append($toggleGroup) var width = this.options.width || Math.max($toggleOn.outerWidth(), $toggleOff.outerWidth())+($toggleHandle.outerWidth()/2) var height = this.options.height || Math.max($toggleOn.outerHeight(), $toggleOff.outerHeight()) $toggleOn.addClass('toggle-on') $toggleOff.addClass('toggle-off') this.$toggle.css({ width: width, height: height }) if (this.options.height) { $toggleOn.css('line-height', $toggleOn.height() + 'px') $toggleOff.css('line-height', $toggleOff.height() + 'px') } this.update(true) this.trigger(true) } Toggle.prototype.toggle = function () { if (this.$element.prop('checked')) this.off() else this.on() } Toggle.prototype.on = function (silent) { if (this.$element.prop('disabled')) return false this.$toggle.removeClass(this._offstyle + ' off').addClass(this._onstyle) this.$element.prop('checked', true) if (!silent) this.trigger() } Toggle.prototype.off = function (silent) { if (this.$element.prop('disabled')) return false this.$toggle.removeClass(this._onstyle).addClass(this._offstyle + ' off') this.$element.prop('checked', false) if (!silent) this.trigger() } Toggle.prototype.enable = function () { this.$toggle.removeAttr('disabled') this.$element.prop('disabled', false) } Toggle.prototype.disable = function () { this.$toggle.attr('disabled', 'disabled') this.$element.prop('disabled', true) } Toggle.prototype.update = function (silent) { if (this.$element.prop('disabled')) this.disable() else this.enable() if (this.$element.prop('checked')) this.on(silent) else this.off(silent) } Toggle.prototype.trigger = function (silent) { this.$element.off('change.bs.toggle') if (!silent) this.$element.change() this.$element.on('change.bs.toggle', $.proxy(function() { this.update() }, this)) } Toggle.prototype.destroy = function() { this.$element.off('change.bs.toggle') this.$toggleGroup.remove() this.$element.removeData('bs.toggle') this.$element.unwrap() } // TOGGLE PLUGIN DEFINITION // ======================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.toggle') var options = typeof option == 'object' && option if (!data) $this.data('bs.toggle', (data = new Toggle(this, options))) if (typeof option == 'string' && data[option]) data[option]() }) } var old = $.fn.bootstrapToggle $.fn.bootstrapToggle = Plugin $.fn.bootstrapToggle.Constructor = Toggle // TOGGLE NO CONFLICT // ================== $.fn.toggle.noConflict = function () { $.fn.bootstrapToggle = old return this } // TOGGLE DATA-API // =============== $(function() { $('input[type=checkbox][data-toggle^=toggle]').bootstrapToggle() }) $(document).on('click.bs.toggle', 'div[data-toggle^=toggle]', function(e) { var $checkbox = $(this).find('input[type=checkbox]') $checkbox.bootstrapToggle('toggle') e.preventDefault() }) }(jQuery); ================================================ FILE: gui/static/js/bootstrap.js ================================================ /*! * Bootstrap v3.3.7 (http://getbootstrap.com) * Copyright 2011-2016 Twitter, Inc. * Licensed under the MIT license */ if (typeof jQuery === 'undefined') { throw new Error('Bootstrap\'s JavaScript requires jQuery') } +function ($) { 'use strict'; var version = $.fn.jquery.split(' ')[0].split('.') if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) { throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4') } }(jQuery); /* ======================================================================== * Bootstrap: transition.js v3.3.7 * http://getbootstrap.com/javascript/#transitions * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) // ============================================================ function transitionEnd() { var el = document.createElement('bootstrap') var transEndEventNames = { WebkitTransition : 'webkitTransitionEnd', MozTransition : 'transitionend', OTransition : 'oTransitionEnd otransitionend', transition : 'transitionend' } for (var name in transEndEventNames) { if (el.style[name] !== undefined) { return { end: transEndEventNames[name] } } } return false // explicit for ie8 ( ._.) } // http://blog.alexmaccaw.com/css-transitions $.fn.emulateTransitionEnd = function (duration) { var called = false var $el = this $(this).one('bsTransitionEnd', function () { called = true }) var callback = function () { if (!called) $($el).trigger($.support.transition.end) } setTimeout(callback, duration) return this } $(function () { $.support.transition = transitionEnd() if (!$.support.transition) return $.event.special.bsTransitionEnd = { bindType: $.support.transition.end, delegateType: $.support.transition.end, handle: function (e) { if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) } } }) }(jQuery); /* ======================================================================== * Bootstrap: alert.js v3.3.7 * http://getbootstrap.com/javascript/#alerts * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // ALERT CLASS DEFINITION // ====================== var dismiss = '[data-dismiss="alert"]' var Alert = function (el) { $(el).on('click', dismiss, this.close) } Alert.VERSION = '3.3.7' Alert.TRANSITION_DURATION = 150 Alert.prototype.close = function (e) { var $this = $(this) var selector = $this.attr('data-target') if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } var $parent = $(selector === '#' ? [] : selector) if (e) e.preventDefault() if (!$parent.length) { $parent = $this.closest('.alert') } $parent.trigger(e = $.Event('close.bs.alert')) if (e.isDefaultPrevented()) return $parent.removeClass('in') function removeElement() { // detach from parent, fire event then clean up data $parent.detach().trigger('closed.bs.alert').remove() } $.support.transition && $parent.hasClass('fade') ? $parent .one('bsTransitionEnd', removeElement) .emulateTransitionEnd(Alert.TRANSITION_DURATION) : removeElement() } // ALERT PLUGIN DEFINITION // ======================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.alert') if (!data) $this.data('bs.alert', (data = new Alert(this))) if (typeof option == 'string') data[option].call($this) }) } var old = $.fn.alert $.fn.alert = Plugin $.fn.alert.Constructor = Alert // ALERT NO CONFLICT // ================= $.fn.alert.noConflict = function () { $.fn.alert = old return this } // ALERT DATA-API // ============== $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) }(jQuery); /* ======================================================================== * Bootstrap: button.js v3.3.7 * http://getbootstrap.com/javascript/#buttons * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // BUTTON PUBLIC CLASS DEFINITION // ============================== var Button = function (element, options) { this.$element = $(element) this.options = $.extend({}, Button.DEFAULTS, options) this.isLoading = false } Button.VERSION = '3.3.7' Button.DEFAULTS = { loadingText: 'loading...' } Button.prototype.setState = function (state) { var d = 'disabled' var $el = this.$element var val = $el.is('input') ? 'val' : 'html' var data = $el.data() state += 'Text' if (data.resetText == null) $el.data('resetText', $el[val]()) // push to event loop to allow forms to submit setTimeout($.proxy(function () { $el[val](data[state] == null ? this.options[state] : data[state]) if (state == 'loadingText') { this.isLoading = true $el.addClass(d).attr(d, d).prop(d, true) } else if (this.isLoading) { this.isLoading = false $el.removeClass(d).removeAttr(d).prop(d, false) } }, this), 0) } Button.prototype.toggle = function () { var changed = true var $parent = this.$element.closest('[data-toggle="buttons"]') if ($parent.length) { var $input = this.$element.find('input') if ($input.prop('type') == 'radio') { if ($input.prop('checked')) changed = false $parent.find('.active').removeClass('active') this.$element.addClass('active') } else if ($input.prop('type') == 'checkbox') { if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false this.$element.toggleClass('active') } $input.prop('checked', this.$element.hasClass('active')) if (changed) $input.trigger('change') } else { this.$element.attr('aria-pressed', !this.$element.hasClass('active')) this.$element.toggleClass('active') } } // BUTTON PLUGIN DEFINITION // ======================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.button') var options = typeof option == 'object' && option if (!data) $this.data('bs.button', (data = new Button(this, options))) if (option == 'toggle') data.toggle() else if (option) data.setState(option) }) } var old = $.fn.button $.fn.button = Plugin $.fn.button.Constructor = Button // BUTTON NO CONFLICT // ================== $.fn.button.noConflict = function () { $.fn.button = old return this } // BUTTON DATA-API // =============== $(document) .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { var $btn = $(e.target).closest('.btn') Plugin.call($btn, 'toggle') if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) { // Prevent double click on radios, and the double selections (so cancellation) on checkboxes e.preventDefault() // The target component still receive the focus if ($btn.is('input,button')) $btn.trigger('focus') else $btn.find('input:visible,button:visible').first().trigger('focus') } }) .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) }) }(jQuery); /* ======================================================================== * Bootstrap: carousel.js v3.3.7 * http://getbootstrap.com/javascript/#carousel * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // CAROUSEL CLASS DEFINITION // ========================= var Carousel = function (element, options) { this.$element = $(element) this.$indicators = this.$element.find('.carousel-indicators') this.options = options this.paused = null this.sliding = null this.interval = null this.$active = null this.$items = null this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) } Carousel.VERSION = '3.3.7' Carousel.TRANSITION_DURATION = 600 Carousel.DEFAULTS = { interval: 5000, pause: 'hover', wrap: true, keyboard: true } Carousel.prototype.keydown = function (e) { if (/input|textarea/i.test(e.target.tagName)) return switch (e.which) { case 37: this.prev(); break case 39: this.next(); break default: return } e.preventDefault() } Carousel.prototype.cycle = function (e) { e || (this.paused = false) this.interval && clearInterval(this.interval) this.options.interval && !this.paused && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) return this } Carousel.prototype.getItemIndex = function (item) { this.$items = item.parent().children('.item') return this.$items.index(item || this.$active) } Carousel.prototype.getItemForDirection = function (direction, active) { var activeIndex = this.getItemIndex(active) var willWrap = (direction == 'prev' && activeIndex === 0) || (direction == 'next' && activeIndex == (this.$items.length - 1)) if (willWrap && !this.options.wrap) return active var delta = direction == 'prev' ? -1 : 1 var itemIndex = (activeIndex + delta) % this.$items.length return this.$items.eq(itemIndex) } Carousel.prototype.to = function (pos) { var that = this var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) if (pos > (this.$items.length - 1) || pos < 0) return if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" if (activeIndex == pos) return this.pause().cycle() return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) } Carousel.prototype.pause = function (e) { e || (this.paused = true) if (this.$element.find('.next, .prev').length && $.support.transition) { this.$element.trigger($.support.transition.end) this.cycle(true) } this.interval = clearInterval(this.interval) return this } Carousel.prototype.next = function () { if (this.sliding) return return this.slide('next') } Carousel.prototype.prev = function () { if (this.sliding) return return this.slide('prev') } Carousel.prototype.slide = function (type, next) { var $active = this.$element.find('.item.active') var $next = next || this.getItemForDirection(type, $active) var isCycling = this.interval var direction = type == 'next' ? 'left' : 'right' var that = this if ($next.hasClass('active')) return (this.sliding = false) var relatedTarget = $next[0] var slideEvent = $.Event('slide.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) this.$element.trigger(slideEvent) if (slideEvent.isDefaultPrevented()) return this.sliding = true isCycling && this.pause() if (this.$indicators.length) { this.$indicators.find('.active').removeClass('active') var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) $nextIndicator && $nextIndicator.addClass('active') } var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" if ($.support.transition && this.$element.hasClass('slide')) { $next.addClass(type) $next[0].offsetWidth // force reflow $active.addClass(direction) $next.addClass(direction) $active .one('bsTransitionEnd', function () { $next.removeClass([type, direction].join(' ')).addClass('active') $active.removeClass(['active', direction].join(' ')) that.sliding = false setTimeout(function () { that.$element.trigger(slidEvent) }, 0) }) .emulateTransitionEnd(Carousel.TRANSITION_DURATION) } else { $active.removeClass('active') $next.addClass('active') this.sliding = false this.$element.trigger(slidEvent) } isCycling && this.cycle() return this } // CAROUSEL PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.carousel') var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) var action = typeof option == 'string' ? option : options.slide if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) if (typeof option == 'number') data.to(option) else if (action) data[action]() else if (options.interval) data.pause().cycle() }) } var old = $.fn.carousel $.fn.carousel = Plugin $.fn.carousel.Constructor = Carousel // CAROUSEL NO CONFLICT // ==================== $.fn.carousel.noConflict = function () { $.fn.carousel = old return this } // CAROUSEL DATA-API // ================= var clickHandler = function (e) { var href var $this = $(this) var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 if (!$target.hasClass('carousel')) return var options = $.extend({}, $target.data(), $this.data()) var slideIndex = $this.attr('data-slide-to') if (slideIndex) options.interval = false Plugin.call($target, options) if (slideIndex) { $target.data('bs.carousel').to(slideIndex) } e.preventDefault() } $(document) .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) $(window).on('load', function () { $('[data-ride="carousel"]').each(function () { var $carousel = $(this) Plugin.call($carousel, $carousel.data()) }) }) }(jQuery); /* ======================================================================== * Bootstrap: collapse.js v3.3.7 * http://getbootstrap.com/javascript/#collapse * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ /* jshint latedef: false */ +function ($) { 'use strict'; // COLLAPSE PUBLIC CLASS DEFINITION // ================================ var Collapse = function (element, options) { this.$element = $(element) this.options = $.extend({}, Collapse.DEFAULTS, options) this.$trigger = $('[data-toggle="collapse"][href="#' + element.id + '"],' + '[data-toggle="collapse"][data-target="#' + element.id + '"]') this.transitioning = null if (this.options.parent) { this.$parent = this.getParent() } else { this.addAriaAndCollapsedClass(this.$element, this.$trigger) } if (this.options.toggle) this.toggle() } Collapse.VERSION = '3.3.7' Collapse.TRANSITION_DURATION = 350 Collapse.DEFAULTS = { toggle: true } Collapse.prototype.dimension = function () { var hasWidth = this.$element.hasClass('width') return hasWidth ? 'width' : 'height' } Collapse.prototype.show = function () { if (this.transitioning || this.$element.hasClass('in')) return var activesData var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') if (actives && actives.length) { activesData = actives.data('bs.collapse') if (activesData && activesData.transitioning) return } var startEvent = $.Event('show.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return if (actives && actives.length) { Plugin.call(actives, 'hide') activesData || actives.data('bs.collapse', null) } var dimension = this.dimension() this.$element .removeClass('collapse') .addClass('collapsing')[dimension](0) .attr('aria-expanded', true) this.$trigger .removeClass('collapsed') .attr('aria-expanded', true) this.transitioning = 1 var complete = function () { this.$element .removeClass('collapsing') .addClass('collapse in')[dimension]('') this.transitioning = 0 this.$element .trigger('shown.bs.collapse') } if (!$.support.transition) return complete.call(this) var scrollSize = $.camelCase(['scroll', dimension].join('-')) this.$element .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) } Collapse.prototype.hide = function () { if (this.transitioning || !this.$element.hasClass('in')) return var startEvent = $.Event('hide.bs.collapse') this.$element.trigger(startEvent) if (startEvent.isDefaultPrevented()) return var dimension = this.dimension() this.$element[dimension](this.$element[dimension]())[0].offsetHeight this.$element .addClass('collapsing') .removeClass('collapse in') .attr('aria-expanded', false) this.$trigger .addClass('collapsed') .attr('aria-expanded', false) this.transitioning = 1 var complete = function () { this.transitioning = 0 this.$element .removeClass('collapsing') .addClass('collapse') .trigger('hidden.bs.collapse') } if (!$.support.transition) return complete.call(this) this.$element [dimension](0) .one('bsTransitionEnd', $.proxy(complete, this)) .emulateTransitionEnd(Collapse.TRANSITION_DURATION) } Collapse.prototype.toggle = function () { this[this.$element.hasClass('in') ? 'hide' : 'show']() } Collapse.prototype.getParent = function () { return $(this.options.parent) .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') .each($.proxy(function (i, element) { var $element = $(element) this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) }, this)) .end() } Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { var isOpen = $element.hasClass('in') $element.attr('aria-expanded', isOpen) $trigger .toggleClass('collapsed', !isOpen) .attr('aria-expanded', isOpen) } function getTargetFromTrigger($trigger) { var href var target = $trigger.attr('data-target') || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 return $(target) } // COLLAPSE PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.collapse') var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.collapse $.fn.collapse = Plugin $.fn.collapse.Constructor = Collapse // COLLAPSE NO CONFLICT // ==================== $.fn.collapse.noConflict = function () { $.fn.collapse = old return this } // COLLAPSE DATA-API // ================= $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { var $this = $(this) if (!$this.attr('data-target')) e.preventDefault() var $target = getTargetFromTrigger($this) var data = $target.data('bs.collapse') var option = data ? 'toggle' : $this.data() Plugin.call($target, option) }) }(jQuery); /* ======================================================================== * Bootstrap: dropdown.js v3.3.7 * http://getbootstrap.com/javascript/#dropdowns * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // DROPDOWN CLASS DEFINITION // ========================= var backdrop = '.dropdown-backdrop' var toggle = '[data-toggle="dropdown"]' var Dropdown = function (element) { $(element).on('click.bs.dropdown', this.toggle) } Dropdown.VERSION = '3.3.7' function getParent($this) { var selector = $this.attr('data-target') if (!selector) { selector = $this.attr('href') selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } var $parent = selector && $(selector) return $parent && $parent.length ? $parent : $this.parent() } function clearMenus(e) { if (e && e.which === 3) return $(backdrop).remove() $(toggle).each(function () { var $this = $(this) var $parent = getParent($this) var relatedTarget = { relatedTarget: this } if (!$parent.hasClass('open')) return if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) if (e.isDefaultPrevented()) return $this.attr('aria-expanded', 'false') $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget)) }) } Dropdown.prototype.toggle = function (e) { var $this = $(this) if ($this.is('.disabled, :disabled')) return var $parent = getParent($this) var isActive = $parent.hasClass('open') clearMenus() if (!isActive) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { // if mobile we use a backdrop because click events don't delegate $(document.createElement('div')) .addClass('dropdown-backdrop') .insertAfter($(this)) .on('click', clearMenus) } var relatedTarget = { relatedTarget: this } $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) if (e.isDefaultPrevented()) return $this .trigger('focus') .attr('aria-expanded', 'true') $parent .toggleClass('open') .trigger($.Event('shown.bs.dropdown', relatedTarget)) } return false } Dropdown.prototype.keydown = function (e) { if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return var $this = $(this) e.preventDefault() e.stopPropagation() if ($this.is('.disabled, :disabled')) return var $parent = getParent($this) var isActive = $parent.hasClass('open') if (!isActive && e.which != 27 || isActive && e.which == 27) { if (e.which == 27) $parent.find(toggle).trigger('focus') return $this.trigger('click') } var desc = ' li:not(.disabled):visible a' var $items = $parent.find('.dropdown-menu' + desc) if (!$items.length) return var index = $items.index(e.target) if (e.which == 38 && index > 0) index-- // up if (e.which == 40 && index < $items.length - 1) index++ // down if (!~index) index = 0 $items.eq(index).trigger('focus') } // DROPDOWN PLUGIN DEFINITION // ========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.dropdown') if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) if (typeof option == 'string') data[option].call($this) }) } var old = $.fn.dropdown $.fn.dropdown = Plugin $.fn.dropdown.Constructor = Dropdown // DROPDOWN NO CONFLICT // ==================== $.fn.dropdown.noConflict = function () { $.fn.dropdown = old return this } // APPLY TO STANDARD DROPDOWN ELEMENTS // =================================== $(document) .on('click.bs.dropdown.data-api', clearMenus) .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown) }(jQuery); /* ======================================================================== * Bootstrap: modal.js v3.3.7 * http://getbootstrap.com/javascript/#modals * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // MODAL CLASS DEFINITION // ====================== var Modal = function (element, options) { this.options = options this.$body = $(document.body) this.$element = $(element) this.$dialog = this.$element.find('.modal-dialog') this.$backdrop = null this.isShown = null this.originalBodyPad = null this.scrollbarWidth = 0 this.ignoreBackdropClick = false if (this.options.remote) { this.$element .find('.modal-content') .load(this.options.remote, $.proxy(function () { this.$element.trigger('loaded.bs.modal') }, this)) } } Modal.VERSION = '3.3.7' Modal.TRANSITION_DURATION = 300 Modal.BACKDROP_TRANSITION_DURATION = 150 Modal.DEFAULTS = { backdrop: true, keyboard: true, show: true } Modal.prototype.toggle = function (_relatedTarget) { return this.isShown ? this.hide() : this.show(_relatedTarget) } Modal.prototype.show = function (_relatedTarget) { var that = this var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) this.$element.trigger(e) if (this.isShown || e.isDefaultPrevented()) return this.isShown = true this.checkScrollbar() this.setScrollbar() this.$body.addClass('modal-open') this.escape() this.resize() this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) this.$dialog.on('mousedown.dismiss.bs.modal', function () { that.$element.one('mouseup.dismiss.bs.modal', function (e) { if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true }) }) this.backdrop(function () { var transition = $.support.transition && that.$element.hasClass('fade') if (!that.$element.parent().length) { that.$element.appendTo(that.$body) // don't move modals dom position } that.$element .show() .scrollTop(0) that.adjustDialog() if (transition) { that.$element[0].offsetWidth // force reflow } that.$element.addClass('in') that.enforceFocus() var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) transition ? that.$dialog // wait for modal to slide in .one('bsTransitionEnd', function () { that.$element.trigger('focus').trigger(e) }) .emulateTransitionEnd(Modal.TRANSITION_DURATION) : that.$element.trigger('focus').trigger(e) }) } Modal.prototype.hide = function (e) { if (e) e.preventDefault() e = $.Event('hide.bs.modal') this.$element.trigger(e) if (!this.isShown || e.isDefaultPrevented()) return this.isShown = false this.escape() this.resize() $(document).off('focusin.bs.modal') this.$element .removeClass('in') .off('click.dismiss.bs.modal') .off('mouseup.dismiss.bs.modal') this.$dialog.off('mousedown.dismiss.bs.modal') $.support.transition && this.$element.hasClass('fade') ? this.$element .one('bsTransitionEnd', $.proxy(this.hideModal, this)) .emulateTransitionEnd(Modal.TRANSITION_DURATION) : this.hideModal() } Modal.prototype.enforceFocus = function () { $(document) .off('focusin.bs.modal') // guard against infinite focus loop .on('focusin.bs.modal', $.proxy(function (e) { if (document !== e.target && this.$element[0] !== e.target && !this.$element.has(e.target).length) { this.$element.trigger('focus') } }, this)) } Modal.prototype.escape = function () { if (this.isShown && this.options.keyboard) { this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { e.which == 27 && this.hide() }, this)) } else if (!this.isShown) { this.$element.off('keydown.dismiss.bs.modal') } } Modal.prototype.resize = function () { if (this.isShown) { $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) } else { $(window).off('resize.bs.modal') } } Modal.prototype.hideModal = function () { var that = this this.$element.hide() this.backdrop(function () { that.$body.removeClass('modal-open') that.resetAdjustments() that.resetScrollbar() that.$element.trigger('hidden.bs.modal') }) } Modal.prototype.removeBackdrop = function () { this.$backdrop && this.$backdrop.remove() this.$backdrop = null } Modal.prototype.backdrop = function (callback) { var that = this var animate = this.$element.hasClass('fade') ? 'fade' : '' if (this.isShown && this.options.backdrop) { var doAnimate = $.support.transition && animate this.$backdrop = $(document.createElement('div')) .addClass('modal-backdrop ' + animate) .appendTo(this.$body) this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) { if (this.ignoreBackdropClick) { this.ignoreBackdropClick = false return } if (e.target !== e.currentTarget) return this.options.backdrop == 'static' ? this.$element[0].focus() : this.hide() }, this)) if (doAnimate) this.$backdrop[0].offsetWidth // force reflow this.$backdrop.addClass('in') if (!callback) return doAnimate ? this.$backdrop .one('bsTransitionEnd', callback) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : callback() } else if (!this.isShown && this.$backdrop) { this.$backdrop.removeClass('in') var callbackRemove = function () { that.removeBackdrop() callback && callback() } $.support.transition && this.$element.hasClass('fade') ? this.$backdrop .one('bsTransitionEnd', callbackRemove) .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : callbackRemove() } else if (callback) { callback() } } // these following methods are used to handle overflowing modals Modal.prototype.handleUpdate = function () { this.adjustDialog() } Modal.prototype.adjustDialog = function () { var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight this.$element.css({ paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' }) } Modal.prototype.resetAdjustments = function () { this.$element.css({ paddingLeft: '', paddingRight: '' }) } Modal.prototype.checkScrollbar = function () { var fullWindowWidth = window.innerWidth if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8 var documentElementRect = document.documentElement.getBoundingClientRect() fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left) } this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth this.scrollbarWidth = this.measureScrollbar() } Modal.prototype.setScrollbar = function () { var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) this.originalBodyPad = document.body.style.paddingRight || '' if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) } Modal.prototype.resetScrollbar = function () { this.$body.css('padding-right', this.originalBodyPad) } Modal.prototype.measureScrollbar = function () { // thx walsh var scrollDiv = document.createElement('div') scrollDiv.className = 'modal-scrollbar-measure' this.$body.append(scrollDiv) var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth this.$body[0].removeChild(scrollDiv) return scrollbarWidth } // MODAL PLUGIN DEFINITION // ======================= function Plugin(option, _relatedTarget) { return this.each(function () { var $this = $(this) var data = $this.data('bs.modal') var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) if (!data) $this.data('bs.modal', (data = new Modal(this, options))) if (typeof option == 'string') data[option](_relatedTarget) else if (options.show) data.show(_relatedTarget) }) } var old = $.fn.modal $.fn.modal = Plugin $.fn.modal.Constructor = Modal // MODAL NO CONFLICT // ================= $.fn.modal.noConflict = function () { $.fn.modal = old return this } // MODAL DATA-API // ============== $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { var $this = $(this) var href = $this.attr('href') var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) if ($this.is('a')) e.preventDefault() $target.one('show.bs.modal', function (showEvent) { if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown $target.one('hidden.bs.modal', function () { $this.is(':visible') && $this.trigger('focus') }) }) Plugin.call($target, option, this) }) }(jQuery); /* ======================================================================== * Bootstrap: tooltip.js v3.3.7 * http://getbootstrap.com/javascript/#tooltip * Inspired by the original jQuery.tipsy by Jason Frame * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // TOOLTIP PUBLIC CLASS DEFINITION // =============================== var Tooltip = function (element, options) { this.type = null this.options = null this.enabled = null this.timeout = null this.hoverState = null this.$element = null this.inState = null this.init('tooltip', element, options) } Tooltip.VERSION = '3.3.7' Tooltip.TRANSITION_DURATION = 150 Tooltip.DEFAULTS = { animation: true, placement: 'top', selector: false, template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', trigger: 'hover focus', title: '', delay: 0, html: false, container: false, viewport: { selector: 'body', padding: 0 } } Tooltip.prototype.init = function (type, element, options) { this.enabled = true this.type = type this.$element = $(element) this.options = this.getOptions(options) this.$viewport = this.options.viewport && $($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport)) this.inState = { click: false, hover: false, focus: false } if (this.$element[0] instanceof document.constructor && !this.options.selector) { throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!') } var triggers = this.options.trigger.split(' ') for (var i = triggers.length; i--;) { var trigger = triggers[i] if (trigger == 'click') { this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) } else if (trigger != 'manual') { var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) } } this.options.selector ? (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : this.fixTitle() } Tooltip.prototype.getDefaults = function () { return Tooltip.DEFAULTS } Tooltip.prototype.getOptions = function (options) { options = $.extend({}, this.getDefaults(), this.$element.data(), options) if (options.delay && typeof options.delay == 'number') { options.delay = { show: options.delay, hide: options.delay } } return options } Tooltip.prototype.getDelegateOptions = function () { var options = {} var defaults = this.getDefaults() this._options && $.each(this._options, function (key, value) { if (defaults[key] != value) options[key] = value }) return options } Tooltip.prototype.enter = function (obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget).data('bs.' + this.type) if (!self) { self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) $(obj.currentTarget).data('bs.' + this.type, self) } if (obj instanceof $.Event) { self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true } if (self.tip().hasClass('in') || self.hoverState == 'in') { self.hoverState = 'in' return } clearTimeout(self.timeout) self.hoverState = 'in' if (!self.options.delay || !self.options.delay.show) return self.show() self.timeout = setTimeout(function () { if (self.hoverState == 'in') self.show() }, self.options.delay.show) } Tooltip.prototype.isInStateTrue = function () { for (var key in this.inState) { if (this.inState[key]) return true } return false } Tooltip.prototype.leave = function (obj) { var self = obj instanceof this.constructor ? obj : $(obj.currentTarget).data('bs.' + this.type) if (!self) { self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) $(obj.currentTarget).data('bs.' + this.type, self) } if (obj instanceof $.Event) { self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false } if (self.isInStateTrue()) return clearTimeout(self.timeout) self.hoverState = 'out' if (!self.options.delay || !self.options.delay.hide) return self.hide() self.timeout = setTimeout(function () { if (self.hoverState == 'out') self.hide() }, self.options.delay.hide) } Tooltip.prototype.show = function () { var e = $.Event('show.bs.' + this.type) if (this.hasContent() && this.enabled) { this.$element.trigger(e) var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) if (e.isDefaultPrevented() || !inDom) return var that = this var $tip = this.tip() var tipId = this.getUID(this.type) this.setContent() $tip.attr('id', tipId) this.$element.attr('aria-describedby', tipId) if (this.options.animation) $tip.addClass('fade') var placement = typeof this.options.placement == 'function' ? this.options.placement.call(this, $tip[0], this.$element[0]) : this.options.placement var autoToken = /\s?auto?\s?/i var autoPlace = autoToken.test(placement) if (autoPlace) placement = placement.replace(autoToken, '') || 'top' $tip .detach() .css({ top: 0, left: 0, display: 'block' }) .addClass(placement) .data('bs.' + this.type, this) this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) this.$element.trigger('inserted.bs.' + this.type) var pos = this.getPosition() var actualWidth = $tip[0].offsetWidth var actualHeight = $tip[0].offsetHeight if (autoPlace) { var orgPlacement = placement var viewportDim = this.getPosition(this.$viewport) placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' : placement == 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' : placement == 'right' && pos.right + actualWidth > viewportDim.width ? 'left' : placement == 'left' && pos.left - actualWidth < viewportDim.left ? 'right' : placement $tip .removeClass(orgPlacement) .addClass(placement) } var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) this.applyPlacement(calculatedOffset, placement) var complete = function () { var prevHoverState = that.hoverState that.$element.trigger('shown.bs.' + that.type) that.hoverState = null if (prevHoverState == 'out') that.leave(that) } $.support.transition && this.$tip.hasClass('fade') ? $tip .one('bsTransitionEnd', complete) .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : complete() } } Tooltip.prototype.applyPlacement = function (offset, placement) { var $tip = this.tip() var width = $tip[0].offsetWidth var height = $tip[0].offsetHeight // manually read margins because getBoundingClientRect includes difference var marginTop = parseInt($tip.css('margin-top'), 10) var marginLeft = parseInt($tip.css('margin-left'), 10) // we must check for NaN for ie 8/9 if (isNaN(marginTop)) marginTop = 0 if (isNaN(marginLeft)) marginLeft = 0 offset.top += marginTop offset.left += marginLeft // $.fn.offset doesn't round pixel values // so we use setOffset directly with our own function B-0 $.offset.setOffset($tip[0], $.extend({ using: function (props) { $tip.css({ top: Math.round(props.top), left: Math.round(props.left) }) } }, offset), 0) $tip.addClass('in') // check to see if placing tip in new offset caused the tip to resize itself var actualWidth = $tip[0].offsetWidth var actualHeight = $tip[0].offsetHeight if (placement == 'top' && actualHeight != height) { offset.top = offset.top + height - actualHeight } var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) if (delta.left) offset.left += delta.left else offset.top += delta.top var isVertical = /top|bottom/.test(placement) var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' $tip.offset(offset) this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) } Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) { this.arrow() .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') .css(isVertical ? 'top' : 'left', '') } Tooltip.prototype.setContent = function () { var $tip = this.tip() var title = this.getTitle() $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) $tip.removeClass('fade in top bottom left right') } Tooltip.prototype.hide = function (callback) { var that = this var $tip = $(this.$tip) var e = $.Event('hide.bs.' + this.type) function complete() { if (that.hoverState != 'in') $tip.detach() if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary. that.$element .removeAttr('aria-describedby') .trigger('hidden.bs.' + that.type) } callback && callback() } this.$element.trigger(e) if (e.isDefaultPrevented()) return $tip.removeClass('in') $.support.transition && $tip.hasClass('fade') ? $tip .one('bsTransitionEnd', complete) .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : complete() this.hoverState = null return this } Tooltip.prototype.fixTitle = function () { var $e = this.$element if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') { $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') } } Tooltip.prototype.hasContent = function () { return this.getTitle() } Tooltip.prototype.getPosition = function ($element) { $element = $element || this.$element var el = $element[0] var isBody = el.tagName == 'BODY' var elRect = el.getBoundingClientRect() if (elRect.width == null) { // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) } var isSvg = window.SVGElement && el instanceof window.SVGElement // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3. // See https://github.com/twbs/bootstrap/issues/20280 var elOffset = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset()) var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null return $.extend({}, elRect, scroll, outerDims, elOffset) } Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } } Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { var delta = { top: 0, left: 0 } if (!this.$viewport) return delta var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 var viewportDimensions = this.getPosition(this.$viewport) if (/right|left/.test(placement)) { var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight if (topEdgeOffset < viewportDimensions.top) { // top overflow delta.top = viewportDimensions.top - topEdgeOffset } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset } } else { var leftEdgeOffset = pos.left - viewportPadding var rightEdgeOffset = pos.left + viewportPadding + actualWidth if (leftEdgeOffset < viewportDimensions.left) { // left overflow delta.left = viewportDimensions.left - leftEdgeOffset } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset } } return delta } Tooltip.prototype.getTitle = function () { var title var $e = this.$element var o = this.options title = $e.attr('data-original-title') || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) return title } Tooltip.prototype.getUID = function (prefix) { do prefix += ~~(Math.random() * 1000000) while (document.getElementById(prefix)) return prefix } Tooltip.prototype.tip = function () { if (!this.$tip) { this.$tip = $(this.options.template) if (this.$tip.length != 1) { throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!') } } return this.$tip } Tooltip.prototype.arrow = function () { return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) } Tooltip.prototype.enable = function () { this.enabled = true } Tooltip.prototype.disable = function () { this.enabled = false } Tooltip.prototype.toggleEnabled = function () { this.enabled = !this.enabled } Tooltip.prototype.toggle = function (e) { var self = this if (e) { self = $(e.currentTarget).data('bs.' + this.type) if (!self) { self = new this.constructor(e.currentTarget, this.getDelegateOptions()) $(e.currentTarget).data('bs.' + this.type, self) } } if (e) { self.inState.click = !self.inState.click if (self.isInStateTrue()) self.enter(self) else self.leave(self) } else { self.tip().hasClass('in') ? self.leave(self) : self.enter(self) } } Tooltip.prototype.destroy = function () { var that = this clearTimeout(this.timeout) this.hide(function () { that.$element.off('.' + that.type).removeData('bs.' + that.type) if (that.$tip) { that.$tip.detach() } that.$tip = null that.$arrow = null that.$viewport = null that.$element = null }) } // TOOLTIP PLUGIN DEFINITION // ========================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.tooltip') var options = typeof option == 'object' && option if (!data && /destroy|hide/.test(option)) return if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.tooltip $.fn.tooltip = Plugin $.fn.tooltip.Constructor = Tooltip // TOOLTIP NO CONFLICT // =================== $.fn.tooltip.noConflict = function () { $.fn.tooltip = old return this } }(jQuery); /* ======================================================================== * Bootstrap: popover.js v3.3.7 * http://getbootstrap.com/javascript/#popovers * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // POPOVER PUBLIC CLASS DEFINITION // =============================== var Popover = function (element, options) { this.init('popover', element, options) } if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') Popover.VERSION = '3.3.7' Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { placement: 'right', trigger: 'click', content: '', template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' }) // NOTE: POPOVER EXTENDS tooltip.js // ================================ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) Popover.prototype.constructor = Popover Popover.prototype.getDefaults = function () { return Popover.DEFAULTS } Popover.prototype.setContent = function () { var $tip = this.tip() var title = this.getTitle() var content = this.getContent() $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' ](content) $tip.removeClass('fade top bottom left right in') // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do // this manually by checking the contents. if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() } Popover.prototype.hasContent = function () { return this.getTitle() || this.getContent() } Popover.prototype.getContent = function () { var $e = this.$element var o = this.options return $e.attr('data-content') || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content) } Popover.prototype.arrow = function () { return (this.$arrow = this.$arrow || this.tip().find('.arrow')) } // POPOVER PLUGIN DEFINITION // ========================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.popover') var options = typeof option == 'object' && option if (!data && /destroy|hide/.test(option)) return if (!data) $this.data('bs.popover', (data = new Popover(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.popover $.fn.popover = Plugin $.fn.popover.Constructor = Popover // POPOVER NO CONFLICT // =================== $.fn.popover.noConflict = function () { $.fn.popover = old return this } }(jQuery); /* ======================================================================== * Bootstrap: scrollspy.js v3.3.7 * http://getbootstrap.com/javascript/#scrollspy * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // SCROLLSPY CLASS DEFINITION // ========================== function ScrollSpy(element, options) { this.$body = $(document.body) this.$scrollElement = $(element).is(document.body) ? $(window) : $(element) this.options = $.extend({}, ScrollSpy.DEFAULTS, options) this.selector = (this.options.target || '') + ' .nav li > a' this.offsets = [] this.targets = [] this.activeTarget = null this.scrollHeight = 0 this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this)) this.refresh() this.process() } ScrollSpy.VERSION = '3.3.7' ScrollSpy.DEFAULTS = { offset: 10 } ScrollSpy.prototype.getScrollHeight = function () { return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) } ScrollSpy.prototype.refresh = function () { var that = this var offsetMethod = 'offset' var offsetBase = 0 this.offsets = [] this.targets = [] this.scrollHeight = this.getScrollHeight() if (!$.isWindow(this.$scrollElement[0])) { offsetMethod = 'position' offsetBase = this.$scrollElement.scrollTop() } this.$body .find(this.selector) .map(function () { var $el = $(this) var href = $el.data('target') || $el.attr('href') var $href = /^#./.test(href) && $(href) return ($href && $href.length && $href.is(':visible') && [[$href[offsetMethod]().top + offsetBase, href]]) || null }) .sort(function (a, b) { return a[0] - b[0] }) .each(function () { that.offsets.push(this[0]) that.targets.push(this[1]) }) } ScrollSpy.prototype.process = function () { var scrollTop = this.$scrollElement.scrollTop() + this.options.offset var scrollHeight = this.getScrollHeight() var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() var offsets = this.offsets var targets = this.targets var activeTarget = this.activeTarget var i if (this.scrollHeight != scrollHeight) { this.refresh() } if (scrollTop >= maxScroll) { return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) } if (activeTarget && scrollTop < offsets[0]) { this.activeTarget = null return this.clear() } for (i = offsets.length; i--;) { activeTarget != targets[i] && scrollTop >= offsets[i] && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1]) && this.activate(targets[i]) } } ScrollSpy.prototype.activate = function (target) { this.activeTarget = target this.clear() var selector = this.selector + '[data-target="' + target + '"],' + this.selector + '[href="' + target + '"]' var active = $(selector) .parents('li') .addClass('active') if (active.parent('.dropdown-menu').length) { active = active .closest('li.dropdown') .addClass('active') } active.trigger('activate.bs.scrollspy') } ScrollSpy.prototype.clear = function () { $(this.selector) .parentsUntil(this.options.target, '.active') .removeClass('active') } // SCROLLSPY PLUGIN DEFINITION // =========================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.scrollspy') var options = typeof option == 'object' && option if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.scrollspy $.fn.scrollspy = Plugin $.fn.scrollspy.Constructor = ScrollSpy // SCROLLSPY NO CONFLICT // ===================== $.fn.scrollspy.noConflict = function () { $.fn.scrollspy = old return this } // SCROLLSPY DATA-API // ================== $(window).on('load.bs.scrollspy.data-api', function () { $('[data-spy="scroll"]').each(function () { var $spy = $(this) Plugin.call($spy, $spy.data()) }) }) }(jQuery); /* ======================================================================== * Bootstrap: tab.js v3.3.7 * http://getbootstrap.com/javascript/#tabs * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // TAB CLASS DEFINITION // ==================== var Tab = function (element) { // jscs:disable requireDollarBeforejQueryAssignment this.element = $(element) // jscs:enable requireDollarBeforejQueryAssignment } Tab.VERSION = '3.3.7' Tab.TRANSITION_DURATION = 150 Tab.prototype.show = function () { var $this = this.element var $ul = $this.closest('ul:not(.dropdown-menu)') var selector = $this.data('target') if (!selector) { selector = $this.attr('href') selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 } if ($this.parent('li').hasClass('active')) return var $previous = $ul.find('.active:last a') var hideEvent = $.Event('hide.bs.tab', { relatedTarget: $this[0] }) var showEvent = $.Event('show.bs.tab', { relatedTarget: $previous[0] }) $previous.trigger(hideEvent) $this.trigger(showEvent) if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return var $target = $(selector) this.activate($this.closest('li'), $ul) this.activate($target, $target.parent(), function () { $previous.trigger({ type: 'hidden.bs.tab', relatedTarget: $this[0] }) $this.trigger({ type: 'shown.bs.tab', relatedTarget: $previous[0] }) }) } Tab.prototype.activate = function (element, container, callback) { var $active = container.find('> .active') var transition = callback && $.support.transition && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length) function next() { $active .removeClass('active') .find('> .dropdown-menu > .active') .removeClass('active') .end() .find('[data-toggle="tab"]') .attr('aria-expanded', false) element .addClass('active') .find('[data-toggle="tab"]') .attr('aria-expanded', true) if (transition) { element[0].offsetWidth // reflow for transition element.addClass('in') } else { element.removeClass('fade') } if (element.parent('.dropdown-menu').length) { element .closest('li.dropdown') .addClass('active') .end() .find('[data-toggle="tab"]') .attr('aria-expanded', true) } callback && callback() } $active.length && transition ? $active .one('bsTransitionEnd', next) .emulateTransitionEnd(Tab.TRANSITION_DURATION) : next() $active.removeClass('in') } // TAB PLUGIN DEFINITION // ===================== function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.tab') if (!data) $this.data('bs.tab', (data = new Tab(this))) if (typeof option == 'string') data[option]() }) } var old = $.fn.tab $.fn.tab = Plugin $.fn.tab.Constructor = Tab // TAB NO CONFLICT // =============== $.fn.tab.noConflict = function () { $.fn.tab = old return this } // TAB DATA-API // ============ var clickHandler = function (e) { e.preventDefault() Plugin.call($(this), 'show') } $(document) .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) }(jQuery); /* ======================================================================== * Bootstrap: affix.js v3.3.7 * http://getbootstrap.com/javascript/#affix * ======================================================================== * Copyright 2011-2016 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) * ======================================================================== */ +function ($) { 'use strict'; // AFFIX CLASS DEFINITION // ====================== var Affix = function (element, options) { this.options = $.extend({}, Affix.DEFAULTS, options) this.$target = $(this.options.target) .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) this.$element = $(element) this.affixed = null this.unpin = null this.pinnedOffset = null this.checkPosition() } Affix.VERSION = '3.3.7' Affix.RESET = 'affix affix-top affix-bottom' Affix.DEFAULTS = { offset: 0, target: window } Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { var scrollTop = this.$target.scrollTop() var position = this.$element.offset() var targetHeight = this.$target.height() if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false if (this.affixed == 'bottom') { if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' } var initializing = this.affixed == null var colliderTop = initializing ? scrollTop : position.top var colliderHeight = initializing ? targetHeight : height if (offsetTop != null && scrollTop <= offsetTop) return 'top' if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' return false } Affix.prototype.getPinnedOffset = function () { if (this.pinnedOffset) return this.pinnedOffset this.$element.removeClass(Affix.RESET).addClass('affix') var scrollTop = this.$target.scrollTop() var position = this.$element.offset() return (this.pinnedOffset = position.top - scrollTop) } Affix.prototype.checkPositionWithEventLoop = function () { setTimeout($.proxy(this.checkPosition, this), 1) } Affix.prototype.checkPosition = function () { if (!this.$element.is(':visible')) return var height = this.$element.height() var offset = this.options.offset var offsetTop = offset.top var offsetBottom = offset.bottom var scrollHeight = Math.max($(document).height(), $(document.body).height()) if (typeof offset != 'object') offsetBottom = offsetTop = offset if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) if (this.affixed != affix) { if (this.unpin != null) this.$element.css('top', '') var affixType = 'affix' + (affix ? '-' + affix : '') var e = $.Event(affixType + '.bs.affix') this.$element.trigger(e) if (e.isDefaultPrevented()) return this.affixed = affix this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null this.$element .removeClass(Affix.RESET) .addClass(affixType) .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') } if (affix == 'bottom') { this.$element.offset({ top: scrollHeight - height - offsetBottom }) } } // AFFIX PLUGIN DEFINITION // ======================= function Plugin(option) { return this.each(function () { var $this = $(this) var data = $this.data('bs.affix') var options = typeof option == 'object' && option if (!data) $this.data('bs.affix', (data = new Affix(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.affix $.fn.affix = Plugin $.fn.affix.Constructor = Affix // AFFIX NO CONFLICT // ================= $.fn.affix.noConflict = function () { $.fn.affix = old return this } // AFFIX DATA-API // ============== $(window).on('load', function () { $('[data-spy="affix"]').each(function () { var $spy = $(this) var data = $spy.data() data.offset = data.offset || {} if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom if (data.offsetTop != null) data.offset.top = data.offsetTop Plugin.call($spy, data) }) }) }(jQuery); ================================================ FILE: gui/static/js/carriergroups.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof validateFields === "undefined") { throw new Error("validateFields() is required and is not defined"); } if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof toggleElemDisabled === "undefined") { throw new Error("toggleElemDisabled() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } // throw an error if required globals not defined if (typeof GUI_BASE_URL === "undefined") { throw new Error("GUI_BASE_URL is required and is not defined"); } // globals var gwgroupid; var gwgroup_table = $('').DataTable(); var plugin_name_sel = $('#plugin_name'); var plugin_name_regex = new RegExp('^([a-zA-Z0-9.-])+$'); // when using twilio plugin we need to do some extra validation for function validatePluginData(fields) { if (plugin_name_sel.val() === "Twilio") { if (!plugin_name_regex.test(fields.get('name').val())) { return { result: false, err_node: fields.get('name'), err_msg: "Endpoint Group Name must be a valid hostname for the Twilio plugin" }; } if (fields.get('plugin_account_sid').val() === '') { return { result: false, err_node: fields.get('plugin_account_sid'), err_msg: "Account SID is required for the Twilio plugin" }; } if (fields.get('plugin_account_token').val() === '') { return { result: false, err_node: fields.get('plugin_account_token'), err_msg: "Account Token is required for the Twilio plugin" }; } } return { result: true }; } // Add EndpointGroup function addCarrierGroup(action) { var selector, modal_body, url; // The default action is a POST (creating a new EndpointGroup) if (typeof action === "undefined") { action = "POST"; } // set the query parameters if (action === "POST") { action = "POST"; selector = "#add-group"; modal_body = $(selector + ' .modal-body'); url = API_BASE_URL + "carriergroups"; } else if (action === "PUT") { selector = "#edit-group"; modal_body = $(selector + ' .modal-body'); gwgroupid = modal_body.find(".gwgroup").val(); url = API_BASE_URL + "carriergroups/" + gwgroupid; } else { throw new Error("addGatewayGroup(): action must be either POST or PUT"); } var requestPayload = {}; requestPayload.name = modal_body.find('.name').val(); if (action === "PUT") { requestPayload.name = modal_body.find('.new_name').val(); } if (plugin_name_sel.val() === "Twilio") { requestPayload.plugin = { name: plugin_name_sel.val(), account_sid: modal_body.find(".plugin_account_sid").val(), account_token: modal_body.find(".plugin_account_token").val(), }; } var auth = {}; if (action === "POST") { auth.type = modal_body.find(".authtype").val(); if (auth.type == "userpwd") { auth.pass = modal_body.find(".auth_password").val(); } } else if (action === "PUT") { auth.type = modal_body.find(".authtype").val(); if (auth.type == "userpwd") { auth.pass = modal_body.find(".auth_password").val(); } } auth.r_username = modal_body.find(".r_username").val(); auth.auth_username = modal_body.find(".auth_username").val(); auth.auth_password = auth.pass auth.auth_domain = modal_body.find(".auth_domain").val(); auth.auth_proxy = modal_body.find(".auth_proxy").val(); requestPayload.auth = auth; requestPayload.lb_enabled = modal_body.find('.lb_enabled').val(); requestPayload.strip = modal_body.find(".strip").val(); requestPayload.prefix = modal_body.find(".prefix").val(); // Put into JSON Message and send over $.ajax({ type: action, url: url, dataType: "json", contentType: "application/json; charset=utf-8", data: JSON.stringify(requestPayload), success: function(response, textStatus, jqXHR) { var btn; var gwgroupid_int = response.data[0].gwgroupid; // Update the Add Button and the table if (action === "POST") { btn = $('#add .modal-footer').find('#addButton'); btn.removeClass("btn-primary"); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.removeClass("btn-warning"); } btn.addClass("btn-success"); btn.html("<span class='glyphicon glyphicon-check'></span> Saved!"); btn.attr("disabled", true); if (action === "POST") { gwgroup_table.row.add({ "name": requestPayload.name, "gwgroupid": gwgroupid_int }).draw(); location.reload(true); } else { /* gwgroup_table.row(function(idx, data, node) { return data.gwgroupid === gwgroupid_int; }).data({ "name": requestPayload.name, "gwgroupid": gwgroupid_int }).draw(); */ location.reload(true); } } }) } function updateCarrierGroup() { addCarrierGroup("PUT"); } /* validate fields before submitting api request */ $('#addButton').click(function(ev) { /* prevent form default submit */ ev.preventDefault(); if (validateFields('#add-group', validatePluginData)) { addCarrierGroup(); // hide the modal after 1.5 sec setTimeout(function() { var add_modal = $('#add-group'); if (add_modal.is(':visible')) { add_modal.modal('hide'); } }, 1500); } }); /* validate fields before submitting api request */ $('#updateGroupButton').click(function(ev) { /* prevent form default submit */ ev.preventDefault(); if (validateFields('#edit-group')) { updateCarrierGroup(); // hide the modal after 1.5 sec setTimeout(function() { var edit_modal = $('#edit-group'); if (edit_modal.is(':visible')) { edit_modal.modal('hide'); } }, 1500); } /* prevent page reload */ return false; }); function setCarrierGroupHandlers() { var carriergroups_tbody = $('#carrier-groups tbody'); $('#carrier-nav').click(function(e) { var target_link = $(e.target); /* fix target if we hit an ancestor elem containing it */ var target_type = $(e.target).get(0).nodeName.toLowerCase(); if (target_type !== "a") { /* no routing necessary if these are clicked */ if (['div', 'ul'].indexOf(target_type) > -1) { return false; } target_link = target_link.find('a'); } var other_links = $(e.currentTarget).find('.nav-tabs a').not(target_link); var modal_body = $(e.currentTarget.parentNode); /* handle dynamic routes (links) */ if (target_link.data("type") === "link") { /* add dynamic routes here for each link */ if (target_link.attr("name") === "carriers-link") { var gwid = modal_body.find('.gwid').val(); var gwgroup = modal_body.find('.gwgroup').val(); if (gwgroup !== undefined) { target_link.attr('href', target_link.attr('href') + "/group/" + gwgroup); } else if (gwid !== undefined) { target_link.attr('href', target_link.attr('href') + "/" + gwid); } } /* add styling to the links */ target_link.addClass('current-navlink'); $.each(other_links, function(i, elem) { $(elem).removeClass('current-navlink'); }); /* make sure we follow the link after returning */ return true; } /* handle dynamic modals (using toggles) */ e.preventDefault(); var modal_toggle_divs = modal_body.children('div[name*="toggle"]'); for (var i = 0; i < modal_toggle_divs.length; i++) { if ($(modal_toggle_divs[i]).attr('name') === target_link.attr('name')) { $(modal_toggle_divs[i]).removeClass("hidden"); } else { $(modal_toggle_divs[i]).addClass("hidden"); } } // add styling to the toggles target_link.addClass('current-navlink'); $.each(other_links, function(i, elem) { $(elem).removeClass('current-navlink'); }); // make sure we don't follow the link return false; }); /* listener for plguin dropdown changes */ plugin_name_sel.change(function() { var modal_body = $('#add-group .modal-body'); // TODO: Use the carriergroup plugin architecture to get meta data about the plugin if ($(this).val() === '') { modal_body.find('#plugin_creds').addClass('hidden'); } else { modal_body.find('#plugin_creds').removeClass('hidden'); } }); /* listener for load balancing toggle */ $('.modal-body .toggle-loadbalancing').change(function() { var modal = $(this).closest('div.modal'); var modal_body = modal.find('.modal-body'); if ($(this).is(":checked") || $(this).prop("checked")) { modal_body.find('.lb_enabled').val(1); } else { modal_body.find('.lb_enabled').val(0); } }); $('#open-CarrierGroupAdd').click(function() { /** Clear out the modal */ var modal_body = $('#add-group .modal-body'); modal_body.find(".name").val(''); modal_body.find(".gwlist").val(''); modal_body.find(".authtype").val(''); modal_body.find(".r_username").val(''); modal_body.find(".auth_username").val(''); modal_body.find(".auth_password").val(''); modal_body.find(".auth_domain").val(''); modal_body.find(".auth_proxy").val(''); /* reset plugin selections */ plugin_name_sel.val('').change(); /* reset toggle buttons */ modal_body.find("input.toggle-loadbalancing").bootstrapToggle('off'); // update gwgroup for all modals $('.modal-body').find(".gwgroup").each(function() { $(this).val(''); }); /* ip auth enabled by default, Set the radio button to true */ modal_body.find('.authtype[data-toggle="ip_enabled"]').trigger('click'); }); carriergroups_tbody.on('click', '#open-Update', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('carrier-groups'); var gwgroup = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); var gwlist = $(c).find('tr:eq(' + row_index + ') td:eq(3)').text(); var authtype = $(c).find('tr:eq(' + row_index + ') td:eq(4)').text(); var r_username = $(c).find('tr:eq(' + row_index + ') td:eq(5)').text(); var auth_password = $(c).find('tr:eq(' + row_index + ') td:eq(6)').text(); var auth_domain = $(c).find('tr:eq(' + row_index + ') td:eq(7)').text(); var auth_username = $(c).find('tr:eq(' + row_index + ') td:eq(8)').text(); var auth_proxy = $(c).find('tr:eq(' + row_index + ') td:eq(9)').text(); var lb_enabled = $(c).find('tr:eq(' + row_index + ') td:eq(10)').text(); // grab modals to change var modal_body = $('#edit-group .modal-body'); var modal_bodies = $('.modal-body'); /* update modal fields */ modal_body.find(".name").val(name); modal_body.find(".new_name").val(name); modal_body.find(".gwlist").val(gwlist); modal_body.find(".authtype").val(authtype); modal_body.find(".r_username").val(r_username); modal_body.find(".auth_password").val(auth_password); modal_body.find(".auth_domain").val(auth_domain); modal_body.find(".auth_username").val(auth_username); modal_body.find(".auth_proxy").val(auth_proxy); /* update toggle buttons */ if (lb_enabled === '1') { modal_body.find("input.toggle-loadbalancing").bootstrapToggle('on'); } else { modal_body.find("input.toggle-loadbalancing").bootstrapToggle('off'); } // update gwgroup for all modals modal_bodies.find(".gwgroup").each(function() { $(this).val(gwgroup); }); if (authtype !== "") { /* userpwd auth enabled, Set the radio button to true */ modal_body.find('.authtype[data-toggle="userpwd_enabled"]').trigger('click'); } else { /* ip auth enabled, Set the radio button to true */ modal_body.find('.authtype[data-toggle="ip_enabled"]').trigger('click'); } /* only show carriers from current group */ $('#carriers > tbody > tr').each(function() { if (gwlist.split(',').indexOf($(this).data('gwid').toString()) > -1) { $(this).removeClass('hidden'); } else { $(this).addClass('hidden'); } }); /* start carrier-nav on first tab */ $('#carrier-nav > .nav-tabs').find('a').first().trigger('click'); }); carriergroups_tbody.on('click', '#open-Delete', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('carrier-groups'); var gwgroup = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); var gwlist = $(c).find('tr:eq(' + row_index + ') td:eq(3)').text(); /* update modal fields */ var modal_body = $('#delete-group .modal-body'); modal_body.find(".gwgroup").val(gwgroup); modal_body.find(".name").val(name); modal_body.find(".gwlist").val(gwlist); }); } function setCarrierHandlers() { var carriers_tbody = $('#carriers tbody'); $('#open-CarrierAdd').on('click', function() { /** Clear out the modal */ var modal_body = $('#add .modal-body'); modal_body.find(".gwid").val(''); modal_body.find(".name").val(''); modal_body.find(".ip_addr").val(''); modal_body.find(".strip").val(''); modal_body.find(".prefix").val(''); modal_body.find(".rweight").val(''); /* make sure ip_addr not disabled */ modal_body.find('.ip_addr').prop('disabled', false); }); carriers_tbody.on('click', '#open-Update', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('carriers'); var gwid = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); var ip_addr = $(c).find('tr:eq(' + row_index + ') td:eq(3)').text(); var strip = $(c).find('tr:eq(' + row_index + ') td:eq(4)').text(); var prefix = $(c).find('tr:eq(' + row_index + ') td:eq(5)').text(); var rweight = $(c).find('tr:eq(' + row_index + ') td:eq(6)').text(); /** Clear out the modal */ var modal_body = $('#edit .modal-body'); modal_body.find(".gwid").val(''); modal_body.find(".name").val(''); modal_body.find(".ip_addr").val(''); modal_body.find(".strip").val(''); modal_body.find(".prefix").val(''); modal_body.find(".rweight").val(''); /* update modal fields */ modal_body.find(".gwid").val(gwid); modal_body.find(".name").val(name); modal_body.find(".ip_addr").val(ip_addr); modal_body.find(".strip").val(strip); modal_body.find(".prefix").val(prefix); modal_body.find(".rweight").val(rweight); }); carriers_tbody.on('click', '#open-Delete', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('carriers'); var gwid = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); var ip_addr = $(c).find('tr:eq(' + row_index + ') td:eq(3)').text(); var related_rules = JSON.parse($(c).find('tr:eq(' + row_index + ') td:eq(7)').text()); var modal_body = $('#delete .modal-body'); /* remove previous rules if they were created */ modal_body.find('div.alert.alert-warning').remove(); /* check if related dr_rules exist */ if (Object.keys(related_rules).length > 0) { /* create an alert and append it to the DOM */ var html_string = '<div class="alert alert-warning centered" role="alert">' + '<h4>Deleting this rule will cause the following Global Outbound Routes to be deleted</h4>' + '<hr>' + '<div class="table-responsive">' + '<table class="table table-centered" style="margin-bottom: 0;">' + '<thead><tr><th>RULE ID</th><th>NAME</th></tr></thead><tbody>'; for (var key in related_rules) { html_string += '<tr><td>' + key + '</td><td>' + related_rules[key] + '</td></tr>'; } html_string += '</tbody></table></div></div>'; /* let jquery parse the string as html */ var html_nodes = jQuery.parseHTML(html_string); modal_body.find("div.alert.alert-danger").after(html_nodes); } /* update modal fields */ modal_body.find(".gwid").val(gwid); modal_body.find(".name").val(name); modal_body.find(".ip_addr").val(ip_addr); modal_body.find(".related_rules").val(JSON.stringify(related_rules)); }); } function setFormHandler(selector, successCallback) { $(selector).submit(function(e) { /* prevent form default submit */ e.preventDefault(); /* store reference to target for callback */ var self = this; var request_url = $(this).attr("action"); var request_method = $(this).attr("method"); var form_data = $(this).serialize(); $.ajax({ url: request_url, type: request_method, data: form_data, success: function(data) { successCallback(data, self) }, error: function(xhr, msg, err) { return true; // follow redirects for error page }, complete: function() { $(e.target).closest('div.modal').modal('hide'); } }); /* make sure we don't reload page */ return false; }); } /* any handlers depending on DOM elements go here */ $(document).ready(function() { /* update the carriers table */ $.ajax({ url: GUI_BASE_URL + "carriers", method: 'GET', headers: { 'Content-Type': 'text/html,text/css,application/javascript,text/plain,*/*' }, success: function(data) { $('#carriers-table').html(data); } }); /* DataTable init */ $('#carrier-groups').DataTable({ "columnDefs": [ {"orderable": true, "targets": [1, 2, 3]}, {"orderable": false, "targets": [0, 4, 5, 6, 7, 8, 9, 10, 11]}, ], "order": [[1, 'asc']] }); /* update view when carriers updated */ setFormHandler('.gwform', function(data, target) { $('#carriers-table').html(data); var modal = $(target).closest('div.modal'); var gwid, gwgroup, td_gwlist, gwlist_arr, gwlist; if (modal.attr('id') === 'add') { gwid = $(data).find('tr.new_gw').data('gwid'); gwgroup = modal.find('.gwgroup').val(); td_gwlist = $('#carrier-groups').find('tr[data-gwgroup="' + gwgroup + '"] > td.gwlist'); gwlist = td_gwlist.text(); gwlist_arr = gwlist === '' ? [] : gwlist.split(','); gwlist_arr.push(gwid); gwlist = gwlist_arr.join(','); td_gwlist.text(gwlist); } else if (modal.attr('id') === 'delete') { gwid = modal.find('.gwid').val(); gwgroup = modal.find('.gwgroup').val(); td_gwlist = $('#carrier-groups').find('tr[data-gwgroup="' + gwgroup + '"] > td.gwlist'); gwlist = td_gwlist.text(); gwlist_arr = gwlist === '' ? [] : gwlist.split(','); gwlist_arr.splice(gwlist_arr.indexOf(gwid), 1); gwlist = gwlist_arr.join(','); td_gwlist.text(gwlist); } }); setCarrierGroupHandlers(); }); /* any handlers that MAY rely on async calls put here */ $(document).ajaxStop(function() { setCarrierHandlers(); }); })(window, document); ================================================ FILE: gui/static/js/cdrs.js ================================================ ;(function (window, document) { 'use strict'; // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } if (typeof delayedCallback === "undefined") { throw new Error("delayedCallback() is required and is not defined"); } // globals for this script var epgroup_select = $("#endpointgroups"); var loading_spinner = $('#loading-spinner'); var showallcalls_inp = $('#toggle_completed_calls'); var cdr_table = null; /** * Show a spinner while loading * @param isLoading {boolean} */ function changeLoadingState(isLoading) { if (isLoading) { loading_spinner.removeClass('hidden'); } else { loading_spinner.addClass('hidden'); } } function getFilteredCdrIds() { var value = $('#cdrs').DataTable().columns( { search: 'applied' } ).data()[0]; // Check if values were selected using the search field. if (value != ",") { return value; } else { return ''; } } function loadCDRDataTable(gwgroupid) { changeLoadingState(true); // load CDR data if ($.fn.dataTable.isDataTable(cdr_table)) { // Clear the contents of the table // cdr_table.clear(); // cdr_table.draw(); cdr_table.ajax.url(API_BASE_URL + "cdrs/endpointgroups/" + gwgroupid); cdr_table.ajax.reload(); } // datatable init else { cdr_table = $('#cdrs').DataTable({ "pagingType": "full_numbers", "processing": false, "serverSide": true, "ajax": { "url": API_BASE_URL + "cdrs/endpointgroups/" + gwgroupid, "data": function (d) { d.nonCompletedCalls = showallcalls_inp.val() === '1'; }, "dataFilter": function (data) { if (data) { var json = jQuery.parseJSON(data); json.recordsTotal = json.total_rows; json.recordsFiltered = json.filtered_rows; return JSON.stringify(json); } return JSON.stringify({ data: [], recordsTotal: 0, recordsFiltered: 0 }); } }, "columns": [ {"data": "cdr_id", "orderable": false}, {"data": "call_start_time"}, {"data": "call_duration"}, {"data": "call_direction"}, {"data": "src_gwgroupid", "visible": false, "searchable": false}, {"data": "src_gwgroupname"}, {"data": "dst_gwgroupid", "visible": false, "searchable": false}, {"data": "dst_gwgroupname"}, {"data": "src_username"}, {"data": "dst_username"}, {"data": "src_address"}, {"data": "dst_address"}, {"data": "call_id", "orderable": false} ], "order": [[1, 'desc']], "pageLength": 100 }); // make searchbox less spammy var searchbox = $('#cdrs input[type="search"]'); searchbox.unbind(); searchbox.bind('input', delayedCallback( function(ev) { cdr_table.search(this.value).draw(); }, 500) ); } changeLoadingState(false); } $(document).ready(function () { // get endpoint group data $.ajax({ type: "GET", url: API_BASE_URL + "endpointgroups", dataType: "json", contentType: "application/json; charset=utf-8", success: function (response, textStatus, jqXHR) { for (var i = 0; i < response.data.length; i++) { epgroup_select.append("<option value='" + response.data[i].gwgroupid + "'>" + response.data[i].name + "</option>"); } } }) // default is enabled showallcalls_inp.bootstrapToggle('on'); /* listener for completed calls toggle */ showallcalls_inp.change(function() { var self = $(this); if (self.is(":checked") || self.prop("checked")) { self.val(1); } else { self.val(0); } loadCDRDataTable(epgroup_select.find('option:selected').val()); }); // change table when endpoint group selected epgroup_select.change(function () { loadCDRDataTable(epgroup_select.find('option:selected').val()); }) $('#downloadCDR').click(function () { var gwgroupid = $("#endpointgroups").val(); window.location.href = API_BASE_URL + 'cdrs/endpointgroups/' + gwgroupid + '?type=csv&filter=' + getFilteredCdrIds().join(','); }); // reload table when the refresh button is clicked $('#refreshCDR').click(function () { loadCDRDataTable(epgroup_select.find('option:selected').val()); }); }); })(window, document); ================================================ FILE: gui/static/js/certificates.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof validateFields === "undefined") { throw new Error("validateFields() is required and is not defined"); } if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof toggleElemDisabled === "undefined") { throw new Error("toggleElemDisabled() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } var ENTITY="certificates"; var id; var table; function clear(modal_selector) { /** Clear out the modal */ var modal_body = $(modal_selector).find('.modal-body'); var btn; if (modal_selector == "#add") { btn = $('#add .modal-footer').find('#addButton'); btn.html("<span class='glyphicon glyphicon-ok-sign'></span> Add"); btn.removeClass("btn-success"); btn.addClass("btn-primary"); modal_body.find('#domain').val(""); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.html("<span class='glyphicon glyphicon-ok-sign'></span> Update"); btn.removeClass("btn-success"); btn.addClass("btn-warning"); } btn.attr('disabled', false); } function addEntity(action) { var selector, modal_body var requestPayload = {}; // The default action is a POST if (typeof action === "undefined") { action = "POST"; } if (action === "POST") { action = "POST"; selector = "#add"; modal_body = $(selector + ' .modal-body'); requestPayload.domain = modal_body.find("#domain").val() } else if (action === "PUT") { action = "PUT"; selector = "#edit"; modal_body = $(selector + ' .modal-body'); requestPayload.domain = modal_body.find("#domain2").val() } if (modal_body.find("#certtype_generate").is(':checked') || (modal_body.find("#certtype_generate2").is(':checked'))) { requestPayload.type = "generated" addGenerated(requestPayload,action) } else { requestPayload.type = "uploaded" addUploaded(requestPayload,action) } } function addUploaded (requestPayload,action) { var formData = new FormData(document.querySelector('#addCertificateForm')) $.ajax({ url: API_BASE_URL + ENTITY + "/upload/" + requestPayload.domain, type: 'POST', data: formData, async: false, cache: false, contentType: false, processData: false, success: function(response, text_status, xhr) { var btn; var id_int = response.data[0].id; // Update the Add Button and the table if (action === "POST") { btn = $('#add .modal-footer').find('#addButton'); btn.removeClass("btn-primary"); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.removeClass("btn-warning"); } btn.addClass("btn-success"); btn.html("<span class='glyphicon glyphicon-check'></span>Saved!"); btn.attr("disabled", true); showNotification("Certificates were uploaded"); if (action === "POST") { table.row.add({ "id": id_int, "domain": requestPayload.domain, "type": requestPayload.type, "assigned_domains": '' }).draw(); } }, error: function(xhr, text_status, error_msg) { showNotification("Certificates were NOT uploaded", true); } }); return false; } function addGenerated(requestPayload,action) { // Put into JSON Message and send over if (action === "POST") var btn = $('#add .modal-footer').find('#addButton').prop('disabled', true); else { var btn = $('#edit .modal-footer').find('#updateButton').prop('disabled', true); } $.ajax({ type: action, url: API_BASE_URL + ENTITY, dataType: "json", contentType: "application/json; charset=utf-8", data: JSON.stringify(requestPayload), success: function(response, textStatus, jqXHR) { var id_int = response.data[0].id; setTimeout(function() { var add_modal = $('#add'); if (add_modal.is(':visible')) { add_modal.modal('hide'); } }, 1500); // Update the Add Button and the table if (action === "POST") { btn = $('#add .modal-footer').find('#addButton'); btn.removeClass("btn-primary"); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.removeClass("btn-warning"); } btn.addClass("btn-success"); btn.html("<span class='glyphicon glyphicon-check'></span>Saved!"); btn.attr("disabled", true); if (action === "POST") { table.row.add({ "id": id_int, "domain": requestPayload.domain, "type": requestPayload.type, "assigned_domains": '' }).draw(); } /* else { table.row(function(idx, data, node) { return data.id === id_int; }).data({ "domain": requestPayload.domain, "id": id_int }).draw(); }*/ } }); } function deleteEntity() { var id_int = parseInt(id, 10); $.ajax({ type: "DELETE", url: API_BASE_URL + ENTITY + "/" + id, dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, textStatus, jqXHR) { $('#delete').modal('hide'); $('#edit').modal('hide'); table.row(function (idx, data, node) { //return data.id === id_int; return data.domain === id; }).remove().draw(); showNotification("Certificate was deleted"); } }); } $(document).ready(function() { // datatable init table = $('#' + ENTITY).DataTable({ "ajax": { "url": API_BASE_URL + ENTITY }, "columns": [ {"data": "id"}, {"data": "domain"}, {"data": "type"}, {"data": "assigned_domains"} //{ "data": "gwlist", visible: false }, ], "order": [[1, 'asc']] }); // table editing by clicking on the row $('#' + ENTITY + ' tbody').on('click', 'tr', function() { //Turn off selected on any other rows $('#' + ENTITY).find('tr').removeClass('selected'); if ($(this).hasClass('selected')) { $(this).removeClass('selected'); } else { //table.$('tr.selected').removeClass('selected'); $(this).addClass('selected'); id = $(this).find('td').eq(1).text() if (id != "") { $('#edit').modal('show'); } } }); $('#open-Add').click(function() { clear('#add'); }); /* validate fields before submitting api request */ $('#addButton').click(function() { if (validateFields('#add')) { addEntity('POST'); } }); $('#updateButton').click(function() { if (validateFields('#edit')) { addEntity('PUT'); } }); /* handler for deleting endpoint group */ $('#deleteButton').click(function() { deleteEntity(); }); $("#domain").keyup(function () { var value = document.getElementById("domain").value; console.log(value); if (value.includes("*")) { var command = "certbot certonly --manual -d " + value + ' --server https://acme-v02.api.letsencrypt.org/directory' + '--force-renewal --preferred-chain "ISRG Root X1"'; $("#terminalCommand").text(command); $("#terminalDiv").removeClass("hide"); $("#certtype_generated").prop('checked', true); var btn = $('#add .modal-footer').find('#addButton'); btn.attr('disabled', true); } else{ $("#terminalDiv").addClass("hide"); } }) $("#domain2").keyup(function () { var value = document.getElementById("domain").value; console.log(value); if (value.includes("*")) { var command = "certbot certonly --manual -d " + value + ' --server https://acme-v02.api.letsencrypt.org/directory' + '--force-renewal --preferred-chain "ISRG Root X1"'; $("#terminalCommand2").text(command); $("#terminalDiv2").removeClass("hide"); $("#certtype_generated2").prop('checked', true); var btn = $('#edit .modal-footer').find('#editButton'); btn.attr('disabled', true); } else { $("#terminalDiv").addClass("hide"); } }) $("#certtype_generate2").change(function () { $("#generate2").removeClass("hide"); $("#uploaded2").addClass("hide"); }) $("#certtype_upload2").change(function () { $("#generate2").addClass("hide"); $("#uploaded2").removeClass("hide"); }) $("#certtype_generate").change(function () { $("#generate").removeClass("hide"); $("#uploaded").addClass("hide"); }) $("#certtype_upload").change(function () { $("#generate").addClass("hide"); $("#uploaded").removeClass("hide"); }) $("#replace_default_cert").change(function () { if ($("#replace_default_cert").is(':checked')) { $("#domain").val("default"); $("#domain").attr('disabled',true); } else { $("#domain").val(""); $("#domain").attr('disabled',false); } }) $('#add').on('show.bs.modal', function() { $("#replace_default_cert").attr('checked',true); $("#replace_default_cert").trigger("change"); $("#certtype_upload").trigger("change"); }) $('#edit').on('show.bs.modal', function() { clear('#edit'); // Show the auth tab by default when the modal shows var modal_body = $('#edit .modal-body'); // Put into JSON Message and send over $.ajax({ type: "GET", url: API_BASE_URL + ENTITY + "/" + id, dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, textStatus, jqXHR) { modal_body.find("#domain2").val(response.data[0].domain) if (response.data[0].type == "generated") { modal_body.find("#certtype_generate2").prop('checked', true); } else if (response.data[0].type == "uploaded") { modal_body.find("#certtype_upload2").prop('checked', true); $("#uploaded2").removeClass("hide"); } }, error: function(response, text_status, error_msg) { showNotification("Could not obtain certificate", true); } }) }); $(document).ajaxStart(function(){ // Show image container $("#loader").show(); }); $(document).ajaxComplete(function(){ // Hide image container $("#loader").hide(); }); $(".close").click(function () { document.getElementById("domain").value = ""; $("#terminalDiv").addClass("hide"); $("#terminalCommand").text(""); $("#certtype_generate").prop('selected', true); }) }) })(window, document); ================================================ FILE: gui/static/js/combobox.js ================================================ /* * THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. * The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. Title to copyright in this work will at all times remain with copyright holders. * * Original Version [listbox-combobox.js]: https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/js/listbox-combobox.js * Copyright © [2015] World Wide Web Consortium, (Massachusetts Institute of Technology, European Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights Reserved. This work is distributed under the W3C® Software License [1] 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. * http://www.w3.org/Consortium/Legal/copyright-software * * Changes made by the dOpenSource Team * Copyright © [2018] W3C®, dOpenSource */ ;(function(window, document) { 'use strict'; /** * @namespace aria */ var aria = aria || {}; /** * Check aria widget for class * @param element * @param className * @returns {boolean} */ aria.hasClass = function (element, className) { return (new RegExp('(\\s|^)' + className + '(\\s|$)')).test(element.className); }; /** * Add class to aria widget * @param element * @param className */ aria.addClass = function (element, className) { if (!aria.hasClass(element, className)) { element.className += ' ' + className; } }; /** * Remove class from aria widget * @param element * @param className */ aria.removeClass = function (element, className) { var classRegex = new RegExp('(\\s|^)' + className + '(\\s|$)'); element.className = element.className.replace(classRegex, ' ').trim(); }; /** * Shortcuts for JS event key codes * @type {{UP: number, DOWN: number, ESC: number, ENTER: number, BACKSPACE: number, TAB: number}} */ var KeyCodes = { 'UP': 38, 'DOWN': 40, 'ESC': 27, 'ENTER': 13, 'BACKSPACE': 8, 'TAB': 9 }; /** * @constructor * * @desc * Combobox object representing the state and interactions for a combobox widget * * @param comboboxNode * The DOM node pointing to the combobox * @param input * The input node * @param listbox * The listbox node to load results in * @param searchFn * The search function. The function accepts a search string and returns an * array of results. * @param shouldAutoSelect * Whether to autoselect the current item when focus toggles * @param onShow * Callback function on show * @param onHide * Callback function on hide */ aria.ListboxCombobox = function(comboboxNode, input, listbox, searchFn, shouldAutoSelect, onShow, onHide) { this.combobox = comboboxNode; this.input = input; this.listbox = listbox; this.searchFn = searchFn; this.shouldAutoSelect = shouldAutoSelect; this.onShow = onShow || function() {}; this.onHide = onHide || function() {}; this.activeIndex = -1; this.resultsCount = 0; this.shown = false; this.hasInlineAutocomplete = input.getAttribute('aria-autocomplete') === 'both'; this.setupEvents(); }; aria.ListboxCombobox.prototype.setupEvents = function () { document.body.addEventListener('click', this.checkHide.bind(this)); this.input.addEventListener('keyup', this.checkKey.bind(this)); this.input.addEventListener('keydown', this.setActiveItem.bind(this)); this.input.addEventListener('focus', this.checkShow.bind(this)); this.input.addEventListener('blur', this.checkSelection.bind(this)); this.listbox.addEventListener('click', this.clickItem.bind(this)); }; aria.ListboxCombobox.prototype.checkKey = function(evt) { var key = evt.which || evt.keyCode; switch (key) { case KeyCodes.UP: case KeyCodes.DOWN: case KeyCodes.ESC: case KeyCodes.ENTER: evt.preventDefault(); return; default: this.updateResults(false); } if (this.hasInlineAutocomplete) { switch (key) { case KeyCodes.BACKSPACE: return; default: this.autocompleteItem(); } } }; aria.ListboxCombobox.prototype.updateResults = function(shouldShowAll) { var searchString = this.input.value; var results = this.searchFn(searchString); this.hideListbox(); if (!shouldShowAll && !searchString) { results = []; } if (results.length) { for (var i = 0; i < results.length; i++) { var resultItem = document.createElement('li'); resultItem.className = 'result'; resultItem.setAttribute('role', 'option'); resultItem.setAttribute('id', 'result-item-' + i); resultItem.innerText = results[i]; if (this.shouldAutoSelect && i === 0) { resultItem.setAttribute('aria-selected', 'true'); aria.addClass(resultItem, 'focused'); this.activeIndex = 0; } this.listbox.appendChild(resultItem); } aria.removeClass(this.listbox, 'hidden'); this.combobox.setAttribute('aria-expanded', 'true'); this.resultsCount = results.length; this.shown = true; this.onShow(); } }; aria.ListboxCombobox.prototype.setActiveItem = function(evt) { var key = evt.which || evt.keyCode; var activeIndex = this.activeIndex; if (key === KeyCodes.ESC) { this.hideListbox(); setTimeout((function() { // On Firefox, input does not get cleared here unless wrapped in a setTimeout this.input.value = ''; }).bind(this), 1); return; } if (this.resultsCount < 1) { if (this.hasInlineAutocomplete && (key === KeyCodes.DOWN || key === KeyCodes.UP)) { this.updateResults(true); } else { return; } } var prevActive = this.getItemAt(activeIndex); var activeItem; switch (key) { case KeyCodes.UP: if (activeIndex <= 0) { activeIndex = this.resultsCount - 1; } else { activeIndex--; } break; case KeyCodes.DOWN: if (activeIndex === -1 || activeIndex >= this.resultsCount - 1) { activeIndex = 0; } else { activeIndex++; } break; case KeyCodes.ENTER: activeItem = this.getItemAt(activeIndex); this.selectItem(activeItem); return; case KeyCodes.TAB: this.checkSelection(); this.hideListbox(); return; default: return; } evt.preventDefault(); activeItem = this.getItemAt(activeIndex); this.activeIndex = activeIndex; if (prevActive) { aria.removeClass(prevActive, 'focused'); prevActive.setAttribute('aria-selected', 'false'); } if (activeItem) { this.input.setAttribute( 'aria-activedescendant', 'result-item-' + activeIndex ); aria.addClass(activeItem, 'focused'); activeItem.setAttribute('aria-selected', 'true'); if (this.hasInlineAutocomplete) { this.input.value = activeItem.innerText; } } else { this.input.setAttribute( 'aria-activedescendant', '' ); } }; aria.ListboxCombobox.prototype.getItemAt = function(index) { return document.getElementById('result-item-' + index); }; aria.ListboxCombobox.prototype.clickItem = function(evt) { if (evt.target && evt.target.nodeName === 'LI') { this.selectItem(evt.target); } }; aria.ListboxCombobox.prototype.selectItem = function(item) { if (item) { this.input.value = item.innerText; this.hideListbox(); } }; aria.ListboxCombobox.prototype.checkShow = function(evt) { if (this.shown) { return; } this.updateResults(false); }; aria.ListboxCombobox.prototype.checkHide = function(evt) { if (evt.target === this.input || this.combobox.contains(evt.target)) { var arrow = $(this.combobox).find('.did-combobox-arrow').get(0); if (evt.target === arrow || this.combobox.contains(arrow)) { if (this.combobox.shown) { this.hideListbox(); this.combobox.shown = false; } else { this.updateResults(true); this.combobox.shown = true; } } return; } this.hideListbox(); }; aria.ListboxCombobox.prototype.hideListbox = function() { this.shown = false; this.activeIndex = -1; this.listbox.innerHTML = ''; aria.addClass(this.listbox, 'hidden'); this.combobox.setAttribute('aria-expanded', 'false'); this.resultsCount = 0; this.input.setAttribute('aria-activedescendant', ''); this.onHide(); }; aria.ListboxCombobox.prototype.checkSelection = function() { if (this.activeIndex < 0) { return; } var activeItem = this.getItemAt(this.activeIndex); this.selectItem(activeItem); }; aria.ListboxCombobox.prototype.autocompleteItem = function() { var autocompletedItem = this.listbox.querySelector('.focused'); var inputText = this.input.value; if (!autocompletedItem || !inputText) { return; } var autocomplete = autocompletedItem.innerText; if (inputText !== autocomplete) { this.input.value = autocomplete; this.input.setSelectionRange(inputText.length, autocomplete.length); } }; /* export the namespace */ window.aria = aria; })(window, document); ================================================ FILE: gui/static/js/dashboard.js ================================================ ;(function(window, document) { 'use strict'; function getKamailioStats(elem) { $.ajax({ type: "GET", url: "/api/v1/kamailio/stats", dataType: "json", success: function(response, text_status, xhr) { var stats = response.data[0]; // set defaults if bad response stats.current = stats.current !== undefined ? stats.current : 0; stats.waiting = stats.waiting !== undefined ? stats.waiting : 0; stats.total = stats.total !== undefined ? stats.total : 0; $("#dashboard_current_calls").text(stats.current); $("#dashboard_calls_inqueue").text(stats.waiting); $("#dashboard_total_calls_processed").text(stats.total); } }); } $(document).ready(function() { getKamailioStats(); }); })(window, document); ================================================ FILE: gui/static/js/datatables.js ================================================ /* * This combined file was created by the DataTables downloader builder: * https://datatables.net/download * * To rebuild or modify this file with the latest versions of the included * software please visit: * https://datatables.net/download/#bs/dt-1.10.21 * * Included libraries: * DataTables 1.10.21 */ /*! DataTables 1.10.21 * ©2008-2020 SpryMedia Ltd - datatables.net/license */ /** * @summary DataTables * @description Paginate, search and order HTML tables * @version 1.10.21 * @file jquery.dataTables.js * @author SpryMedia Ltd * @contact www.datatables.net * @copyright Copyright 2008-2020 SpryMedia Ltd. * * This source file is free software, available under the following license: * MIT license - http://datatables.net/license * * This source file 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 license files for details. * * For details please refer to: http://www.datatables.net */ /*jslint evil: true, undef: true, browser: true */ /*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/ (function( factory ) { "use strict"; if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = function (root, $) { if ( ! root ) { // CommonJS environments without a window global must pass a // root. This will give an error otherwise root = window; } if ( ! $ ) { $ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window require('jquery') : require('jquery')( root ); } return factory( $, root, root.document ); }; } else { // Browser factory( jQuery, window, document ); } } (function( $, window, document, undefined ) { "use strict"; /** * DataTables is a plug-in for the jQuery Javascript library. It is a highly * flexible tool, based upon the foundations of progressive enhancement, * which will add advanced interaction controls to any HTML table. For a * full list of features please refer to * [DataTables.net](href="http://datatables.net). * * Note that the `DataTable` object is not a global variable but is aliased * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may * be accessed. * * @class * @param {object} [init={}] Configuration object for DataTables. Options * are defined by {@link DataTable.defaults} * @requires jQuery 1.7+ * * @example * // Basic initialisation * $(document).ready( function { * $('#example').dataTable(); * } ); * * @example * // Initialisation with configuration options - in this case, disable * // pagination and sorting. * $(document).ready( function { * $('#example').dataTable( { * "paginate": false, * "sort": false * } ); * } ); */ var DataTable = function ( options ) { /** * Perform a jQuery selector action on the table's TR elements (from the tbody) and * return the resulting jQuery object. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select TR elements that meet the current filter * criterion ("applied") or all TR elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the TR elements in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {object} jQuery object, filtered by the given selector. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Highlight every second row * oTable.$('tr:odd').css('backgroundColor', 'blue'); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to rows with 'Webkit' in them, add a background colour and then * // remove the filter, thus highlighting the 'Webkit' rows only. * oTable.fnFilter('Webkit'); * oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue'); * oTable.fnFilter(''); * } ); */ this.$ = function ( sSelector, oOpts ) { return this.api(true).$( sSelector, oOpts ); }; /** * Almost identical to $ in operation, but in this case returns the data for the matched * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes * rather than any descendants, so the data can be obtained for the row/cell. If matching * rows are found, the data returned is the original data array/object that was used to * create the row (or a generated array if from a DOM source). * * This method is often useful in-combination with $ where both functions are given the * same parameters and the array indexes will match identically. * @param {string|node|jQuery} sSelector jQuery selector or node collection to act on * @param {object} [oOpts] Optional parameters for modifying the rows to be included * @param {string} [oOpts.filter=none] Select elements that meet the current filter * criterion ("applied") or all elements (i.e. no filter). * @param {string} [oOpts.order=current] Order of the data in the processed array. * Can be either 'current', whereby the current sorting of the table is used, or * 'original' whereby the original order the data was read into the table is used. * @param {string} [oOpts.page=all] Limit the selection to the currently displayed page * ("current") or not ("all"). If 'current' is given, then order is assumed to be * 'current' and filter is 'applied', regardless of what they might be given as. * @returns {array} Data for the matched elements. If any elements, as a result of the * selector, were not TR, TD or TH elements in the DataTable, they will have a null * entry in the array. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the data from the first row in the table * var data = oTable._('tr:first'); * * // Do something useful with the data * alert( "First cell is: "+data[0] ); * } ); * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Filter to 'Webkit' and get all data for * oTable.fnFilter('Webkit'); * var data = oTable._('tr', {"search": "applied"}); * * // Do something with the data * alert( data.length+" rows matched the search" ); * } ); */ this._ = function ( sSelector, oOpts ) { return this.api(true).rows( sSelector, oOpts ).data(); }; /** * Create a DataTables Api instance, with the currently selected tables for * the Api's context. * @param {boolean} [traditional=false] Set the API instance's context to be * only the table referred to by the `DataTable.ext.iApiIndex` option, as was * used in the API presented by DataTables 1.9- (i.e. the traditional mode), * or if all tables captured in the jQuery object should be used. * @return {DataTables.Api} */ this.api = function ( traditional ) { return traditional ? new _Api( _fnSettingsFromNode( this[ _ext.iApiIndex ] ) ) : new _Api( this ); }; /** * Add a single new row or multiple rows of data to the table. Please note * that this is suitable for client-side processing only - if you are using * server-side processing (i.e. "bServerSide": true), then to add data, you * must add it to the data source, i.e. the server-side, through an Ajax call. * @param {array|object} data The data to be added to the table. This can be: * <ul> * <li>1D array of data - add a single row with the data provided</li> * <li>2D array of arrays - add multiple rows in a single call</li> * <li>object - data object when using <i>mData</i></li> * <li>array of objects - multiple data objects when using <i>mData</i></li> * </ul> * @param {bool} [redraw=true] redraw the table or not * @returns {array} An array of integers, representing the list of indexes in * <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to * the table. * @dtopt API * @deprecated Since v1.10 * * @example * // Global var for counter * var giCount = 2; * * $(document).ready(function() { * $('#example').dataTable(); * } ); * * function fnClickAddRow() { * $('#example').dataTable().fnAddData( [ * giCount+".1", * giCount+".2", * giCount+".3", * giCount+".4" ] * ); * * giCount++; * } */ this.fnAddData = function( data, redraw ) { var api = this.api( true ); /* Check if we want to add multiple rows or not */ var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ? api.rows.add( data ) : api.row.add( data ); if ( redraw === undefined || redraw ) { api.draw(); } return rows.flatten().toArray(); }; /** * This function will make DataTables recalculate the column sizes, based on the data * contained in the table and the sizes applied to the columns (in the DOM, CSS or * through the sWidth parameter). This can be useful when the width of the table's * parent element changes (for example a window resize). * @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable( { * "sScrollY": "200px", * "bPaginate": false * } ); * * $(window).on('resize', function () { * oTable.fnAdjustColumnSizing(); * } ); * } ); */ this.fnAdjustColumnSizing = function ( bRedraw ) { var api = this.api( true ).columns.adjust(); var settings = api.settings()[0]; var scroll = settings.oScroll; if ( bRedraw === undefined || bRedraw ) { api.draw( false ); } else if ( scroll.sX !== "" || scroll.sY !== "" ) { /* If not redrawing, but scrolling, we want to apply the new column sizes anyway */ _fnScrollDraw( settings ); } }; /** * Quickly and simply clear a table * @param {bool} [bRedraw=true] redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...) * oTable.fnClearTable(); * } ); */ this.fnClearTable = function( bRedraw ) { var api = this.api( true ).clear(); if ( bRedraw === undefined || bRedraw ) { api.draw(); } }; /** * The exact opposite of 'opening' a row, this function will close any rows which * are currently 'open'. * @param {node} nTr the table row to 'close' * @returns {int} 0 on success, or 1 if failed (can't find the row) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnClose = function( nTr ) { this.api( true ).row( nTr ).child.hide(); }; /** * Remove a row for the table * @param {mixed} target The index of the row from aoData to be deleted, or * the TR element you want to delete * @param {function|null} [callBack] Callback function * @param {bool} [redraw=true] Redraw the table or not * @returns {array} The row that was deleted * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Immediately remove the first row * oTable.fnDeleteRow( 0 ); * } ); */ this.fnDeleteRow = function( target, callback, redraw ) { var api = this.api( true ); var rows = api.rows( target ); var settings = rows.settings()[0]; var data = settings.aoData[ rows[0][0] ]; rows.remove(); if ( callback ) { callback.call( this, settings, data ); } if ( redraw === undefined || redraw ) { api.draw(); } return data; }; /** * Restore the table to it's original state in the DOM by removing all of DataTables * enhancements, alterations to the DOM structure of the table and event listeners. * @param {boolean} [remove=false] Completely remove the table from the DOM * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * // This example is fairly pointless in reality, but shows how fnDestroy can be used * var oTable = $('#example').dataTable(); * oTable.fnDestroy(); * } ); */ this.fnDestroy = function ( remove ) { this.api( true ).destroy( remove ); }; /** * Redraw the table * @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Re-draw the table - you wouldn't want to do it here, but it's an example :-) * oTable.fnDraw(); * } ); */ this.fnDraw = function( complete ) { // Note that this isn't an exact match to the old call to _fnDraw - it takes // into account the new data, but can hold position. this.api( true ).draw( complete ); }; /** * Filter the input based on data * @param {string} sInput String to filter the table on * @param {int|null} [iColumn] Column to limit filtering to * @param {bool} [bRegex=false] Treat as regular expression or not * @param {bool} [bSmart=true] Perform smart filtering or not * @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es) * @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false) * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sometime later - filter... * oTable.fnFilter( 'test string' ); * } ); */ this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive ) { var api = this.api( true ); if ( iColumn === null || iColumn === undefined ) { api.search( sInput, bRegex, bSmart, bCaseInsensitive ); } else { api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive ); } api.draw(); }; /** * Get the data for the whole table, an individual row or an individual cell based on the * provided parameters. * @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as * a TR node then the data source for the whole row will be returned. If given as a * TD/TH cell node then iCol will be automatically calculated and the data for the * cell returned. If given as an integer, then this is treated as the aoData internal * data index for the row (see fnGetPosition) and the data for that row used. * @param {int} [col] Optional column index that you want the data of. * @returns {array|object|string} If mRow is undefined, then the data for all rows is * returned. If mRow is defined, just data for that row, and is iCol is * defined, only data for the designated cell is returned. * @dtopt API * @deprecated Since v1.10 * * @example * // Row data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('tr').click( function () { * var data = oTable.fnGetData( this ); * // ... do something with the array / object of data for the row * } ); * } ); * * @example * // Individual cell data * $(document).ready(function() { * oTable = $('#example').dataTable(); * * oTable.$('td').click( function () { * var sData = oTable.fnGetData( this ); * alert( 'The cell clicked on had the value of '+sData ); * } ); * } ); */ this.fnGetData = function( src, col ) { var api = this.api( true ); if ( src !== undefined ) { var type = src.nodeName ? src.nodeName.toLowerCase() : ''; return col !== undefined || type == 'td' || type == 'th' ? api.cell( src, col ).data() : api.row( src ).data() || null; } return api.data().toArray(); }; /** * Get an array of the TR nodes that are used in the table's body. Note that you will * typically want to use the '$' API method in preference to this as it is more * flexible. * @param {int} [iRow] Optional row index for the TR element you want * @returns {array|node} If iRow is undefined, returns an array of all TR elements * in the table's body, or iRow is defined, just the TR element requested. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Get the nodes from the table * var nNodes = oTable.fnGetNodes( ); * } ); */ this.fnGetNodes = function( iRow ) { var api = this.api( true ); return iRow !== undefined ? api.row( iRow ).node() : api.rows().nodes().flatten().toArray(); }; /** * Get the array indexes of a particular cell from it's DOM element * and column index including hidden columns * @param {node} node this can either be a TR, TD or TH in the table's body * @returns {int} If nNode is given as a TR, then a single index is returned, or * if given as a cell, an array of [row index, column index (visible), * column index (all)] is given. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * $('#example tbody td').click( function () { * // Get the position of the current data from the node * var aPos = oTable.fnGetPosition( this ); * * // Get the data array for this row * var aData = oTable.fnGetData( aPos[0] ); * * // Update the data array and return the value * aData[ aPos[1] ] = 'clicked'; * this.innerHTML = 'clicked'; * } ); * * // Init DataTables * oTable = $('#example').dataTable(); * } ); */ this.fnGetPosition = function( node ) { var api = this.api( true ); var nodeName = node.nodeName.toUpperCase(); if ( nodeName == 'TR' ) { return api.row( node ).index(); } else if ( nodeName == 'TD' || nodeName == 'TH' ) { var cell = api.cell( node ).index(); return [ cell.row, cell.columnVisible, cell.column ]; } return null; }; /** * Check to see if a row is 'open' or not. * @param {node} nTr the table row to check * @returns {boolean} true if the row is currently open, false otherwise * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnIsOpen = function( nTr ) { return this.api( true ).row( nTr ).child.isShown(); }; /** * This function will place a new row directly after a row which is currently * on display on the page, with the HTML contents that is passed into the * function. This can be used, for example, to ask for confirmation that a * particular record should be deleted. * @param {node} nTr The table row to 'open' * @param {string|node|jQuery} mHtml The HTML to put into the row * @param {string} sClass Class to give the new TD cell * @returns {node} The row opened. Note that if the table row passed in as the * first parameter, is not found in the table, this method will silently * return. * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable; * * // 'open' an information row when a row is clicked on * $('#example tbody tr').click( function () { * if ( oTable.fnIsOpen(this) ) { * oTable.fnClose( this ); * } else { * oTable.fnOpen( this, "Temporary row opened", "info_row" ); * } * } ); * * oTable = $('#example').dataTable(); * } ); */ this.fnOpen = function( nTr, mHtml, sClass ) { return this.api( true ) .row( nTr ) .child( mHtml, sClass ) .show() .child()[0]; }; /** * Change the pagination - provides the internal logic for pagination in a simple API * function. With this function you can have a DataTables table go to the next, * previous, first or last pages. * @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last" * or page number to jump to (integer), note that page 0 is the first page. * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnPageChange( 'next' ); * } ); */ this.fnPageChange = function ( mAction, bRedraw ) { var api = this.api( true ).page( mAction ); if ( bRedraw === undefined || bRedraw ) { api.draw(false); } }; /** * Show a particular column * @param {int} iCol The column whose display should be changed * @param {bool} bShow Show (true) or hide (false) the column * @param {bool} [bRedraw=true] Redraw the table or not * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Hide the second column after initialisation * oTable.fnSetColumnVis( 1, false ); * } ); */ this.fnSetColumnVis = function ( iCol, bShow, bRedraw ) { var api = this.api( true ).column( iCol ).visible( bShow ); if ( bRedraw === undefined || bRedraw ) { api.columns.adjust().draw(); } }; /** * Get the settings for a particular table for external manipulation * @returns {object} DataTables settings object. See * {@link DataTable.models.oSettings} * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * var oSettings = oTable.fnSettings(); * * // Show an example parameter from the settings * alert( oSettings._iDisplayStart ); * } ); */ this.fnSettings = function() { return _fnSettingsFromNode( this[_ext.iApiIndex] ); }; /** * Sort the table by a particular column * @param {int} iCol the data index to sort on. Note that this will not match the * 'display index' if you have hidden data entries * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort immediately with columns 0 and 1 * oTable.fnSort( [ [0,'asc'], [1,'asc'] ] ); * } ); */ this.fnSort = function( aaSort ) { this.api( true ).order( aaSort ).draw(); }; /** * Attach a sort listener to an element for a given column * @param {node} nNode the element to attach the sort listener to * @param {int} iColumn the column that a click on this node will sort on * @param {function} [fnCallback] callback function when sort is run * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * * // Sort on column 1, when 'sorter' is clicked on * oTable.fnSortListener( document.getElementById('sorter'), 1 ); * } ); */ this.fnSortListener = function( nNode, iColumn, fnCallback ) { this.api( true ).order.listener( nNode, iColumn, fnCallback ); }; /** * Update a table cell or row - this method will accept either a single value to * update the cell with, an array of values with one element for each column or * an object in the same format as the original data source. The function is * self-referencing in order to make the multi column updates easier. * @param {object|array|string} mData Data to update the cell/row with * @param {node|int} mRow TR element you want to update or the aoData index * @param {int} [iColumn] The column to update, give as null or undefined to * update a whole row. * @param {bool} [bRedraw=true] Redraw the table or not * @param {bool} [bAction=true] Perform pre-draw actions or not * @returns {int} 0 on success, 1 on error * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell * oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row * } ); */ this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction ) { var api = this.api( true ); if ( iColumn === undefined || iColumn === null ) { api.row( mRow ).data( mData ); } else { api.cell( mRow, iColumn ).data( mData ); } if ( bAction === undefined || bAction ) { api.columns.adjust(); } if ( bRedraw === undefined || bRedraw ) { api.draw(); } return 0; }; /** * Provide a common method for plug-ins to check the version of DataTables being used, in order * to ensure compatibility. * @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the * formats "X" and "X.Y" are also acceptable. * @returns {boolean} true if this version of DataTables is greater or equal to the required * version, or false if this version of DataTales is not suitable * @method * @dtopt API * @deprecated Since v1.10 * * @example * $(document).ready(function() { * var oTable = $('#example').dataTable(); * alert( oTable.fnVersionCheck( '1.9.0' ) ); * } ); */ this.fnVersionCheck = _ext.fnVersionCheck; var _that = this; var emptyInit = options === undefined; var len = this.length; if ( emptyInit ) { options = {}; } this.oApi = this.internal = _ext.internal; // Extend with old style plug-in API methods for ( var fn in DataTable.ext.internal ) { if ( fn ) { this[fn] = _fnExternApiFunc(fn); } } this.each(function() { // For each initialisation we want to give it a clean initialisation // object that can be bashed around var o = {}; var oInit = len > 1 ? // optimisation for single table case _fnExtend( o, options, true ) : options; /*global oInit,_that,emptyInit*/ var i=0, iLen, j, jLen, k, kLen; var sId = this.getAttribute( 'id' ); var bInitHandedOff = false; var defaults = DataTable.defaults; var $this = $(this); /* Sanity check */ if ( this.nodeName.toLowerCase() != 'table' ) { _fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 ); return; } /* Backwards compatibility for the defaults */ _fnCompatOpts( defaults ); _fnCompatCols( defaults.column ); /* Convert the camel-case defaults to Hungarian */ _fnCamelToHungarian( defaults, defaults, true ); _fnCamelToHungarian( defaults.column, defaults.column, true ); /* Setting up the initialisation object */ _fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ), true ); /* Check to see if we are re-initialising a table */ var allSettings = DataTable.settings; for ( i=0, iLen=allSettings.length ; i<iLen ; i++ ) { var s = allSettings[i]; /* Base check on table node */ if ( s.nTable == this || (s.nTHead && s.nTHead.parentNode == this) || (s.nTFoot && s.nTFoot.parentNode == this) ) { var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve; var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy; if ( emptyInit || bRetrieve ) { return s.oInstance; } else if ( bDestroy ) { s.oInstance.fnDestroy(); break; } else { _fnLog( s, 0, 'Cannot reinitialise DataTable', 3 ); return; } } /* If the element we are initialising has the same ID as a table which was previously * initialised, but the table nodes don't match (from before) then we destroy the old * instance by simply deleting it. This is under the assumption that the table has been * destroyed by other methods. Anyone using non-id selectors will need to do this manually */ if ( s.sTableId == this.id ) { allSettings.splice( i, 1 ); break; } } /* Ensure the table has an ID - required for accessibility */ if ( sId === null || sId === "" ) { sId = "DataTables_Table_"+(DataTable.ext._unique++); this.id = sId; } /* Create the settings object for this table and set some of the default parameters */ var oSettings = $.extend( true, {}, DataTable.models.oSettings, { "sDestroyWidth": $this[0].style.width, "sInstance": sId, "sTableId": sId } ); oSettings.nTable = this; oSettings.oApi = _that.internal; oSettings.oInit = oInit; allSettings.push( oSettings ); // Need to add the instance after the instance after the settings object has been added // to the settings array, so we can self reference the table instance if more than one oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable(); // Backwards compatibility, before we apply all the defaults _fnCompatOpts( oInit ); _fnLanguageCompat( oInit.oLanguage ); // If the length menu is given, but the init display length is not, use the length menu if ( oInit.aLengthMenu && ! oInit.iDisplayLength ) { oInit.iDisplayLength = $.isArray( oInit.aLengthMenu[0] ) ? oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0]; } // Apply the defaults and init options to make a single init object will all // options defined from defaults and instance options. oInit = _fnExtend( $.extend( true, {}, defaults ), oInit ); // Map the initialisation options onto the settings object _fnMap( oSettings.oFeatures, oInit, [ "bPaginate", "bLengthChange", "bFilter", "bSort", "bSortMulti", "bInfo", "bProcessing", "bAutoWidth", "bSortClasses", "bServerSide", "bDeferRender" ] ); _fnMap( oSettings, oInit, [ "asStripeClasses", "ajax", "fnServerData", "fnFormatNumber", "sServerMethod", "aaSorting", "aaSortingFixed", "aLengthMenu", "sPaginationType", "sAjaxSource", "sAjaxDataProp", "iStateDuration", "sDom", "bSortCellsTop", "iTabIndex", "fnStateLoadCallback", "fnStateSaveCallback", "renderer", "searchDelay", "rowId", [ "iCookieDuration", "iStateDuration" ], // backwards compat [ "oSearch", "oPreviousSearch" ], [ "aoSearchCols", "aoPreSearchCols" ], [ "iDisplayLength", "_iDisplayLength" ] ] ); _fnMap( oSettings.oScroll, oInit, [ [ "sScrollX", "sX" ], [ "sScrollXInner", "sXInner" ], [ "sScrollY", "sY" ], [ "bScrollCollapse", "bCollapse" ] ] ); _fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" ); /* Callback functions which are array driven */ _fnCallbackReg( oSettings, 'aoDrawCallback', oInit.fnDrawCallback, 'user' ); _fnCallbackReg( oSettings, 'aoServerParams', oInit.fnServerParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateSaveParams', oInit.fnStateSaveParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateLoadParams', oInit.fnStateLoadParams, 'user' ); _fnCallbackReg( oSettings, 'aoStateLoaded', oInit.fnStateLoaded, 'user' ); _fnCallbackReg( oSettings, 'aoRowCallback', oInit.fnRowCallback, 'user' ); _fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow, 'user' ); _fnCallbackReg( oSettings, 'aoHeaderCallback', oInit.fnHeaderCallback, 'user' ); _fnCallbackReg( oSettings, 'aoFooterCallback', oInit.fnFooterCallback, 'user' ); _fnCallbackReg( oSettings, 'aoInitComplete', oInit.fnInitComplete, 'user' ); _fnCallbackReg( oSettings, 'aoPreDrawCallback', oInit.fnPreDrawCallback, 'user' ); oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId ); /* Browser support detection */ _fnBrowserDetect( oSettings ); var oClasses = oSettings.oClasses; $.extend( oClasses, DataTable.ext.classes, oInit.oClasses ); $this.addClass( oClasses.sTable ); if ( oSettings.iInitDisplayStart === undefined ) { /* Display start point, taking into account the save saving */ oSettings.iInitDisplayStart = oInit.iDisplayStart; oSettings._iDisplayStart = oInit.iDisplayStart; } if ( oInit.iDeferLoading !== null ) { oSettings.bDeferLoading = true; var tmp = $.isArray( oInit.iDeferLoading ); oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading; oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading; } /* Language definitions */ var oLanguage = oSettings.oLanguage; $.extend( true, oLanguage, oInit.oLanguage ); if ( oLanguage.sUrl ) { /* Get the language definitions from a file - because this Ajax call makes the language * get async to the remainder of this function we use bInitHandedOff to indicate that * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor */ $.ajax( { dataType: 'json', url: oLanguage.sUrl, success: function ( json ) { _fnLanguageCompat( json ); _fnCamelToHungarian( defaults.oLanguage, json ); $.extend( true, oLanguage, json ); _fnInitialise( oSettings ); }, error: function () { // Error occurred loading language file, continue on as best we can _fnInitialise( oSettings ); } } ); bInitHandedOff = true; } /* * Stripes */ if ( oInit.asStripeClasses === null ) { oSettings.asStripeClasses =[ oClasses.sStripeOdd, oClasses.sStripeEven ]; } /* Remove row stripe classes if they are already on the table row */ var stripeClasses = oSettings.asStripeClasses; var rowOne = $this.children('tbody').find('tr').eq(0); if ( $.inArray( true, $.map( stripeClasses, function(el, i) { return rowOne.hasClass(el); } ) ) !== -1 ) { $('tbody tr', this).removeClass( stripeClasses.join(' ') ); oSettings.asDestroyStripes = stripeClasses.slice(); } /* * Columns * See if we should load columns automatically or use defined ones */ var anThs = []; var aoColumnsInit; var nThead = this.getElementsByTagName('thead'); if ( nThead.length !== 0 ) { _fnDetectHeader( oSettings.aoHeader, nThead[0] ); anThs = _fnGetUniqueThs( oSettings ); } /* If not given a column array, generate one with nulls */ if ( oInit.aoColumns === null ) { aoColumnsInit = []; for ( i=0, iLen=anThs.length ; i<iLen ; i++ ) { aoColumnsInit.push( null ); } } else { aoColumnsInit = oInit.aoColumns; } /* Add the columns */ for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ ) { _fnAddColumn( oSettings, anThs ? anThs[i] : null ); } /* Apply the column definitions */ _fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) { _fnColumnOptions( oSettings, iCol, oDef ); } ); /* HTML5 attribute detection - build an mData object automatically if the * attributes are found */ if ( rowOne.length ) { var a = function ( cell, name ) { return cell.getAttribute( 'data-'+name ) !== null ? name : null; }; $( rowOne[0] ).children('th, td').each( function (i, cell) { var col = oSettings.aoColumns[i]; if ( col.mData === i ) { var sort = a( cell, 'sort' ) || a( cell, 'order' ); var filter = a( cell, 'filter' ) || a( cell, 'search' ); if ( sort !== null || filter !== null ) { col.mData = { _: i+'.display', sort: sort !== null ? i+'.@data-'+sort : undefined, type: sort !== null ? i+'.@data-'+sort : undefined, filter: filter !== null ? i+'.@data-'+filter : undefined }; _fnColumnOptions( oSettings, i ); } } } ); } var features = oSettings.oFeatures; var loadedInit = function () { /* * Sorting * @todo For modularisation (1.11) this needs to do into a sort start up handler */ // If aaSorting is not defined, then we use the first indicator in asSorting // in case that has been altered, so the default sort reflects that option if ( oInit.aaSorting === undefined ) { var sorting = oSettings.aaSorting; for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) { sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0]; } } /* Do a first pass on the sorting classes (allows any size changes to be taken into * account, and also will apply sorting disabled classes if disabled */ _fnSortingClasses( oSettings ); if ( features.bSort ) { _fnCallbackReg( oSettings, 'aoDrawCallback', function () { if ( oSettings.bSorted ) { var aSort = _fnSortFlatten( oSettings ); var sortedColumns = {}; $.each( aSort, function (i, val) { sortedColumns[ val.src ] = val.dir; } ); _fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] ); _fnSortAria( oSettings ); } } ); } _fnCallbackReg( oSettings, 'aoDrawCallback', function () { if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) { _fnSortingClasses( oSettings ); } }, 'sc' ); /* * Final init * Cache the header, body and footer as required, creating them if needed */ // Work around for Webkit bug 83867 - store the caption-side before removing from doc var captions = $this.children('caption').each( function () { this._captionSide = $(this).css('caption-side'); } ); var thead = $this.children('thead'); if ( thead.length === 0 ) { thead = $('<thead/>').appendTo($this); } oSettings.nTHead = thead[0]; var tbody = $this.children('tbody'); if ( tbody.length === 0 ) { tbody = $('<tbody/>').appendTo($this); } oSettings.nTBody = tbody[0]; var tfoot = $this.children('tfoot'); if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) { // If we are a scrolling table, and no footer has been given, then we need to create // a tfoot element for the caption element to be appended to tfoot = $('<tfoot/>').appendTo($this); } if ( tfoot.length === 0 || tfoot.children().length === 0 ) { $this.addClass( oClasses.sNoFooter ); } else if ( tfoot.length > 0 ) { oSettings.nTFoot = tfoot[0]; _fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot ); } /* Check if there is data passing into the constructor */ if ( oInit.aaData ) { for ( i=0 ; i<oInit.aaData.length ; i++ ) { _fnAddData( oSettings, oInit.aaData[ i ] ); } } else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) { /* Grab the data from the page - only do this when deferred loading or no Ajax * source since there is no point in reading the DOM data if we are then going * to replace it with Ajax data */ _fnAddTr( oSettings, $(oSettings.nTBody).children('tr') ); } /* Copy the data index array */ oSettings.aiDisplay = oSettings.aiDisplayMaster.slice(); /* Initialisation complete - table can be drawn */ oSettings.bInitialised = true; /* Check if we need to initialise the table (it might not have been handed off to the * language processor) */ if ( bInitHandedOff === false ) { _fnInitialise( oSettings ); } }; /* Must be done after everything which can be overridden by the state saving! */ if ( oInit.bStateSave ) { features.bStateSave = true; _fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' ); _fnLoadState( oSettings, oInit, loadedInit ); } else { loadedInit(); } } ); _that = null; return this; }; /* * It is useful to have variables which are scoped locally so only the * DataTables functions can access them and they don't leak into global space. * At the same time these functions are often useful over multiple files in the * core and API, so we list, or at least document, all variables which are used * by DataTables as private variables here. This also ensures that there is no * clashing of variable names and that they can easily referenced for reuse. */ // Defined else where // _selector_run // _selector_opts // _selector_first // _selector_row_indexes var _ext; // DataTable.ext var _Api; // DataTable.Api var _api_register; // DataTable.Api.register var _api_registerPlural; // DataTable.Api.registerPlural var _re_dic = {}; var _re_new_lines = /[\r\n\u2028]/g; var _re_html = /<.*?>/g; // This is not strict ISO8601 - Date.parse() is quite lax, although // implementations differ between browsers. var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/; // Escape regular expression special characters var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' ); // http://en.wikipedia.org/wiki/Foreign_exchange_market // - \u20BD - Russian ruble. // - \u20a9 - South Korean Won // - \u20BA - Turkish Lira // - \u20B9 - Indian Rupee // - R - Brazil (R$) and South Africa // - fr - Swiss Franc // - kr - Swedish krona, Norwegian krone and Danish krone // - \u2009 is thin space and \u202F is narrow no-break space, both used in many // - Ƀ - Bitcoin // - Ξ - Ethereum // standards as thousands separators. var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfkɃΞ]/gi; var _empty = function ( d ) { return !d || d === true || d === '-' ? true : false; }; var _intVal = function ( s ) { var integer = parseInt( s, 10 ); return !isNaN(integer) && isFinite(s) ? integer : null; }; // Convert from a formatted number with characters other than `.` as the // decimal place, to a Javascript number var _numToDecimal = function ( num, decimalPoint ) { // Cache created regular expressions for speed as this function is called often if ( ! _re_dic[ decimalPoint ] ) { _re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' ); } return typeof num === 'string' && decimalPoint !== '.' ? num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) : num; }; var _isNumber = function ( d, decimalPoint, formatted ) { var strType = typeof d === 'string'; // If empty return immediately so there must be a number if it is a // formatted string (this stops the string "k", or "kr", etc being detected // as a formatted number for currency if ( _empty( d ) ) { return true; } if ( decimalPoint && strType ) { d = _numToDecimal( d, decimalPoint ); } if ( formatted && strType ) { d = d.replace( _re_formatted_numeric, '' ); } return !isNaN( parseFloat(d) ) && isFinite( d ); }; // A string without HTML in it can be considered to be HTML still var _isHtml = function ( d ) { return _empty( d ) || typeof d === 'string'; }; var _htmlNumeric = function ( d, decimalPoint, formatted ) { if ( _empty( d ) ) { return true; } var html = _isHtml( d ); return ! html ? null : _isNumber( _stripHtml( d ), decimalPoint, formatted ) ? true : null; }; var _pluck = function ( a, prop, prop2 ) { var out = []; var i=0, ien=a.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { for ( ; i<ien ; i++ ) { if ( a[i] && a[i][ prop ] ) { out.push( a[i][ prop ][ prop2 ] ); } } } else { for ( ; i<ien ; i++ ) { if ( a[i] ) { out.push( a[i][ prop ] ); } } } return out; }; // Basically the same as _pluck, but rather than looping over `a` we use `order` // as the indexes to pick from `a` var _pluck_order = function ( a, order, prop, prop2 ) { var out = []; var i=0, ien=order.length; // Could have the test in the loop for slightly smaller code, but speed // is essential here if ( prop2 !== undefined ) { for ( ; i<ien ; i++ ) { if ( a[ order[i] ][ prop ] ) { out.push( a[ order[i] ][ prop ][ prop2 ] ); } } } else { for ( ; i<ien ; i++ ) { out.push( a[ order[i] ][ prop ] ); } } return out; }; var _range = function ( len, start ) { var out = []; var end; if ( start === undefined ) { start = 0; end = len; } else { end = start; start = len; } for ( var i=start ; i<end ; i++ ) { out.push( i ); } return out; }; var _removeEmpty = function ( a ) { var out = []; for ( var i=0, ien=a.length ; i<ien ; i++ ) { if ( a[i] ) { // careful - will remove all falsy values! out.push( a[i] ); } } return out; }; var _stripHtml = function ( d ) { return d.replace( _re_html, '' ); }; /** * Determine if all values in the array are unique. This means we can short * cut the _unique method at the cost of a single loop. A sorted array is used * to easily check the values. * * @param {array} src Source array * @return {boolean} true if all unique, false otherwise * @ignore */ var _areAllUnique = function ( src ) { if ( src.length < 2 ) { return true; } var sorted = src.slice().sort(); var last = sorted[0]; for ( var i=1, ien=sorted.length ; i<ien ; i++ ) { if ( sorted[i] === last ) { return false; } last = sorted[i]; } return true; }; /** * Find the unique elements in a source array. * * @param {array} src Source array * @return {array} Array of unique items * @ignore */ var _unique = function ( src ) { if ( _areAllUnique( src ) ) { return src.slice(); } // A faster unique method is to use object keys to identify used values, // but this doesn't work with arrays or objects, which we must also // consider. See jsperf.com/compare-array-unique-versions/4 for more // information. var out = [], val, i, ien=src.length, j, k=0; again: for ( i=0 ; i<ien ; i++ ) { val = src[i]; for ( j=0 ; j<k ; j++ ) { if ( out[j] === val ) { continue again; } } out.push( val ); k++; } return out; }; /** * DataTables utility methods * * This namespace provides helper methods that DataTables uses internally to * create a DataTable, but which are not exclusively used only for DataTables. * These methods can be used by extension authors to save the duplication of * code. * * @namespace */ DataTable.util = { /** * Throttle the calls to a function. Arguments and context are maintained * for the throttled function. * * @param {function} fn Function to be called * @param {integer} freq Call frequency in mS * @return {function} Wrapped function */ throttle: function ( fn, freq ) { var frequency = freq !== undefined ? freq : 200, last, timer; return function () { var that = this, now = +new Date(), args = arguments; if ( last && now < last + frequency ) { clearTimeout( timer ); timer = setTimeout( function () { last = undefined; fn.apply( that, args ); }, frequency ); } else { last = now; fn.apply( that, args ); } }; }, /** * Escape a string such that it can be used in a regular expression * * @param {string} val string to escape * @returns {string} escaped string */ escapeRegex: function ( val ) { return val.replace( _re_escape_regex, '\\$1' ); } }; /** * Create a mapping object that allows camel case parameters to be looked up * for their Hungarian counterparts. The mapping is stored in a private * parameter called `_hungarianMap` which can be accessed on the source object. * @param {object} o * @memberof DataTable#oApi */ function _fnHungarianMap ( o ) { var hungarian = 'a aa ai ao as b fn i m o s ', match, newKey, map = {}; $.each( o, function (key, val) { match = key.match(/^([^A-Z]+?)([A-Z])/); if ( match && hungarian.indexOf(match[1]+' ') !== -1 ) { newKey = key.replace( match[0], match[2].toLowerCase() ); map[ newKey ] = key; if ( match[1] === 'o' ) { _fnHungarianMap( o[key] ); } } } ); o._hungarianMap = map; } /** * Convert from camel case parameters to Hungarian, based on a Hungarian map * created by _fnHungarianMap. * @param {object} src The model object which holds all parameters that can be * mapped. * @param {object} user The object to convert from camel case to Hungarian. * @param {boolean} force When set to `true`, properties which already have a * Hungarian value in the `user` object will be overwritten. Otherwise they * won't be. * @memberof DataTable#oApi */ function _fnCamelToHungarian ( src, user, force ) { if ( ! src._hungarianMap ) { _fnHungarianMap( src ); } var hungarianKey; $.each( user, function (key, val) { hungarianKey = src._hungarianMap[ key ]; if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) ) { // For objects, we need to buzz down into the object to copy parameters if ( hungarianKey.charAt(0) === 'o' ) { // Copy the camelCase options over to the hungarian if ( ! user[ hungarianKey ] ) { user[ hungarianKey ] = {}; } $.extend( true, user[hungarianKey], user[key] ); _fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force ); } else { user[hungarianKey] = user[ key ]; } } } ); } /** * Language compatibility - when certain options are given, and others aren't, we * need to duplicate the values over, in order to provide backwards compatibility * with older language files. * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnLanguageCompat( lang ) { // Note the use of the Hungarian notation for the parameters in this method as // this is called after the mapping of camelCase to Hungarian var defaults = DataTable.defaults.oLanguage; // Default mapping var defaultDecimal = defaults.sDecimal; if ( defaultDecimal ) { _addNumericSort( defaultDecimal ); } if ( lang ) { var zeroRecords = lang.sZeroRecords; // Backwards compatibility - if there is no sEmptyTable given, then use the same as // sZeroRecords - assuming that is given. if ( ! lang.sEmptyTable && zeroRecords && defaults.sEmptyTable === "No data available in table" ) { _fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' ); } // Likewise with loading records if ( ! lang.sLoadingRecords && zeroRecords && defaults.sLoadingRecords === "Loading..." ) { _fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' ); } // Old parameter name of the thousands separator mapped onto the new if ( lang.sInfoThousands ) { lang.sThousands = lang.sInfoThousands; } var decimal = lang.sDecimal; if ( decimal && defaultDecimal !== decimal ) { _addNumericSort( decimal ); } } } /** * Map one parameter onto another * @param {object} o Object to map * @param {*} knew The new parameter name * @param {*} old The old parameter name */ var _fnCompatMap = function ( o, knew, old ) { if ( o[ knew ] !== undefined ) { o[ old ] = o[ knew ]; } }; /** * Provide backwards compatibility for the main DT options. Note that the new * options are mapped onto the old parameters, so this is an external interface * change only. * @param {object} init Object to map */ function _fnCompatOpts ( init ) { _fnCompatMap( init, 'ordering', 'bSort' ); _fnCompatMap( init, 'orderMulti', 'bSortMulti' ); _fnCompatMap( init, 'orderClasses', 'bSortClasses' ); _fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' ); _fnCompatMap( init, 'order', 'aaSorting' ); _fnCompatMap( init, 'orderFixed', 'aaSortingFixed' ); _fnCompatMap( init, 'paging', 'bPaginate' ); _fnCompatMap( init, 'pagingType', 'sPaginationType' ); _fnCompatMap( init, 'pageLength', 'iDisplayLength' ); _fnCompatMap( init, 'searching', 'bFilter' ); // Boolean initialisation of x-scrolling if ( typeof init.sScrollX === 'boolean' ) { init.sScrollX = init.sScrollX ? '100%' : ''; } if ( typeof init.scrollX === 'boolean' ) { init.scrollX = init.scrollX ? '100%' : ''; } // Column search objects are in an array, so it needs to be converted // element by element var searchCols = init.aoSearchCols; if ( searchCols ) { for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) { if ( searchCols[i] ) { _fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] ); } } } } /** * Provide backwards compatibility for column options. Note that the new options * are mapped onto the old parameters, so this is an external interface change * only. * @param {object} init Object to map */ function _fnCompatCols ( init ) { _fnCompatMap( init, 'orderable', 'bSortable' ); _fnCompatMap( init, 'orderData', 'aDataSort' ); _fnCompatMap( init, 'orderSequence', 'asSorting' ); _fnCompatMap( init, 'orderDataType', 'sortDataType' ); // orderData can be given as an integer var dataSort = init.aDataSort; if ( typeof dataSort === 'number' && ! $.isArray( dataSort ) ) { init.aDataSort = [ dataSort ]; } } /** * Browser feature detection for capabilities, quirks * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnBrowserDetect( settings ) { // We don't need to do this every time DataTables is constructed, the values // calculated are specific to the browser and OS configuration which we // don't expect to change between initialisations if ( ! DataTable.__browser ) { var browser = {}; DataTable.__browser = browser; // Scrolling feature / quirks detection var n = $('<div/>') .css( { position: 'fixed', top: 0, left: $(window).scrollLeft()*-1, // allow for scrolling height: 1, width: 1, overflow: 'hidden' } ) .append( $('<div/>') .css( { position: 'absolute', top: 1, left: 1, width: 100, overflow: 'scroll' } ) .append( $('<div/>') .css( { width: '100%', height: 10 } ) ) ) .appendTo( 'body' ); var outer = n.children(); var inner = outer.children(); // Numbers below, in order, are: // inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth // // IE6 XP: 100 100 100 83 // IE7 Vista: 100 100 100 83 // IE 8+ Windows: 83 83 100 83 // Evergreen Windows: 83 83 100 83 // Evergreen Mac with scrollbars: 85 85 100 85 // Evergreen Mac without scrollbars: 100 100 100 100 // Get scrollbar width browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth; // IE6/7 will oversize a width 100% element inside a scrolling element, to // include the width of the scrollbar, while other browsers ensure the inner // element is contained without forcing scrolling browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100; // In rtl text layout, some browsers (most, but not all) will place the // scrollbar on the left, rather than the right. browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1; // IE8- don't provide height and width for getBoundingClientRect browser.bBounding = n[0].getBoundingClientRect().width ? true : false; n.remove(); } $.extend( settings.oBrowser, DataTable.__browser ); settings.oScroll.iBarWidth = DataTable.__browser.barWidth; } /** * Array.prototype reduce[Right] method, used for browsers which don't support * JS 1.6. Done this way to reduce code size, since we iterate either way * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnReduce ( that, fn, init, start, end, inc ) { var i = start, value, isSet = false; if ( init !== undefined ) { value = init; isSet = true; } while ( i !== end ) { if ( ! that.hasOwnProperty(i) ) { continue; } value = isSet ? fn( value, that[i], i, that ) : that[i]; isSet = true; i += inc; } return value; } /** * Add a column to the list used for the table with default values * @param {object} oSettings dataTables settings object * @param {node} nTh The th element for this column * @memberof DataTable#oApi */ function _fnAddColumn( oSettings, nTh ) { // Add column to aoColumns array var oDefaults = DataTable.defaults.column; var iCol = oSettings.aoColumns.length; var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, { "nTh": nTh ? nTh : document.createElement('th'), "sTitle": oDefaults.sTitle ? oDefaults.sTitle : nTh ? nTh.innerHTML : '', "aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol], "mData": oDefaults.mData ? oDefaults.mData : iCol, idx: iCol } ); oSettings.aoColumns.push( oCol ); // Add search object for column specific search. Note that the `searchCols[ iCol ]` // passed into extend can be undefined. This allows the user to give a default // with only some of the parameters defined, and also not give a default var searchCols = oSettings.aoPreSearchCols; searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] ); // Use the default column options function to initialise classes etc _fnColumnOptions( oSettings, iCol, $(nTh).data() ); } /** * Apply options for a column * @param {object} oSettings dataTables settings object * @param {int} iCol column index to consider * @param {object} oOptions object with sType, bVisible and bSearchable etc * @memberof DataTable#oApi */ function _fnColumnOptions( oSettings, iCol, oOptions ) { var oCol = oSettings.aoColumns[ iCol ]; var oClasses = oSettings.oClasses; var th = $(oCol.nTh); // Try to get width information from the DOM. We can't get it from CSS // as we'd need to parse the CSS stylesheet. `width` option can override if ( ! oCol.sWidthOrig ) { // Width attribute oCol.sWidthOrig = th.attr('width') || null; // Style attribute var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/); if ( t ) { oCol.sWidthOrig = t[1]; } } /* User specified column options */ if ( oOptions !== undefined && oOptions !== null ) { // Backwards compatibility _fnCompatCols( oOptions ); // Map camel case parameters to their Hungarian counterparts _fnCamelToHungarian( DataTable.defaults.column, oOptions, true ); /* Backwards compatibility for mDataProp */ if ( oOptions.mDataProp !== undefined && !oOptions.mData ) { oOptions.mData = oOptions.mDataProp; } if ( oOptions.sType ) { oCol._sManualType = oOptions.sType; } // `class` is a reserved word in Javascript, so we need to provide // the ability to use a valid name for the camel case input if ( oOptions.className && ! oOptions.sClass ) { oOptions.sClass = oOptions.className; } if ( oOptions.sClass ) { th.addClass( oOptions.sClass ); } $.extend( oCol, oOptions ); _fnMap( oCol, oOptions, "sWidth", "sWidthOrig" ); /* iDataSort to be applied (backwards compatibility), but aDataSort will take * priority if defined */ if ( oOptions.iDataSort !== undefined ) { oCol.aDataSort = [ oOptions.iDataSort ]; } _fnMap( oCol, oOptions, "aDataSort" ); } /* Cache the data get and set functions for speed */ var mDataSrc = oCol.mData; var mData = _fnGetObjectDataFn( mDataSrc ); var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null; var attrTest = function( src ) { return typeof src === 'string' && src.indexOf('@') !== -1; }; oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && ( attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter) ); oCol._setter = null; oCol.fnGetData = function (rowData, type, meta) { var innerData = mData( rowData, type, undefined, meta ); return mRender && type ? mRender( innerData, type, rowData, meta ) : innerData; }; oCol.fnSetData = function ( rowData, val, meta ) { return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta ); }; // Indicate if DataTables should read DOM data as an object or array // Used in _fnGetRowElements if ( typeof mDataSrc !== 'number' ) { oSettings._rowReadObject = true; } /* Feature sorting overrides column specific when off */ if ( !oSettings.oFeatures.bSort ) { oCol.bSortable = false; th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called } /* Check that the class assignment is correct for sorting */ var bAsc = $.inArray('asc', oCol.asSorting) !== -1; var bDesc = $.inArray('desc', oCol.asSorting) !== -1; if ( !oCol.bSortable || (!bAsc && !bDesc) ) { oCol.sSortingClass = oClasses.sSortableNone; oCol.sSortingClassJUI = ""; } else if ( bAsc && !bDesc ) { oCol.sSortingClass = oClasses.sSortableAsc; oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed; } else if ( !bAsc && bDesc ) { oCol.sSortingClass = oClasses.sSortableDesc; oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed; } else { oCol.sSortingClass = oClasses.sSortable; oCol.sSortingClassJUI = oClasses.sSortJUI; } } /** * Adjust the table column widths for new data. Note: you would probably want to * do a redraw after calling this function! * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnAdjustColumnSizing ( settings ) { /* Not interested in doing column width calculation if auto-width is disabled */ if ( settings.oFeatures.bAutoWidth !== false ) { var columns = settings.aoColumns; _fnCalculateColumnWidths( settings ); for ( var i=0 , iLen=columns.length ; i<iLen ; i++ ) { columns[i].nTh.style.width = columns[i].sWidth; } } var scroll = settings.oScroll; if ( scroll.sY !== '' || scroll.sX !== '') { _fnScrollDraw( settings ); } _fnCallbackFire( settings, null, 'column-sizing', [settings] ); } /** * Covert the index of a visible column to the index in the data array (take account * of hidden columns) * @param {object} oSettings dataTables settings object * @param {int} iMatch Visible column index to lookup * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnVisibleToColumnIndex( oSettings, iMatch ) { var aiVis = _fnGetColumns( oSettings, 'bVisible' ); return typeof aiVis[iMatch] === 'number' ? aiVis[iMatch] : null; } /** * Covert the index of an index in the data array and convert it to the visible * column index (take account of hidden columns) * @param {int} iMatch Column index to lookup * @param {object} oSettings dataTables settings object * @returns {int} i the data index * @memberof DataTable#oApi */ function _fnColumnIndexToVisible( oSettings, iMatch ) { var aiVis = _fnGetColumns( oSettings, 'bVisible' ); var iPos = $.inArray( iMatch, aiVis ); return iPos !== -1 ? iPos : null; } /** * Get the number of visible columns * @param {object} oSettings dataTables settings object * @returns {int} i the number of visible columns * @memberof DataTable#oApi */ function _fnVisbleColumns( oSettings ) { var vis = 0; // No reduce in IE8, use a loop for now $.each( oSettings.aoColumns, function ( i, col ) { if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) { vis++; } } ); return vis; } /** * Get an array of column indexes that match a given property * @param {object} oSettings dataTables settings object * @param {string} sParam Parameter in aoColumns to look for - typically * bVisible or bSearchable * @returns {array} Array of indexes with matched properties * @memberof DataTable#oApi */ function _fnGetColumns( oSettings, sParam ) { var a = []; $.map( oSettings.aoColumns, function(val, i) { if ( val[sParam] ) { a.push( i ); } } ); return a; } /** * Calculate the 'type' of a column * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnColumnTypes ( settings ) { var columns = settings.aoColumns; var data = settings.aoData; var types = DataTable.ext.type.detect; var i, ien, j, jen, k, ken; var col, cell, detectedType, cache; // For each column, spin over the for ( i=0, ien=columns.length ; i<ien ; i++ ) { col = columns[i]; cache = []; if ( ! col.sType && col._sManualType ) { col.sType = col._sManualType; } else if ( ! col.sType ) { for ( j=0, jen=types.length ; j<jen ; j++ ) { for ( k=0, ken=data.length ; k<ken ; k++ ) { // Use a cache array so we only need to get the type data // from the formatter once (when using multiple detectors) if ( cache[k] === undefined ) { cache[k] = _fnGetCellData( settings, k, i, 'type' ); } detectedType = types[j]( cache[k], settings ); // If null, then this type can't apply to this column, so // rather than testing all cells, break out. There is an // exception for the last type which is `html`. We need to // scan all rows since it is possible to mix string and HTML // types if ( ! detectedType && j !== types.length-1 ) { break; } // Only a single match is needed for html type since it is // bottom of the pile and very similar to string if ( detectedType === 'html' ) { break; } } // Type is valid for all data points in the column - use this // type if ( detectedType ) { col.sType = detectedType; break; } } // Fall back - if no type was detected, always use string if ( ! col.sType ) { col.sType = 'string'; } } } } /** * Take the column definitions and static columns arrays and calculate how * they relate to column indexes. The callback function will then apply the * definition found for a column to a suitable configuration object. * @param {object} oSettings dataTables settings object * @param {array} aoColDefs The aoColumnDefs array that is to be applied * @param {array} aoCols The aoColumns array that defines columns individually * @param {function} fn Callback function - takes two parameters, the calculated * column index and the definition for that column. * @memberof DataTable#oApi */ function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn ) { var i, iLen, j, jLen, k, kLen, def; var columns = oSettings.aoColumns; // Column definitions with aTargets if ( aoColDefs ) { /* Loop over the definitions array - loop in reverse so first instance has priority */ for ( i=aoColDefs.length-1 ; i>=0 ; i-- ) { def = aoColDefs[i]; /* Each definition can target multiple columns, as it is an array */ var aTargets = def.targets !== undefined ? def.targets : def.aTargets; if ( ! $.isArray( aTargets ) ) { aTargets = [ aTargets ]; } for ( j=0, jLen=aTargets.length ; j<jLen ; j++ ) { if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 ) { /* Add columns that we don't yet know about */ while( columns.length <= aTargets[j] ) { _fnAddColumn( oSettings ); } /* Integer, basic index */ fn( aTargets[j], def ); } else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 ) { /* Negative integer, right to left column counting */ fn( columns.length+aTargets[j], def ); } else if ( typeof aTargets[j] === 'string' ) { /* Class name matching on TH element */ for ( k=0, kLen=columns.length ; k<kLen ; k++ ) { if ( aTargets[j] == "_all" || $(columns[k].nTh).hasClass( aTargets[j] ) ) { fn( k, def ); } } } } } } // Statically defined columns array if ( aoCols ) { for ( i=0, iLen=aoCols.length ; i<iLen ; i++ ) { fn( i, aoCols[i] ); } } } /** * Add a data array to the table, creating DOM node etc. This is the parallel to * _fnGatherData, but for adding rows from a Javascript source, rather than a * DOM source. * @param {object} oSettings dataTables settings object * @param {array} aData data array to be added * @param {node} [nTr] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @returns {int} >=0 if successful (index of new aoData entry), -1 if failed * @memberof DataTable#oApi */ function _fnAddData ( oSettings, aDataIn, nTr, anTds ) { /* Create the object for storing information about this new row */ var iRow = oSettings.aoData.length; var oData = $.extend( true, {}, DataTable.models.oRow, { src: nTr ? 'dom' : 'data', idx: iRow } ); oData._aData = aDataIn; oSettings.aoData.push( oData ); /* Create the cells */ var nTd, sThisType; var columns = oSettings.aoColumns; // Invalidate the column types as the new data needs to be revalidated for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) { columns[i].sType = null; } /* Add to the display array */ oSettings.aiDisplayMaster.push( iRow ); var id = oSettings.rowIdFn( aDataIn ); if ( id !== undefined ) { oSettings.aIds[ id ] = oData; } /* Create the DOM information, or register it if already present */ if ( nTr || ! oSettings.oFeatures.bDeferRender ) { _fnCreateTr( oSettings, iRow, nTr, anTds ); } return iRow; } /** * Add one or more TR elements to the table. Generally we'd expect to * use this for reading data from a DOM sourced table, but it could be * used for an TR element. Note that if a TR is given, it is used (i.e. * it is not cloned). * @param {object} settings dataTables settings object * @param {array|node|jQuery} trs The TR element(s) to add to the table * @returns {array} Array of indexes for the added rows * @memberof DataTable#oApi */ function _fnAddTr( settings, trs ) { var row; // Allow an individual node to be passed in if ( ! (trs instanceof $) ) { trs = $(trs); } return trs.map( function (i, el) { row = _fnGetRowElements( settings, el ); return _fnAddData( settings, row.data, el, row.cells ); } ); } /** * Take a TR element and convert it to an index in aoData * @param {object} oSettings dataTables settings object * @param {node} n the TR element to find * @returns {int} index if the node is found, null if not * @memberof DataTable#oApi */ function _fnNodeToDataIndex( oSettings, n ) { return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null; } /** * Take a TD element and convert it into a column data index (not the visible index) * @param {object} oSettings dataTables settings object * @param {int} iRow The row number the TD/TH can be found in * @param {node} n The TD/TH element to find * @returns {int} index if the node is found, -1 if not * @memberof DataTable#oApi */ function _fnNodeToColumnIndex( oSettings, iRow, n ) { return $.inArray( n, oSettings.aoData[ iRow ].anCells ); } /** * Get the data for a given cell from the internal cache, taking into account data mapping * @param {object} settings dataTables settings object * @param {int} rowIdx aoData row id * @param {int} colIdx Column index * @param {string} type data get type ('display', 'type' 'filter' 'sort') * @returns {*} Cell data * @memberof DataTable#oApi */ function _fnGetCellData( settings, rowIdx, colIdx, type ) { var draw = settings.iDraw; var col = settings.aoColumns[colIdx]; var rowData = settings.aoData[rowIdx]._aData; var defaultContent = col.sDefaultContent; var cellData = col.fnGetData( rowData, type, { settings: settings, row: rowIdx, col: colIdx } ); if ( cellData === undefined ) { if ( settings.iDrawError != draw && defaultContent === null ) { _fnLog( settings, 0, "Requested unknown parameter "+ (typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+ " for row "+rowIdx+", column "+colIdx, 4 ); settings.iDrawError = draw; } return defaultContent; } // When the data source is null and a specific data type is requested (i.e. // not the original data), we can use default column data if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) { cellData = defaultContent; } else if ( typeof cellData === 'function' ) { // If the data source is a function, then we run it and use the return, // executing in the scope of the data object (for instances) return cellData.call( rowData ); } if ( cellData === null && type == 'display' ) { return ''; } return cellData; } /** * Set the value for a specific cell, into the internal data cache * @param {object} settings dataTables settings object * @param {int} rowIdx aoData row id * @param {int} colIdx Column index * @param {*} val Value to set * @memberof DataTable#oApi */ function _fnSetCellData( settings, rowIdx, colIdx, val ) { var col = settings.aoColumns[colIdx]; var rowData = settings.aoData[rowIdx]._aData; col.fnSetData( rowData, val, { settings: settings, row: rowIdx, col: colIdx } ); } // Private variable that is used to match action syntax in the data property object var __reArray = /\[.*?\]$/; var __reFn = /\(\)$/; /** * Split string on periods, taking into account escaped periods * @param {string} str String to split * @return {array} Split string */ function _fnSplitObjNotation( str ) { return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) { return s.replace(/\\\./g, '.'); } ); } /** * Return a function that can be used to get data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data get function * @memberof DataTable#oApi */ function _fnGetObjectDataFn( mSource ) { if ( $.isPlainObject( mSource ) ) { /* Build an object of get functions, and wrap them in a single call */ var o = {}; $.each( mSource, function (key, val) { if ( val ) { o[key] = _fnGetObjectDataFn( val ); } } ); return function (data, type, row, meta) { var t = o[type] || o._; return t !== undefined ? t(data, type, row, meta) : data; }; } else if ( mSource === null ) { /* Give an empty string for rendering / sorting etc */ return function (data) { // type, row and meta also passed, but not used return data; }; } else if ( typeof mSource === 'function' ) { return function (data, type, row, meta) { return mSource( data, type, row, meta ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) { /* If there is a . in the source string then the data source is in a * nested object so we loop over the data for each level to get the next * level down. On each loop we test for undefined, and if found immediately * return. This allows entire objects to be missing and sDefaultContent to * be used if defined, rather than throwing an error */ var fetchData = function (data, type, src) { var arrayNotation, funcNotation, out, innerSrc; if ( src !== "" ) { var a = _fnSplitObjNotation( src ); for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { // Check if we are dealing with special notation arrayNotation = a[i].match(__reArray); funcNotation = a[i].match(__reFn); if ( arrayNotation ) { // Array notation a[i] = a[i].replace(__reArray, ''); // Condition allows simply [] to be passed in if ( a[i] !== "" ) { data = data[ a[i] ]; } out = []; // Get the remainder of the nested object to get a.splice( 0, i+1 ); innerSrc = a.join('.'); // Traverse each entry in the array getting the properties requested if ( $.isArray( data ) ) { for ( var j=0, jLen=data.length ; j<jLen ; j++ ) { out.push( fetchData( data[j], type, innerSrc ) ); } } // If a string is given in between the array notation indicators, that // is used to join the strings together, otherwise an array is returned var join = arrayNotation[0].substring(1, arrayNotation[0].length-1); data = (join==="") ? out : out.join(join); // The inner call to fetchData has already traversed through the remainder // of the source requested, so we exit from the loop break; } else if ( funcNotation ) { // Function call a[i] = a[i].replace(__reFn, ''); data = data[ a[i] ](); continue; } if ( data === null || data[ a[i] ] === undefined ) { return undefined; } data = data[ a[i] ]; } } return data; }; return function (data, type) { // row and meta also passed, but not used return fetchData( data, type, mSource ); }; } else { /* Array or flat object mapping */ return function (data, type) { // row and meta also passed, but not used return data[mSource]; }; } } /** * Return a function that can be used to set data from a source object, taking * into account the ability to use nested objects as a source * @param {string|int|function} mSource The data source for the object * @returns {function} Data set function * @memberof DataTable#oApi */ function _fnSetObjectDataFn( mSource ) { if ( $.isPlainObject( mSource ) ) { /* Unlike get, only the underscore (global) option is used for for * setting data since we don't know the type here. This is why an object * option is not documented for `mData` (which is read/write), but it is * for `mRender` which is read only. */ return _fnSetObjectDataFn( mSource._ ); } else if ( mSource === null ) { /* Nothing to do when the data source is null */ return function () {}; } else if ( typeof mSource === 'function' ) { return function (data, val, meta) { mSource( data, 'set', val, meta ); }; } else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 || mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) ) { /* Like the get, we need to get data from a nested object */ var setData = function (data, val, src) { var a = _fnSplitObjNotation( src ), b; var aLast = a[a.length-1]; var arrayNotation, funcNotation, o, innerSrc; for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ ) { // Check if we are dealing with an array notation request arrayNotation = a[i].match(__reArray); funcNotation = a[i].match(__reFn); if ( arrayNotation ) { a[i] = a[i].replace(__reArray, ''); data[ a[i] ] = []; // Get the remainder of the nested object to set so we can recurse b = a.slice(); b.splice( 0, i+1 ); innerSrc = b.join('.'); // Traverse each entry in the array setting the properties requested if ( $.isArray( val ) ) { for ( var j=0, jLen=val.length ; j<jLen ; j++ ) { o = {}; setData( o, val[j], innerSrc ); data[ a[i] ].push( o ); } } else { // We've been asked to save data to an array, but it // isn't array data to be saved. Best that can be done // is to just save the value. data[ a[i] ] = val; } // The inner call to setData has already traversed through the remainder // of the source and has set the data, thus we can exit here return; } else if ( funcNotation ) { // Function call a[i] = a[i].replace(__reFn, ''); data = data[ a[i] ]( val ); } // If the nested object doesn't currently exist - since we are // trying to set the value - create it if ( data[ a[i] ] === null || data[ a[i] ] === undefined ) { data[ a[i] ] = {}; } data = data[ a[i] ]; } // Last item in the input - i.e, the actual set if ( aLast.match(__reFn ) ) { // Function call data = data[ aLast.replace(__reFn, '') ]( val ); } else { // If array notation is used, we just want to strip it and use the property name // and assign the value. If it isn't used, then we get the result we want anyway data[ aLast.replace(__reArray, '') ] = val; } }; return function (data, val) { // meta is also passed in, but not used return setData( data, val, mSource ); }; } else { /* Array or flat object mapping */ return function (data, val) { // meta is also passed in, but not used data[mSource] = val; }; } } /** * Return an array with the full table data * @param {object} oSettings dataTables settings object * @returns array {array} aData Master data array * @memberof DataTable#oApi */ function _fnGetDataMaster ( settings ) { return _pluck( settings.aoData, '_aData' ); } /** * Nuke the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnClearTable( settings ) { settings.aoData.length = 0; settings.aiDisplayMaster.length = 0; settings.aiDisplay.length = 0; settings.aIds = {}; } /** * Take an array of integers (index array) and remove a target integer (value - not * the key!) * @param {array} a Index array to target * @param {int} iTarget value to find * @memberof DataTable#oApi */ function _fnDeleteIndex( a, iTarget, splice ) { var iTargetIndex = -1; for ( var i=0, iLen=a.length ; i<iLen ; i++ ) { if ( a[i] == iTarget ) { iTargetIndex = i; } else if ( a[i] > iTarget ) { a[i]--; } } if ( iTargetIndex != -1 && splice === undefined ) { a.splice( iTargetIndex, 1 ); } } /** * Mark cached data as invalid such that a re-read of the data will occur when * the cached data is next requested. Also update from the data source object. * * @param {object} settings DataTables settings object * @param {int} rowIdx Row index to invalidate * @param {string} [src] Source to invalidate from: undefined, 'auto', 'dom' * or 'data' * @param {int} [colIdx] Column index to invalidate. If undefined the whole * row will be invalidated * @memberof DataTable#oApi * * @todo For the modularisation of v1.11 this will need to become a callback, so * the sort and filter methods can subscribe to it. That will required * initialisation options for sorting, which is why it is not already baked in */ function _fnInvalidate( settings, rowIdx, src, colIdx ) { var row = settings.aoData[ rowIdx ]; var i, ien; var cellWrite = function ( cell, col ) { // This is very frustrating, but in IE if you just write directly // to innerHTML, and elements that are overwritten are GC'ed, // even if there is a reference to them elsewhere while ( cell.childNodes.length ) { cell.removeChild( cell.firstChild ); } cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' ); }; // Are we reading last data from DOM or the data object? if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) { // Read the data from the DOM row._aData = _fnGetRowElements( settings, row, colIdx, colIdx === undefined ? undefined : row._aData ) .data; } else { // Reading from data object, update the DOM var cells = row.anCells; if ( cells ) { if ( colIdx !== undefined ) { cellWrite( cells[colIdx], colIdx ); } else { for ( i=0, ien=cells.length ; i<ien ; i++ ) { cellWrite( cells[i], i ); } } } } // For both row and cell invalidation, the cached data for sorting and // filtering is nulled out row._aSortData = null; row._aFilterData = null; // Invalidate the type for a specific column (if given) or all columns since // the data might have changed var cols = settings.aoColumns; if ( colIdx !== undefined ) { cols[ colIdx ].sType = null; } else { for ( i=0, ien=cols.length ; i<ien ; i++ ) { cols[i].sType = null; } // Update DataTables special `DT_*` attributes for the row _fnRowAttributes( settings, row ); } } /** * Build a data source object from an HTML row, reading the contents of the * cells that are in the row. * * @param {object} settings DataTables settings object * @param {node|object} TR element from which to read data or existing row * object from which to re-read the data from the cells * @param {int} [colIdx] Optional column index * @param {array|object} [d] Data source object. If `colIdx` is given then this * parameter should also be given and will be used to write the data into. * Only the column in question will be written * @returns {object} Object with two parameters: `data` the data read, in * document order, and `cells` and array of nodes (they can be useful to the * caller, so rather than needing a second traversal to get them, just return * them from here). * @memberof DataTable#oApi */ function _fnGetRowElements( settings, row, colIdx, d ) { var tds = [], td = row.firstChild, name, col, o, i=0, contents, columns = settings.aoColumns, objectRead = settings._rowReadObject; // Allow the data object to be passed in, or construct d = d !== undefined ? d : objectRead ? {} : []; var attr = function ( str, td ) { if ( typeof str === 'string' ) { var idx = str.indexOf('@'); if ( idx !== -1 ) { var attr = str.substring( idx+1 ); var setter = _fnSetObjectDataFn( str ); setter( d, td.getAttribute( attr ) ); } } }; // Read data from a cell and store into the data object var cellProcess = function ( cell ) { if ( colIdx === undefined || colIdx === i ) { col = columns[i]; contents = $.trim(cell.innerHTML); if ( col && col._bAttrSrc ) { var setter = _fnSetObjectDataFn( col.mData._ ); setter( d, contents ); attr( col.mData.sort, cell ); attr( col.mData.type, cell ); attr( col.mData.filter, cell ); } else { // Depending on the `data` option for the columns the data can // be read to either an object or an array. if ( objectRead ) { if ( ! col._setter ) { // Cache the setter function col._setter = _fnSetObjectDataFn( col.mData ); } col._setter( d, contents ); } else { d[i] = contents; } } } i++; }; if ( td ) { // `tr` element was passed in while ( td ) { name = td.nodeName.toUpperCase(); if ( name == "TD" || name == "TH" ) { cellProcess( td ); tds.push( td ); } td = td.nextSibling; } } else { // Existing row object passed in tds = row.anCells; for ( var j=0, jen=tds.length ; j<jen ; j++ ) { cellProcess( tds[j] ); } } // Read the ID from the DOM if present var rowNode = row.firstChild ? row : row.nTr; if ( rowNode ) { var id = rowNode.getAttribute( 'id' ); if ( id ) { _fnSetObjectDataFn( settings.rowId )( d, id ); } } return { data: d, cells: tds }; } /** * Create a new TR element (and it's TD children) for a row * @param {object} oSettings dataTables settings object * @param {int} iRow Row to consider * @param {node} [nTrIn] TR element to add to the table - optional. If not given, * DataTables will create a row automatically * @param {array} [anTds] Array of TD|TH elements for the row - must be given * if nTr is. * @memberof DataTable#oApi */ function _fnCreateTr ( oSettings, iRow, nTrIn, anTds ) { var row = oSettings.aoData[iRow], rowData = row._aData, cells = [], nTr, nTd, oCol, i, iLen, create; if ( row.nTr === null ) { nTr = nTrIn || document.createElement('tr'); row.nTr = nTr; row.anCells = cells; /* Use a private property on the node to allow reserve mapping from the node * to the aoData array for fast look up */ nTr._DT_RowIndex = iRow; /* Special parameters can be given by the data source to be used on the row */ _fnRowAttributes( oSettings, row ); /* Process each column */ for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ ) { oCol = oSettings.aoColumns[i]; create = nTrIn ? false : true; nTd = create ? document.createElement( oCol.sCellType ) : anTds[i]; nTd._DT_CellIndex = { row: iRow, column: i }; cells.push( nTd ); // Need to create the HTML if new, or if a rendering function is defined if ( create || ((!nTrIn || oCol.mRender || oCol.mData !== i) && (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display') )) { nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' ); } /* Add user defined class */ if ( oCol.sClass ) { nTd.className += ' '+oCol.sClass; } // Visibility - add or remove as required if ( oCol.bVisible && ! nTrIn ) { nTr.appendChild( nTd ); } else if ( ! oCol.bVisible && nTrIn ) { nTd.parentNode.removeChild( nTd ); } if ( oCol.fnCreatedCell ) { oCol.fnCreatedCell.call( oSettings.oInstance, nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i ); } } _fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow, cells] ); } // Remove once webkit bug 131819 and Chromium bug 365619 have been resolved // and deployed row.nTr.setAttribute( 'role', 'row' ); } /** * Add attributes to a row based on the special `DT_*` parameters in a data * source object. * @param {object} settings DataTables settings object * @param {object} DataTables row object for the row to be modified * @memberof DataTable#oApi */ function _fnRowAttributes( settings, row ) { var tr = row.nTr; var data = row._aData; if ( tr ) { var id = settings.rowIdFn( data ); if ( id ) { tr.id = id; } if ( data.DT_RowClass ) { // Remove any classes added by DT_RowClass before var a = data.DT_RowClass.split(' '); row.__rowc = row.__rowc ? _unique( row.__rowc.concat( a ) ) : a; $(tr) .removeClass( row.__rowc.join(' ') ) .addClass( data.DT_RowClass ); } if ( data.DT_RowAttr ) { $(tr).attr( data.DT_RowAttr ); } if ( data.DT_RowData ) { $(tr).data( data.DT_RowData ); } } } /** * Create the HTML header for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnBuildHead( oSettings ) { var i, ien, cell, row, column; var thead = oSettings.nTHead; var tfoot = oSettings.nTFoot; var createHeader = $('th, td', thead).length === 0; var classes = oSettings.oClasses; var columns = oSettings.aoColumns; if ( createHeader ) { row = $('<tr/>').appendTo( thead ); } for ( i=0, ien=columns.length ; i<ien ; i++ ) { column = columns[i]; cell = $( column.nTh ).addClass( column.sClass ); if ( createHeader ) { cell.appendTo( row ); } // 1.11 move into sorting if ( oSettings.oFeatures.bSort ) { cell.addClass( column.sSortingClass ); if ( column.bSortable !== false ) { cell .attr( 'tabindex', oSettings.iTabIndex ) .attr( 'aria-controls', oSettings.sTableId ); _fnSortAttachListener( oSettings, column.nTh, i ); } } if ( column.sTitle != cell[0].innerHTML ) { cell.html( column.sTitle ); } _fnRenderer( oSettings, 'header' )( oSettings, cell, column, classes ); } if ( createHeader ) { _fnDetectHeader( oSettings.aoHeader, thead ); } /* ARIA role for the rows */ $(thead).find('>tr').attr('role', 'row'); /* Deal with the footer - add classes if required */ $(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH ); $(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH ); // Cache the footer cells. Note that we only take the cells from the first // row in the footer. If there is more than one row the user wants to // interact with, they need to use the table().foot() method. Note also this // allows cells to be used for multiple columns using colspan if ( tfoot !== null ) { var cells = oSettings.aoFooter[0]; for ( i=0, ien=cells.length ; i<ien ; i++ ) { column = columns[i]; column.nTf = cells[i].cell; if ( column.sClass ) { $(column.nTf).addClass( column.sClass ); } } } } /** * Draw the header (or footer) element based on the column visibility states. The * methodology here is to use the layout array from _fnDetectHeader, modified for * the instantaneous column visibility, to construct the new layout. The grid is * traversed over cell at a time in a rows x columns grid fashion, although each * cell insert can cover multiple elements in the grid - which is tracks using the * aApplied array. Cell inserts in the grid will only occur where there isn't * already a cell in that position. * @param {object} oSettings dataTables settings object * @param array {objects} aoSource Layout array from _fnDetectHeader * @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc, * @memberof DataTable#oApi */ function _fnDrawHead( oSettings, aoSource, bIncludeHidden ) { var i, iLen, j, jLen, k, kLen, n, nLocalTr; var aoLocal = []; var aApplied = []; var iColumns = oSettings.aoColumns.length; var iRowspan, iColspan; if ( ! aoSource ) { return; } if ( bIncludeHidden === undefined ) { bIncludeHidden = false; } /* Make a copy of the master layout array, but without the visible columns in it */ for ( i=0, iLen=aoSource.length ; i<iLen ; i++ ) { aoLocal[i] = aoSource[i].slice(); aoLocal[i].nTr = aoSource[i].nTr; /* Remove any columns which are currently hidden */ for ( j=iColumns-1 ; j>=0 ; j-- ) { if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden ) { aoLocal[i].splice( j, 1 ); } } /* Prep the applied array - it needs an element for each row */ aApplied.push( [] ); } for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ ) { nLocalTr = aoLocal[i].nTr; /* All cells are going to be replaced, so empty out the row */ if ( nLocalTr ) { while( (n = nLocalTr.firstChild) ) { nLocalTr.removeChild( n ); } } for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ ) { iRowspan = 1; iColspan = 1; /* Check to see if there is already a cell (row/colspan) covering our target * insert point. If there is, then there is nothing to do. */ if ( aApplied[i][j] === undefined ) { nLocalTr.appendChild( aoLocal[i][j].cell ); aApplied[i][j] = 1; /* Expand the cell to cover as many rows as needed */ while ( aoLocal[i+iRowspan] !== undefined && aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell ) { aApplied[i+iRowspan][j] = 1; iRowspan++; } /* Expand the cell to cover as many columns as needed */ while ( aoLocal[i][j+iColspan] !== undefined && aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell ) { /* Must update the applied array over the rows for the columns */ for ( k=0 ; k<iRowspan ; k++ ) { aApplied[i+k][j+iColspan] = 1; } iColspan++; } /* Do the actual expansion in the DOM */ $(aoLocal[i][j].cell) .attr('rowspan', iRowspan) .attr('colspan', iColspan); } } } } /** * Insert the required TR nodes into the table for display * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnDraw( oSettings ) { /* Provide a pre-callback function which can be used to cancel the draw is false is returned */ var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] ); if ( $.inArray( false, aPreDraw ) !== -1 ) { _fnProcessingDisplay( oSettings, false ); return; } var i, iLen, n; var anRows = []; var iRowCount = 0; var asStripeClasses = oSettings.asStripeClasses; var iStripes = asStripeClasses.length; var iOpenRows = oSettings.aoOpenRows.length; var oLang = oSettings.oLanguage; var iInitDisplayStart = oSettings.iInitDisplayStart; var bServerSide = _fnDataSource( oSettings ) == 'ssp'; var aiDisplay = oSettings.aiDisplay; oSettings.bDrawing = true; /* Check and see if we have an initial draw position from state saving */ if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 ) { oSettings._iDisplayStart = bServerSide ? iInitDisplayStart : iInitDisplayStart >= oSettings.fnRecordsDisplay() ? 0 : iInitDisplayStart; oSettings.iInitDisplayStart = -1; } var iDisplayStart = oSettings._iDisplayStart; var iDisplayEnd = oSettings.fnDisplayEnd(); /* Server-side processing draw intercept */ if ( oSettings.bDeferLoading ) { oSettings.bDeferLoading = false; oSettings.iDraw++; _fnProcessingDisplay( oSettings, false ); } else if ( !bServerSide ) { oSettings.iDraw++; } else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) ) { return; } if ( aiDisplay.length !== 0 ) { var iStart = bServerSide ? 0 : iDisplayStart; var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd; for ( var j=iStart ; j<iEnd ; j++ ) { var iDataIndex = aiDisplay[j]; var aoData = oSettings.aoData[ iDataIndex ]; if ( aoData.nTr === null ) { _fnCreateTr( oSettings, iDataIndex ); } var nRow = aoData.nTr; /* Remove the old striping classes and then add the new one */ if ( iStripes !== 0 ) { var sStripe = asStripeClasses[ iRowCount % iStripes ]; if ( aoData._sRowStripe != sStripe ) { $(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe ); aoData._sRowStripe = sStripe; } } // Row callback functions - might want to manipulate the row // iRowCount and j are not currently documented. Are they at all // useful? _fnCallbackFire( oSettings, 'aoRowCallback', null, [nRow, aoData._aData, iRowCount, j, iDataIndex] ); anRows.push( nRow ); iRowCount++; } } else { /* Table is empty - create a row with an empty message in it */ var sZero = oLang.sZeroRecords; if ( oSettings.iDraw == 1 && _fnDataSource( oSettings ) == 'ajax' ) { sZero = oLang.sLoadingRecords; } else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 ) { sZero = oLang.sEmptyTable; } anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } ) .append( $('<td />', { 'valign': 'top', 'colSpan': _fnVisbleColumns( oSettings ), 'class': oSettings.oClasses.sRowEmpty } ).html( sZero ) )[0]; } /* Header and footer callbacks */ _fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0], _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); _fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0], _fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] ); var body = $(oSettings.nTBody); body.children().detach(); body.append( $(anRows) ); /* Call all required callback functions for the end of a draw */ _fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] ); /* Draw is complete, sorting and filtering must be as well */ oSettings.bSorted = false; oSettings.bFiltered = false; oSettings.bDrawing = false; } /** * Redraw the table - taking account of the various features which are enabled * @param {object} oSettings dataTables settings object * @param {boolean} [holdPosition] Keep the current paging position. By default * the paging is reset to the first page * @memberof DataTable#oApi */ function _fnReDraw( settings, holdPosition ) { var features = settings.oFeatures, sort = features.bSort, filter = features.bFilter; if ( sort ) { _fnSort( settings ); } if ( filter ) { _fnFilterComplete( settings, settings.oPreviousSearch ); } else { // No filtering, so we want to just use the display master settings.aiDisplay = settings.aiDisplayMaster.slice(); } if ( holdPosition !== true ) { settings._iDisplayStart = 0; } // Let any modules know about the draw hold position state (used by // scrolling internally) settings._drawHold = holdPosition; _fnDraw( settings ); settings._drawHold = false; } /** * Add the options to the page HTML for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnAddOptionsHtml ( oSettings ) { var classes = oSettings.oClasses; var table = $(oSettings.nTable); var holding = $('<div/>').insertBefore( table ); // Holding element for speed var features = oSettings.oFeatures; // All DataTables are wrapped in a div var insert = $('<div/>', { id: oSettings.sTableId+'_wrapper', 'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter) } ); oSettings.nHolding = holding[0]; oSettings.nTableWrapper = insert[0]; oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling; /* Loop over the user set positioning and place the elements as needed */ var aDom = oSettings.sDom.split(''); var featureNode, cOption, nNewNode, cNext, sAttr, j; for ( var i=0 ; i<aDom.length ; i++ ) { featureNode = null; cOption = aDom[i]; if ( cOption == '<' ) { /* New container div */ nNewNode = $('<div/>')[0]; /* Check to see if we should append an id and/or a class name to the container */ cNext = aDom[i+1]; if ( cNext == "'" || cNext == '"' ) { sAttr = ""; j = 2; while ( aDom[i+j] != cNext ) { sAttr += aDom[i+j]; j++; } /* Replace jQuery UI constants @todo depreciated */ if ( sAttr == "H" ) { sAttr = classes.sJUIHeader; } else if ( sAttr == "F" ) { sAttr = classes.sJUIFooter; } /* The attribute can be in the format of "#id.class", "#id" or "class" This logic * breaks the string into parts and applies them as needed */ if ( sAttr.indexOf('.') != -1 ) { var aSplit = sAttr.split('.'); nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1); nNewNode.className = aSplit[1]; } else if ( sAttr.charAt(0) == "#" ) { nNewNode.id = sAttr.substr(1, sAttr.length-1); } else { nNewNode.className = sAttr; } i += j; /* Move along the position array */ } insert.append( nNewNode ); insert = $(nNewNode); } else if ( cOption == '>' ) { /* End container div */ insert = insert.parent(); } // @todo Move options into their own plugins? else if ( cOption == 'l' && features.bPaginate && features.bLengthChange ) { /* Length */ featureNode = _fnFeatureHtmlLength( oSettings ); } else if ( cOption == 'f' && features.bFilter ) { /* Filter */ featureNode = _fnFeatureHtmlFilter( oSettings ); } else if ( cOption == 'r' && features.bProcessing ) { /* pRocessing */ featureNode = _fnFeatureHtmlProcessing( oSettings ); } else if ( cOption == 't' ) { /* Table */ featureNode = _fnFeatureHtmlTable( oSettings ); } else if ( cOption == 'i' && features.bInfo ) { /* Info */ featureNode = _fnFeatureHtmlInfo( oSettings ); } else if ( cOption == 'p' && features.bPaginate ) { /* Pagination */ featureNode = _fnFeatureHtmlPaginate( oSettings ); } else if ( DataTable.ext.feature.length !== 0 ) { /* Plug-in features */ var aoFeatures = DataTable.ext.feature; for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ ) { if ( cOption == aoFeatures[k].cFeature ) { featureNode = aoFeatures[k].fnInit( oSettings ); break; } } } /* Add to the 2D features array */ if ( featureNode ) { var aanFeatures = oSettings.aanFeatures; if ( ! aanFeatures[cOption] ) { aanFeatures[cOption] = []; } aanFeatures[cOption].push( featureNode ); insert.append( featureNode ); } } /* Built our DOM structure - replace the holding div with what we want */ holding.replaceWith( insert ); oSettings.nHolding = null; } /** * Use the DOM source to create up an array of header cells. The idea here is to * create a layout grid (array) of rows x columns, which contains a reference * to the cell that that point in the grid (regardless of col/rowspan), such that * any column / row could be removed and the new grid constructed * @param array {object} aLayout Array to store the calculated layout in * @param {node} nThead The header/footer element for the table * @memberof DataTable#oApi */ function _fnDetectHeader ( aLayout, nThead ) { var nTrs = $(nThead).children('tr'); var nTr, nCell; var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan; var bUnique; var fnShiftCol = function ( a, i, j ) { var k = a[i]; while ( k[j] ) { j++; } return j; }; aLayout.splice( 0, aLayout.length ); /* We know how many rows there are in the layout - so prep it */ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { aLayout.push( [] ); } /* Calculate a layout array */ for ( i=0, iLen=nTrs.length ; i<iLen ; i++ ) { nTr = nTrs[i]; iColumn = 0; /* For every cell in the row... */ nCell = nTr.firstChild; while ( nCell ) { if ( nCell.nodeName.toUpperCase() == "TD" || nCell.nodeName.toUpperCase() == "TH" ) { /* Get the col and rowspan attributes from the DOM and sanitise them */ iColspan = nCell.getAttribute('colspan') * 1; iRowspan = nCell.getAttribute('rowspan') * 1; iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan; iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan; /* There might be colspan cells already in this row, so shift our target * accordingly */ iColShifted = fnShiftCol( aLayout, i, iColumn ); /* Cache calculation for unique columns */ bUnique = iColspan === 1 ? true : false; /* If there is col / rowspan, copy the information into the layout grid */ for ( l=0 ; l<iColspan ; l++ ) { for ( k=0 ; k<iRowspan ; k++ ) { aLayout[i+k][iColShifted+l] = { "cell": nCell, "unique": bUnique }; aLayout[i+k].nTr = nTr; } } } nCell = nCell.nextSibling; } } } /** * Get an array of unique th elements, one for each column * @param {object} oSettings dataTables settings object * @param {node} nHeader automatically detect the layout from this node - optional * @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional * @returns array {node} aReturn list of unique th's * @memberof DataTable#oApi */ function _fnGetUniqueThs ( oSettings, nHeader, aLayout ) { var aReturn = []; if ( !aLayout ) { aLayout = oSettings.aoHeader; if ( nHeader ) { aLayout = []; _fnDetectHeader( aLayout, nHeader ); } } for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ ) { for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ ) { if ( aLayout[i][j].unique && (!aReturn[j] || !oSettings.bSortCellsTop) ) { aReturn[j] = aLayout[i][j].cell; } } } return aReturn; } /** * Create an Ajax call based on the table's settings, taking into account that * parameters can have multiple forms, and backwards compatibility. * * @param {object} oSettings dataTables settings object * @param {array} data Data to send to the server, required by * DataTables - may be augmented by developer callbacks * @param {function} fn Callback function to run when data is obtained */ function _fnBuildAjax( oSettings, data, fn ) { // Compatibility with 1.9-, allow fnServerData and event to manipulate _fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] ); // Convert to object based for 1.10+ if using the old array scheme which can // come from server-side processing or serverParams if ( data && $.isArray(data) ) { var tmp = {}; var rbracket = /(.*?)\[\]$/; $.each( data, function (key, val) { var match = val.name.match(rbracket); if ( match ) { // Support for arrays var name = match[0]; if ( ! tmp[ name ] ) { tmp[ name ] = []; } tmp[ name ].push( val.value ); } else { tmp[val.name] = val.value; } } ); data = tmp; } var ajaxData; var ajax = oSettings.ajax; var instance = oSettings.oInstance; var callback = function ( json ) { _fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] ); fn( json ); }; if ( $.isPlainObject( ajax ) && ajax.data ) { ajaxData = ajax.data; var newData = typeof ajaxData === 'function' ? ajaxData( data, oSettings ) : // fn can manipulate data or return ajaxData; // an object object or array to merge // If the function returned something, use that alone data = typeof ajaxData === 'function' && newData ? newData : $.extend( true, data, newData ); // Remove the data property as we've resolved it already and don't want // jQuery to do it again (it is restored at the end of the function) delete ajax.data; } var baseAjax = { "data": data, "success": function (json) { var error = json.error || json.sError; if ( error ) { _fnLog( oSettings, 0, error ); } oSettings.json = json; callback( json ); }, "dataType": "json", "cache": false, "type": oSettings.sServerMethod, "error": function (xhr, error, thrown) { var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] ); if ( $.inArray( true, ret ) === -1 ) { if ( error == "parsererror" ) { _fnLog( oSettings, 0, 'Invalid JSON response', 1 ); } else if ( xhr.readyState === 4 ) { _fnLog( oSettings, 0, 'Ajax error', 7 ); } } _fnProcessingDisplay( oSettings, false ); } }; // Store the data submitted for the API oSettings.oAjaxData = data; // Allow plug-ins and external processes to modify the data _fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] ); if ( oSettings.fnServerData ) { // DataTables 1.9- compatibility oSettings.fnServerData.call( instance, oSettings.sAjaxSource, $.map( data, function (val, key) { // Need to convert back to 1.9 trad format return { name: key, value: val }; } ), callback, oSettings ); } else if ( oSettings.sAjaxSource || typeof ajax === 'string' ) { // DataTables 1.9- compatibility oSettings.jqXHR = $.ajax( $.extend( baseAjax, { url: ajax || oSettings.sAjaxSource } ) ); } else if ( typeof ajax === 'function' ) { // Is a function - let the caller define what needs to be done oSettings.jqXHR = ajax.call( instance, data, callback, oSettings ); } else { // Object to extend the base settings oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) ); // Restore for next time around ajax.data = ajaxData; } } /** * Update the table using an Ajax call * @param {object} settings dataTables settings object * @returns {boolean} Block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxUpdate( settings ) { if ( settings.bAjaxDataGet ) { settings.iDraw++; _fnProcessingDisplay( settings, true ); _fnBuildAjax( settings, _fnAjaxParameters( settings ), function(json) { _fnAjaxUpdateDraw( settings, json ); } ); return false; } return true; } /** * Build up the parameters in an object needed for a server-side processing * request. Note that this is basically done twice, is different ways - a modern * method which is used by default in DataTables 1.10 which uses objects and * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if * the sAjaxSource option is used in the initialisation, or the legacyAjax * option is set. * @param {object} oSettings dataTables settings object * @returns {bool} block the table drawing or not * @memberof DataTable#oApi */ function _fnAjaxParameters( settings ) { var columns = settings.aoColumns, columnCount = columns.length, features = settings.oFeatures, preSearch = settings.oPreviousSearch, preColSearch = settings.aoPreSearchCols, i, data = [], dataProp, column, columnSearch, sort = _fnSortFlatten( settings ), displayStart = settings._iDisplayStart, displayLength = features.bPaginate !== false ? settings._iDisplayLength : -1; var param = function ( name, value ) { data.push( { 'name': name, 'value': value } ); }; // DataTables 1.9- compatible method param( 'sEcho', settings.iDraw ); param( 'iColumns', columnCount ); param( 'sColumns', _pluck( columns, 'sName' ).join(',') ); param( 'iDisplayStart', displayStart ); param( 'iDisplayLength', displayLength ); // DataTables 1.10+ method var d = { draw: settings.iDraw, columns: [], order: [], start: displayStart, length: displayLength, search: { value: preSearch.sSearch, regex: preSearch.bRegex } }; for ( i=0 ; i<columnCount ; i++ ) { column = columns[i]; columnSearch = preColSearch[i]; dataProp = typeof column.mData=="function" ? 'function' : column.mData ; d.columns.push( { data: dataProp, name: column.sName, searchable: column.bSearchable, orderable: column.bSortable, search: { value: columnSearch.sSearch, regex: columnSearch.bRegex } } ); param( "mDataProp_"+i, dataProp ); if ( features.bFilter ) { param( 'sSearch_'+i, columnSearch.sSearch ); param( 'bRegex_'+i, columnSearch.bRegex ); param( 'bSearchable_'+i, column.bSearchable ); } if ( features.bSort ) { param( 'bSortable_'+i, column.bSortable ); } } if ( features.bFilter ) { param( 'sSearch', preSearch.sSearch ); param( 'bRegex', preSearch.bRegex ); } if ( features.bSort ) { $.each( sort, function ( i, val ) { d.order.push( { column: val.col, dir: val.dir } ); param( 'iSortCol_'+i, val.col ); param( 'sSortDir_'+i, val.dir ); } ); param( 'iSortingCols', sort.length ); } // If the legacy.ajax parameter is null, then we automatically decide which // form to use, based on sAjaxSource var legacy = DataTable.ext.legacy.ajax; if ( legacy === null ) { return settings.sAjaxSource ? data : d; } // Otherwise, if legacy has been specified then we use that to decide on the // form return legacy ? data : d; } /** * Data the data from the server (nuking the old) and redraw the table * @param {object} oSettings dataTables settings object * @param {object} json json data return from the server. * @param {string} json.sEcho Tracking flag for DataTables to match requests * @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering * @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering * @param {array} json.aaData The data to display on this page * @param {string} [json.sColumns] Column ordering (sName, comma separated) * @memberof DataTable#oApi */ function _fnAjaxUpdateDraw ( settings, json ) { // v1.10 uses camelCase variables, while 1.9 uses Hungarian notation. // Support both var compat = function ( old, modern ) { return json[old] !== undefined ? json[old] : json[modern]; }; var data = _fnAjaxDataSrc( settings, json ); var draw = compat( 'sEcho', 'draw' ); var recordsTotal = compat( 'iTotalRecords', 'recordsTotal' ); var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' ); if ( draw !== undefined ) { // Protect against out of sequence returns if ( draw*1 < settings.iDraw ) { return; } settings.iDraw = draw * 1; } _fnClearTable( settings ); settings._iRecordsTotal = parseInt(recordsTotal, 10); settings._iRecordsDisplay = parseInt(recordsFiltered, 10); for ( var i=0, ien=data.length ; i<ien ; i++ ) { _fnAddData( settings, data[i] ); } settings.aiDisplay = settings.aiDisplayMaster.slice(); settings.bAjaxDataGet = false; _fnDraw( settings ); if ( ! settings._bInitComplete ) { _fnInitComplete( settings, json ); } settings.bAjaxDataGet = true; _fnProcessingDisplay( settings, false ); } /** * Get the data from the JSON data source to use for drawing a table. Using * `_fnGetObjectDataFn` allows the data to be sourced from a property of the * source object, or from a processing function. * @param {object} oSettings dataTables settings object * @param {object} json Data source object / array from the server * @return {array} Array of data to use */ function _fnAjaxDataSrc ( oSettings, json ) { var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ? oSettings.ajax.dataSrc : oSettings.sAjaxDataProp; // Compatibility with 1.9-. // Compatibility with 1.9-. In order to read from aaData, check if the // default has been changed, if not, check for aaData if ( dataSrc === 'data' ) { return json.aaData || json[dataSrc]; } return dataSrc !== "" ? _fnGetObjectDataFn( dataSrc )( json ) : json; } /** * Generate the node required for filtering text * @returns {node} Filter control element * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnFeatureHtmlFilter ( settings ) { var classes = settings.oClasses; var tableId = settings.sTableId; var language = settings.oLanguage; var previousSearch = settings.oPreviousSearch; var features = settings.aanFeatures; var input = '<input type="search" class="'+classes.sFilterInput+'"/>'; var str = language.sSearch; str = str.match(/_INPUT_/) ? str.replace('_INPUT_', input) : str+input; var filter = $('<div/>', { 'id': ! features.f ? tableId+'_filter' : null, 'class': classes.sFilter } ) .append( $('<label/>' ).append( str ) ); var searchFn = function() { /* Update all other filter input elements for the new display */ var n = features.f; var val = !this.value ? "" : this.value; // mental IE8 fix :-( /* Now do the filter */ if ( val != previousSearch.sSearch ) { _fnFilterComplete( settings, { "sSearch": val, "bRegex": previousSearch.bRegex, "bSmart": previousSearch.bSmart , "bCaseInsensitive": previousSearch.bCaseInsensitive } ); // Need to redraw, without resorting settings._iDisplayStart = 0; _fnDraw( settings ); } }; var searchDelay = settings.searchDelay !== null ? settings.searchDelay : _fnDataSource( settings ) === 'ssp' ? 400 : 0; var jqFilter = $('input', filter) .val( previousSearch.sSearch ) .attr( 'placeholder', language.sSearchPlaceholder ) .on( 'keyup.DT search.DT input.DT paste.DT cut.DT', searchDelay ? _fnThrottle( searchFn, searchDelay ) : searchFn ) .on( 'mouseup', function(e) { // Edge fix! Edge 17 does not trigger anything other than mouse events when clicking // on the clear icon (Edge bug 17584515). This is safe in other browsers as `searchFn` // checks the value to see if it has changed. In other browsers it won't have. setTimeout( function () { searchFn.call(jqFilter[0]); }, 10); } ) .on( 'keypress.DT', function(e) { /* Prevent form submission */ if ( e.keyCode == 13 ) { return false; } } ) .attr('aria-controls', tableId); // Update the input elements whenever the table is filtered $(settings.nTable).on( 'search.dt.DT', function ( ev, s ) { if ( settings === s ) { // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame... try { if ( jqFilter[0] !== document.activeElement ) { jqFilter.val( previousSearch.sSearch ); } } catch ( e ) {} } } ); return filter[0]; } /** * Filter the table using both the global filter and column based filtering * @param {object} oSettings dataTables settings object * @param {object} oSearch search information * @param {int} [iForce] force a research of the master array (1) or not (undefined or 0) * @memberof DataTable#oApi */ function _fnFilterComplete ( oSettings, oInput, iForce ) { var oPrevSearch = oSettings.oPreviousSearch; var aoPrevSearch = oSettings.aoPreSearchCols; var fnSaveFilter = function ( oFilter ) { /* Save the filtering values */ oPrevSearch.sSearch = oFilter.sSearch; oPrevSearch.bRegex = oFilter.bRegex; oPrevSearch.bSmart = oFilter.bSmart; oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive; }; var fnRegex = function ( o ) { // Backwards compatibility with the bEscapeRegex option return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex; }; // Resolve any column types that are unknown due to addition or invalidation // @todo As per sort - can this be moved into an event handler? _fnColumnTypes( oSettings ); /* In server-side processing all filtering is done by the server, so no point hanging around here */ if ( _fnDataSource( oSettings ) != 'ssp' ) { /* Global filter */ _fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive ); fnSaveFilter( oInput ); /* Now do the individual column filter */ for ( var i=0 ; i<aoPrevSearch.length ; i++ ) { _fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]), aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive ); } /* Custom filtering */ _fnFilterCustom( oSettings ); } else { fnSaveFilter( oInput ); } /* Tell the draw function we have been filtering */ oSettings.bFiltered = true; _fnCallbackFire( oSettings, null, 'search', [oSettings] ); } /** * Apply custom filtering functions * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnFilterCustom( settings ) { var filters = DataTable.ext.search; var displayRows = settings.aiDisplay; var row, rowIdx; for ( var i=0, ien=filters.length ; i<ien ; i++ ) { var rows = []; // Loop over each row and see if it should be included for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) { rowIdx = displayRows[ j ]; row = settings.aoData[ rowIdx ]; if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) { rows.push( rowIdx ); } } // So the array reference doesn't break set the results into the // existing array displayRows.length = 0; $.merge( displayRows, rows ); } } /** * Filter the table on a per-column basis * @param {object} oSettings dataTables settings object * @param {string} sInput string to filter on * @param {int} iColumn column to filter * @param {bool} bRegex treat search string as a regular expression or not * @param {bool} bSmart use smart filtering or not * @param {bool} bCaseInsensitive Do case insenstive matching or not * @memberof DataTable#oApi */ function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive ) { if ( searchStr === '' ) { return; } var data; var out = []; var display = settings.aiDisplay; var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive ); for ( var i=0 ; i<display.length ; i++ ) { data = settings.aoData[ display[i] ]._aFilterData[ colIdx ]; if ( rpSearch.test( data ) ) { out.push( display[i] ); } } settings.aiDisplay = out; } /** * Filter the data table based on user input and draw the table * @param {object} settings dataTables settings object * @param {string} input string to filter on * @param {int} force optional - force a research of the master array (1) or not (undefined or 0) * @param {bool} regex treat as a regular expression or not * @param {bool} smart perform smart filtering or not * @param {bool} caseInsensitive Do case insenstive matching or not * @memberof DataTable#oApi */ function _fnFilter( settings, input, force, regex, smart, caseInsensitive ) { var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive ); var prevSearch = settings.oPreviousSearch.sSearch; var displayMaster = settings.aiDisplayMaster; var display, invalidated, i; var filtered = []; // Need to take account of custom filtering functions - always filter if ( DataTable.ext.search.length !== 0 ) { force = true; } // Check if any of the rows were invalidated invalidated = _fnFilterData( settings ); // If the input is blank - we just want the full data set if ( input.length <= 0 ) { settings.aiDisplay = displayMaster.slice(); } else { // New search - start from the master array if ( invalidated || force || regex || prevSearch.length > input.length || input.indexOf(prevSearch) !== 0 || settings.bSorted // On resort, the display master needs to be // re-filtered since indexes will have changed ) { settings.aiDisplay = displayMaster.slice(); } // Search the display array display = settings.aiDisplay; for ( i=0 ; i<display.length ; i++ ) { if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) { filtered.push( display[i] ); } } settings.aiDisplay = filtered; } } /** * Build a regular expression object suitable for searching a table * @param {string} sSearch string to search for * @param {bool} bRegex treat as a regular expression or not * @param {bool} bSmart perform smart filtering or not * @param {bool} bCaseInsensitive Do case insensitive matching or not * @returns {RegExp} constructed object * @memberof DataTable#oApi */ function _fnFilterCreateSearch( search, regex, smart, caseInsensitive ) { search = regex ? search : _fnEscapeRegex( search ); if ( smart ) { /* For smart filtering we want to allow the search to work regardless of * word order. We also want double quoted text to be preserved, so word * order is important - a la google. So this is what we want to * generate: * * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$ */ var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) { if ( word.charAt(0) === '"' ) { var m = word.match( /^"(.*)"$/ ); word = m ? m[1] : word; } return word.replace('"', ''); } ); search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$'; } return new RegExp( search, caseInsensitive ? 'i' : '' ); } /** * Escape a string such that it can be used in a regular expression * @param {string} sVal string to escape * @returns {string} escaped string * @memberof DataTable#oApi */ var _fnEscapeRegex = DataTable.util.escapeRegex; var __filter_div = $('<div>')[0]; var __filter_div_textContent = __filter_div.textContent !== undefined; // Update the filtering data for each row if needed (by invalidation or first run) function _fnFilterData ( settings ) { var columns = settings.aoColumns; var column; var i, j, ien, jen, filterData, cellData, row; var fomatters = DataTable.ext.type.search; var wasInvalidated = false; for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { row = settings.aoData[i]; if ( ! row._aFilterData ) { filterData = []; for ( j=0, jen=columns.length ; j<jen ; j++ ) { column = columns[j]; if ( column.bSearchable ) { cellData = _fnGetCellData( settings, i, j, 'filter' ); if ( fomatters[ column.sType ] ) { cellData = fomatters[ column.sType ]( cellData ); } // Search in DataTables 1.10 is string based. In 1.11 this // should be altered to also allow strict type checking. if ( cellData === null ) { cellData = ''; } if ( typeof cellData !== 'string' && cellData.toString ) { cellData = cellData.toString(); } } else { cellData = ''; } // If it looks like there is an HTML entity in the string, // attempt to decode it so sorting works as expected. Note that // we could use a single line of jQuery to do this, but the DOM // method used here is much faster http://jsperf.com/html-decode if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) { __filter_div.innerHTML = cellData; cellData = __filter_div_textContent ? __filter_div.textContent : __filter_div.innerText; } if ( cellData.replace ) { cellData = cellData.replace(/[\r\n\u2028]/g, ''); } filterData.push( cellData ); } row._aFilterData = filterData; row._sFilterRow = filterData.join(' '); wasInvalidated = true; } } return wasInvalidated; } /** * Convert from the internal Hungarian notation to camelCase for external * interaction * @param {object} obj Object to convert * @returns {object} Inverted object * @memberof DataTable#oApi */ function _fnSearchToCamel ( obj ) { return { search: obj.sSearch, smart: obj.bSmart, regex: obj.bRegex, caseInsensitive: obj.bCaseInsensitive }; } /** * Convert from camelCase notation to the internal Hungarian. We could use the * Hungarian convert function here, but this is cleaner * @param {object} obj Object to convert * @returns {object} Inverted object * @memberof DataTable#oApi */ function _fnSearchToHung ( obj ) { return { sSearch: obj.search, bSmart: obj.smart, bRegex: obj.regex, bCaseInsensitive: obj.caseInsensitive }; } /** * Generate the node required for the info display * @param {object} oSettings dataTables settings object * @returns {node} Information element * @memberof DataTable#oApi */ function _fnFeatureHtmlInfo ( settings ) { var tid = settings.sTableId, nodes = settings.aanFeatures.i, n = $('<div/>', { 'class': settings.oClasses.sInfo, 'id': ! nodes ? tid+'_info' : null } ); if ( ! nodes ) { // Update display on each draw settings.aoDrawCallback.push( { "fn": _fnUpdateInfo, "sName": "information" } ); n .attr( 'role', 'status' ) .attr( 'aria-live', 'polite' ); // Table is described by our info div $(settings.nTable).attr( 'aria-describedby', tid+'_info' ); } return n[0]; } /** * Update the information elements in the display * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnUpdateInfo ( settings ) { /* Show information about the table */ var nodes = settings.aanFeatures.i; if ( nodes.length === 0 ) { return; } var lang = settings.oLanguage, start = settings._iDisplayStart+1, end = settings.fnDisplayEnd(), max = settings.fnRecordsTotal(), total = settings.fnRecordsDisplay(), out = total ? lang.sInfo : lang.sInfoEmpty; if ( total !== max ) { /* Record set after filtering */ out += ' ' + lang.sInfoFiltered; } // Convert the macros out += lang.sInfoPostFix; out = _fnInfoMacros( settings, out ); var callback = lang.fnInfoCallback; if ( callback !== null ) { out = callback.call( settings.oInstance, settings, start, end, max, total, out ); } $(nodes).html( out ); } function _fnInfoMacros ( settings, str ) { // When infinite scrolling, we are always starting at 1. _iDisplayStart is used only // internally var formatter = settings.fnFormatNumber, start = settings._iDisplayStart+1, len = settings._iDisplayLength, vis = settings.fnRecordsDisplay(), all = len === -1; return str. replace(/_START_/g, formatter.call( settings, start ) ). replace(/_END_/g, formatter.call( settings, settings.fnDisplayEnd() ) ). replace(/_MAX_/g, formatter.call( settings, settings.fnRecordsTotal() ) ). replace(/_TOTAL_/g, formatter.call( settings, vis ) ). replace(/_PAGE_/g, formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ). replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) ); } /** * Draw the table for the first time, adding all required features * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnInitialise ( settings ) { var i, iLen, iAjaxStart=settings.iInitDisplayStart; var columns = settings.aoColumns, column; var features = settings.oFeatures; var deferLoading = settings.bDeferLoading; // value modified by the draw /* Ensure that the table data is fully initialised */ if ( ! settings.bInitialised ) { setTimeout( function(){ _fnInitialise( settings ); }, 200 ); return; } /* Show the display HTML options */ _fnAddOptionsHtml( settings ); /* Build and draw the header / footer for the table */ _fnBuildHead( settings ); _fnDrawHead( settings, settings.aoHeader ); _fnDrawHead( settings, settings.aoFooter ); /* Okay to show that something is going on now */ _fnProcessingDisplay( settings, true ); /* Calculate sizes for columns */ if ( features.bAutoWidth ) { _fnCalculateColumnWidths( settings ); } for ( i=0, iLen=columns.length ; i<iLen ; i++ ) { column = columns[i]; if ( column.sWidth ) { column.nTh.style.width = _fnStringToCss( column.sWidth ); } } _fnCallbackFire( settings, null, 'preInit', [settings] ); // If there is default sorting required - let's do it. The sort function // will do the drawing for us. Otherwise we draw the table regardless of the // Ajax source - this allows the table to look initialised for Ajax sourcing // data (show 'loading' message possibly) _fnReDraw( settings ); // Server-side processing init complete is done by _fnAjaxUpdateDraw var dataSrc = _fnDataSource( settings ); if ( dataSrc != 'ssp' || deferLoading ) { // if there is an ajax source load the data if ( dataSrc == 'ajax' ) { _fnBuildAjax( settings, [], function(json) { var aData = _fnAjaxDataSrc( settings, json ); // Got the data - add it to the table for ( i=0 ; i<aData.length ; i++ ) { _fnAddData( settings, aData[i] ); } // Reset the init display for cookie saving. We've already done // a filter, and therefore cleared it before. So we need to make // it appear 'fresh' settings.iInitDisplayStart = iAjaxStart; _fnReDraw( settings ); _fnProcessingDisplay( settings, false ); _fnInitComplete( settings, json ); }, settings ); } else { _fnProcessingDisplay( settings, false ); _fnInitComplete( settings ); } } } /** * Draw the table for the first time, adding all required features * @param {object} oSettings dataTables settings object * @param {object} [json] JSON from the server that completed the table, if using Ajax source * with client-side processing (optional) * @memberof DataTable#oApi */ function _fnInitComplete ( settings, json ) { settings._bInitComplete = true; // When data was added after the initialisation (data or Ajax) we need to // calculate the column sizing if ( json || settings.oInit.aaData ) { _fnAdjustColumnSizing( settings ); } _fnCallbackFire( settings, null, 'plugin-init', [settings, json] ); _fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] ); } function _fnLengthChange ( settings, val ) { var len = parseInt( val, 10 ); settings._iDisplayLength = len; _fnLengthOverflow( settings ); // Fire length change event _fnCallbackFire( settings, null, 'length', [settings, len] ); } /** * Generate the node required for user display length changing * @param {object} settings dataTables settings object * @returns {node} Display length feature node * @memberof DataTable#oApi */ function _fnFeatureHtmlLength ( settings ) { var classes = settings.oClasses, tableId = settings.sTableId, menu = settings.aLengthMenu, d2 = $.isArray( menu[0] ), lengths = d2 ? menu[0] : menu, language = d2 ? menu[1] : menu; var select = $('<select/>', { 'name': tableId+'_length', 'aria-controls': tableId, 'class': classes.sLengthSelect } ); for ( var i=0, ien=lengths.length ; i<ien ; i++ ) { select[0][ i ] = new Option( typeof language[i] === 'number' ? settings.fnFormatNumber( language[i] ) : language[i], lengths[i] ); } var div = $('<div><label/></div>').addClass( classes.sLength ); if ( ! settings.aanFeatures.l ) { div[0].id = tableId+'_length'; } div.children().append( settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML ) ); // Can't use `select` variable as user might provide their own and the // reference is broken by the use of outerHTML $('select', div) .val( settings._iDisplayLength ) .on( 'change.DT', function(e) { _fnLengthChange( settings, $(this).val() ); _fnDraw( settings ); } ); // Update node value whenever anything changes the table's length $(settings.nTable).on( 'length.dt.DT', function (e, s, len) { if ( settings === s ) { $('select', div).val( len ); } } ); return div[0]; } /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Note that most of the paging logic is done in * DataTable.ext.pager */ /** * Generate the node required for default pagination * @param {object} oSettings dataTables settings object * @returns {node} Pagination feature node * @memberof DataTable#oApi */ function _fnFeatureHtmlPaginate ( settings ) { var type = settings.sPaginationType, plugin = DataTable.ext.pager[ type ], modern = typeof plugin === 'function', redraw = function( settings ) { _fnDraw( settings ); }, node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0], features = settings.aanFeatures; if ( ! modern ) { plugin.fnInit( settings, node, redraw ); } /* Add a draw callback for the pagination on first instance, to update the paging display */ if ( ! features.p ) { node.id = settings.sTableId+'_paginate'; settings.aoDrawCallback.push( { "fn": function( settings ) { if ( modern ) { var start = settings._iDisplayStart, len = settings._iDisplayLength, visRecords = settings.fnRecordsDisplay(), all = len === -1, page = all ? 0 : Math.ceil( start / len ), pages = all ? 1 : Math.ceil( visRecords / len ), buttons = plugin(page, pages), i, ien; for ( i=0, ien=features.p.length ; i<ien ; i++ ) { _fnRenderer( settings, 'pageButton' )( settings, features.p[i], i, buttons, page, pages ); } } else { plugin.fnUpdate( settings, redraw ); } }, "sName": "pagination" } ); } return node; } /** * Alter the display settings to change the page * @param {object} settings DataTables settings object * @param {string|int} action Paging action to take: "first", "previous", * "next" or "last" or page number to jump to (integer) * @param [bool] redraw Automatically draw the update or not * @returns {bool} true page has changed, false - no change * @memberof DataTable#oApi */ function _fnPageChange ( settings, action, redraw ) { var start = settings._iDisplayStart, len = settings._iDisplayLength, records = settings.fnRecordsDisplay(); if ( records === 0 || len === -1 ) { start = 0; } else if ( typeof action === "number" ) { start = action * len; if ( start > records ) { start = 0; } } else if ( action == "first" ) { start = 0; } else if ( action == "previous" ) { start = len >= 0 ? start - len : 0; if ( start < 0 ) { start = 0; } } else if ( action == "next" ) { if ( start + len < records ) { start += len; } } else if ( action == "last" ) { start = Math.floor( (records-1) / len) * len; } else { _fnLog( settings, 0, "Unknown paging action: "+action, 5 ); } var changed = settings._iDisplayStart !== start; settings._iDisplayStart = start; if ( changed ) { _fnCallbackFire( settings, null, 'page', [settings] ); if ( redraw ) { _fnDraw( settings ); } } return changed; } /** * Generate the node required for the processing node * @param {object} settings dataTables settings object * @returns {node} Processing element * @memberof DataTable#oApi */ function _fnFeatureHtmlProcessing ( settings ) { return $('<div/>', { 'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null, 'class': settings.oClasses.sProcessing } ) .html( settings.oLanguage.sProcessing ) .insertBefore( settings.nTable )[0]; } /** * Display or hide the processing indicator * @param {object} settings dataTables settings object * @param {bool} show Show the processing indicator (true) or not (false) * @memberof DataTable#oApi */ function _fnProcessingDisplay ( settings, show ) { if ( settings.oFeatures.bProcessing ) { $(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' ); } _fnCallbackFire( settings, null, 'processing', [settings, show] ); } /** * Add any control elements for the table - specifically scrolling * @param {object} settings dataTables settings object * @returns {node} Node to add to the DOM * @memberof DataTable#oApi */ function _fnFeatureHtmlTable ( settings ) { var table = $(settings.nTable); // Add the ARIA grid role to the table table.attr( 'role', 'grid' ); // Scrolling from here on in var scroll = settings.oScroll; if ( scroll.sX === '' && scroll.sY === '' ) { return settings.nTable; } var scrollX = scroll.sX; var scrollY = scroll.sY; var classes = settings.oClasses; var caption = table.children('caption'); var captionSide = caption.length ? caption[0]._captionSide : null; var headerClone = $( table[0].cloneNode(false) ); var footerClone = $( table[0].cloneNode(false) ); var footer = table.children('tfoot'); var _div = '<div/>'; var size = function ( s ) { return !s ? null : _fnStringToCss( s ); }; if ( ! footer.length ) { footer = null; } /* * The HTML structure that we want to generate in this function is: * div - scroller * div - scroll head * div - scroll head inner * table - scroll head table * thead - thead * div - scroll body * table - table (master table) * thead - thead clone for sizing * tbody - tbody * div - scroll foot * div - scroll foot inner * table - scroll foot table * tfoot - tfoot */ var scroller = $( _div, { 'class': classes.sScrollWrapper } ) .append( $(_div, { 'class': classes.sScrollHead } ) .css( { overflow: 'hidden', position: 'relative', border: 0, width: scrollX ? size(scrollX) : '100%' } ) .append( $(_div, { 'class': classes.sScrollHeadInner } ) .css( { 'box-sizing': 'content-box', width: scroll.sXInner || '100%' } ) .append( headerClone .removeAttr('id') .css( 'margin-left', 0 ) .append( captionSide === 'top' ? caption : null ) .append( table.children('thead') ) ) ) ) .append( $(_div, { 'class': classes.sScrollBody } ) .css( { position: 'relative', overflow: 'auto', width: size( scrollX ) } ) .append( table ) ); if ( footer ) { scroller.append( $(_div, { 'class': classes.sScrollFoot } ) .css( { overflow: 'hidden', border: 0, width: scrollX ? size(scrollX) : '100%' } ) .append( $(_div, { 'class': classes.sScrollFootInner } ) .append( footerClone .removeAttr('id') .css( 'margin-left', 0 ) .append( captionSide === 'bottom' ? caption : null ) .append( table.children('tfoot') ) ) ) ); } var children = scroller.children(); var scrollHead = children[0]; var scrollBody = children[1]; var scrollFoot = footer ? children[2] : null; // When the body is scrolled, then we also want to scroll the headers if ( scrollX ) { $(scrollBody).on( 'scroll.DT', function (e) { var scrollLeft = this.scrollLeft; scrollHead.scrollLeft = scrollLeft; if ( footer ) { scrollFoot.scrollLeft = scrollLeft; } } ); } $(scrollBody).css('max-height', scrollY); if (! scroll.bCollapse) { $(scrollBody).css('height', scrollY); } settings.nScrollHead = scrollHead; settings.nScrollBody = scrollBody; settings.nScrollFoot = scrollFoot; // On redraw - align columns settings.aoDrawCallback.push( { "fn": _fnScrollDraw, "sName": "scrolling" } ); return scroller[0]; } /** * Update the header, footer and body tables for resizing - i.e. column * alignment. * * Welcome to the most horrible function DataTables. The process that this * function follows is basically: * 1. Re-create the table inside the scrolling div * 2. Take live measurements from the DOM * 3. Apply the measurements to align the columns * 4. Clean up * * @param {object} settings dataTables settings object * @memberof DataTable#oApi */ function _fnScrollDraw ( settings ) { // Given that this is such a monster function, a lot of variables are use // to try and keep the minimised size as small as possible var scroll = settings.oScroll, scrollX = scroll.sX, scrollXInner = scroll.sXInner, scrollY = scroll.sY, barWidth = scroll.iBarWidth, divHeader = $(settings.nScrollHead), divHeaderStyle = divHeader[0].style, divHeaderInner = divHeader.children('div'), divHeaderInnerStyle = divHeaderInner[0].style, divHeaderTable = divHeaderInner.children('table'), divBodyEl = settings.nScrollBody, divBody = $(divBodyEl), divBodyStyle = divBodyEl.style, divFooter = $(settings.nScrollFoot), divFooterInner = divFooter.children('div'), divFooterTable = divFooterInner.children('table'), header = $(settings.nTHead), table = $(settings.nTable), tableEl = table[0], tableStyle = tableEl.style, footer = settings.nTFoot ? $(settings.nTFoot) : null, browser = settings.oBrowser, ie67 = browser.bScrollOversize, dtHeaderCells = _pluck( settings.aoColumns, 'nTh' ), headerTrgEls, footerTrgEls, headerSrcEls, footerSrcEls, headerCopy, footerCopy, headerWidths=[], footerWidths=[], headerContent=[], footerContent=[], idx, correction, sanityWidth, zeroOut = function(nSizer) { var style = nSizer.style; style.paddingTop = "0"; style.paddingBottom = "0"; style.borderTopWidth = "0"; style.borderBottomWidth = "0"; style.height = 0; }; // If the scrollbar visibility has changed from the last draw, we need to // adjust the column sizes as the table width will have changed to account // for the scrollbar var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight; if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) { settings.scrollBarVis = scrollBarVis; _fnAdjustColumnSizing( settings ); return; // adjust column sizing will call this function again } else { settings.scrollBarVis = scrollBarVis; } /* * 1. Re-create the table inside the scrolling div */ // Remove the old minimised thead and tfoot elements in the inner table table.children('thead, tfoot').remove(); if ( footer ) { footerCopy = footer.clone().prependTo( table ); footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized footerSrcEls = footerCopy.find('tr'); } // Clone the current header and footer elements and then place it into the inner table headerCopy = header.clone().prependTo( table ); headerTrgEls = header.find('tr'); // original header is in its own table headerSrcEls = headerCopy.find('tr'); headerCopy.find('th, td').removeAttr('tabindex'); /* * 2. Take live measurements from the DOM - do not alter the DOM itself! */ // Remove old sizing and apply the calculated column widths // Get the unique column headers in the newly created (cloned) header. We want to apply the // calculated sizes to this header if ( ! scrollX ) { divBodyStyle.width = '100%'; divHeader[0].style.width = '100%'; } $.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) { idx = _fnVisibleToColumnIndex( settings, i ); el.style.width = settings.aoColumns[idx].sWidth; } ); if ( footer ) { _fnApplyToChildren( function(n) { n.style.width = ""; }, footerSrcEls ); } // Size the table as a whole sanityWidth = table.outerWidth(); if ( scrollX === "" ) { // No x scrolling tableStyle.width = "100%"; // IE7 will make the width of the table when 100% include the scrollbar // - which is shouldn't. When there is a scrollbar we need to take this // into account. if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") ) { tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth); } // Recalculate the sanity width sanityWidth = table.outerWidth(); } else if ( scrollXInner !== "" ) { // legacy x scroll inner has been given - use it tableStyle.width = _fnStringToCss(scrollXInner); // Recalculate the sanity width sanityWidth = table.outerWidth(); } // Hidden header should have zero height, so remove padding and borders. Then // set the width based on the real headers // Apply all styles in one pass _fnApplyToChildren( zeroOut, headerSrcEls ); // Read all widths in next pass _fnApplyToChildren( function(nSizer) { headerContent.push( nSizer.innerHTML ); headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); }, headerSrcEls ); // Apply all widths in final pass _fnApplyToChildren( function(nToSize, i) { // Only apply widths to the DataTables detected header cells - this // prevents complex headers from having contradictory sizes applied if ( $.inArray( nToSize, dtHeaderCells ) !== -1 ) { nToSize.style.width = headerWidths[i]; } }, headerTrgEls ); $(headerSrcEls).height(0); /* Same again with the footer if we have one */ if ( footer ) { _fnApplyToChildren( zeroOut, footerSrcEls ); _fnApplyToChildren( function(nSizer) { footerContent.push( nSizer.innerHTML ); footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) ); }, footerSrcEls ); _fnApplyToChildren( function(nToSize, i) { nToSize.style.width = footerWidths[i]; }, footerTrgEls ); $(footerSrcEls).height(0); } /* * 3. Apply the measurements */ // "Hide" the header and footer that we used for the sizing. We need to keep // the content of the cell so that the width applied to the header and body // both match, but we want to hide it completely. We want to also fix their // width to what they currently are _fnApplyToChildren( function(nSizer, i) { nSizer.innerHTML = '<div class="dataTables_sizing">'+headerContent[i]+'</div>'; nSizer.childNodes[0].style.height = "0"; nSizer.childNodes[0].style.overflow = "hidden"; nSizer.style.width = headerWidths[i]; }, headerSrcEls ); if ( footer ) { _fnApplyToChildren( function(nSizer, i) { nSizer.innerHTML = '<div class="dataTables_sizing">'+footerContent[i]+'</div>'; nSizer.childNodes[0].style.height = "0"; nSizer.childNodes[0].style.overflow = "hidden"; nSizer.style.width = footerWidths[i]; }, footerSrcEls ); } // Sanity check that the table is of a sensible width. If not then we are going to get // misalignment - try to prevent this by not allowing the table to shrink below its min width if ( table.outerWidth() < sanityWidth ) { // The min width depends upon if we have a vertical scrollbar visible or not */ correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")) ? sanityWidth+barWidth : sanityWidth; // IE6/7 are a law unto themselves... if ( ie67 && (divBodyEl.scrollHeight > divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll") ) { tableStyle.width = _fnStringToCss( correction-barWidth ); } // And give the user a warning that we've stopped the table getting too small if ( scrollX === "" || scrollXInner !== "" ) { _fnLog( settings, 1, 'Possible column misalignment', 6 ); } } else { correction = '100%'; } // Apply to the container elements divBodyStyle.width = _fnStringToCss( correction ); divHeaderStyle.width = _fnStringToCss( correction ); if ( footer ) { settings.nScrollFoot.style.width = _fnStringToCss( correction ); } /* * 4. Clean up */ if ( ! scrollY ) { /* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting * the scrollbar height from the visible display, rather than adding it on. We need to * set the height in order to sort this. Don't want to do it in any other browsers. */ if ( ie67 ) { divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth ); } } /* Finally set the width's of the header and footer tables */ var iOuterWidth = table.outerWidth(); divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth ); divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth ); // Figure out if there are scrollbar present - if so then we need a the header and footer to // provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar) var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll"; var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' ); divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px"; if ( footer ) { divFooterTable[0].style.width = _fnStringToCss( iOuterWidth ); divFooterInner[0].style.width = _fnStringToCss( iOuterWidth ); divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px"; } // Correct DOM ordering for colgroup - comes before the thead table.children('colgroup').insertBefore( table.children('thead') ); /* Adjust the position of the header in case we loose the y-scrollbar */ divBody.trigger('scroll'); // If sorting or filtering has occurred, jump the scrolling back to the top // only if we aren't holding the position if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) { divBodyEl.scrollTop = 0; } } /** * Apply a given function to the display child nodes of an element array (typically * TD children of TR rows * @param {function} fn Method to apply to the objects * @param array {nodes} an1 List of elements to look through for display children * @param array {nodes} an2 Another list (identical structure to the first) - optional * @memberof DataTable#oApi */ function _fnApplyToChildren( fn, an1, an2 ) { var index=0, i=0, iLen=an1.length; var nNode1, nNode2; while ( i < iLen ) { nNode1 = an1[i].firstChild; nNode2 = an2 ? an2[i].firstChild : null; while ( nNode1 ) { if ( nNode1.nodeType === 1 ) { if ( an2 ) { fn( nNode1, nNode2, index ); } else { fn( nNode1, index ); } index++; } nNode1 = nNode1.nextSibling; nNode2 = an2 ? nNode2.nextSibling : null; } i++; } } var __re_html_remove = /<.*?>/g; /** * Calculate the width of columns for the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnCalculateColumnWidths ( oSettings ) { var table = oSettings.nTable, columns = oSettings.aoColumns, scroll = oSettings.oScroll, scrollY = scroll.sY, scrollX = scroll.sX, scrollXInner = scroll.sXInner, columnCount = columns.length, visibleColumns = _fnGetColumns( oSettings, 'bVisible' ), headerCells = $('th', oSettings.nTHead), tableWidthAttr = table.getAttribute('width'), // from DOM element tableContainer = table.parentNode, userInputs = false, i, column, columnIdx, width, outerWidth, browser = oSettings.oBrowser, ie67 = browser.bScrollOversize; var styleWidth = table.style.width; if ( styleWidth && styleWidth.indexOf('%') !== -1 ) { tableWidthAttr = styleWidth; } /* Convert any user input sizes into pixel sizes */ for ( i=0 ; i<visibleColumns.length ; i++ ) { column = columns[ visibleColumns[i] ]; if ( column.sWidth !== null ) { column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer ); userInputs = true; } } /* If the number of columns in the DOM equals the number that we have to * process in DataTables, then we can use the offsets that are created by * the web- browser. No custom sizes can be set in order for this to happen, * nor scrolling used */ if ( ie67 || ! userInputs && ! scrollX && ! scrollY && columnCount == _fnVisbleColumns( oSettings ) && columnCount == headerCells.length ) { for ( i=0 ; i<columnCount ; i++ ) { var colIdx = _fnVisibleToColumnIndex( oSettings, i ); if ( colIdx !== null ) { columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() ); } } } else { // Otherwise construct a single row, worst case, table with the widest // node in the data, assign any user defined widths, then insert it into // the DOM and allow the browser to do all the hard work of calculating // table widths var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table .css( 'visibility', 'hidden' ) .removeAttr( 'id' ); // Clean up the table body tmpTable.find('tbody tr').remove(); var tr = $('<tr/>').appendTo( tmpTable.find('tbody') ); // Clone the table header and footer - we can't use the header / footer // from the cloned table, since if scrolling is active, the table's // real header and footer are contained in different table tags tmpTable.find('thead, tfoot').remove(); tmpTable .append( $(oSettings.nTHead).clone() ) .append( $(oSettings.nTFoot).clone() ); // Remove any assigned widths from the footer (from scrolling) tmpTable.find('tfoot th, tfoot td').css('width', ''); // Apply custom sizing to the cloned header headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] ); for ( i=0 ; i<visibleColumns.length ; i++ ) { column = columns[ visibleColumns[i] ]; headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ? _fnStringToCss( column.sWidthOrig ) : ''; // For scrollX we need to force the column width otherwise the // browser will collapse it. If this width is smaller than the // width the column requires, then it will have no effect if ( column.sWidthOrig && scrollX ) { $( headerCells[i] ).append( $('<div/>').css( { width: column.sWidthOrig, margin: 0, padding: 0, border: 0, height: 1 } ) ); } } // Find the widest cell for each column and put it into the table if ( oSettings.aoData.length ) { for ( i=0 ; i<visibleColumns.length ; i++ ) { columnIdx = visibleColumns[i]; column = columns[ columnIdx ]; $( _fnGetWidestNode( oSettings, columnIdx ) ) .clone( false ) .append( column.sContentPadding ) .appendTo( tr ); } } // Tidy the temporary table - remove name attributes so there aren't // duplicated in the dom (radio elements for example) $('[name]', tmpTable).removeAttr('name'); // Table has been built, attach to the document so we can work with it. // A holding element is used, positioned at the top of the container // with minimal height, so it has no effect on if the container scrolls // or not. Otherwise it might trigger scrolling when it actually isn't // needed var holder = $('<div/>').css( scrollX || scrollY ? { position: 'absolute', top: 0, left: 0, height: 1, right: 0, overflow: 'hidden' } : {} ) .append( tmpTable ) .appendTo( tableContainer ); // When scrolling (X or Y) we want to set the width of the table as // appropriate. However, when not scrolling leave the table width as it // is. This results in slightly different, but I think correct behaviour if ( scrollX && scrollXInner ) { tmpTable.width( scrollXInner ); } else if ( scrollX ) { tmpTable.css( 'width', 'auto' ); tmpTable.removeAttr('width'); // If there is no width attribute or style, then allow the table to // collapse if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) { tmpTable.width( tableContainer.clientWidth ); } } else if ( scrollY ) { tmpTable.width( tableContainer.clientWidth ); } else if ( tableWidthAttr ) { tmpTable.width( tableWidthAttr ); } // Get the width of each column in the constructed table - we need to // know the inner width (so it can be assigned to the other table's // cells) and the outer width so we can calculate the full width of the // table. This is safe since DataTables requires a unique cell for each // column, but if ever a header can span multiple columns, this will // need to be modified. var total = 0; for ( i=0 ; i<visibleColumns.length ; i++ ) { var cell = $(headerCells[i]); var border = cell.outerWidth() - cell.width(); // Use getBounding... where possible (not IE8-) because it can give // sub-pixel accuracy, which we then want to round up! var bounding = browser.bBounding ? Math.ceil( headerCells[i].getBoundingClientRect().width ) : cell.outerWidth(); // Total is tracked to remove any sub-pixel errors as the outerWidth // of the table might not equal the total given here (IE!). total += bounding; // Width for each column to use columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border ); } table.style.width = _fnStringToCss( total ); // Finished with the table - ditch it holder.remove(); } // If there is a width attr, we want to attach an event listener which // allows the table sizing to automatically adjust when the window is // resized. Use the width attr rather than CSS, since we can't know if the // CSS is a relative value or absolute - DOM read is always px. if ( tableWidthAttr ) { table.style.width = _fnStringToCss( tableWidthAttr ); } if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) { var bindResize = function () { $(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () { _fnAdjustColumnSizing( oSettings ); } ) ); }; // IE6/7 will crash if we bind a resize event handler on page load. // To be removed in 1.11 which drops IE6/7 support if ( ie67 ) { setTimeout( bindResize, 1000 ); } else { bindResize(); } oSettings._reszEvt = true; } } /** * Throttle the calls to a function. Arguments and context are maintained for * the throttled function * @param {function} fn Function to be called * @param {int} [freq=200] call frequency in mS * @returns {function} wrapped function * @memberof DataTable#oApi */ var _fnThrottle = DataTable.util.throttle; /** * Convert a CSS unit width to pixels (e.g. 2em) * @param {string} width width to be converted * @param {node} parent parent to get the with for (required for relative widths) - optional * @returns {int} width in pixels * @memberof DataTable#oApi */ function _fnConvertToWidth ( width, parent ) { if ( ! width ) { return 0; } var n = $('<div/>') .css( 'width', _fnStringToCss( width ) ) .appendTo( parent || document.body ); var val = n[0].offsetWidth; n.remove(); return val; } /** * Get the widest node * @param {object} settings dataTables settings object * @param {int} colIdx column of interest * @returns {node} widest table node * @memberof DataTable#oApi */ function _fnGetWidestNode( settings, colIdx ) { var idx = _fnGetMaxLenString( settings, colIdx ); if ( idx < 0 ) { return null; } var data = settings.aoData[ idx ]; return ! data.nTr ? // Might not have been created when deferred rendering $('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] : data.anCells[ colIdx ]; } /** * Get the maximum strlen for each data column * @param {object} settings dataTables settings object * @param {int} colIdx column of interest * @returns {string} max string length for each column * @memberof DataTable#oApi */ function _fnGetMaxLenString( settings, colIdx ) { var s, max=-1, maxIdx = -1; for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { s = _fnGetCellData( settings, i, colIdx, 'display' )+''; s = s.replace( __re_html_remove, '' ); s = s.replace( /&nbsp;/g, ' ' ); if ( s.length > max ) { max = s.length; maxIdx = i; } } return maxIdx; } /** * Append a CSS unit (only if required) to a string * @param {string} value to css-ify * @returns {string} value with css unit * @memberof DataTable#oApi */ function _fnStringToCss( s ) { if ( s === null ) { return '0px'; } if ( typeof s == 'number' ) { return s < 0 ? '0px' : s+'px'; } // Check it has a unit character already return s.match(/\d$/) ? s+'px' : s; } function _fnSortFlatten ( settings ) { var i, iLen, k, kLen, aSort = [], aiOrig = [], aoColumns = settings.aoColumns, aDataSort, iCol, sType, srcCol, fixed = settings.aaSortingFixed, fixedObj = $.isPlainObject( fixed ), nestedSort = [], add = function ( a ) { if ( a.length && ! $.isArray( a[0] ) ) { // 1D array nestedSort.push( a ); } else { // 2D array $.merge( nestedSort, a ); } }; // Build the sort array, with pre-fix and post-fix options if they have been // specified if ( $.isArray( fixed ) ) { add( fixed ); } if ( fixedObj && fixed.pre ) { add( fixed.pre ); } add( settings.aaSorting ); if (fixedObj && fixed.post ) { add( fixed.post ); } for ( i=0 ; i<nestedSort.length ; i++ ) { srcCol = nestedSort[i][0]; aDataSort = aoColumns[ srcCol ].aDataSort; for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ ) { iCol = aDataSort[k]; sType = aoColumns[ iCol ].sType || 'string'; if ( nestedSort[i]._idx === undefined ) { nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting ); } aSort.push( { src: srcCol, col: iCol, dir: nestedSort[i][1], index: nestedSort[i]._idx, type: sType, formatter: DataTable.ext.type.order[ sType+"-pre" ] } ); } } return aSort; } /** * Change the order of the table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi * @todo This really needs split up! */ function _fnSort ( oSettings ) { var i, ien, iLen, j, jLen, k, kLen, sDataType, nTh, aiOrig = [], oExtSort = DataTable.ext.type.order, aoData = oSettings.aoData, aoColumns = oSettings.aoColumns, aDataSort, data, iCol, sType, oSort, formatters = 0, sortCol, displayMaster = oSettings.aiDisplayMaster, aSort; // Resolve any column types that are unknown due to addition or invalidation // @todo Can this be moved into a 'data-ready' handler which is called when // data is going to be used in the table? _fnColumnTypes( oSettings ); aSort = _fnSortFlatten( oSettings ); for ( i=0, ien=aSort.length ; i<ien ; i++ ) { sortCol = aSort[i]; // Track if we can use the fast sort algorithm if ( sortCol.formatter ) { formatters++; } // Load the data needed for the sort, for each cell _fnSortData( oSettings, sortCol.col ); } /* No sorting required if server-side or no sorting array */ if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 ) { // Create a value - key array of the current row positions such that we can use their // current position during the sort, if values match, in order to perform stable sorting for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) { aiOrig[ displayMaster[i] ] = i; } /* Do the sort - here we want multi-column sorting based on a given data source (column) * and sorting function (from oSort) in a certain direction. It's reasonably complex to * follow on it's own, but this is what we want (example two column sorting): * fnLocalSorting = function(a,b){ * var iTest; * iTest = oSort['string-asc']('data11', 'data12'); * if (iTest !== 0) * return iTest; * iTest = oSort['numeric-desc']('data21', 'data22'); * if (iTest !== 0) * return iTest; * return oSort['numeric-asc']( aiOrig[a], aiOrig[b] ); * } * Basically we have a test for each sorting column, if the data in that column is equal, * test the next column. If all columns match, then we use a numeric sort on the row * positions in the original data array to provide a stable sort. * * Note - I know it seems excessive to have two sorting methods, but the first is around * 15% faster, so the second is only maintained for backwards compatibility with sorting * methods which do not have a pre-sort formatting function. */ if ( formatters === aSort.length ) { // All sort types have formatting functions displayMaster.sort( function ( a, b ) { var x, y, k, test, sort, len=aSort.length, dataA = aoData[a]._aSortData, dataB = aoData[b]._aSortData; for ( k=0 ; k<len ; k++ ) { sort = aSort[k]; x = dataA[ sort.col ]; y = dataB[ sort.col ]; test = x<y ? -1 : x>y ? 1 : 0; if ( test !== 0 ) { return sort.dir === 'asc' ? test : -test; } } x = aiOrig[a]; y = aiOrig[b]; return x<y ? -1 : x>y ? 1 : 0; } ); } else { // Depreciated - remove in 1.11 (providing a plug-in option) // Not all sort types have formatting methods, so we have to call their sorting // methods. displayMaster.sort( function ( a, b ) { var x, y, k, l, test, sort, fn, len=aSort.length, dataA = aoData[a]._aSortData, dataB = aoData[b]._aSortData; for ( k=0 ; k<len ; k++ ) { sort = aSort[k]; x = dataA[ sort.col ]; y = dataB[ sort.col ]; fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ]; test = fn( x, y ); if ( test !== 0 ) { return test; } } x = aiOrig[a]; y = aiOrig[b]; return x<y ? -1 : x>y ? 1 : 0; } ); } } /* Tell the draw function that we have sorted the data */ oSettings.bSorted = true; } function _fnSortAria ( settings ) { var label; var nextSort; var columns = settings.aoColumns; var aSort = _fnSortFlatten( settings ); var oAria = settings.oLanguage.oAria; // ARIA attributes - need to loop all columns, to update all (removing old // attributes as needed) for ( var i=0, iLen=columns.length ; i<iLen ; i++ ) { var col = columns[i]; var asSorting = col.asSorting; var sTitle = col.sTitle.replace( /<.*?>/g, "" ); var th = col.nTh; // IE7 is throwing an error when setting these properties with jQuery's // attr() and removeAttr() methods... th.removeAttribute('aria-sort'); /* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */ if ( col.bSortable ) { if ( aSort.length > 0 && aSort[0].col == i ) { th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" ); nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0]; } else { nextSort = asSorting[0]; } label = sTitle + ( nextSort === "asc" ? oAria.sSortAscending : oAria.sSortDescending ); } else { label = sTitle; } th.setAttribute('aria-label', label); } } /** * Function to run on user sort request * @param {object} settings dataTables settings object * @param {node} attachTo node to attach the handler to * @param {int} colIdx column sorting index * @param {boolean} [append=false] Append the requested sort to the existing * sort if true (i.e. multi-column sort) * @param {function} [callback] callback function * @memberof DataTable#oApi */ function _fnSortListener ( settings, colIdx, append, callback ) { var col = settings.aoColumns[ colIdx ]; var sorting = settings.aaSorting; var asSorting = col.asSorting; var nextSortIdx; var next = function ( a, overflow ) { var idx = a._idx; if ( idx === undefined ) { idx = $.inArray( a[1], asSorting ); } return idx+1 < asSorting.length ? idx+1 : overflow ? null : 0; }; // Convert to 2D array if needed if ( typeof sorting[0] === 'number' ) { sorting = settings.aaSorting = [ sorting ]; } // If appending the sort then we are multi-column sorting if ( append && settings.oFeatures.bSortMulti ) { // Are we already doing some kind of sort on this column? var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') ); if ( sortIdx !== -1 ) { // Yes, modify the sort nextSortIdx = next( sorting[sortIdx], true ); if ( nextSortIdx === null && sorting.length === 1 ) { nextSortIdx = 0; // can't remove sorting completely } if ( nextSortIdx === null ) { sorting.splice( sortIdx, 1 ); } else { sorting[sortIdx][1] = asSorting[ nextSortIdx ]; sorting[sortIdx]._idx = nextSortIdx; } } else { // No sort on this column yet sorting.push( [ colIdx, asSorting[0], 0 ] ); sorting[sorting.length-1]._idx = 0; } } else if ( sorting.length && sorting[0][0] == colIdx ) { // Single column - already sorting on this column, modify the sort nextSortIdx = next( sorting[0] ); sorting.length = 1; sorting[0][1] = asSorting[ nextSortIdx ]; sorting[0]._idx = nextSortIdx; } else { // Single column - sort only on this column sorting.length = 0; sorting.push( [ colIdx, asSorting[0] ] ); sorting[0]._idx = 0; } // Run the sort by calling a full redraw _fnReDraw( settings ); // callback used for async user interaction if ( typeof callback == 'function' ) { callback( settings ); } } /** * Attach a sort handler (click) to a node * @param {object} settings dataTables settings object * @param {node} attachTo node to attach the handler to * @param {int} colIdx column sorting index * @param {function} [callback] callback function * @memberof DataTable#oApi */ function _fnSortAttachListener ( settings, attachTo, colIdx, callback ) { var col = settings.aoColumns[ colIdx ]; _fnBindAction( attachTo, {}, function (e) { /* If the column is not sortable - don't to anything */ if ( col.bSortable === false ) { return; } // If processing is enabled use a timeout to allow the processing // display to be shown - otherwise to it synchronously if ( settings.oFeatures.bProcessing ) { _fnProcessingDisplay( settings, true ); setTimeout( function() { _fnSortListener( settings, colIdx, e.shiftKey, callback ); // In server-side processing, the draw callback will remove the // processing display if ( _fnDataSource( settings ) !== 'ssp' ) { _fnProcessingDisplay( settings, false ); } }, 0 ); } else { _fnSortListener( settings, colIdx, e.shiftKey, callback ); } } ); } /** * Set the sorting classes on table's body, Note: it is safe to call this function * when bSort and bSortClasses are false * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnSortingClasses( settings ) { var oldSort = settings.aLastSort; var sortClass = settings.oClasses.sSortColumn; var sort = _fnSortFlatten( settings ); var features = settings.oFeatures; var i, ien, colIdx; if ( features.bSort && features.bSortClasses ) { // Remove old sorting classes for ( i=0, ien=oldSort.length ; i<ien ; i++ ) { colIdx = oldSort[i].src; // Remove column sorting $( _pluck( settings.aoData, 'anCells', colIdx ) ) .removeClass( sortClass + (i<2 ? i+1 : 3) ); } // Add new column sorting for ( i=0, ien=sort.length ; i<ien ; i++ ) { colIdx = sort[i].src; $( _pluck( settings.aoData, 'anCells', colIdx ) ) .addClass( sortClass + (i<2 ? i+1 : 3) ); } } settings.aLastSort = sort; } // Get the data to sort a column, be it from cache, fresh (populating the // cache), or from a sort formatter function _fnSortData( settings, idx ) { // Custom sorting function - provided by the sort data type var column = settings.aoColumns[ idx ]; var customSort = DataTable.ext.order[ column.sSortDataType ]; var customData; if ( customSort ) { customData = customSort.call( settings.oInstance, settings, idx, _fnColumnIndexToVisible( settings, idx ) ); } // Use / populate cache var row, cellData; var formatter = DataTable.ext.type.order[ column.sType+"-pre" ]; for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { row = settings.aoData[i]; if ( ! row._aSortData ) { row._aSortData = []; } if ( ! row._aSortData[idx] || customSort ) { cellData = customSort ? customData[i] : // If there was a custom sort function, use data from there _fnGetCellData( settings, i, idx, 'sort' ); row._aSortData[ idx ] = formatter ? formatter( cellData ) : cellData; } } } /** * Save the state of a table * @param {object} oSettings dataTables settings object * @memberof DataTable#oApi */ function _fnSaveState ( settings ) { if ( !settings.oFeatures.bStateSave || settings.bDestroying ) { return; } /* Store the interesting variables */ var state = { time: +new Date(), start: settings._iDisplayStart, length: settings._iDisplayLength, order: $.extend( true, [], settings.aaSorting ), search: _fnSearchToCamel( settings.oPreviousSearch ), columns: $.map( settings.aoColumns, function ( col, i ) { return { visible: col.bVisible, search: _fnSearchToCamel( settings.aoPreSearchCols[i] ) }; } ) }; _fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] ); settings.oSavedState = state; settings.fnStateSaveCallback.call( settings.oInstance, settings, state ); } /** * Attempt to load a saved table state * @param {object} oSettings dataTables settings object * @param {object} oInit DataTables init object so we can override settings * @param {function} callback Callback to execute when the state has been loaded * @memberof DataTable#oApi */ function _fnLoadState ( settings, oInit, callback ) { var i, ien; var columns = settings.aoColumns; var loaded = function ( s ) { if ( ! s || ! s.time ) { callback(); return; } // Allow custom and plug-in manipulation functions to alter the saved data set and // cancelling of loading by returning false var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, s] ); if ( $.inArray( false, abStateLoad ) !== -1 ) { callback(); return; } // Reject old data var duration = settings.iStateDuration; if ( duration > 0 && s.time < +new Date() - (duration*1000) ) { callback(); return; } // Number of columns have changed - all bets are off, no restore of settings if ( s.columns && columns.length !== s.columns.length ) { callback(); return; } // Store the saved state so it might be accessed at any time settings.oLoadedState = $.extend( true, {}, s ); // Restore key features - todo - for 1.11 this needs to be done by // subscribed events if ( s.start !== undefined ) { settings._iDisplayStart = s.start; settings.iInitDisplayStart = s.start; } if ( s.length !== undefined ) { settings._iDisplayLength = s.length; } // Order if ( s.order !== undefined ) { settings.aaSorting = []; $.each( s.order, function ( i, col ) { settings.aaSorting.push( col[0] >= columns.length ? [ 0, col[1] ] : col ); } ); } // Search if ( s.search !== undefined ) { $.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) ); } // Columns // if ( s.columns ) { for ( i=0, ien=s.columns.length ; i<ien ; i++ ) { var col = s.columns[i]; // Visibility if ( col.visible !== undefined ) { columns[i].bVisible = col.visible; } // Search if ( col.search !== undefined ) { $.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) ); } } } _fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, s] ); callback(); }; if ( ! settings.oFeatures.bStateSave ) { callback(); return; } var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded ); if ( state !== undefined ) { loaded( state ); } // otherwise, wait for the loaded callback to be executed } /** * Return the settings object for a particular table * @param {node} table table we are using as a dataTable * @returns {object} Settings object - or null if not found * @memberof DataTable#oApi */ function _fnSettingsFromNode ( table ) { var settings = DataTable.settings; var idx = $.inArray( table, _pluck( settings, 'nTable' ) ); return idx !== -1 ? settings[ idx ] : null; } /** * Log an error message * @param {object} settings dataTables settings object * @param {int} level log error messages, or display them to the user * @param {string} msg error message * @param {int} tn Technical note id to get more information about the error. * @memberof DataTable#oApi */ function _fnLog( settings, level, msg, tn ) { msg = 'DataTables warning: '+ (settings ? 'table id='+settings.sTableId+' - ' : '')+msg; if ( tn ) { msg += '. For more information about this error, please see '+ 'http://datatables.net/tn/'+tn; } if ( ! level ) { // Backwards compatibility pre 1.10 var ext = DataTable.ext; var type = ext.sErrMode || ext.errMode; if ( settings ) { _fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] ); } if ( type == 'alert' ) { alert( msg ); } else if ( type == 'throw' ) { throw new Error(msg); } else if ( typeof type == 'function' ) { type( settings, tn, msg ); } } else if ( window.console && console.log ) { console.log( msg ); } } /** * See if a property is defined on one object, if so assign it to the other object * @param {object} ret target object * @param {object} src source object * @param {string} name property * @param {string} [mappedName] name to map too - optional, name used if not given * @memberof DataTable#oApi */ function _fnMap( ret, src, name, mappedName ) { if ( $.isArray( name ) ) { $.each( name, function (i, val) { if ( $.isArray( val ) ) { _fnMap( ret, src, val[0], val[1] ); } else { _fnMap( ret, src, val ); } } ); return; } if ( mappedName === undefined ) { mappedName = name; } if ( src[name] !== undefined ) { ret[mappedName] = src[name]; } } /** * Extend objects - very similar to jQuery.extend, but deep copy objects, and * shallow copy arrays. The reason we need to do this, is that we don't want to * deep copy array init values (such as aaSorting) since the dev wouldn't be * able to override them, but we do want to deep copy arrays. * @param {object} out Object to extend * @param {object} extender Object from which the properties will be applied to * out * @param {boolean} breakRefs If true, then arrays will be sliced to take an * independent copy with the exception of the `data` or `aaData` parameters * if they are present. This is so you can pass in a collection to * DataTables and have that used as your data source without breaking the * references * @returns {object} out Reference, just for convenience - out === the return. * @memberof DataTable#oApi * @todo This doesn't take account of arrays inside the deep copied objects. */ function _fnExtend( out, extender, breakRefs ) { var val; for ( var prop in extender ) { if ( extender.hasOwnProperty(prop) ) { val = extender[prop]; if ( $.isPlainObject( val ) ) { if ( ! $.isPlainObject( out[prop] ) ) { out[prop] = {}; } $.extend( true, out[prop], val ); } else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val) ) { out[prop] = val.slice(); } else { out[prop] = val; } } } return out; } /** * Bind an event handers to allow a click or return key to activate the callback. * This is good for accessibility since a return on the keyboard will have the * same effect as a click, if the element has focus. * @param {element} n Element to bind the action to * @param {object} oData Data object to pass to the triggered function * @param {function} fn Callback function for when the event is triggered * @memberof DataTable#oApi */ function _fnBindAction( n, oData, fn ) { $(n) .on( 'click.DT', oData, function (e) { $(n).trigger('blur'); // Remove focus outline for mouse users fn(e); } ) .on( 'keypress.DT', oData, function (e){ if ( e.which === 13 ) { e.preventDefault(); fn(e); } } ) .on( 'selectstart.DT', function () { /* Take the brutal approach to cancelling text selection */ return false; } ); } /** * Register a callback function. Easily allows a callback function to be added to * an array store of callback functions that can then all be called together. * @param {object} oSettings dataTables settings object * @param {string} sStore Name of the array storage for the callbacks in oSettings * @param {function} fn Function to be called back * @param {string} sName Identifying name for the callback (i.e. a label) * @memberof DataTable#oApi */ function _fnCallbackReg( oSettings, sStore, fn, sName ) { if ( fn ) { oSettings[sStore].push( { "fn": fn, "sName": sName } ); } } /** * Fire callback functions and trigger events. Note that the loop over the * callback array store is done backwards! Further note that you do not want to * fire off triggers in time sensitive applications (for example cell creation) * as its slow. * @param {object} settings dataTables settings object * @param {string} callbackArr Name of the array storage for the callbacks in * oSettings * @param {string} eventName Name of the jQuery custom event to trigger. If * null no trigger is fired * @param {array} args Array of arguments to pass to the callback function / * trigger * @memberof DataTable#oApi */ function _fnCallbackFire( settings, callbackArr, eventName, args ) { var ret = []; if ( callbackArr ) { ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) { return val.fn.apply( settings.oInstance, args ); } ); } if ( eventName !== null ) { var e = $.Event( eventName+'.dt' ); $(settings.nTable).trigger( e, args ); ret.push( e.result ); } return ret; } function _fnLengthOverflow ( settings ) { var start = settings._iDisplayStart, end = settings.fnDisplayEnd(), len = settings._iDisplayLength; /* If we have space to show extra rows (backing up from the end point - then do so */ if ( start >= end ) { start = end - len; } // Keep the start record on the current page start -= (start % len); if ( len === -1 || start < 0 ) { start = 0; } settings._iDisplayStart = start; } function _fnRenderer( settings, type ) { var renderer = settings.renderer; var host = DataTable.ext.renderer[type]; if ( $.isPlainObject( renderer ) && renderer[type] ) { // Specific renderer for this type. If available use it, otherwise use // the default. return host[renderer[type]] || host._; } else if ( typeof renderer === 'string' ) { // Common renderer - if there is one available for this type use it, // otherwise use the default return host[renderer] || host._; } // Use the default return host._; } /** * Detect the data source being used for the table. Used to simplify the code * a little (ajax) and to make it compress a little smaller. * * @param {object} settings dataTables settings object * @returns {string} Data source * @memberof DataTable#oApi */ function _fnDataSource ( settings ) { if ( settings.oFeatures.bServerSide ) { return 'ssp'; } else if ( settings.ajax || settings.sAjaxSource ) { return 'ajax'; } return 'dom'; } /** * Computed structure of the DataTables API, defined by the options passed to * `DataTable.Api.register()` when building the API. * * The structure is built in order to speed creation and extension of the Api * objects since the extensions are effectively pre-parsed. * * The array is an array of objects with the following structure, where this * base array represents the Api prototype base: * * [ * { * name: 'data' -- string - Property name * val: function () {}, -- function - Api method (or undefined if just an object * methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result * propExt: [ ... ] -- array - Array of Api object definitions to extend the property * }, * { * name: 'row' * val: {}, * methodExt: [ ... ], * propExt: [ * { * name: 'data' * val: function () {}, * methodExt: [ ... ], * propExt: [ ... ] * }, * ... * ] * } * ] * * @type {Array} * @ignore */ var __apiStruct = []; /** * `Array.prototype` reference. * * @type object * @ignore */ var __arrayProto = Array.prototype; /** * Abstraction for `context` parameter of the `Api` constructor to allow it to * take several different forms for ease of use. * * Each of the input parameter types will be converted to a DataTables settings * object where possible. * * @param {string|node|jQuery|object} mixed DataTable identifier. Can be one * of: * * * `string` - jQuery selector. Any DataTables' matching the given selector * with be found and used. * * `node` - `TABLE` node which has already been formed into a DataTable. * * `jQuery` - A jQuery object of `TABLE` nodes. * * `object` - DataTables settings object * * `DataTables.Api` - API instance * @return {array|null} Matching DataTables settings objects. `null` or * `undefined` is returned if no matching DataTable is found. * @ignore */ var _toSettings = function ( mixed ) { var idx, jq; var settings = DataTable.settings; var tables = $.map( settings, function (el, i) { return el.nTable; } ); if ( ! mixed ) { return []; } else if ( mixed.nTable && mixed.oApi ) { // DataTables settings object return [ mixed ]; } else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) { // Table node idx = $.inArray( mixed, tables ); return idx !== -1 ? [ settings[idx] ] : null; } else if ( mixed && typeof mixed.settings === 'function' ) { return mixed.settings().toArray(); } else if ( typeof mixed === 'string' ) { // jQuery selector jq = $(mixed); } else if ( mixed instanceof $ ) { // jQuery object (also DataTables instance) jq = mixed; } if ( jq ) { return jq.map( function(i) { idx = $.inArray( this, tables ); return idx !== -1 ? settings[idx] : null; } ).toArray(); } }; /** * DataTables API class - used to control and interface with one or more * DataTables enhanced tables. * * The API class is heavily based on jQuery, presenting a chainable interface * that you can use to interact with tables. Each instance of the API class has * a "context" - i.e. the tables that it will operate on. This could be a single * table, all tables on a page or a sub-set thereof. * * Additionally the API is designed to allow you to easily work with the data in * the tables, retrieving and manipulating it as required. This is done by * presenting the API class as an array like interface. The contents of the * array depend upon the actions requested by each method (for example * `rows().nodes()` will return an array of nodes, while `rows().data()` will * return an array of objects or arrays depending upon your table's * configuration). The API object has a number of array like methods (`push`, * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`, * `unique` etc) to assist your working with the data held in a table. * * Most methods (those which return an Api instance) are chainable, which means * the return from a method call also has all of the methods available that the * top level object had. For example, these two calls are equivalent: * * // Not chained * api.row.add( {...} ); * api.draw(); * * // Chained * api.row.add( {...} ).draw(); * * @class DataTable.Api * @param {array|object|string|jQuery} context DataTable identifier. This is * used to define which DataTables enhanced tables this API will operate on. * Can be one of: * * * `string` - jQuery selector. Any DataTables' matching the given selector * with be found and used. * * `node` - `TABLE` node which has already been formed into a DataTable. * * `jQuery` - A jQuery object of `TABLE` nodes. * * `object` - DataTables settings object * @param {array} [data] Data to initialise the Api instance with. * * @example * // Direct initialisation during DataTables construction * var api = $('#example').DataTable(); * * @example * // Initialisation using a DataTables jQuery object * var api = $('#example').dataTable().api(); * * @example * // Initialisation as a constructor * var api = new $.fn.DataTable.Api( 'table.dataTable' ); */ _Api = function ( context, data ) { if ( ! (this instanceof _Api) ) { return new _Api( context, data ); } var settings = []; var ctxSettings = function ( o ) { var a = _toSettings( o ); if ( a ) { settings.push.apply( settings, a ); } }; if ( $.isArray( context ) ) { for ( var i=0, ien=context.length ; i<ien ; i++ ) { ctxSettings( context[i] ); } } else { ctxSettings( context ); } // Remove duplicates this.context = _unique( settings ); // Initial data if ( data ) { $.merge( this, data ); } // selector this.selector = { rows: null, cols: null, opts: null }; _Api.extend( this, this, __apiStruct ); }; DataTable.Api = _Api; // Don't destroy the existing prototype, just extend it. Required for jQuery 2's // isPlainObject. $.extend( _Api.prototype, { any: function () { return this.count() !== 0; }, concat: __arrayProto.concat, context: [], // array of table settings objects count: function () { return this.flatten().length; }, each: function ( fn ) { for ( var i=0, ien=this.length ; i<ien; i++ ) { fn.call( this, this[i], i, this ); } return this; }, eq: function ( idx ) { var ctx = this.context; return ctx.length > idx ? new _Api( ctx[idx], this[idx] ) : null; }, filter: function ( fn ) { var a = []; if ( __arrayProto.filter ) { a = __arrayProto.filter.call( this, fn, this ); } else { // Compatibility for browsers without EMCA-252-5 (JS 1.6) for ( var i=0, ien=this.length ; i<ien ; i++ ) { if ( fn.call( this, this[i], i, this ) ) { a.push( this[i] ); } } } return new _Api( this.context, a ); }, flatten: function () { var a = []; return new _Api( this.context, a.concat.apply( a, this.toArray() ) ); }, join: __arrayProto.join, indexOf: __arrayProto.indexOf || function (obj, start) { for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) { if ( this[i] === obj ) { return i; } } return -1; }, iterator: function ( flatten, type, fn, alwaysNew ) { var a = [], ret, i, ien, j, jen, context = this.context, rows, items, item, selector = this.selector; // Argument shifting if ( typeof flatten === 'string' ) { alwaysNew = fn; fn = type; type = flatten; flatten = false; } for ( i=0, ien=context.length ; i<ien ; i++ ) { var apiInst = new _Api( context[i] ); if ( type === 'table' ) { ret = fn.call( apiInst, context[i], i ); if ( ret !== undefined ) { a.push( ret ); } } else if ( type === 'columns' || type === 'rows' ) { // this has same length as context - one entry for each table ret = fn.call( apiInst, context[i], this[i], i ); if ( ret !== undefined ) { a.push( ret ); } } else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) { // columns and rows share the same structure. // 'this' is an array of column indexes for each context items = this[i]; if ( type === 'column-rows' ) { rows = _selector_row_indexes( context[i], selector.opts ); } for ( j=0, jen=items.length ; j<jen ; j++ ) { item = items[j]; if ( type === 'cell' ) { ret = fn.call( apiInst, context[i], item.row, item.column, i, j ); } else { ret = fn.call( apiInst, context[i], item, i, j, rows ); } if ( ret !== undefined ) { a.push( ret ); } } } } if ( a.length || alwaysNew ) { var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a ); var apiSelector = api.selector; apiSelector.rows = selector.rows; apiSelector.cols = selector.cols; apiSelector.opts = selector.opts; return api; } return this; }, lastIndexOf: __arrayProto.lastIndexOf || function (obj, start) { // Bit cheeky... return this.indexOf.apply( this.toArray.reverse(), arguments ); }, length: 0, map: function ( fn ) { var a = []; if ( __arrayProto.map ) { a = __arrayProto.map.call( this, fn, this ); } else { // Compatibility for browsers without EMCA-252-5 (JS 1.6) for ( var i=0, ien=this.length ; i<ien ; i++ ) { a.push( fn.call( this, this[i], i ) ); } } return new _Api( this.context, a ); }, pluck: function ( prop ) { return this.map( function ( el ) { return el[ prop ]; } ); }, pop: __arrayProto.pop, push: __arrayProto.push, // Does not return an API instance reduce: __arrayProto.reduce || function ( fn, init ) { return _fnReduce( this, fn, init, 0, this.length, 1 ); }, reduceRight: __arrayProto.reduceRight || function ( fn, init ) { return _fnReduce( this, fn, init, this.length-1, -1, -1 ); }, reverse: __arrayProto.reverse, // Object with rows, columns and opts selector: null, shift: __arrayProto.shift, slice: function () { return new _Api( this.context, this ); }, sort: __arrayProto.sort, // ? name - order? splice: __arrayProto.splice, toArray: function () { return __arrayProto.slice.call( this ); }, to$: function () { return $( this ); }, toJQuery: function () { return $( this ); }, unique: function () { return new _Api( this.context, _unique(this) ); }, unshift: __arrayProto.unshift } ); _Api.extend = function ( scope, obj, ext ) { // Only extend API instances and static properties of the API if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) { return; } var i, ien, struct, methodScoping = function ( scope, fn, struc ) { return function () { var ret = fn.apply( scope, arguments ); // Method extension _Api.extend( ret, ret, struc.methodExt ); return ret; }; }; for ( i=0, ien=ext.length ; i<ien ; i++ ) { struct = ext[i]; // Value obj[ struct.name ] = struct.type === 'function' ? methodScoping( scope, struct.val, struct ) : struct.type === 'object' ? {} : struct.val; obj[ struct.name ].__dt_wrapper = true; // Property extension _Api.extend( scope, obj[ struct.name ], struct.propExt ); } }; // @todo - Is there need for an augment function? // _Api.augment = function ( inst, name ) // { // // Find src object in the structure from the name // var parts = name.split('.'); // _Api.extend( inst, obj ); // }; // [ // { // name: 'data' -- string - Property name // val: function () {}, -- function - Api method (or undefined if just an object // methodExt: [ ... ], -- array - Array of Api object definitions to extend the method result // propExt: [ ... ] -- array - Array of Api object definitions to extend the property // }, // { // name: 'row' // val: {}, // methodExt: [ ... ], // propExt: [ // { // name: 'data' // val: function () {}, // methodExt: [ ... ], // propExt: [ ... ] // }, // ... // ] // } // ] _Api.register = _api_register = function ( name, val ) { if ( $.isArray( name ) ) { for ( var j=0, jen=name.length ; j<jen ; j++ ) { _Api.register( name[j], val ); } return; } var i, ien, heir = name.split('.'), struct = __apiStruct, key, method; var find = function ( src, name ) { for ( var i=0, ien=src.length ; i<ien ; i++ ) { if ( src[i].name === name ) { return src[i]; } } return null; }; for ( i=0, ien=heir.length ; i<ien ; i++ ) { method = heir[i].indexOf('()') !== -1; key = method ? heir[i].replace('()', '') : heir[i]; var src = find( struct, key ); if ( ! src ) { src = { name: key, val: {}, methodExt: [], propExt: [], type: 'object' }; struct.push( src ); } if ( i === ien-1 ) { src.val = val; src.type = typeof val === 'function' ? 'function' : $.isPlainObject( val ) ? 'object' : 'other'; } else { struct = method ? src.methodExt : src.propExt; } } }; _Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) { _Api.register( pluralName, val ); _Api.register( singularName, function () { var ret = val.apply( this, arguments ); if ( ret === this ) { // Returned item is the API instance that was passed in, return it return this; } else if ( ret instanceof _Api ) { // New API instance returned, want the value from the first item // in the returned array for the singular result. return ret.length ? $.isArray( ret[0] ) ? new _Api( ret.context, ret[0] ) : // Array results are 'enhanced' ret[0] : undefined; } // Non-API return - just fire it back return ret; } ); }; /** * Selector for HTML tables. Apply the given selector to the give array of * DataTables settings objects. * * @param {string|integer} [selector] jQuery selector string or integer * @param {array} Array of DataTables settings objects to be filtered * @return {array} * @ignore */ var __table_selector = function ( selector, a ) { if ( $.isArray(selector) ) { return $.map( selector, function (item) { return __table_selector(item, a); } ); } // Integer is used to pick out a table by index if ( typeof selector === 'number' ) { return [ a[ selector ] ]; } // Perform a jQuery selector on the table nodes var nodes = $.map( a, function (el, i) { return el.nTable; } ); return $(nodes) .filter( selector ) .map( function (i) { // Need to translate back from the table node to the settings var idx = $.inArray( this, nodes ); return a[ idx ]; } ) .toArray(); }; /** * Context selector for the API's context (i.e. the tables the API instance * refers to. * * @name DataTable.Api#tables * @param {string|integer} [selector] Selector to pick which tables the iterator * should operate on. If not given, all tables in the current context are * used. This can be given as a jQuery selector (for example `':gt(0)'`) to * select multiple tables or as an integer to select a single table. * @returns {DataTable.Api} Returns a new API instance if a selector is given. */ _api_register( 'tables()', function ( selector ) { // A new instance is created if there was a selector specified return selector !== undefined && selector !== null ? new _Api( __table_selector( selector, this.context ) ) : this; } ); _api_register( 'table()', function ( selector ) { var tables = this.tables( selector ); var ctx = tables.context; // Truncate to the first matched table return ctx.length ? new _Api( ctx[0] ) : tables; } ); _api_registerPlural( 'tables().nodes()', 'table().node()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTable; }, 1 ); } ); _api_registerPlural( 'tables().body()', 'table().body()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTBody; }, 1 ); } ); _api_registerPlural( 'tables().header()', 'table().header()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTHead; }, 1 ); } ); _api_registerPlural( 'tables().footer()', 'table().footer()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTFoot; }, 1 ); } ); _api_registerPlural( 'tables().containers()', 'table().container()' , function () { return this.iterator( 'table', function ( ctx ) { return ctx.nTableWrapper; }, 1 ); } ); /** * Redraw the tables in the current context. */ _api_register( 'draw()', function ( paging ) { return this.iterator( 'table', function ( settings ) { if ( paging === 'page' ) { _fnDraw( settings ); } else { if ( typeof paging === 'string' ) { paging = paging === 'full-hold' ? false : true; } _fnReDraw( settings, paging===false ); } } ); } ); /** * Get the current page index. * * @return {integer} Current page index (zero based) *//** * Set the current page. * * Note that if you attempt to show a page which does not exist, DataTables will * not throw an error, but rather reset the paging. * * @param {integer|string} action The paging action to take. This can be one of: * * `integer` - The page index to jump to * * `string` - An action to take: * * `first` - Jump to first page. * * `next` - Jump to the next page * * `previous` - Jump to previous page * * `last` - Jump to the last page. * @returns {DataTables.Api} this */ _api_register( 'page()', function ( action ) { if ( action === undefined ) { return this.page.info().page; // not an expensive call } // else, have an action to take on all tables return this.iterator( 'table', function ( settings ) { _fnPageChange( settings, action ); } ); } ); /** * Paging information for the first table in the current context. * * If you require paging information for another table, use the `table()` method * with a suitable selector. * * @return {object} Object with the following properties set: * * `page` - Current page index (zero based - i.e. the first page is `0`) * * `pages` - Total number of pages * * `start` - Display index for the first record shown on the current page * * `end` - Display index for the last record shown on the current page * * `length` - Display length (number of records). Note that generally `start * + length = end`, but this is not always true, for example if there are * only 2 records to show on the final page, with a length of 10. * * `recordsTotal` - Full data set length * * `recordsDisplay` - Data set length once the current filtering criterion * are applied. */ _api_register( 'page.info()', function ( action ) { if ( this.context.length === 0 ) { return undefined; } var settings = this.context[0], start = settings._iDisplayStart, len = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1, visRecords = settings.fnRecordsDisplay(), all = len === -1; return { "page": all ? 0 : Math.floor( start / len ), "pages": all ? 1 : Math.ceil( visRecords / len ), "start": start, "end": settings.fnDisplayEnd(), "length": len, "recordsTotal": settings.fnRecordsTotal(), "recordsDisplay": visRecords, "serverSide": _fnDataSource( settings ) === 'ssp' }; } ); /** * Get the current page length. * * @return {integer} Current page length. Note `-1` indicates that all records * are to be shown. *//** * Set the current page length. * * @param {integer} Page length to set. Use `-1` to show all records. * @returns {DataTables.Api} this */ _api_register( 'page.len()', function ( len ) { // Note that we can't call this function 'length()' because `length` // is a Javascript property of functions which defines how many arguments // the function expects. if ( len === undefined ) { return this.context.length !== 0 ? this.context[0]._iDisplayLength : undefined; } // else, set the page length return this.iterator( 'table', function ( settings ) { _fnLengthChange( settings, len ); } ); } ); var __reload = function ( settings, holdPosition, callback ) { // Use the draw event to trigger a callback if ( callback ) { var api = new _Api( settings ); api.one( 'draw', function () { callback( api.ajax.json() ); } ); } if ( _fnDataSource( settings ) == 'ssp' ) { _fnReDraw( settings, holdPosition ); } else { _fnProcessingDisplay( settings, true ); // Cancel an existing request var xhr = settings.jqXHR; if ( xhr && xhr.readyState !== 4 ) { xhr.abort(); } // Trigger xhr _fnBuildAjax( settings, [], function( json ) { _fnClearTable( settings ); var data = _fnAjaxDataSrc( settings, json ); for ( var i=0, ien=data.length ; i<ien ; i++ ) { _fnAddData( settings, data[i] ); } _fnReDraw( settings, holdPosition ); _fnProcessingDisplay( settings, false ); } ); } }; /** * Get the JSON response from the last Ajax request that DataTables made to the * server. Note that this returns the JSON from the first table in the current * context. * * @return {object} JSON received from the server. */ _api_register( 'ajax.json()', function () { var ctx = this.context; if ( ctx.length > 0 ) { return ctx[0].json; } // else return undefined; } ); /** * Get the data submitted in the last Ajax request */ _api_register( 'ajax.params()', function () { var ctx = this.context; if ( ctx.length > 0 ) { return ctx[0].oAjaxData; } // else return undefined; } ); /** * Reload tables from the Ajax data source. Note that this function will * automatically re-draw the table when the remote data has been loaded. * * @param {boolean} [reset=true] Reset (default) or hold the current paging * position. A full re-sort and re-filter is performed when this method is * called, which is why the pagination reset is the default action. * @returns {DataTables.Api} this */ _api_register( 'ajax.reload()', function ( callback, resetPaging ) { return this.iterator( 'table', function (settings) { __reload( settings, resetPaging===false, callback ); } ); } ); /** * Get the current Ajax URL. Note that this returns the URL from the first * table in the current context. * * @return {string} Current Ajax source URL *//** * Set the Ajax URL. Note that this will set the URL for all tables in the * current context. * * @param {string} url URL to set. * @returns {DataTables.Api} this */ _api_register( 'ajax.url()', function ( url ) { var ctx = this.context; if ( url === undefined ) { // get if ( ctx.length === 0 ) { return undefined; } ctx = ctx[0]; return ctx.ajax ? $.isPlainObject( ctx.ajax ) ? ctx.ajax.url : ctx.ajax : ctx.sAjaxSource; } // set return this.iterator( 'table', function ( settings ) { if ( $.isPlainObject( settings.ajax ) ) { settings.ajax.url = url; } else { settings.ajax = url; } // No need to consider sAjaxSource here since DataTables gives priority // to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any // value of `sAjaxSource` redundant. } ); } ); /** * Load data from the newly set Ajax URL. Note that this method is only * available when `ajax.url()` is used to set a URL. Additionally, this method * has the same effect as calling `ajax.reload()` but is provided for * convenience when setting a new URL. Like `ajax.reload()` it will * automatically redraw the table once the remote data has been loaded. * * @returns {DataTables.Api} this */ _api_register( 'ajax.url().load()', function ( callback, resetPaging ) { // Same as a reload, but makes sense to present it for easy access after a // url change return this.iterator( 'table', function ( ctx ) { __reload( ctx, resetPaging===false, callback ); } ); } ); var _selector_run = function ( type, selector, selectFn, settings, opts ) { var out = [], res, a, i, ien, j, jen, selectorType = typeof selector; // Can't just check for isArray here, as an API or jQuery instance might be // given with their array like look if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) { selector = [ selector ]; } for ( i=0, ien=selector.length ; i<ien ; i++ ) { // Only split on simple strings - complex expressions will be jQuery selectors a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ? selector[i].split(',') : [ selector[i] ]; for ( j=0, jen=a.length ; j<jen ; j++ ) { res = selectFn( typeof a[j] === 'string' ? $.trim(a[j]) : a[j] ); if ( res && res.length ) { out = out.concat( res ); } } } // selector extensions var ext = _ext.selector[ type ]; if ( ext.length ) { for ( i=0, ien=ext.length ; i<ien ; i++ ) { out = ext[i]( settings, opts, out ); } } return _unique( out ); }; var _selector_opts = function ( opts ) { if ( ! opts ) { opts = {}; } // Backwards compatibility for 1.9- which used the terminology filter rather // than search if ( opts.filter && opts.search === undefined ) { opts.search = opts.filter; } return $.extend( { search: 'none', order: 'current', page: 'all' }, opts ); }; var _selector_first = function ( inst ) { // Reduce the API instance to the first item found for ( var i=0, ien=inst.length ; i<ien ; i++ ) { if ( inst[i].length > 0 ) { // Assign the first element to the first item in the instance // and truncate the instance and context inst[0] = inst[i]; inst[0].length = 1; inst.length = 1; inst.context = [ inst.context[i] ]; return inst; } } // Not found - return an empty instance inst.length = 0; return inst; }; var _selector_row_indexes = function ( settings, opts ) { var i, ien, tmp, a=[], displayFiltered = settings.aiDisplay, displayMaster = settings.aiDisplayMaster; var search = opts.search, // none, applied, removed order = opts.order, // applied, current, index (original - compatibility with 1.9) page = opts.page; // all, current if ( _fnDataSource( settings ) == 'ssp' ) { // In server-side processing mode, most options are irrelevant since // rows not shown don't exist and the index order is the applied order // Removed is a special case - for consistency just return an empty // array return search === 'removed' ? [] : _range( 0, displayMaster.length ); } else if ( page == 'current' ) { // Current page implies that order=current and fitler=applied, since it is // fairly senseless otherwise, regardless of what order and search actually // are for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) { a.push( displayFiltered[i] ); } } else if ( order == 'current' || order == 'applied' ) { if ( search == 'none') { a = displayMaster.slice(); } else if ( search == 'applied' ) { a = displayFiltered.slice(); } else if ( search == 'removed' ) { // O(n+m) solution by creating a hash map var displayFilteredMap = {}; for ( var i=0, ien=displayFiltered.length ; i<ien ; i++ ) { displayFilteredMap[displayFiltered[i]] = null; } a = $.map( displayMaster, function (el) { return ! displayFilteredMap.hasOwnProperty(el) ? el : null; } ); } } else if ( order == 'index' || order == 'original' ) { for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) { if ( search == 'none' ) { a.push( i ); } else { // applied | removed tmp = $.inArray( i, displayFiltered ); if ((tmp === -1 && search == 'removed') || (tmp >= 0 && search == 'applied') ) { a.push( i ); } } } } return a; }; /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Rows * * {} - no selector - use all available rows * {integer} - row aoData index * {node} - TR node * {string} - jQuery selector to apply to the TR elements * {array} - jQuery array of nodes, or simply an array of TR nodes * */ var __row_selector = function ( settings, selector, opts ) { var rows; var run = function ( sel ) { var selInt = _intVal( sel ); var i, ien; var aoData = settings.aoData; // Short cut - selector is a number and no options provided (default is // all records, so no need to check if the index is in there, since it // must be - dev error if the index doesn't exist). if ( selInt !== null && ! opts ) { return [ selInt ]; } if ( ! rows ) { rows = _selector_row_indexes( settings, opts ); } if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) { // Selector - integer return [ selInt ]; } else if ( sel === null || sel === undefined || sel === '' ) { // Selector - none return rows; } // Selector - function if ( typeof sel === 'function' ) { return $.map( rows, function (idx) { var row = aoData[ idx ]; return sel( idx, row._aData, row.nTr ) ? idx : null; } ); } // Selector - node if ( sel.nodeName ) { var rowIdx = sel._DT_RowIndex; // Property added by DT for fast lookup var cellIdx = sel._DT_CellIndex; if ( rowIdx !== undefined ) { // Make sure that the row is actually still present in the table return aoData[ rowIdx ] && aoData[ rowIdx ].nTr === sel ? [ rowIdx ] : []; } else if ( cellIdx ) { return aoData[ cellIdx.row ] && aoData[ cellIdx.row ].nTr === sel.parentNode ? [ cellIdx.row ] : []; } else { var host = $(sel).closest('*[data-dt-row]'); return host.length ? [ host.data('dt-row') ] : []; } } // ID selector. Want to always be able to select rows by id, regardless // of if the tr element has been created or not, so can't rely upon // jQuery here - hence a custom implementation. This does not match // Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything, // but to select it using a CSS selector engine (like Sizzle or // querySelect) it would need to need to be escaped for some characters. // DataTables simplifies this for row selectors since you can select // only a row. A # indicates an id any anything that follows is the id - // unescaped. if ( typeof sel === 'string' && sel.charAt(0) === '#' ) { // get row index from id var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ]; if ( rowObj !== undefined ) { return [ rowObj.idx ]; } // need to fall through to jQuery in case there is DOM id that // matches } // Get nodes in the order from the `rows` array with null values removed var nodes = _removeEmpty( _pluck_order( settings.aoData, rows, 'nTr' ) ); // Selector - jQuery selector string, array of nodes or jQuery object/ // As jQuery's .filter() allows jQuery objects to be passed in filter, // it also allows arrays, so this will cope with all three options return $(nodes) .filter( sel ) .map( function () { return this._DT_RowIndex; } ) .toArray(); }; return _selector_run( 'row', selector, run, settings, opts ); }; _api_register( 'rows()', function ( selector, opts ) { // argument shifting if ( selector === undefined ) { selector = ''; } else if ( $.isPlainObject( selector ) ) { opts = selector; selector = ''; } opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __row_selector( settings, selector, opts ); }, 1 ); // Want argument shifting here and in __row_selector? inst.selector.rows = selector; inst.selector.opts = opts; return inst; } ); _api_register( 'rows().nodes()', function () { return this.iterator( 'row', function ( settings, row ) { return settings.aoData[ row ].nTr || undefined; }, 1 ); } ); _api_register( 'rows().data()', function () { return this.iterator( true, 'rows', function ( settings, rows ) { return _pluck_order( settings.aoData, rows, '_aData' ); }, 1 ); } ); _api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) { return this.iterator( 'row', function ( settings, row ) { var r = settings.aoData[ row ]; return type === 'search' ? r._aFilterData : r._aSortData; }, 1 ); } ); _api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) { return this.iterator( 'row', function ( settings, row ) { _fnInvalidate( settings, row, src ); } ); } ); _api_registerPlural( 'rows().indexes()', 'row().index()', function () { return this.iterator( 'row', function ( settings, row ) { return row; }, 1 ); } ); _api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) { var a = []; var context = this.context; // `iterator` will drop undefined values, but in this case we want them for ( var i=0, ien=context.length ; i<ien ; i++ ) { for ( var j=0, jen=this[i].length ; j<jen ; j++ ) { var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData ); a.push( (hash === true ? '#' : '' )+ id ); } } return new _Api( context, a ); } ); _api_registerPlural( 'rows().remove()', 'row().remove()', function () { var that = this; this.iterator( 'row', function ( settings, row, thatIdx ) { var data = settings.aoData; var rowData = data[ row ]; var i, ien, j, jen; var loopRow, loopCells; data.splice( row, 1 ); // Update the cached indexes for ( i=0, ien=data.length ; i<ien ; i++ ) { loopRow = data[i]; loopCells = loopRow.anCells; // Rows if ( loopRow.nTr !== null ) { loopRow.nTr._DT_RowIndex = i; } // Cells if ( loopCells !== null ) { for ( j=0, jen=loopCells.length ; j<jen ; j++ ) { loopCells[j]._DT_CellIndex.row = i; } } } // Delete from the display arrays _fnDeleteIndex( settings.aiDisplayMaster, row ); _fnDeleteIndex( settings.aiDisplay, row ); _fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes // For server-side processing tables - subtract the deleted row from the count if ( settings._iRecordsDisplay > 0 ) { settings._iRecordsDisplay--; } // Check for an 'overflow' they case for displaying the table _fnLengthOverflow( settings ); // Remove the row's ID reference if there is one var id = settings.rowIdFn( rowData._aData ); if ( id !== undefined ) { delete settings.aIds[ id ]; } } ); this.iterator( 'table', function ( settings ) { for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) { settings.aoData[i].idx = i; } } ); return this; } ); _api_register( 'rows.add()', function ( rows ) { var newRows = this.iterator( 'table', function ( settings ) { var row, i, ien; var out = []; for ( i=0, ien=rows.length ; i<ien ; i++ ) { row = rows[i]; if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { out.push( _fnAddTr( settings, row )[0] ); } else { out.push( _fnAddData( settings, row ) ); } } return out; }, 1 ); // Return an Api.rows() extended instance, so rows().nodes() etc can be used var modRows = this.rows( -1 ); modRows.pop(); $.merge( modRows, newRows ); return modRows; } ); /** * */ _api_register( 'row()', function ( selector, opts ) { return _selector_first( this.rows( selector, opts ) ); } ); _api_register( 'row().data()', function ( data ) { var ctx = this.context; if ( data === undefined ) { // Get return ctx.length && this.length ? ctx[0].aoData[ this[0] ]._aData : undefined; } // Set var row = ctx[0].aoData[ this[0] ]; row._aData = data; // If the DOM has an id, and the data source is an array if ( $.isArray( data ) && row.nTr && row.nTr.id ) { _fnSetObjectDataFn( ctx[0].rowId )( data, row.nTr.id ); } // Automatically invalidate _fnInvalidate( ctx[0], this[0], 'data' ); return this; } ); _api_register( 'row().node()', function () { var ctx = this.context; return ctx.length && this.length ? ctx[0].aoData[ this[0] ].nTr || null : null; } ); _api_register( 'row.add()', function ( row ) { // Allow a jQuery object to be passed in - only a single row is added from // it though - the first element in the set if ( row instanceof $ && row.length ) { row = row[0]; } var rows = this.iterator( 'table', function ( settings ) { if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) { return _fnAddTr( settings, row )[0]; } return _fnAddData( settings, row ); } ); // Return an Api.rows() extended instance, with the newly added row selected return this.row( rows[0] ); } ); var __details_add = function ( ctx, row, data, klass ) { // Convert to array of TR elements var rows = []; var addRow = function ( r, k ) { // Recursion to allow for arrays of jQuery objects if ( $.isArray( r ) || r instanceof $ ) { for ( var i=0, ien=r.length ; i<ien ; i++ ) { addRow( r[i], k ); } return; } // If we get a TR element, then just add it directly - up to the dev // to add the correct number of columns etc if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) { rows.push( r ); } else { // Otherwise create a row with a wrapper var created = $('<tr><td/></tr>').addClass( k ); $('td', created) .addClass( k ) .html( r ) [0].colSpan = _fnVisbleColumns( ctx ); rows.push( created[0] ); } }; addRow( data, klass ); if ( row._details ) { row._details.detach(); } row._details = $(rows); // If the children were already shown, that state should be retained if ( row._detailsShow ) { row._details.insertAfter( row.nTr ); } }; var __details_remove = function ( api, idx ) { var ctx = api.context; if ( ctx.length ) { var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ]; if ( row && row._details ) { row._details.remove(); row._detailsShow = undefined; row._details = undefined; } } }; var __details_display = function ( api, show ) { var ctx = api.context; if ( ctx.length && api.length ) { var row = ctx[0].aoData[ api[0] ]; if ( row._details ) { row._detailsShow = show; if ( show ) { row._details.insertAfter( row.nTr ); } else { row._details.detach(); } __details_events( ctx[0] ); } } }; var __details_events = function ( settings ) { var api = new _Api( settings ); var namespace = '.dt.DT_details'; var drawEvent = 'draw'+namespace; var colvisEvent = 'column-visibility'+namespace; var destroyEvent = 'destroy'+namespace; var data = settings.aoData; api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent ); if ( _pluck( data, '_details' ).length > 0 ) { // On each draw, insert the required elements into the document api.on( drawEvent, function ( e, ctx ) { if ( settings !== ctx ) { return; } api.rows( {page:'current'} ).eq(0).each( function (idx) { // Internal data grab var row = data[ idx ]; if ( row._detailsShow ) { row._details.insertAfter( row.nTr ); } } ); } ); // Column visibility change - update the colspan api.on( colvisEvent, function ( e, ctx, idx, vis ) { if ( settings !== ctx ) { return; } // Update the colspan for the details rows (note, only if it already has // a colspan) var row, visible = _fnVisbleColumns( ctx ); for ( var i=0, ien=data.length ; i<ien ; i++ ) { row = data[i]; if ( row._details ) { row._details.children('td[colspan]').attr('colspan', visible ); } } } ); // Table destroyed - nuke any child rows api.on( destroyEvent, function ( e, ctx ) { if ( settings !== ctx ) { return; } for ( var i=0, ien=data.length ; i<ien ; i++ ) { if ( data[i]._details ) { __details_remove( api, i ); } } } ); } }; // Strings for the method names to help minification var _emp = ''; var _child_obj = _emp+'row().child'; var _child_mth = _child_obj+'()'; // data can be: // tr // string // jQuery or array of any of the above _api_register( _child_mth, function ( data, klass ) { var ctx = this.context; if ( data === undefined ) { // get return ctx.length && this.length ? ctx[0].aoData[ this[0] ]._details : undefined; } else if ( data === true ) { // show this.child.show(); } else if ( data === false ) { // remove __details_remove( this ); } else if ( ctx.length && this.length ) { // set __details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass ); } return this; } ); _api_register( [ _child_obj+'.show()', _child_mth+'.show()' // only when `child()` was called with parameters (without ], function ( show ) { // it returns an object and this method is not executed) __details_display( this, true ); return this; } ); _api_register( [ _child_obj+'.hide()', _child_mth+'.hide()' // only when `child()` was called with parameters (without ], function () { // it returns an object and this method is not executed) __details_display( this, false ); return this; } ); _api_register( [ _child_obj+'.remove()', _child_mth+'.remove()' // only when `child()` was called with parameters (without ], function () { // it returns an object and this method is not executed) __details_remove( this ); return this; } ); _api_register( _child_obj+'.isShown()', function () { var ctx = this.context; if ( ctx.length && this.length ) { // _detailsShown as false or undefined will fall through to return false return ctx[0].aoData[ this[0] ]._detailsShow || false; } return false; } ); /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Columns * * {integer} - column index (>=0 count from left, <0 count from right) * "{integer}:visIdx" - visible column index (i.e. translate to column index) (>=0 count from left, <0 count from right) * "{integer}:visible" - alias for {integer}:visIdx (>=0 count from left, <0 count from right) * "{string}:name" - column name * "{string}" - jQuery selector on column header nodes * */ // can be an array of these items, comma separated list, or an array of comma // separated lists var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/; // r1 and r2 are redundant - but it means that the parameters match for the // iterator callback in columns().data() var __columnData = function ( settings, column, r1, r2, rows ) { var a = []; for ( var row=0, ien=rows.length ; row<ien ; row++ ) { a.push( _fnGetCellData( settings, rows[row], column ) ); } return a; }; var __column_selector = function ( settings, selector, opts ) { var columns = settings.aoColumns, names = _pluck( columns, 'sName' ), nodes = _pluck( columns, 'nTh' ); var run = function ( s ) { var selInt = _intVal( s ); // Selector - all if ( s === '' ) { return _range( columns.length ); } // Selector - index if ( selInt !== null ) { return [ selInt >= 0 ? selInt : // Count from left columns.length + selInt // Count from right (+ because its a negative value) ]; } // Selector = function if ( typeof s === 'function' ) { var rows = _selector_row_indexes( settings, opts ); return $.map( columns, function (col, idx) { return s( idx, __columnData( settings, idx, 0, 0, rows ), nodes[ idx ] ) ? idx : null; } ); } // jQuery or string selector var match = typeof s === 'string' ? s.match( __re_column_selector ) : ''; if ( match ) { switch( match[2] ) { case 'visIdx': case 'visible': var idx = parseInt( match[1], 10 ); // Visible index given, convert to column index if ( idx < 0 ) { // Counting from the right var visColumns = $.map( columns, function (col,i) { return col.bVisible ? i : null; } ); return [ visColumns[ visColumns.length + idx ] ]; } // Counting from the left return [ _fnVisibleToColumnIndex( settings, idx ) ]; case 'name': // match by name. `names` is column index complete and in order return $.map( names, function (name, i) { return name === match[1] ? i : null; } ); default: return []; } } // Cell in the table body if ( s.nodeName && s._DT_CellIndex ) { return [ s._DT_CellIndex.column ]; } // jQuery selector on the TH elements for the columns var jqResult = $( nodes ) .filter( s ) .map( function () { return $.inArray( this, nodes ); // `nodes` is column index complete and in order } ) .toArray(); if ( jqResult.length || ! s.nodeName ) { return jqResult; } // Otherwise a node which might have a `dt-column` data attribute, or be // a child or such an element var host = $(s).closest('*[data-dt-column]'); return host.length ? [ host.data('dt-column') ] : []; }; return _selector_run( 'column', selector, run, settings, opts ); }; var __setColumnVis = function ( settings, column, vis ) { var cols = settings.aoColumns, col = cols[ column ], data = settings.aoData, row, cells, i, ien, tr; // Get if ( vis === undefined ) { return col.bVisible; } // Set // No change if ( col.bVisible === vis ) { return; } if ( vis ) { // Insert column // Need to decide if we should use appendChild or insertBefore var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 ); for ( i=0, ien=data.length ; i<ien ; i++ ) { tr = data[i].nTr; cells = data[i].anCells; if ( tr ) { // insertBefore can act like appendChild if 2nd arg is null tr.insertBefore( cells[ column ], cells[ insertBefore ] || null ); } } } else { // Remove column $( _pluck( settings.aoData, 'anCells', column ) ).detach(); } // Common actions col.bVisible = vis; }; _api_register( 'columns()', function ( selector, opts ) { // argument shifting if ( selector === undefined ) { selector = ''; } else if ( $.isPlainObject( selector ) ) { opts = selector; selector = ''; } opts = _selector_opts( opts ); var inst = this.iterator( 'table', function ( settings ) { return __column_selector( settings, selector, opts ); }, 1 ); // Want argument shifting here and in _row_selector? inst.selector.cols = selector; inst.selector.opts = opts; return inst; } ); _api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTh; }, 1 ); } ); _api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].nTf; }, 1 ); } ); _api_registerPlural( 'columns().data()', 'column().data()', function () { return this.iterator( 'column-rows', __columnData, 1 ); } ); _api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () { return this.iterator( 'column', function ( settings, column ) { return settings.aoColumns[column].mData; }, 1 ); } ); _api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, type === 'search' ? '_aFilterData' : '_aSortData', column ); }, 1 ); } ); _api_registerPlural( 'columns().nodes()', 'column().nodes()', function () { return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) { return _pluck_order( settings.aoData, rows, 'anCells', column ) ; }, 1 ); } ); _api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) { var that = this; var ret = this.iterator( 'column', function ( settings, column ) { if ( vis === undefined ) { return settings.aoColumns[ column ].bVisible; } // else __setColumnVis( settings, column, vis ); } ); // Group the column visibility changes if ( vis !== undefined ) { this.iterator( 'table', function ( settings ) { // Redraw the header after changes _fnDrawHead( settings, settings.aoHeader ); _fnDrawHead( settings, settings.aoFooter ); // Update colspan for no records display. Child rows and extensions will use their own // listeners to do this - only need to update the empty table item here if ( ! settings.aiDisplay.length ) { $(settings.nTBody).find('td[colspan]').attr('colspan', _fnVisbleColumns(settings)); } _fnSaveState( settings ); // Second loop once the first is done for events that.iterator( 'column', function ( settings, column ) { _fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] ); } ); if ( calc === undefined || calc ) { that.columns.adjust(); } }); } return ret; } ); _api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) { return this.iterator( 'column', function ( settings, column ) { return type === 'visible' ? _fnColumnIndexToVisible( settings, column ) : column; }, 1 ); } ); _api_register( 'columns.adjust()', function () { return this.iterator( 'table', function ( settings ) { _fnAdjustColumnSizing( settings ); }, 1 ); } ); _api_register( 'column.index()', function ( type, idx ) { if ( this.context.length !== 0 ) { var ctx = this.context[0]; if ( type === 'fromVisible' || type === 'toData' ) { return _fnVisibleToColumnIndex( ctx, idx ); } else if ( type === 'fromData' || type === 'toVisible' ) { return _fnColumnIndexToVisible( ctx, idx ); } } } ); _api_register( 'column()', function ( selector, opts ) { return _selector_first( this.columns( selector, opts ) ); } ); var __cell_selector = function ( settings, selector, opts ) { var data = settings.aoData; var rows = _selector_row_indexes( settings, opts ); var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) ); var allCells = $( [].concat.apply([], cells) ); var row; var columns = settings.aoColumns.length; var a, i, ien, j, o, host; var run = function ( s ) { var fnSelector = typeof s === 'function'; if ( s === null || s === undefined || fnSelector ) { // All cells and function selectors a = []; for ( i=0, ien=rows.length ; i<ien ; i++ ) { row = rows[i]; for ( j=0 ; j<columns ; j++ ) { o = { row: row, column: j }; if ( fnSelector ) { // Selector - function host = data[ row ]; if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) { a.push( o ); } } else { // Selector - all a.push( o ); } } } return a; } // Selector - index if ( $.isPlainObject( s ) ) { // Valid cell index and its in the array of selectable rows return s.column !== undefined && s.row !== undefined && $.inArray( s.row, rows ) !== -1 ? [s] : []; } // Selector - jQuery filtered cells var jqResult = allCells .filter( s ) .map( function (i, el) { return { // use a new object, in case someone changes the values row: el._DT_CellIndex.row, column: el._DT_CellIndex.column }; } ) .toArray(); if ( jqResult.length || ! s.nodeName ) { return jqResult; } // Otherwise the selector is a node, and there is one last option - the // element might be a child of an element which has dt-row and dt-column // data attributes host = $(s).closest('*[data-dt-row]'); return host.length ? [ { row: host.data('dt-row'), column: host.data('dt-column') } ] : []; }; return _selector_run( 'cell', selector, run, settings, opts ); }; _api_register( 'cells()', function ( rowSelector, columnSelector, opts ) { // Argument shifting if ( $.isPlainObject( rowSelector ) ) { // Indexes if ( rowSelector.row === undefined ) { // Selector options in first parameter opts = rowSelector; rowSelector = null; } else { // Cell index objects in first parameter opts = columnSelector; columnSelector = null; } } if ( $.isPlainObject( columnSelector ) ) { opts = columnSelector; columnSelector = null; } // Cell selector if ( columnSelector === null || columnSelector === undefined ) { return this.iterator( 'table', function ( settings ) { return __cell_selector( settings, rowSelector, _selector_opts( opts ) ); } ); } // The default built in options need to apply to row and columns var internalOpts = opts ? { page: opts.page, order: opts.order, search: opts.search } : {}; // Row + column selector var columns = this.columns( columnSelector, internalOpts ); var rows = this.rows( rowSelector, internalOpts ); var i, ien, j, jen; var cellsNoOpts = this.iterator( 'table', function ( settings, idx ) { var a = []; for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) { for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) { a.push( { row: rows[idx][i], column: columns[idx][j] } ); } } return a; }, 1 ); // There is currently only one extension which uses a cell selector extension // It is a _major_ performance drag to run this if it isn't needed, so this is // an extension specific check at the moment var cells = opts && opts.selected ? this.cells( cellsNoOpts, opts ) : cellsNoOpts; $.extend( cells.selector, { cols: columnSelector, rows: rowSelector, opts: opts } ); return cells; } ); _api_registerPlural( 'cells().nodes()', 'cell().node()', function () { return this.iterator( 'cell', function ( settings, row, column ) { var data = settings.aoData[ row ]; return data && data.anCells ? data.anCells[ column ] : undefined; }, 1 ); } ); _api_register( 'cells().data()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column ); }, 1 ); } ); _api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) { type = type === 'search' ? '_aFilterData' : '_aSortData'; return this.iterator( 'cell', function ( settings, row, column ) { return settings.aoData[ row ][ type ][ column ]; }, 1 ); } ); _api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) { return this.iterator( 'cell', function ( settings, row, column ) { return _fnGetCellData( settings, row, column, type ); }, 1 ); } ); _api_registerPlural( 'cells().indexes()', 'cell().index()', function () { return this.iterator( 'cell', function ( settings, row, column ) { return { row: row, column: column, columnVisible: _fnColumnIndexToVisible( settings, column ) }; }, 1 ); } ); _api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) { return this.iterator( 'cell', function ( settings, row, column ) { _fnInvalidate( settings, row, src, column ); } ); } ); _api_register( 'cell()', function ( rowSelector, columnSelector, opts ) { return _selector_first( this.cells( rowSelector, columnSelector, opts ) ); } ); _api_register( 'cell().data()', function ( data ) { var ctx = this.context; var cell = this[0]; if ( data === undefined ) { // Get return ctx.length && cell.length ? _fnGetCellData( ctx[0], cell[0].row, cell[0].column ) : undefined; } // Set _fnSetCellData( ctx[0], cell[0].row, cell[0].column, data ); _fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column ); return this; } ); /** * Get current ordering (sorting) that has been applied to the table. * * @returns {array} 2D array containing the sorting information for the first * table in the current context. Each element in the parent array represents * a column being sorted upon (i.e. multi-sorting with two columns would have * 2 inner arrays). The inner arrays may have 2 or 3 elements. The first is * the column index that the sorting condition applies to, the second is the * direction of the sort (`desc` or `asc`) and, optionally, the third is the * index of the sorting order from the `column.sorting` initialisation array. *//** * Set the ordering for the table. * * @param {integer} order Column index to sort upon. * @param {string} direction Direction of the sort to be applied (`asc` or `desc`) * @returns {DataTables.Api} this *//** * Set the ordering for the table. * * @param {array} order 1D array of sorting information to be applied. * @param {array} [...] Optional additional sorting conditions * @returns {DataTables.Api} this *//** * Set the ordering for the table. * * @param {array} order 2D array of sorting information to be applied. * @returns {DataTables.Api} this */ _api_register( 'order()', function ( order, dir ) { var ctx = this.context; if ( order === undefined ) { // get return ctx.length !== 0 ? ctx[0].aaSorting : undefined; } // set if ( typeof order === 'number' ) { // Simple column / direction passed in order = [ [ order, dir ] ]; } else if ( order.length && ! $.isArray( order[0] ) ) { // Arguments passed in (list of 1D arrays) order = Array.prototype.slice.call( arguments ); } // otherwise a 2D array was passed in return this.iterator( 'table', function ( settings ) { settings.aaSorting = order.slice(); } ); } ); /** * Attach a sort listener to an element for a given column * * @param {node|jQuery|string} node Identifier for the element(s) to attach the * listener to. This can take the form of a single DOM node, a jQuery * collection of nodes or a jQuery selector which will identify the node(s). * @param {integer} column the column that a click on this node will sort on * @param {function} [callback] callback function when sort is run * @returns {DataTables.Api} this */ _api_register( 'order.listener()', function ( node, column, callback ) { return this.iterator( 'table', function ( settings ) { _fnSortAttachListener( settings, node, column, callback ); } ); } ); _api_register( 'order.fixed()', function ( set ) { if ( ! set ) { var ctx = this.context; var fixed = ctx.length ? ctx[0].aaSortingFixed : undefined; return $.isArray( fixed ) ? { pre: fixed } : fixed; } return this.iterator( 'table', function ( settings ) { settings.aaSortingFixed = $.extend( true, {}, set ); } ); } ); // Order by the selected column(s) _api_register( [ 'columns().order()', 'column().order()' ], function ( dir ) { var that = this; return this.iterator( 'table', function ( settings, i ) { var sort = []; $.each( that[i], function (j, col) { sort.push( [ col, dir ] ); } ); settings.aaSorting = sort; } ); } ); _api_register( 'search()', function ( input, regex, smart, caseInsen ) { var ctx = this.context; if ( input === undefined ) { // get return ctx.length !== 0 ? ctx[0].oPreviousSearch.sSearch : undefined; } // set return this.iterator( 'table', function ( settings ) { if ( ! settings.oFeatures.bFilter ) { return; } _fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, { "sSearch": input+"", "bRegex": regex === null ? false : regex, "bSmart": smart === null ? true : smart, "bCaseInsensitive": caseInsen === null ? true : caseInsen } ), 1 ); } ); } ); _api_registerPlural( 'columns().search()', 'column().search()', function ( input, regex, smart, caseInsen ) { return this.iterator( 'column', function ( settings, column ) { var preSearch = settings.aoPreSearchCols; if ( input === undefined ) { // get return preSearch[ column ].sSearch; } // set if ( ! settings.oFeatures.bFilter ) { return; } $.extend( preSearch[ column ], { "sSearch": input+"", "bRegex": regex === null ? false : regex, "bSmart": smart === null ? true : smart, "bCaseInsensitive": caseInsen === null ? true : caseInsen } ); _fnFilterComplete( settings, settings.oPreviousSearch, 1 ); } ); } ); /* * State API methods */ _api_register( 'state()', function () { return this.context.length ? this.context[0].oSavedState : null; } ); _api_register( 'state.clear()', function () { return this.iterator( 'table', function ( settings ) { // Save an empty object settings.fnStateSaveCallback.call( settings.oInstance, settings, {} ); } ); } ); _api_register( 'state.loaded()', function () { return this.context.length ? this.context[0].oLoadedState : null; } ); _api_register( 'state.save()', function () { return this.iterator( 'table', function ( settings ) { _fnSaveState( settings ); } ); } ); /** * Provide a common method for plug-ins to check the version of DataTables being * used, in order to ensure compatibility. * * @param {string} version Version string to check for, in the format "X.Y.Z". * Note that the formats "X" and "X.Y" are also acceptable. * @returns {boolean} true if this version of DataTables is greater or equal to * the required version, or false if this version of DataTales is not * suitable * @static * @dtopt API-Static * * @example * alert( $.fn.dataTable.versionCheck( '1.9.0' ) ); */ DataTable.versionCheck = DataTable.fnVersionCheck = function( version ) { var aThis = DataTable.version.split('.'); var aThat = version.split('.'); var iThis, iThat; for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) { iThis = parseInt( aThis[i], 10 ) || 0; iThat = parseInt( aThat[i], 10 ) || 0; // Parts are the same, keep comparing if (iThis === iThat) { continue; } // Parts are different, return immediately return iThis > iThat; } return true; }; /** * Check if a `<table>` node is a DataTable table already or not. * * @param {node|jquery|string} table Table node, jQuery object or jQuery * selector for the table to test. Note that if more than more than one * table is passed on, only the first will be checked * @returns {boolean} true the table given is a DataTable, or false otherwise * @static * @dtopt API-Static * * @example * if ( ! $.fn.DataTable.isDataTable( '#example' ) ) { * $('#example').dataTable(); * } */ DataTable.isDataTable = DataTable.fnIsDataTable = function ( table ) { var t = $(table).get(0); var is = false; if ( table instanceof DataTable.Api ) { return true; } $.each( DataTable.settings, function (i, o) { var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null; var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null; if ( o.nTable === t || head === t || foot === t ) { is = true; } } ); return is; }; /** * Get all DataTable tables that have been initialised - optionally you can * select to get only currently visible tables. * * @param {boolean} [visible=false] Flag to indicate if you want all (default) * or visible tables only. * @returns {array} Array of `table` nodes (not DataTable instances) which are * DataTables * @static * @dtopt API-Static * * @example * $.each( $.fn.dataTable.tables(true), function () { * $(table).DataTable().columns.adjust(); * } ); */ DataTable.tables = DataTable.fnTables = function ( visible ) { var api = false; if ( $.isPlainObject( visible ) ) { api = visible.api; visible = visible.visible; } var a = $.map( DataTable.settings, function (o) { if ( !visible || (visible && $(o.nTable).is(':visible')) ) { return o.nTable; } } ); return api ? new _Api( a ) : a; }; /** * Convert from camel case parameters to Hungarian notation. This is made public * for the extensions to provide the same ability as DataTables core to accept * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase * parameters. * * @param {object} src The model object which holds all parameters that can be * mapped. * @param {object} user The object to convert from camel case to Hungarian. * @param {boolean} force When set to `true`, properties which already have a * Hungarian value in the `user` object will be overwritten. Otherwise they * won't be. */ DataTable.camelToHungarian = _fnCamelToHungarian; /** * */ _api_register( '$()', function ( selector, opts ) { var rows = this.rows( opts ).nodes(), // Get all rows jqRows = $(rows); return $( [].concat( jqRows.filter( selector ).toArray(), jqRows.find( selector ).toArray() ) ); } ); // jQuery functions to operate on the tables $.each( [ 'on', 'one', 'off' ], function (i, key) { _api_register( key+'()', function ( /* event, handler */ ) { var args = Array.prototype.slice.call(arguments); // Add the `dt` namespace automatically if it isn't already present args[0] = $.map( args[0].split( /\s/ ), function ( e ) { return ! e.match(/\.dt\b/) ? e+'.dt' : e; } ).join( ' ' ); var inst = $( this.tables().nodes() ); inst[key].apply( inst, args ); return this; } ); } ); _api_register( 'clear()', function () { return this.iterator( 'table', function ( settings ) { _fnClearTable( settings ); } ); } ); _api_register( 'settings()', function () { return new _Api( this.context, this.context ); } ); _api_register( 'init()', function () { var ctx = this.context; return ctx.length ? ctx[0].oInit : null; } ); _api_register( 'data()', function () { return this.iterator( 'table', function ( settings ) { return _pluck( settings.aoData, '_aData' ); } ).flatten(); } ); _api_register( 'destroy()', function ( remove ) { remove = remove || false; return this.iterator( 'table', function ( settings ) { var orig = settings.nTableWrapper.parentNode; var classes = settings.oClasses; var table = settings.nTable; var tbody = settings.nTBody; var thead = settings.nTHead; var tfoot = settings.nTFoot; var jqTable = $(table); var jqTbody = $(tbody); var jqWrapper = $(settings.nTableWrapper); var rows = $.map( settings.aoData, function (r) { return r.nTr; } ); var i, ien; // Flag to note that the table is currently being destroyed - no action // should be taken settings.bDestroying = true; // Fire off the destroy callbacks for plug-ins etc _fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] ); // If not being removed from the document, make all columns visible if ( ! remove ) { new _Api( settings ).columns().visible( true ); } // Blitz all `DT` namespaced events (these are internal events, the // lowercase, `dt` events are user subscribed and they are responsible // for removing them jqWrapper.off('.DT').find(':not(tbody *)').off('.DT'); $(window).off('.DT-'+settings.sInstance); // When scrolling we had to break the table up - restore it if ( table != thead.parentNode ) { jqTable.children('thead').detach(); jqTable.append( thead ); } if ( tfoot && table != tfoot.parentNode ) { jqTable.children('tfoot').detach(); jqTable.append( tfoot ); } settings.aaSorting = []; settings.aaSortingFixed = []; _fnSortingClasses( settings ); $( rows ).removeClass( settings.asStripeClasses.join(' ') ); $('th, td', thead).removeClass( classes.sSortable+' '+ classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone ); // Add the TR elements back into the table in their original order jqTbody.children().detach(); jqTbody.append( rows ); // Remove the DataTables generated nodes, events and classes var removedMethod = remove ? 'remove' : 'detach'; jqTable[ removedMethod ](); jqWrapper[ removedMethod ](); // If we need to reattach the table to the document if ( ! remove && orig ) { // insertBefore acts like appendChild if !arg[1] orig.insertBefore( table, settings.nTableReinsertBefore ); // Restore the width of the original table - was read from the style property, // so we can restore directly to that jqTable .css( 'width', settings.sDestroyWidth ) .removeClass( classes.sTable ); // If the were originally stripe classes - then we add them back here. // Note this is not fool proof (for example if not all rows had stripe // classes - but it's a good effort without getting carried away ien = settings.asDestroyStripes.length; if ( ien ) { jqTbody.children().each( function (i) { $(this).addClass( settings.asDestroyStripes[i % ien] ); } ); } } /* Remove the settings object from the settings array */ var idx = $.inArray( settings, DataTable.settings ); if ( idx !== -1 ) { DataTable.settings.splice( idx, 1 ); } } ); } ); // Add the `every()` method for rows, columns and cells in a compact form $.each( [ 'column', 'row', 'cell' ], function ( i, type ) { _api_register( type+'s().every()', function ( fn ) { var opts = this.selector.opts; var api = this; return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) { // Rows and columns: // arg1 - index // arg2 - table counter // arg3 - loop counter // arg4 - undefined // Cells: // arg1 - row index // arg2 - column index // arg3 - table counter // arg4 - loop counter fn.call( api[ type ]( arg1, type==='cell' ? arg2 : opts, type==='cell' ? opts : undefined ), arg1, arg2, arg3, arg4 ); } ); } ); } ); // i18n method for extensions to be able to use the language object from the // DataTable _api_register( 'i18n()', function ( token, def, plural ) { var ctx = this.context[0]; var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage ); if ( resolved === undefined ) { resolved = def; } if ( plural !== undefined && $.isPlainObject( resolved ) ) { resolved = resolved[ plural ] !== undefined ? resolved[ plural ] : resolved._; } return resolved.replace( '%d', plural ); // nb: plural might be undefined, } ); /** * Version string for plug-ins to check compatibility. Allowed format is * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used * only for non-release builds. See http://semver.org/ for more information. * @member * @type string * @default Version number */ DataTable.version = "1.10.21"; /** * Private data store, containing all of the settings objects that are * created for the tables on a given page. * * Note that the `DataTable.settings` object is aliased to * `jQuery.fn.dataTableExt` through which it may be accessed and * manipulated, or `jQuery.fn.dataTable.settings`. * @member * @type array * @default [] * @private */ DataTable.settings = []; /** * Object models container, for the various models that DataTables has * available to it. These models define the objects that are used to hold * the active state and configuration of the table. * @namespace */ DataTable.models = {}; /** * Template object for the way in which DataTables holds information about * search information for the global filter and individual column filters. * @namespace */ DataTable.models.oSearch = { /** * Flag to indicate if the filtering should be case insensitive or not * @type boolean * @default true */ "bCaseInsensitive": true, /** * Applied search term * @type string * @default <i>Empty string</i> */ "sSearch": "", /** * Flag to indicate if the search term should be interpreted as a * regular expression (true) or not (false) and therefore and special * regex characters escaped. * @type boolean * @default false */ "bRegex": false, /** * Flag to indicate if DataTables is to use its smart filtering or not. * @type boolean * @default true */ "bSmart": true }; /** * Template object for the way in which DataTables holds information about * each individual row. This is the object format used for the settings * aoData array. * @namespace */ DataTable.models.oRow = { /** * TR element for the row * @type node * @default null */ "nTr": null, /** * Array of TD elements for each row. This is null until the row has been * created. * @type array nodes * @default [] */ "anCells": null, /** * Data object from the original data source for the row. This is either * an array if using the traditional form of DataTables, or an object if * using mData options. The exact type will depend on the passed in * data from the data source, or will be an array if using DOM a data * source. * @type array|object * @default [] */ "_aData": [], /** * Sorting data cache - this array is ostensibly the same length as the * number of columns (although each index is generated only as it is * needed), and holds the data that is used for sorting each column in the * row. We do this cache generation at the start of the sort in order that * the formatting of the sort data need be done only once for each cell * per sort. This array should not be read from or written to by anything * other than the master sorting methods. * @type array * @default null * @private */ "_aSortData": null, /** * Per cell filtering data cache. As per the sort data cache, used to * increase the performance of the filtering in DataTables * @type array * @default null * @private */ "_aFilterData": null, /** * Filtering data cache. This is the same as the cell filtering cache, but * in this case a string rather than an array. This is easily computed with * a join on `_aFilterData`, but is provided as a cache so the join isn't * needed on every search (memory traded for performance) * @type array * @default null * @private */ "_sFilterRow": null, /** * Cache of the class name that DataTables has applied to the row, so we * can quickly look at this variable rather than needing to do a DOM check * on className for the nTr property. * @type string * @default <i>Empty string</i> * @private */ "_sRowStripe": "", /** * Denote if the original data source was from the DOM, or the data source * object. This is used for invalidating data, so DataTables can * automatically read data from the original source, unless uninstructed * otherwise. * @type string * @default null * @private */ "src": null, /** * Index in the aoData array. This saves an indexOf lookup when we have the * object, but want to know the index * @type integer * @default -1 * @private */ "idx": -1 }; /** * Template object for the column information object in DataTables. This object * is held in the settings aoColumns array and contains all the information that * DataTables needs about each individual column. * * Note that this object is related to {@link DataTable.defaults.column} * but this one is the internal data store for DataTables's cache of columns. * It should NOT be manipulated outside of DataTables. Any configuration should * be done through the initialisation options. * @namespace */ DataTable.models.oColumn = { /** * Column index. This could be worked out on-the-fly with $.inArray, but it * is faster to just hold it as a variable * @type integer * @default null */ "idx": null, /** * A list of the columns that sorting should occur on when this column * is sorted. That this property is an array allows multi-column sorting * to be defined for a column (for example first name / last name columns * would benefit from this). The values are integers pointing to the * columns to be sorted on (typically it will be a single integer pointing * at itself, but that doesn't need to be the case). * @type array */ "aDataSort": null, /** * Define the sorting directions that are applied to the column, in sequence * as the column is repeatedly sorted upon - i.e. the first value is used * as the sorting direction when the column if first sorted (clicked on). * Sort it again (click again) and it will move on to the next index. * Repeat until loop. * @type array */ "asSorting": null, /** * Flag to indicate if the column is searchable, and thus should be included * in the filtering or not. * @type boolean */ "bSearchable": null, /** * Flag to indicate if the column is sortable or not. * @type boolean */ "bSortable": null, /** * Flag to indicate if the column is currently visible in the table or not * @type boolean */ "bVisible": null, /** * Store for manual type assignment using the `column.type` option. This * is held in store so we can manipulate the column's `sType` property. * @type string * @default null * @private */ "_sManualType": null, /** * Flag to indicate if HTML5 data attributes should be used as the data * source for filtering or sorting. True is either are. * @type boolean * @default false * @private */ "_bAttrSrc": false, /** * Developer definable function that is called whenever a cell is created (Ajax source, * etc) or processed for input (DOM source). This can be used as a compliment to mRender * allowing you to modify the DOM element (add background colour for example) when the * element is available. * @type function * @param {element} nTd The TD node that has been created * @param {*} sData The Data for the cell * @param {array|object} oData The data for the whole row * @param {int} iRow The row index for the aoData data store * @default null */ "fnCreatedCell": null, /** * Function to get data from a cell in a column. You should <b>never</b> * access data directly through _aData internally in DataTables - always use * the method attached to this property. It allows mData to function as * required. This function is automatically assigned by the column * initialisation method * @type function * @param {array|object} oData The data array/object for the array * (i.e. aoData[]._aData) * @param {string} sSpecific The specific data type you want to get - * 'display', 'type' 'filter' 'sort' * @returns {*} The data for the cell from the given row's data * @default null */ "fnGetData": null, /** * Function to set data for a cell in the column. You should <b>never</b> * set the data directly to _aData internally in DataTables - always use * this method. It allows mData to function as required. This function * is automatically assigned by the column initialisation method * @type function * @param {array|object} oData The data array/object for the array * (i.e. aoData[]._aData) * @param {*} sValue Value to set * @default null */ "fnSetData": null, /** * Property to read the value for the cells in the column from the data * source array / object. If null, then the default content is used, if a * function is given then the return from the function is used. * @type function|int|string|null * @default null */ "mData": null, /** * Partner property to mData which is used (only when defined) to get * the data - i.e. it is basically the same as mData, but without the * 'set' option, and also the data fed to it is the result from mData. * This is the rendering method to match the data method of mData. * @type function|int|string|null * @default null */ "mRender": null, /** * Unique header TH/TD element for this column - this is what the sorting * listener is attached to (if sorting is enabled.) * @type node * @default null */ "nTh": null, /** * Unique footer TH/TD element for this column (if there is one). Not used * in DataTables as such, but can be used for plug-ins to reference the * footer for each column. * @type node * @default null */ "nTf": null, /** * The class to apply to all TD elements in the table's TBODY for the column * @type string * @default null */ "sClass": null, /** * When DataTables calculates the column widths to assign to each column, * it finds the longest string in each column and then constructs a * temporary table and reads the widths from that. The problem with this * is that "mmm" is much wider then "iiii", but the latter is a longer * string - thus the calculation can go wrong (doing it properly and putting * it into an DOM object and measuring that is horribly(!) slow). Thus as * a "work around" we provide this option. It will append its value to the * text that is found to be the longest string for the column - i.e. padding. * @type string */ "sContentPadding": null, /** * Allows a default value to be given for a column's data, and will be used * whenever a null data source is encountered (this can be because mData * is set to null, or because the data source itself is null). * @type string * @default null */ "sDefaultContent": null, /** * Name for the column, allowing reference to the column by name as well as * by index (needs a lookup to work by name). * @type string */ "sName": null, /** * Custom sorting data type - defines which of the available plug-ins in * afnSortData the custom sorting will use - if any is defined. * @type string * @default std */ "sSortDataType": 'std', /** * Class to be applied to the header element when sorting on this column * @type string * @default null */ "sSortingClass": null, /** * Class to be applied to the header element when sorting on this column - * when jQuery UI theming is used. * @type string * @default null */ "sSortingClassJUI": null, /** * Title of the column - what is seen in the TH element (nTh). * @type string */ "sTitle": null, /** * Column sorting and filtering type * @type string * @default null */ "sType": null, /** * Width of the column * @type string * @default null */ "sWidth": null, /** * Width of the column when it was first "encountered" * @type string * @default null */ "sWidthOrig": null }; /* * Developer note: The properties of the object below are given in Hungarian * notation, that was used as the interface for DataTables prior to v1.10, however * from v1.10 onwards the primary interface is camel case. In order to avoid * breaking backwards compatibility utterly with this change, the Hungarian * version is still, internally the primary interface, but is is not documented * - hence the @name tags in each doc comment. This allows a Javascript function * to create a map from Hungarian notation to camel case (going the other direction * would require each property to be listed, which would at around 3K to the size * of DataTables, while this method is about a 0.5K hit. * * Ultimately this does pave the way for Hungarian notation to be dropped * completely, but that is a massive amount of work and will break current * installs (therefore is on-hold until v2). */ /** * Initialisation options that can be given to DataTables at initialisation * time. * @namespace */ DataTable.defaults = { /** * An array of data to use for the table, passed in at initialisation which * will be used in preference to any data which is already in the DOM. This is * particularly useful for constructing tables purely in Javascript, for * example with a custom Ajax call. * @type array * @default null * * @dtopt Option * @name DataTable.defaults.data * * @example * // Using a 2D array data source * $(document).ready( function () { * $('#example').dataTable( { * "data": [ * ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'], * ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'], * ], * "columns": [ * { "title": "Engine" }, * { "title": "Browser" }, * { "title": "Platform" }, * { "title": "Version" }, * { "title": "Grade" } * ] * } ); * } ); * * @example * // Using an array of objects as a data source (`data`) * $(document).ready( function () { * $('#example').dataTable( { * "data": [ * { * "engine": "Trident", * "browser": "Internet Explorer 4.0", * "platform": "Win 95+", * "version": 4, * "grade": "X" * }, * { * "engine": "Trident", * "browser": "Internet Explorer 5.0", * "platform": "Win 95+", * "version": 5, * "grade": "C" * } * ], * "columns": [ * { "title": "Engine", "data": "engine" }, * { "title": "Browser", "data": "browser" }, * { "title": "Platform", "data": "platform" }, * { "title": "Version", "data": "version" }, * { "title": "Grade", "data": "grade" } * ] * } ); * } ); */ "aaData": null, /** * If ordering is enabled, then DataTables will perform a first pass sort on * initialisation. You can define which column(s) the sort is performed * upon, and the sorting direction, with this variable. The `sorting` array * should contain an array for each column to be sorted initially containing * the column's index and a direction string ('asc' or 'desc'). * @type array * @default [[0,'asc']] * * @dtopt Option * @name DataTable.defaults.order * * @example * // Sort by 3rd column first, and then 4th column * $(document).ready( function() { * $('#example').dataTable( { * "order": [[2,'asc'], [3,'desc']] * } ); * } ); * * // No initial sorting * $(document).ready( function() { * $('#example').dataTable( { * "order": [] * } ); * } ); */ "aaSorting": [[0,'asc']], /** * This parameter is basically identical to the `sorting` parameter, but * cannot be overridden by user interaction with the table. What this means * is that you could have a column (visible or hidden) which the sorting * will always be forced on first - any sorting after that (from the user) * will then be performed as required. This can be useful for grouping rows * together. * @type array * @default null * * @dtopt Option * @name DataTable.defaults.orderFixed * * @example * $(document).ready( function() { * $('#example').dataTable( { * "orderFixed": [[0,'asc']] * } ); * } ) */ "aaSortingFixed": [], /** * DataTables can be instructed to load data to display in the table from a * Ajax source. This option defines how that Ajax call is made and where to. * * The `ajax` property has three different modes of operation, depending on * how it is defined. These are: * * * `string` - Set the URL from where the data should be loaded from. * * `object` - Define properties for `jQuery.ajax`. * * `function` - Custom data get function * * `string` * -------- * * As a string, the `ajax` property simply defines the URL from which * DataTables will load data. * * `object` * -------- * * As an object, the parameters in the object are passed to * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control * of the Ajax request. DataTables has a number of default parameters which * you can override using this option. Please refer to the jQuery * documentation for a full description of the options available, although * the following parameters provide additional options in DataTables or * require special consideration: * * * `data` - As with jQuery, `data` can be provided as an object, but it * can also be used as a function to manipulate the data DataTables sends * to the server. The function takes a single parameter, an object of * parameters with the values that DataTables has readied for sending. An * object may be returned which will be merged into the DataTables * defaults, or you can add the items to the object that was passed in and * not return anything from the function. This supersedes `fnServerParams` * from DataTables 1.9-. * * * `dataSrc` - By default DataTables will look for the property `data` (or * `aaData` for compatibility with DataTables 1.9-) when obtaining data * from an Ajax source or for server-side processing - this parameter * allows that property to be changed. You can use Javascript dotted * object notation to get a data source for multiple levels of nesting, or * it my be used as a function. As a function it takes a single parameter, * the JSON returned from the server, which can be manipulated as * required, with the returned value being that used by DataTables as the * data source for the table. This supersedes `sAjaxDataProp` from * DataTables 1.9-. * * * `success` - Should not be overridden it is used internally in * DataTables. To manipulate / transform the data returned by the server * use `ajax.dataSrc`, or use `ajax` as a function (see below). * * `function` * ---------- * * As a function, making the Ajax call is left up to yourself allowing * complete control of the Ajax request. Indeed, if desired, a method other * than Ajax could be used to obtain the required data, such as Web storage * or an AIR database. * * The function is given four parameters and no return is required. The * parameters are: * * 1. _object_ - Data to send to the server * 2. _function_ - Callback function that must be executed when the required * data has been obtained. That data should be passed into the callback * as the only parameter * 3. _object_ - DataTables settings object for the table * * Note that this supersedes `fnServerData` from DataTables 1.9-. * * @type string|object|function * @default null * * @dtopt Option * @name DataTable.defaults.ajax * @since 1.10.0 * * @example * // Get JSON data from a file via Ajax. * // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default). * $('#example').dataTable( { * "ajax": "data.json" * } ); * * @example * // Get JSON data from a file via Ajax, using `dataSrc` to change * // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`) * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": "tableData" * } * } ); * * @example * // Get JSON data from a file via Ajax, using `dataSrc` to read data * // from a plain array rather than an array in an object * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": "" * } * } ); * * @example * // Manipulate the data returned from the server - add a link to data * // (note this can, should, be done using `render` for the column - this * // is just a simple example of how the data can be manipulated). * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "dataSrc": function ( json ) { * for ( var i=0, ien=json.length ; i<ien ; i++ ) { * json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>'; * } * return json; * } * } * } ); * * @example * // Add data to the request * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "data": function ( d ) { * return { * "extra_search": $('#extra').val() * }; * } * } * } ); * * @example * // Send request as POST * $('#example').dataTable( { * "ajax": { * "url": "data.json", * "type": "POST" * } * } ); * * @example * // Get the data from localStorage (could interface with a form for * // adding, editing and removing rows). * $('#example').dataTable( { * "ajax": function (data, callback, settings) { * callback( * JSON.parse( localStorage.getItem('dataTablesData') ) * ); * } * } ); */ "ajax": null, /** * This parameter allows you to readily specify the entries in the length drop * down menu that DataTables shows when pagination is enabled. It can be * either a 1D array of options which will be used for both the displayed * option and the value, or a 2D array which will use the array in the first * position as the value, and the array in the second position as the * displayed options (useful for language strings such as 'All'). * * Note that the `pageLength` property will be automatically set to the * first value given in this array, unless `pageLength` is also provided. * @type array * @default [ 10, 25, 50, 100 ] * * @dtopt Option * @name DataTable.defaults.lengthMenu * * @example * $(document).ready( function() { * $('#example').dataTable( { * "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]] * } ); * } ); */ "aLengthMenu": [ 10, 25, 50, 100 ], /** * The `columns` option in the initialisation parameter allows you to define * details about the way individual columns behave. For a full list of * column options that can be set, please see * {@link DataTable.defaults.column}. Note that if you use `columns` to * define your columns, you must have an entry in the array for every single * column that you have in your table (these can be null if you don't which * to specify any options). * @member * * @name DataTable.defaults.column */ "aoColumns": null, /** * Very similar to `columns`, `columnDefs` allows you to target a specific * column, multiple columns, or all columns, using the `targets` property of * each object in the array. This allows great flexibility when creating * tables, as the `columnDefs` arrays can be of any length, targeting the * columns you specifically want. `columnDefs` may use any of the column * options available: {@link DataTable.defaults.column}, but it _must_ * have `targets` defined in each object in the array. Values in the `targets` * array may be: * <ul> * <li>a string - class name will be matched on the TH for the column</li> * <li>0 or a positive integer - column index counting from the left</li> * <li>a negative integer - column index counting from the right</li> * <li>the string "_all" - all columns (i.e. assign a default)</li> * </ul> * @member * * @name DataTable.defaults.columnDefs */ "aoColumnDefs": null, /** * Basically the same as `search`, this parameter defines the individual column * filtering state at initialisation time. The array must be of the same size * as the number of columns, and each element be an object with the parameters * `search` and `escapeRegex` (the latter is optional). 'null' is also * accepted and the default will be used. * @type array * @default [] * * @dtopt Option * @name DataTable.defaults.searchCols * * @example * $(document).ready( function() { * $('#example').dataTable( { * "searchCols": [ * null, * { "search": "My filter" }, * null, * { "search": "^[0-9]", "escapeRegex": false } * ] * } ); * } ) */ "aoSearchCols": [], /** * An array of CSS classes that should be applied to displayed rows. This * array may be of any length, and DataTables will apply each class * sequentially, looping when required. * @type array * @default null <i>Will take the values determined by the `oClasses.stripe*` * options</i> * * @dtopt Option * @name DataTable.defaults.stripeClasses * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stripeClasses": [ 'strip1', 'strip2', 'strip3' ] * } ); * } ) */ "asStripeClasses": null, /** * Enable or disable automatic column width calculation. This can be disabled * as an optimisation (it takes some time to calculate the widths) if the * tables widths are passed in using `columns`. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.autoWidth * * @example * $(document).ready( function () { * $('#example').dataTable( { * "autoWidth": false * } ); * } ); */ "bAutoWidth": true, /** * Deferred rendering can provide DataTables with a huge speed boost when you * are using an Ajax or JS data source for the table. This option, when set to * true, will cause DataTables to defer the creation of the table elements for * each row until they are needed for a draw - saving a significant amount of * time. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.deferRender * * @example * $(document).ready( function() { * $('#example').dataTable( { * "ajax": "sources/arrays.txt", * "deferRender": true * } ); * } ); */ "bDeferRender": false, /** * Replace a DataTable which matches the given selector and replace it with * one which has the properties of the new initialisation object passed. If no * table matches the selector, then the new DataTable will be constructed as * per normal. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.destroy * * @example * $(document).ready( function() { * $('#example').dataTable( { * "srollY": "200px", * "paginate": false * } ); * * // Some time later.... * $('#example').dataTable( { * "filter": false, * "destroy": true * } ); * } ); */ "bDestroy": false, /** * Enable or disable filtering of data. Filtering in DataTables is "smart" in * that it allows the end user to input multiple words (space separated) and * will match a row containing those words, even if not in the order that was * specified (this allow matching across multiple columns). Note that if you * wish to use filtering in DataTables this must remain 'true' - to remove the * default filtering input box and retain filtering abilities, please use * {@link DataTable.defaults.dom}. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.searching * * @example * $(document).ready( function () { * $('#example').dataTable( { * "searching": false * } ); * } ); */ "bFilter": true, /** * Enable or disable the table information display. This shows information * about the data that is currently visible on the page, including information * about filtered data if that action is being performed. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.info * * @example * $(document).ready( function () { * $('#example').dataTable( { * "info": false * } ); * } ); */ "bInfo": true, /** * Allows the end user to select the size of a formatted page from a select * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`). * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.lengthChange * * @example * $(document).ready( function () { * $('#example').dataTable( { * "lengthChange": false * } ); * } ); */ "bLengthChange": true, /** * Enable or disable pagination. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.paging * * @example * $(document).ready( function () { * $('#example').dataTable( { * "paging": false * } ); * } ); */ "bPaginate": true, /** * Enable or disable the display of a 'processing' indicator when the table is * being processed (e.g. a sort). This is particularly useful for tables with * large amounts of data where it can take a noticeable amount of time to sort * the entries. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.processing * * @example * $(document).ready( function () { * $('#example').dataTable( { * "processing": true * } ); * } ); */ "bProcessing": false, /** * Retrieve the DataTables object for the given selector. Note that if the * table has already been initialised, this parameter will cause DataTables * to simply return the object that has already been set up - it will not take * account of any changes you might have made to the initialisation object * passed to DataTables (setting this parameter to true is an acknowledgement * that you understand this). `destroy` can be used to reinitialise a table if * you need. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.retrieve * * @example * $(document).ready( function() { * initTable(); * tableActions(); * } ); * * function initTable () * { * return $('#example').dataTable( { * "scrollY": "200px", * "paginate": false, * "retrieve": true * } ); * } * * function tableActions () * { * var table = initTable(); * // perform API operations with oTable * } */ "bRetrieve": false, /** * When vertical (y) scrolling is enabled, DataTables will force the height of * the table's viewport to the given height at all times (useful for layout). * However, this can look odd when filtering data down to a small data set, * and the footer is left "floating" further down. This parameter (when * enabled) will cause DataTables to collapse the table's viewport down when * the result set will fit within the given Y height. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.scrollCollapse * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollY": "200", * "scrollCollapse": true * } ); * } ); */ "bScrollCollapse": false, /** * Configure DataTables to use server-side processing. Note that the * `ajax` parameter must also be given in order to give DataTables a * source to obtain the required data for each draw. * @type boolean * @default false * * @dtopt Features * @dtopt Server-side * @name DataTable.defaults.serverSide * * @example * $(document).ready( function () { * $('#example').dataTable( { * "serverSide": true, * "ajax": "xhr.php" * } ); * } ); */ "bServerSide": false, /** * Enable or disable sorting of columns. Sorting of individual columns can be * disabled by the `sortable` option for each column. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.ordering * * @example * $(document).ready( function () { * $('#example').dataTable( { * "ordering": false * } ); * } ); */ "bSort": true, /** * Enable or display DataTables' ability to sort multiple columns at the * same time (activated by shift-click by the user). * @type boolean * @default true * * @dtopt Options * @name DataTable.defaults.orderMulti * * @example * // Disable multiple column sorting ability * $(document).ready( function () { * $('#example').dataTable( { * "orderMulti": false * } ); * } ); */ "bSortMulti": true, /** * Allows control over whether DataTables should use the top (true) unique * cell that is found for a single column, or the bottom (false - default). * This is useful when using complex headers. * @type boolean * @default false * * @dtopt Options * @name DataTable.defaults.orderCellsTop * * @example * $(document).ready( function() { * $('#example').dataTable( { * "orderCellsTop": true * } ); * } ); */ "bSortCellsTop": false, /** * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and * `sorting\_3` to the columns which are currently being sorted on. This is * presented as a feature switch as it can increase processing time (while * classes are removed and added) so for large data sets you might want to * turn this off. * @type boolean * @default true * * @dtopt Features * @name DataTable.defaults.orderClasses * * @example * $(document).ready( function () { * $('#example').dataTable( { * "orderClasses": false * } ); * } ); */ "bSortClasses": true, /** * Enable or disable state saving. When enabled HTML5 `localStorage` will be * used to save table display information such as pagination information, * display length, filtering and sorting. As such when the end user reloads * the page the display display will match what thy had previously set up. * * Due to the use of `localStorage` the default state saving is not supported * in IE6 or 7. If state saving is required in those browsers, use * `stateSaveCallback` to provide a storage solution such as cookies. * @type boolean * @default false * * @dtopt Features * @name DataTable.defaults.stateSave * * @example * $(document).ready( function () { * $('#example').dataTable( { * "stateSave": true * } ); * } ); */ "bStateSave": false, /** * This function is called when a TR element is created (and all TD child * elements have been inserted), or registered if using a DOM source, allowing * manipulation of the TR element (adding classes etc). * @type function * @param {node} row "TR" element for the current row * @param {array} data Raw data array for this row * @param {int} dataIndex The index of this row in the internal aoData array * * @dtopt Callbacks * @name DataTable.defaults.createdRow * * @example * $(document).ready( function() { * $('#example').dataTable( { * "createdRow": function( row, data, dataIndex ) { * // Bold the grade for all 'A' grade browsers * if ( data[4] == "A" ) * { * $('td:eq(4)', row).html( '<b>A</b>' ); * } * } * } ); * } ); */ "fnCreatedRow": null, /** * This function is called on every 'draw' event, and allows you to * dynamically modify any aspect you want about the created DOM. * @type function * @param {object} settings DataTables settings object * * @dtopt Callbacks * @name DataTable.defaults.drawCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "drawCallback": function( settings ) { * alert( 'DataTables has redrawn the table' ); * } * } ); * } ); */ "fnDrawCallback": null, /** * Identical to fnHeaderCallback() but for the table footer this function * allows you to modify the table footer on every 'draw' event. * @type function * @param {node} foot "TR" element for the footer * @param {array} data Full table data (as derived from the original HTML) * @param {int} start Index for the current display starting point in the * display array * @param {int} end Index for the current display ending point in the * display array * @param {array int} display Index array to translate the visual position * to the full data array * * @dtopt Callbacks * @name DataTable.defaults.footerCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "footerCallback": function( tfoot, data, start, end, display ) { * tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start; * } * } ); * } ) */ "fnFooterCallback": null, /** * When rendering large numbers in the information element for the table * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers * to have a comma separator for the 'thousands' units (e.g. 1 million is * rendered as "1,000,000") to help readability for the end user. This * function will override the default method DataTables uses. * @type function * @member * @param {int} toFormat number to be formatted * @returns {string} formatted string for DataTables to show the number * * @dtopt Callbacks * @name DataTable.defaults.formatNumber * * @example * // Format a number using a single quote for the separator (note that * // this can also be done with the language.thousands option) * $(document).ready( function() { * $('#example').dataTable( { * "formatNumber": function ( toFormat ) { * return toFormat.toString().replace( * /\B(?=(\d{3})+(?!\d))/g, "'" * ); * }; * } ); * } ); */ "fnFormatNumber": function ( toFormat ) { return toFormat.toString().replace( /\B(?=(\d{3})+(?!\d))/g, this.oLanguage.sThousands ); }, /** * This function is called on every 'draw' event, and allows you to * dynamically modify the header row. This can be used to calculate and * display useful information about the table. * @type function * @param {node} head "TR" element for the header * @param {array} data Full table data (as derived from the original HTML) * @param {int} start Index for the current display starting point in the * display array * @param {int} end Index for the current display ending point in the * display array * @param {array int} display Index array to translate the visual position * to the full data array * * @dtopt Callbacks * @name DataTable.defaults.headerCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "fheaderCallback": function( head, data, start, end, display ) { * head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records"; * } * } ); * } ) */ "fnHeaderCallback": null, /** * The information element can be used to convey information about the current * state of the table. Although the internationalisation options presented by * DataTables are quite capable of dealing with most customisations, there may * be times where you wish to customise the string further. This callback * allows you to do exactly that. * @type function * @param {object} oSettings DataTables settings object * @param {int} start Starting position in data for the draw * @param {int} end End position in data for the draw * @param {int} max Total number of rows in the table (regardless of * filtering) * @param {int} total Total number of rows in the data set, after filtering * @param {string} pre The string that DataTables has formatted using it's * own rules * @returns {string} The string to be displayed in the information element. * * @dtopt Callbacks * @name DataTable.defaults.infoCallback * * @example * $('#example').dataTable( { * "infoCallback": function( settings, start, end, max, total, pre ) { * return start +" to "+ end; * } * } ); */ "fnInfoCallback": null, /** * Called when the table has been initialised. Normally DataTables will * initialise sequentially and there will be no need for this function, * however, this does not hold true when using external language information * since that is obtained using an async XHR call. * @type function * @param {object} settings DataTables settings object * @param {object} json The JSON object request from the server - only * present if client-side Ajax sourced data is used * * @dtopt Callbacks * @name DataTable.defaults.initComplete * * @example * $(document).ready( function() { * $('#example').dataTable( { * "initComplete": function(settings, json) { * alert( 'DataTables has finished its initialisation.' ); * } * } ); * } ) */ "fnInitComplete": null, /** * Called at the very start of each table draw and can be used to cancel the * draw by returning false, any other return (including undefined) results in * the full draw occurring). * @type function * @param {object} settings DataTables settings object * @returns {boolean} False will cancel the draw, anything else (including no * return) will allow it to complete. * * @dtopt Callbacks * @name DataTable.defaults.preDrawCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "preDrawCallback": function( settings ) { * if ( $('#test').val() == 1 ) { * return false; * } * } * } ); * } ); */ "fnPreDrawCallback": null, /** * This function allows you to 'post process' each row after it have been * generated for each table draw, but before it is rendered on screen. This * function might be used for setting the row class name etc. * @type function * @param {node} row "TR" element for the current row * @param {array} data Raw data array for this row * @param {int} displayIndex The display index for the current table draw * @param {int} displayIndexFull The index of the data in the full list of * rows (after filtering) * * @dtopt Callbacks * @name DataTable.defaults.rowCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "rowCallback": function( row, data, displayIndex, displayIndexFull ) { * // Bold the grade for all 'A' grade browsers * if ( data[4] == "A" ) { * $('td:eq(4)', row).html( '<b>A</b>' ); * } * } * } ); * } ); */ "fnRowCallback": null, /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * This parameter allows you to override the default function which obtains * the data from the server so something more suitable for your application. * For example you could use POST data, or pull information from a Gears or * AIR database. * @type function * @member * @param {string} source HTTP source to obtain the data from (`ajax`) * @param {array} data A key/value pair object containing the data to send * to the server * @param {function} callback to be called on completion of the data get * process that will draw the data on the page. * @param {object} settings DataTables settings object * * @dtopt Callbacks * @dtopt Server-side * @name DataTable.defaults.serverData * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "fnServerData": null, /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * It is often useful to send extra data to the server when making an Ajax * request - for example custom filtering information, and this callback * function makes it trivial to send extra information to the server. The * passed in parameter is the data set that has been constructed by * DataTables, and you can add to this or modify it as you require. * @type function * @param {array} data Data array (array of objects which are name/value * pairs) that has been constructed by DataTables and will be sent to the * server. In the case of Ajax sourced data with server-side processing * this will be an empty array, for server-side processing there will be a * significant number of parameters! * @returns {undefined} Ensure that you modify the data array passed in, * as this is passed by reference. * * @dtopt Callbacks * @dtopt Server-side * @name DataTable.defaults.serverParams * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "fnServerParams": null, /** * Load the table state. With this function you can define from where, and how, the * state of a table is loaded. By default DataTables will load from `localStorage` * but you might wish to use a server-side database or cookies. * @type function * @member * @param {object} settings DataTables settings object * @param {object} callback Callback that can be executed when done. It * should be passed the loaded state object. * @return {object} The DataTables state object to be loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoadCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadCallback": function (settings, callback) { * $.ajax( { * "url": "/state_load", * "dataType": "json", * "success": function (json) { * callback( json ); * } * } ); * } * } ); * } ); */ "fnStateLoadCallback": function ( settings ) { try { return JSON.parse( (settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem( 'DataTables_'+settings.sInstance+'_'+location.pathname ) ); } catch (e) { return {}; } }, /** * Callback which allows modification of the saved state prior to loading that state. * This callback is called when the table is loading state from the stored data, but * prior to the settings object being modified by the saved state. Note that for * plug-in authors, you should use the `stateLoadParams` event to load parameters for * a plug-in. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object that is to be loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoadParams * * @example * // Remove a saved filter, so filtering is never loaded * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadParams": function (settings, data) { * data.oSearch.sSearch = ""; * } * } ); * } ); * * @example * // Disallow state loading by returning false * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoadParams": function (settings, data) { * return false; * } * } ); * } ); */ "fnStateLoadParams": null, /** * Callback that is called when the state has been loaded from the state saving method * and the DataTables settings object has been modified as a result of the loaded state. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object that was loaded * * @dtopt Callbacks * @name DataTable.defaults.stateLoaded * * @example * // Show an alert with the filtering value that was saved * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateLoaded": function (settings, data) { * alert( 'Saved filter was: '+data.oSearch.sSearch ); * } * } ); * } ); */ "fnStateLoaded": null, /** * Save the table state. This function allows you to define where and how the state * information for the table is stored By default DataTables will use `localStorage` * but you might wish to use a server-side database or cookies. * @type function * @member * @param {object} settings DataTables settings object * @param {object} data The state object to be saved * * @dtopt Callbacks * @name DataTable.defaults.stateSaveCallback * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateSaveCallback": function (settings, data) { * // Send an Ajax request to the server with the state object * $.ajax( { * "url": "/state_save", * "data": data, * "dataType": "json", * "method": "POST" * "success": function () {} * } ); * } * } ); * } ); */ "fnStateSaveCallback": function ( settings, data ) { try { (settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem( 'DataTables_'+settings.sInstance+'_'+location.pathname, JSON.stringify( data ) ); } catch (e) {} }, /** * Callback which allows modification of the state to be saved. Called when the table * has changed state a new state save is required. This method allows modification of * the state saving object prior to actually doing the save, including addition or * other state properties or modification. Note that for plug-in authors, you should * use the `stateSaveParams` event to save parameters for a plug-in. * @type function * @param {object} settings DataTables settings object * @param {object} data The state object to be saved * * @dtopt Callbacks * @name DataTable.defaults.stateSaveParams * * @example * // Remove a saved filter, so filtering is never saved * $(document).ready( function() { * $('#example').dataTable( { * "stateSave": true, * "stateSaveParams": function (settings, data) { * data.oSearch.sSearch = ""; * } * } ); * } ); */ "fnStateSaveParams": null, /** * Duration for which the saved state information is considered valid. After this period * has elapsed the state will be returned to the default. * Value is given in seconds. * @type int * @default 7200 <i>(2 hours)</i> * * @dtopt Options * @name DataTable.defaults.stateDuration * * @example * $(document).ready( function() { * $('#example').dataTable( { * "stateDuration": 60*60*24; // 1 day * } ); * } ) */ "iStateDuration": 7200, /** * When enabled DataTables will not make a request to the server for the first * page draw - rather it will use the data already on the page (no sorting etc * will be applied to it), thus saving on an XHR at load time. `deferLoading` * is used to indicate that deferred loading is required, but it is also used * to tell DataTables how many records there are in the full table (allowing * the information element and pagination to be displayed correctly). In the case * where a filtering is applied to the table on initial load, this can be * indicated by giving the parameter as an array, where the first element is * the number of records available after filtering and the second element is the * number of records without filtering (allowing the table information element * to be shown correctly). * @type int | array * @default null * * @dtopt Options * @name DataTable.defaults.deferLoading * * @example * // 57 records available in the table, no filtering applied * $(document).ready( function() { * $('#example').dataTable( { * "serverSide": true, * "ajax": "scripts/server_processing.php", * "deferLoading": 57 * } ); * } ); * * @example * // 57 records after filtering, 100 without filtering (an initial filter applied) * $(document).ready( function() { * $('#example').dataTable( { * "serverSide": true, * "ajax": "scripts/server_processing.php", * "deferLoading": [ 57, 100 ], * "search": { * "search": "my_filter" * } * } ); * } ); */ "iDeferLoading": null, /** * Number of rows to display on a single page when using pagination. If * feature enabled (`lengthChange`) then the end user will be able to override * this to a custom setting using a pop-up menu. * @type int * @default 10 * * @dtopt Options * @name DataTable.defaults.pageLength * * @example * $(document).ready( function() { * $('#example').dataTable( { * "pageLength": 50 * } ); * } ) */ "iDisplayLength": 10, /** * Define the starting point for data display when using DataTables with * pagination. Note that this parameter is the number of records, rather than * the page number, so if you have 10 records per page and want to start on * the third page, it should be "20". * @type int * @default 0 * * @dtopt Options * @name DataTable.defaults.displayStart * * @example * $(document).ready( function() { * $('#example').dataTable( { * "displayStart": 20 * } ); * } ) */ "iDisplayStart": 0, /** * By default DataTables allows keyboard navigation of the table (sorting, paging, * and filtering) by adding a `tabindex` attribute to the required elements. This * allows you to tab through the controls and press the enter key to activate them. * The tabindex is default 0, meaning that the tab follows the flow of the document. * You can overrule this using this parameter if you wish. Use a value of -1 to * disable built-in keyboard navigation. * @type int * @default 0 * * @dtopt Options * @name DataTable.defaults.tabIndex * * @example * $(document).ready( function() { * $('#example').dataTable( { * "tabIndex": 1 * } ); * } ); */ "iTabIndex": 0, /** * Classes that DataTables assigns to the various components and features * that it adds to the HTML table. This allows classes to be configured * during initialisation in addition to through the static * {@link DataTable.ext.oStdClasses} object). * @namespace * @name DataTable.defaults.classes */ "oClasses": {}, /** * All strings that DataTables uses in the user interface that it creates * are defined in this object, allowing you to modified them individually or * completely replace them all as required. * @namespace * @name DataTable.defaults.language */ "oLanguage": { /** * Strings that are used for WAI-ARIA labels and controls only (these are not * actually visible on the page, but will be read by screenreaders, and thus * must be internationalised as well). * @namespace * @name DataTable.defaults.language.aria */ "oAria": { /** * ARIA label that is added to the table headers when the column may be * sorted ascending by activing the column (click or return when focused). * Note that the column header is prefixed to this string. * @type string * @default : activate to sort column ascending * * @dtopt Language * @name DataTable.defaults.language.aria.sortAscending * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "aria": { * "sortAscending": " - click/return to sort ascending" * } * } * } ); * } ); */ "sSortAscending": ": activate to sort column ascending", /** * ARIA label that is added to the table headers when the column may be * sorted descending by activing the column (click or return when focused). * Note that the column header is prefixed to this string. * @type string * @default : activate to sort column ascending * * @dtopt Language * @name DataTable.defaults.language.aria.sortDescending * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "aria": { * "sortDescending": " - click/return to sort descending" * } * } * } ); * } ); */ "sSortDescending": ": activate to sort column descending" }, /** * Pagination string used by DataTables for the built-in pagination * control types. * @namespace * @name DataTable.defaults.language.paginate */ "oPaginate": { /** * Text to use when using the 'full_numbers' type of pagination for the * button to take the user to the first page. * @type string * @default First * * @dtopt Language * @name DataTable.defaults.language.paginate.first * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "first": "First page" * } * } * } ); * } ); */ "sFirst": "First", /** * Text to use when using the 'full_numbers' type of pagination for the * button to take the user to the last page. * @type string * @default Last * * @dtopt Language * @name DataTable.defaults.language.paginate.last * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "last": "Last page" * } * } * } ); * } ); */ "sLast": "Last", /** * Text to use for the 'next' pagination button (to take the user to the * next page). * @type string * @default Next * * @dtopt Language * @name DataTable.defaults.language.paginate.next * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "next": "Next page" * } * } * } ); * } ); */ "sNext": "Next", /** * Text to use for the 'previous' pagination button (to take the user to * the previous page). * @type string * @default Previous * * @dtopt Language * @name DataTable.defaults.language.paginate.previous * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "paginate": { * "previous": "Previous page" * } * } * } ); * } ); */ "sPrevious": "Previous" }, /** * This string is shown in preference to `zeroRecords` when the table is * empty of data (regardless of filtering). Note that this is an optional * parameter - if it is not given, the value of `zeroRecords` will be used * instead (either the default or given value). * @type string * @default No data available in table * * @dtopt Language * @name DataTable.defaults.language.emptyTable * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "emptyTable": "No data available in table" * } * } ); * } ); */ "sEmptyTable": "No data available in table", /** * This string gives information to the end user about the information * that is current on display on the page. The following tokens can be * used in the string and will be dynamically replaced as the table * display updates. This tokens can be placed anywhere in the string, or * removed as needed by the language requires: * * * `\_START\_` - Display index of the first record on the current page * * `\_END\_` - Display index of the last record on the current page * * `\_TOTAL\_` - Number of records in the table after filtering * * `\_MAX\_` - Number of records in the table without filtering * * `\_PAGE\_` - Current page number * * `\_PAGES\_` - Total number of pages of data in the table * * @type string * @default Showing _START_ to _END_ of _TOTAL_ entries * * @dtopt Language * @name DataTable.defaults.language.info * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "info": "Showing page _PAGE_ of _PAGES_" * } * } ); * } ); */ "sInfo": "Showing _START_ to _END_ of _TOTAL_ entries", /** * Display information string for when the table is empty. Typically the * format of this string should match `info`. * @type string * @default Showing 0 to 0 of 0 entries * * @dtopt Language * @name DataTable.defaults.language.infoEmpty * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoEmpty": "No entries to show" * } * } ); * } ); */ "sInfoEmpty": "Showing 0 to 0 of 0 entries", /** * When a user filters the information in a table, this string is appended * to the information (`info`) to give an idea of how strong the filtering * is. The variable _MAX_ is dynamically updated. * @type string * @default (filtered from _MAX_ total entries) * * @dtopt Language * @name DataTable.defaults.language.infoFiltered * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoFiltered": " - filtering from _MAX_ records" * } * } ); * } ); */ "sInfoFiltered": "(filtered from _MAX_ total entries)", /** * If can be useful to append extra information to the info string at times, * and this variable does exactly that. This information will be appended to * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are * being used) at all times. * @type string * @default <i>Empty string</i> * * @dtopt Language * @name DataTable.defaults.language.infoPostFix * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "infoPostFix": "All records shown are derived from real information." * } * } ); * } ); */ "sInfoPostFix": "", /** * This decimal place operator is a little different from the other * language options since DataTables doesn't output floating point * numbers, so it won't ever use this for display of a number. Rather, * what this parameter does is modify the sort methods of the table so * that numbers which are in a format which has a character other than * a period (`.`) as a decimal place will be sorted numerically. * * Note that numbers with different decimal places cannot be shown in * the same table and still be sortable, the table must be consistent. * However, multiple different tables on the page can use different * decimal place characters. * @type string * @default * * @dtopt Language * @name DataTable.defaults.language.decimal * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "decimal": "," * "thousands": "." * } * } ); * } ); */ "sDecimal": "", /** * DataTables has a build in number formatter (`formatNumber`) which is * used to format large numbers that are used in the table information. * By default a comma is used, but this can be trivially changed to any * character you wish with this parameter. * @type string * @default , * * @dtopt Language * @name DataTable.defaults.language.thousands * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "thousands": "'" * } * } ); * } ); */ "sThousands": ",", /** * Detail the action that will be taken when the drop down menu for the * pagination length option is changed. The '_MENU_' variable is replaced * with a default select list of 10, 25, 50 and 100, and can be replaced * with a custom select box if required. * @type string * @default Show _MENU_ entries * * @dtopt Language * @name DataTable.defaults.language.lengthMenu * * @example * // Language change only * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "lengthMenu": "Display _MENU_ records" * } * } ); * } ); * * @example * // Language and options change * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "lengthMenu": 'Display <select>'+ * '<option value="10">10</option>'+ * '<option value="20">20</option>'+ * '<option value="30">30</option>'+ * '<option value="40">40</option>'+ * '<option value="50">50</option>'+ * '<option value="-1">All</option>'+ * '</select> records' * } * } ); * } ); */ "sLengthMenu": "Show _MENU_ entries", /** * When using Ajax sourced data and during the first draw when DataTables is * gathering the data, this message is shown in an empty row in the table to * indicate to the end user the the data is being loaded. Note that this * parameter is not used when loading data by server-side processing, just * Ajax sourced data with client-side processing. * @type string * @default Loading... * * @dtopt Language * @name DataTable.defaults.language.loadingRecords * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "loadingRecords": "Please wait - loading..." * } * } ); * } ); */ "sLoadingRecords": "Loading...", /** * Text which is displayed when the table is processing a user action * (usually a sort command or similar). * @type string * @default Processing... * * @dtopt Language * @name DataTable.defaults.language.processing * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "processing": "DataTables is currently busy" * } * } ); * } ); */ "sProcessing": "Processing...", /** * Details the actions that will be taken when the user types into the * filtering input text box. The variable "_INPUT_", if used in the string, * is replaced with the HTML text box for the filtering input allowing * control over where it appears in the string. If "_INPUT_" is not given * then the input box is appended to the string automatically. * @type string * @default Search: * * @dtopt Language * @name DataTable.defaults.language.search * * @example * // Input text box will be appended at the end automatically * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "search": "Filter records:" * } * } ); * } ); * * @example * // Specify where the filter should appear * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "search": "Apply filter _INPUT_ to table" * } * } ); * } ); */ "sSearch": "Search:", /** * Assign a `placeholder` attribute to the search `input` element * @type string * @default * * @dtopt Language * @name DataTable.defaults.language.searchPlaceholder */ "sSearchPlaceholder": "", /** * All of the language information can be stored in a file on the * server-side, which DataTables will look up if this parameter is passed. * It must store the URL of the language file, which is in a JSON format, * and the object has the same properties as the oLanguage object in the * initialiser object (i.e. the above parameters). Please refer to one of * the example language files to see how this works in action. * @type string * @default <i>Empty string - i.e. disabled</i> * * @dtopt Language * @name DataTable.defaults.language.url * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "url": "http://www.sprymedia.co.uk/dataTables/lang.txt" * } * } ); * } ); */ "sUrl": "", /** * Text shown inside the table records when the is no information to be * displayed after filtering. `emptyTable` is shown when there is simply no * information in the table at all (regardless of filtering). * @type string * @default No matching records found * * @dtopt Language * @name DataTable.defaults.language.zeroRecords * * @example * $(document).ready( function() { * $('#example').dataTable( { * "language": { * "zeroRecords": "No records to display" * } * } ); * } ); */ "sZeroRecords": "No matching records found" }, /** * This parameter allows you to have define the global filtering state at * initialisation time. As an object the `search` parameter must be * defined, but all other parameters are optional. When `regex` is true, * the search string will be treated as a regular expression, when false * (default) it will be treated as a straight string. When `smart` * DataTables will use it's smart filtering methods (to word match at * any point in the data), when false this will not be done. * @namespace * @extends DataTable.models.oSearch * * @dtopt Options * @name DataTable.defaults.search * * @example * $(document).ready( function() { * $('#example').dataTable( { * "search": {"search": "Initial search"} * } ); * } ) */ "oSearch": $.extend( {}, DataTable.models.oSearch ), /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * By default DataTables will look for the property `data` (or `aaData` for * compatibility with DataTables 1.9-) when obtaining data from an Ajax * source or for server-side processing - this parameter allows that * property to be changed. You can use Javascript dotted object notation to * get a data source for multiple levels of nesting. * @type string * @default data * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.ajaxDataProp * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sAjaxDataProp": "data", /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * You can instruct DataTables to load data from an external * source using this parameter (use aData if you want to pass data in you * already have). Simply provide a url a JSON object can be obtained from. * @type string * @default null * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.ajaxSource * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sAjaxSource": null, /** * This initialisation variable allows you to specify exactly where in the * DOM you want DataTables to inject the various controls it adds to the page * (for example you might want the pagination controls at the top of the * table). DIV elements (with or without a custom class) can also be added to * aid styling. The follow syntax is used: * <ul> * <li>The following options are allowed: * <ul> * <li>'l' - Length changing</li> * <li>'f' - Filtering input</li> * <li>'t' - The table!</li> * <li>'i' - Information</li> * <li>'p' - Pagination</li> * <li>'r' - pRocessing</li> * </ul> * </li> * <li>The following constants are allowed: * <ul> * <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li> * <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li> * </ul> * </li> * <li>The following syntax is expected: * <ul> * <li>'&lt;' and '&gt;' - div elements</li> * <li>'&lt;"class" and '&gt;' - div with a class</li> * <li>'&lt;"#id" and '&gt;' - div with an ID</li> * </ul> * </li> * <li>Examples: * <ul> * <li>'&lt;"wrapper"flipt&gt;'</li> * <li>'&lt;lf&lt;t&gt;ip&gt;'</li> * </ul> * </li> * </ul> * @type string * @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b> * <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i> * * @dtopt Options * @name DataTable.defaults.dom * * @example * $(document).ready( function() { * $('#example').dataTable( { * "dom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;' * } ); * } ); */ "sDom": "lfrtip", /** * Search delay option. This will throttle full table searches that use the * DataTables provided search input element (it does not effect calls to * `dt-api search()`, providing a delay before the search is made. * @type integer * @default 0 * * @dtopt Options * @name DataTable.defaults.searchDelay * * @example * $(document).ready( function() { * $('#example').dataTable( { * "searchDelay": 200 * } ); * } ) */ "searchDelay": null, /** * DataTables features six different built-in options for the buttons to * display for pagination control: * * * `numbers` - Page number buttons only * * `simple` - 'Previous' and 'Next' buttons only * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers * * Further methods can be added using {@link DataTable.ext.oPagination}. * @type string * @default simple_numbers * * @dtopt Options * @name DataTable.defaults.pagingType * * @example * $(document).ready( function() { * $('#example').dataTable( { * "pagingType": "full_numbers" * } ); * } ) */ "sPaginationType": "simple_numbers", /** * Enable horizontal scrolling. When a table is too wide to fit into a * certain layout, or you have a large number of columns in the table, you * can enable x-scrolling to show the table in a viewport, which can be * scrolled. This property can be `true` which will allow the table to * scroll horizontally when needed, or any CSS unit, or a number (in which * case it will be treated as a pixel measurement). Setting as simply `true` * is recommended. * @type boolean|string * @default <i>blank string - i.e. disabled</i> * * @dtopt Features * @name DataTable.defaults.scrollX * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollX": true, * "scrollCollapse": true * } ); * } ); */ "sScrollX": "", /** * This property can be used to force a DataTable to use more width than it * might otherwise do when x-scrolling is enabled. For example if you have a * table which requires to be well spaced, this parameter is useful for * "over-sizing" the table, and thus forcing scrolling. This property can by * any CSS unit, or a number (in which case it will be treated as a pixel * measurement). * @type string * @default <i>blank string - i.e. disabled</i> * * @dtopt Options * @name DataTable.defaults.scrollXInner * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollX": "100%", * "scrollXInner": "110%" * } ); * } ); */ "sScrollXInner": "", /** * Enable vertical scrolling. Vertical scrolling will constrain the DataTable * to the given height, and enable scrolling for any data which overflows the * current viewport. This can be used as an alternative to paging to display * a lot of data in a small area (although paging and scrolling can both be * enabled at the same time). This property can be any CSS unit, or a number * (in which case it will be treated as a pixel measurement). * @type string * @default <i>blank string - i.e. disabled</i> * * @dtopt Features * @name DataTable.defaults.scrollY * * @example * $(document).ready( function() { * $('#example').dataTable( { * "scrollY": "200px", * "paginate": false * } ); * } ); */ "sScrollY": "", /** * __Deprecated__ The functionality provided by this parameter has now been * superseded by that provided through `ajax`, which should be used instead. * * Set the HTTP method that is used to make the Ajax call for server-side * processing or Ajax sourced data. * @type string * @default GET * * @dtopt Options * @dtopt Server-side * @name DataTable.defaults.serverMethod * * @deprecated 1.10. Please use `ajax` for this functionality now. */ "sServerMethod": "GET", /** * DataTables makes use of renderers when displaying HTML elements for * a table. These renderers can be added or modified by plug-ins to * generate suitable mark-up for a site. For example the Bootstrap * integration plug-in for DataTables uses a paging button renderer to * display pagination buttons in the mark-up required by Bootstrap. * * For further information about the renderers available see * DataTable.ext.renderer * @type string|object * @default null * * @name DataTable.defaults.renderer * */ "renderer": null, /** * Set the data property name that DataTables should use to get a row's id * to set as the `id` property in the node. * @type string * @default DT_RowId * * @name DataTable.defaults.rowId */ "rowId": "DT_RowId" }; _fnHungarianMap( DataTable.defaults ); /* * Developer note - See note in model.defaults.js about the use of Hungarian * notation and camel case. */ /** * Column options that can be given to DataTables at initialisation time. * @namespace */ DataTable.defaults.column = { /** * Define which column(s) an order will occur on for this column. This * allows a column's ordering to take multiple columns into account when * doing a sort or use the data from a different column. For example first * name / last name columns make sense to do a multi-column sort over the * two columns. * @type array|int * @default null <i>Takes the value of the column index automatically</i> * * @name DataTable.defaults.column.orderData * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderData": [ 0, 1 ], "targets": [ 0 ] }, * { "orderData": [ 1, 0 ], "targets": [ 1 ] }, * { "orderData": 2, "targets": [ 2 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "orderData": [ 0, 1 ] }, * { "orderData": [ 1, 0 ] }, * { "orderData": 2 }, * null, * null * ] * } ); * } ); */ "aDataSort": null, "iDataSort": -1, /** * You can control the default ordering direction, and even alter the * behaviour of the sort handler (i.e. only allow ascending ordering etc) * using this parameter. * @type array * @default [ 'asc', 'desc' ] * * @name DataTable.defaults.column.orderSequence * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderSequence": [ "asc" ], "targets": [ 1 ] }, * { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] }, * { "orderSequence": [ "desc" ], "targets": [ 3 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * { "orderSequence": [ "asc" ] }, * { "orderSequence": [ "desc", "asc", "asc" ] }, * { "orderSequence": [ "desc" ] }, * null * ] * } ); * } ); */ "asSorting": [ 'asc', 'desc' ], /** * Enable or disable filtering on the data in this column. * @type boolean * @default true * * @name DataTable.defaults.column.searchable * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "searchable": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "searchable": false }, * null, * null, * null, * null * ] } ); * } ); */ "bSearchable": true, /** * Enable or disable ordering on this column. * @type boolean * @default true * * @name DataTable.defaults.column.orderable * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderable": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "orderable": false }, * null, * null, * null, * null * ] } ); * } ); */ "bSortable": true, /** * Enable or disable the display of this column. * @type boolean * @default true * * @name DataTable.defaults.column.visible * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "visible": false, "targets": [ 0 ] } * ] } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "visible": false }, * null, * null, * null, * null * ] } ); * } ); */ "bVisible": true, /** * Developer definable function that is called whenever a cell is created (Ajax source, * etc) or processed for input (DOM source). This can be used as a compliment to mRender * allowing you to modify the DOM element (add background colour for example) when the * element is available. * @type function * @param {element} td The TD node that has been created * @param {*} cellData The Data for the cell * @param {array|object} rowData The data for the whole row * @param {int} row The row index for the aoData data store * @param {int} col The column index for aoColumns * * @name DataTable.defaults.column.createdCell * @dtopt Columns * * @example * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [3], * "createdCell": function (td, cellData, rowData, row, col) { * if ( cellData == "1.7" ) { * $(td).css('color', 'blue') * } * } * } ] * }); * } ); */ "fnCreatedCell": null, /** * This parameter has been replaced by `data` in DataTables to ensure naming * consistency. `dataProp` can still be used, as there is backwards * compatibility in DataTables for this option, but it is strongly * recommended that you use `data` in preference to `dataProp`. * @name DataTable.defaults.column.dataProp */ /** * This property can be used to read data from any data source property, * including deeply nested objects / properties. `data` can be given in a * number of different ways which effect its behaviour: * * * `integer` - treated as an array index for the data source. This is the * default that DataTables uses (incrementally increased for each column). * * `string` - read an object property from the data source. There are * three 'special' options that can be used in the string to alter how * DataTables reads the data from the source object: * * `.` - Dotted Javascript notation. Just as you use a `.` in * Javascript to read from nested objects, so to can the options * specified in `data`. For example: `browser.version` or * `browser.name`. If your object parameter name contains a period, use * `\\` to escape it - i.e. `first\\.name`. * * `[]` - Array notation. DataTables can automatically combine data * from and array source, joining the data with the characters provided * between the two brackets. For example: `name[, ]` would provide a * comma-space separated list from the source array. If no characters * are provided between the brackets, the original array source is * returned. * * `()` - Function notation. Adding `()` to the end of a parameter will * execute a function of the name given. For example: `browser()` for a * simple function on the data source, `browser.version()` for a * function in a nested property or even `browser().version` to get an * object property if the function called returns an object. Note that * function notation is recommended for use in `render` rather than * `data` as it is much simpler to use as a renderer. * * `null` - use the original data source for the row rather than plucking * data directly from it. This action has effects on two other * initialisation options: * * `defaultContent` - When null is given as the `data` option and * `defaultContent` is specified for the column, the value defined by * `defaultContent` will be used for the cell. * * `render` - When null is used for the `data` option and the `render` * option is specified for the column, the whole data source for the * row is used for the renderer. * * `function` - the function given will be executed whenever DataTables * needs to set or get the data for a cell in the column. The function * takes three parameters: * * Parameters: * * `{array|object}` The data source for the row * * `{string}` The type call data requested - this will be 'set' when * setting data or 'filter', 'display', 'type', 'sort' or undefined * when gathering data. Note that when `undefined` is given for the * type DataTables expects to get the raw data for the object back< * * `{*}` Data to set when the second parameter is 'set'. * * Return: * * The return value from the function is not required when 'set' is * the type of call, but otherwise the return is what will be used * for the data requested. * * Note that `data` is a getter and setter option. If you just require * formatting of data for output, you will likely want to use `render` which * is simply a getter and thus simpler to use. * * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The * name change reflects the flexibility of this property and is consistent * with the naming of mRender. If 'mDataProp' is given, then it will still * be used by DataTables, as it automatically maps the old name to the new * if required. * * @type string|int|function|null * @default null <i>Use automatically calculated column index</i> * * @name DataTable.defaults.column.data * @dtopt Columns * * @example * // Read table data from objects * // JSON structure for each row: * // { * // "engine": {value}, * // "browser": {value}, * // "platform": {value}, * // "version": {value}, * // "grade": {value} * // } * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/objects.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { "data": "platform" }, * { "data": "version" }, * { "data": "grade" } * ] * } ); * } ); * * @example * // Read information from deeply nested objects * // JSON structure for each row: * // { * // "engine": {value}, * // "browser": {value}, * // "platform": { * // "inner": {value} * // }, * // "details": [ * // {value}, {value} * // ] * // } * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/deep.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { "data": "platform.inner" }, * { "data": "details.0" }, * { "data": "details.1" } * ] * } ); * } ); * * @example * // Using `data` as a function to provide different information for * // sorting, filtering and display. In this case, currency (price) * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": function ( source, type, val ) { * if (type === 'set') { * source.price = val; * // Store the computed dislay and filter values for efficiency * source.price_display = val=="" ? "" : "$"+numberFormat(val); * source.price_filter = val=="" ? "" : "$"+numberFormat(val)+" "+val; * return; * } * else if (type === 'display') { * return source.price_display; * } * else if (type === 'filter') { * return source.price_filter; * } * // 'sort', 'type' and undefined all just use the integer * return source.price; * } * } ] * } ); * } ); * * @example * // Using default content * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, * "defaultContent": "Click to edit" * } ] * } ); * } ); * * @example * // Using array notation - outputting a list from an array * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": "name[, ]" * } ] * } ); * } ); * */ "mData": null, /** * This property is the rendering partner to `data` and it is suggested that * when you want to manipulate data for display (including filtering, * sorting etc) without altering the underlying data for the table, use this * property. `render` can be considered to be the the read only companion to * `data` which is read / write (then as such more complex). Like `data` * this option can be given in a number of different ways to effect its * behaviour: * * * `integer` - treated as an array index for the data source. This is the * default that DataTables uses (incrementally increased for each column). * * `string` - read an object property from the data source. There are * three 'special' options that can be used in the string to alter how * DataTables reads the data from the source object: * * `.` - Dotted Javascript notation. Just as you use a `.` in * Javascript to read from nested objects, so to can the options * specified in `data`. For example: `browser.version` or * `browser.name`. If your object parameter name contains a period, use * `\\` to escape it - i.e. `first\\.name`. * * `[]` - Array notation. DataTables can automatically combine data * from and array source, joining the data with the characters provided * between the two brackets. For example: `name[, ]` would provide a * comma-space separated list from the source array. If no characters * are provided between the brackets, the original array source is * returned. * * `()` - Function notation. Adding `()` to the end of a parameter will * execute a function of the name given. For example: `browser()` for a * simple function on the data source, `browser.version()` for a * function in a nested property or even `browser().version` to get an * object property if the function called returns an object. * * `object` - use different data for the different data types requested by * DataTables ('filter', 'display', 'type' or 'sort'). The property names * of the object is the data type the property refers to and the value can * defined using an integer, string or function using the same rules as * `render` normally does. Note that an `_` option _must_ be specified. * This is the default value to use if you haven't specified a value for * the data type requested by DataTables. * * `function` - the function given will be executed whenever DataTables * needs to set or get the data for a cell in the column. The function * takes three parameters: * * Parameters: * * {array|object} The data source for the row (based on `data`) * * {string} The type call data requested - this will be 'filter', * 'display', 'type' or 'sort'. * * {array|object} The full data source for the row (not based on * `data`) * * Return: * * The return value from the function is what will be used for the * data requested. * * @type string|int|function|object|null * @default null Use the data source value. * * @name DataTable.defaults.column.render * @dtopt Columns * * @example * // Create a comma separated list from an array of objects * $(document).ready( function() { * $('#example').dataTable( { * "ajaxSource": "sources/deep.txt", * "columns": [ * { "data": "engine" }, * { "data": "browser" }, * { * "data": "platform", * "render": "[, ].name" * } * ] * } ); * } ); * * @example * // Execute a function to obtain data * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, // Use the full data source object for the renderer's source * "render": "browserName()" * } ] * } ); * } ); * * @example * // As an object, extracting different data for the different types * // This would be used with a data source such as: * // { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" } * // Here the `phone` integer is used for sorting and type detection, while `phone_filter` * // (which has both forms) is used for filtering for if a user inputs either format, while * // the formatted phone number is the one that is shown in the table. * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": null, // Use the full data source object for the renderer's source * "render": { * "_": "phone", * "filter": "phone_filter", * "display": "phone_display" * } * } ] * } ); * } ); * * @example * // Use as a function to create a link from the data source * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "data": "download_link", * "render": function ( data, type, full ) { * return '<a href="'+data+'">Download</a>'; * } * } ] * } ); * } ); */ "mRender": null, /** * Change the cell type created for the column - either TD cells or TH cells. This * can be useful as TH cells have semantic meaning in the table body, allowing them * to act as a header for a row (you may wish to add scope='row' to the TH elements). * @type string * @default td * * @name DataTable.defaults.column.cellType * @dtopt Columns * * @example * // Make the first column use TH cells * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ { * "targets": [ 0 ], * "cellType": "th" * } ] * } ); * } ); */ "sCellType": "td", /** * Class to give to each cell in this column. * @type string * @default <i>Empty string</i> * * @name DataTable.defaults.column.class * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "class": "my_class", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "class": "my_class" }, * null, * null, * null, * null * ] * } ); * } ); */ "sClass": "", /** * When DataTables calculates the column widths to assign to each column, * it finds the longest string in each column and then constructs a * temporary table and reads the widths from that. The problem with this * is that "mmm" is much wider then "iiii", but the latter is a longer * string - thus the calculation can go wrong (doing it properly and putting * it into an DOM object and measuring that is horribly(!) slow). Thus as * a "work around" we provide this option. It will append its value to the * text that is found to be the longest string for the column - i.e. padding. * Generally you shouldn't need this! * @type string * @default <i>Empty string<i> * * @name DataTable.defaults.column.contentPadding * @dtopt Columns * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * null, * { * "contentPadding": "mmm" * } * ] * } ); * } ); */ "sContentPadding": "", /** * Allows a default value to be given for a column's data, and will be used * whenever a null data source is encountered (this can be because `data` * is set to null, or because the data source itself is null). * @type string * @default null * * @name DataTable.defaults.column.defaultContent * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { * "data": null, * "defaultContent": "Edit", * "targets": [ -1 ] * } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * null, * { * "data": null, * "defaultContent": "Edit" * } * ] * } ); * } ); */ "sDefaultContent": null, /** * This parameter is only used in DataTables' server-side processing. It can * be exceptionally useful to know what columns are being displayed on the * client side, and to map these to database fields. When defined, the names * also allow DataTables to reorder information from the server if it comes * back in an unexpected order (i.e. if you switch your columns around on the * client-side, your server-side code does not also need updating). * @type string * @default <i>Empty string</i> * * @name DataTable.defaults.column.name * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "name": "engine", "targets": [ 0 ] }, * { "name": "browser", "targets": [ 1 ] }, * { "name": "platform", "targets": [ 2 ] }, * { "name": "version", "targets": [ 3 ] }, * { "name": "grade", "targets": [ 4 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "name": "engine" }, * { "name": "browser" }, * { "name": "platform" }, * { "name": "version" }, * { "name": "grade" } * ] * } ); * } ); */ "sName": "", /** * Defines a data source type for the ordering which can be used to read * real-time information from the table (updating the internally cached * version) prior to ordering. This allows ordering to occur on user * editable elements such as form inputs. * @type string * @default std * * @name DataTable.defaults.column.orderDataType * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "orderDataType": "dom-text", "targets": [ 2, 3 ] }, * { "type": "numeric", "targets": [ 3 ] }, * { "orderDataType": "dom-select", "targets": [ 4 ] }, * { "orderDataType": "dom-checkbox", "targets": [ 5 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * null, * null, * { "orderDataType": "dom-text" }, * { "orderDataType": "dom-text", "type": "numeric" }, * { "orderDataType": "dom-select" }, * { "orderDataType": "dom-checkbox" } * ] * } ); * } ); */ "sSortDataType": "std", /** * The title of this column. * @type string * @default null <i>Derived from the 'TH' value for this column in the * original HTML table.</i> * * @name DataTable.defaults.column.title * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "title": "My column title", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "title": "My column title" }, * null, * null, * null, * null * ] * } ); * } ); */ "sTitle": null, /** * The type allows you to specify how the data for this column will be * ordered. Four types (string, numeric, date and html (which will strip * HTML tags before ordering)) are currently available. Note that only date * formats understood by Javascript's Date() object will be accepted as type * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string', * 'numeric', 'date' or 'html' (by default). Further types can be adding * through plug-ins. * @type string * @default null <i>Auto-detected from raw data</i> * * @name DataTable.defaults.column.type * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "type": "html", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "type": "html" }, * null, * null, * null, * null * ] * } ); * } ); */ "sType": null, /** * Defining the width of the column, this parameter may take any CSS value * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not * been given a specific width through this interface ensuring that the table * remains readable. * @type string * @default null <i>Automatic</i> * * @name DataTable.defaults.column.width * @dtopt Columns * * @example * // Using `columnDefs` * $(document).ready( function() { * $('#example').dataTable( { * "columnDefs": [ * { "width": "20%", "targets": [ 0 ] } * ] * } ); * } ); * * @example * // Using `columns` * $(document).ready( function() { * $('#example').dataTable( { * "columns": [ * { "width": "20%" }, * null, * null, * null, * null * ] * } ); * } ); */ "sWidth": null }; _fnHungarianMap( DataTable.defaults.column ); /** * DataTables settings object - this holds all the information needed for a * given table, including configuration, data and current application of the * table options. DataTables does not have a single instance for each DataTable * with the settings attached to that instance, but rather instances of the * DataTable "class" are created on-the-fly as needed (typically by a * $().dataTable() call) and the settings object is then applied to that * instance. * * Note that this object is related to {@link DataTable.defaults} but this * one is the internal data store for DataTables's cache of columns. It should * NOT be manipulated outside of DataTables. Any configuration should be done * through the initialisation options. * @namespace * @todo Really should attach the settings object to individual instances so we * don't need to create new instances on each $().dataTable() call (if the * table already exists). It would also save passing oSettings around and * into every single function. However, this is a very significant * architecture change for DataTables and will almost certainly break * backwards compatibility with older installations. This is something that * will be done in 2.0. */ DataTable.models.oSettings = { /** * Primary features of DataTables and their enablement state. * @namespace */ "oFeatures": { /** * Flag to say if DataTables should automatically try to calculate the * optimum table and columns widths (true) or not (false). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bAutoWidth": null, /** * Delay the creation of TR and TD elements until they are actually * needed by a driven page draw. This can give a significant speed * increase for Ajax source and Javascript source data, but makes no * difference at all fro DOM and server-side processing tables. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bDeferRender": null, /** * Enable filtering on the table or not. Note that if this is disabled * then there is no filtering at all on the table, including fnFilter. * To just remove the filtering input use sDom and remove the 'f' option. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bFilter": null, /** * Table information element (the 'Showing x of y records' div) enable * flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bInfo": null, /** * Present a user control allowing the end user to change the page size * when pagination is enabled. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bLengthChange": null, /** * Pagination enabled or not. Note that if this is disabled then length * changing must also be disabled. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bPaginate": null, /** * Processing indicator enable flag whenever DataTables is enacting a * user request - typically an Ajax request for server-side processing. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bProcessing": null, /** * Server-side processing enabled flag - when enabled DataTables will * get all data from the server for every draw - there is no filtering, * sorting or paging done on the client-side. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bServerSide": null, /** * Sorting enablement flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSort": null, /** * Multi-column sorting * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortMulti": null, /** * Apply a class to the columns which are being sorted to provide a * visual highlight or not. This can slow things down when enabled since * there is a lot of DOM interaction. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortClasses": null, /** * State saving enablement flag. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bStateSave": null }, /** * Scrolling settings for a table. * @namespace */ "oScroll": { /** * When the table is shorter in height than sScrollY, collapse the * table container down to the height of the table (when true). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bCollapse": null, /** * Width of the scrollbar for the web-browser's platform. Calculated * during table initialisation. * @type int * @default 0 */ "iBarWidth": 0, /** * Viewport width for horizontal scrolling. Horizontal scrolling is * disabled if an empty string. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sX": null, /** * Width to expand the table to when using x-scrolling. Typically you * should not need to use this. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @deprecated */ "sXInner": null, /** * Viewport height for vertical scrolling. Vertical scrolling is disabled * if an empty string. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sY": null }, /** * Language information for the table. * @namespace * @extends DataTable.defaults.oLanguage */ "oLanguage": { /** * Information callback function. See * {@link DataTable.defaults.fnInfoCallback} * @type function * @default null */ "fnInfoCallback": null }, /** * Browser support parameters * @namespace */ "oBrowser": { /** * Indicate if the browser incorrectly calculates width:100% inside a * scrolling element (IE6/7) * @type boolean * @default false */ "bScrollOversize": false, /** * Determine if the vertical scrollbar is on the right or left of the * scrolling container - needed for rtl language layout, although not * all browsers move the scrollbar (Safari). * @type boolean * @default false */ "bScrollbarLeft": false, /** * Flag for if `getBoundingClientRect` is fully supported or not * @type boolean * @default false */ "bBounding": false, /** * Browser scrollbar width * @type integer * @default 0 */ "barWidth": 0 }, "ajax": null, /** * Array referencing the nodes which are used for the features. The * parameters of this object match what is allowed by sDom - i.e. * <ul> * <li>'l' - Length changing</li> * <li>'f' - Filtering input</li> * <li>'t' - The table!</li> * <li>'i' - Information</li> * <li>'p' - Pagination</li> * <li>'r' - pRocessing</li> * </ul> * @type array * @default [] */ "aanFeatures": [], /** * Store data information - see {@link DataTable.models.oRow} for detailed * information. * @type array * @default [] */ "aoData": [], /** * Array of indexes which are in the current display (after filtering etc) * @type array * @default [] */ "aiDisplay": [], /** * Array of indexes for display - no filtering * @type array * @default [] */ "aiDisplayMaster": [], /** * Map of row ids to data indexes * @type object * @default {} */ "aIds": {}, /** * Store information about each column that is in use * @type array * @default [] */ "aoColumns": [], /** * Store information about the table's header * @type array * @default [] */ "aoHeader": [], /** * Store information about the table's footer * @type array * @default [] */ "aoFooter": [], /** * Store the applied global search information in case we want to force a * research or compare the old search to a new one. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @namespace * @extends DataTable.models.oSearch */ "oPreviousSearch": {}, /** * Store the applied search for each column - see * {@link DataTable.models.oSearch} for the format that is used for the * filtering information for each column. * @type array * @default [] */ "aoPreSearchCols": [], /** * Sorting that is applied to the table. Note that the inner arrays are * used in the following manner: * <ul> * <li>Index 0 - column number</li> * <li>Index 1 - current sorting direction</li> * </ul> * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @todo These inner arrays should really be objects */ "aaSorting": null, /** * Sorting that is always applied to the table (i.e. prefixed in front of * aaSorting). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "aaSortingFixed": [], /** * Classes to use for the striping of a table. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "asStripeClasses": null, /** * If restoring a table - we should restore its striping classes as well * @type array * @default [] */ "asDestroyStripes": [], /** * If restoring a table - we should restore its width * @type int * @default 0 */ "sDestroyWidth": 0, /** * Callback functions array for every time a row is inserted (i.e. on a draw). * @type array * @default [] */ "aoRowCallback": [], /** * Callback functions for the header on each draw. * @type array * @default [] */ "aoHeaderCallback": [], /** * Callback function for the footer on each draw. * @type array * @default [] */ "aoFooterCallback": [], /** * Array of callback functions for draw callback functions * @type array * @default [] */ "aoDrawCallback": [], /** * Array of callback functions for row created function * @type array * @default [] */ "aoRowCreatedCallback": [], /** * Callback functions for just before the table is redrawn. A return of * false will be used to cancel the draw. * @type array * @default [] */ "aoPreDrawCallback": [], /** * Callback functions for when the table has been initialised. * @type array * @default [] */ "aoInitComplete": [], /** * Callbacks for modifying the settings to be stored for state saving, prior to * saving state. * @type array * @default [] */ "aoStateSaveParams": [], /** * Callbacks for modifying the settings that have been stored for state saving * prior to using the stored values to restore the state. * @type array * @default [] */ "aoStateLoadParams": [], /** * Callbacks for operating on the settings object once the saved state has been * loaded * @type array * @default [] */ "aoStateLoaded": [], /** * Cache the table ID for quick access * @type string * @default <i>Empty string</i> */ "sTableId": "", /** * The TABLE node for the main table * @type node * @default null */ "nTable": null, /** * Permanent ref to the thead element * @type node * @default null */ "nTHead": null, /** * Permanent ref to the tfoot element - if it exists * @type node * @default null */ "nTFoot": null, /** * Permanent ref to the tbody element * @type node * @default null */ "nTBody": null, /** * Cache the wrapper node (contains all DataTables controlled elements) * @type node * @default null */ "nTableWrapper": null, /** * Indicate if when using server-side processing the loading of data * should be deferred until the second draw. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean * @default false */ "bDeferLoading": false, /** * Indicate if all required information has been read in * @type boolean * @default false */ "bInitialised": false, /** * Information about open rows. Each object in the array has the parameters * 'nTr' and 'nParent' * @type array * @default [] */ "aoOpenRows": [], /** * Dictate the positioning of DataTables' control elements - see * {@link DataTable.model.oInit.sDom}. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default null */ "sDom": null, /** * Search delay (in mS) * @type integer * @default null */ "searchDelay": null, /** * Which type of pagination should be used. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default two_button */ "sPaginationType": "two_button", /** * The state duration (for `stateSave`) in seconds. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type int * @default 0 */ "iStateDuration": 0, /** * Array of callback functions for state saving. Each array element is an * object with the following parameters: * <ul> * <li>function:fn - function to call. Takes two parameters, oSettings * and the JSON string to save that has been thus far created. Returns * a JSON string to be inserted into a json object * (i.e. '"param": [ 0, 1, 2]')</li> * <li>string:sName - name of callback</li> * </ul> * @type array * @default [] */ "aoStateSave": [], /** * Array of callback functions for state loading. Each array element is an * object with the following parameters: * <ul> * <li>function:fn - function to call. Takes two parameters, oSettings * and the object stored. May return false to cancel state loading</li> * <li>string:sName - name of callback</li> * </ul> * @type array * @default [] */ "aoStateLoad": [], /** * State that was saved. Useful for back reference * @type object * @default null */ "oSavedState": null, /** * State that was loaded. Useful for back reference * @type object * @default null */ "oLoadedState": null, /** * Source url for AJAX data for the table. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string * @default null */ "sAjaxSource": null, /** * Property from a given object from which to read the table data from. This * can be an empty string (when not server-side processing), in which case * it is assumed an an array is given directly. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sAjaxDataProp": null, /** * Note if draw should be blocked while getting data * @type boolean * @default true */ "bAjaxDataGet": true, /** * The last jQuery XHR object that was used for server-side data gathering. * This can be used for working with the XHR information in one of the * callbacks * @type object * @default null */ "jqXHR": null, /** * JSON returned from the server in the last Ajax request * @type object * @default undefined */ "json": undefined, /** * Data submitted as part of the last Ajax request * @type object * @default undefined */ "oAjaxData": undefined, /** * Function to get the server-side data. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type function */ "fnServerData": null, /** * Functions which are called prior to sending an Ajax request so extra * parameters can easily be sent to the server * @type array * @default [] */ "aoServerParams": [], /** * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if * required). * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type string */ "sServerMethod": null, /** * Format numbers for display. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type function */ "fnFormatNumber": null, /** * List of options that can be used for the user selectable length menu. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type array * @default [] */ "aLengthMenu": null, /** * Counter for the draws that the table does. Also used as a tracker for * server-side processing * @type int * @default 0 */ "iDraw": 0, /** * Indicate if a redraw is being done - useful for Ajax * @type boolean * @default false */ "bDrawing": false, /** * Draw index (iDraw) of the last error when parsing the returned data * @type int * @default -1 */ "iDrawError": -1, /** * Paging display length * @type int * @default 10 */ "_iDisplayLength": 10, /** * Paging start point - aiDisplay index * @type int * @default 0 */ "_iDisplayStart": 0, /** * Server-side processing - number of records in the result set * (i.e. before filtering), Use fnRecordsTotal rather than * this property to get the value of the number of records, regardless of * the server-side processing setting. * @type int * @default 0 * @private */ "_iRecordsTotal": 0, /** * Server-side processing - number of records in the current display set * (i.e. after filtering). Use fnRecordsDisplay rather than * this property to get the value of the number of records, regardless of * the server-side processing setting. * @type boolean * @default 0 * @private */ "_iRecordsDisplay": 0, /** * The classes to use for the table * @type object * @default {} */ "oClasses": {}, /** * Flag attached to the settings object so you can check in the draw * callback if filtering has been done in the draw. Deprecated in favour of * events. * @type boolean * @default false * @deprecated */ "bFiltered": false, /** * Flag attached to the settings object so you can check in the draw * callback if sorting has been done in the draw. Deprecated in favour of * events. * @type boolean * @default false * @deprecated */ "bSorted": false, /** * Indicate that if multiple rows are in the header and there is more than * one unique cell per column, if the top one (true) or bottom one (false) * should be used for sorting / title by DataTables. * Note that this parameter will be set by the initialisation routine. To * set a default use {@link DataTable.defaults}. * @type boolean */ "bSortCellsTop": null, /** * Initialisation object that is used for the table * @type object * @default null */ "oInit": null, /** * Destroy callback functions - for plug-ins to attach themselves to the * destroy so they can clean up markup and events. * @type array * @default [] */ "aoDestroyCallback": [], /** * Get the number of records in the current record set, before filtering * @type function */ "fnRecordsTotal": function () { return _fnDataSource( this ) == 'ssp' ? this._iRecordsTotal * 1 : this.aiDisplayMaster.length; }, /** * Get the number of records in the current record set, after filtering * @type function */ "fnRecordsDisplay": function () { return _fnDataSource( this ) == 'ssp' ? this._iRecordsDisplay * 1 : this.aiDisplay.length; }, /** * Get the display end point - aiDisplay index * @type function */ "fnDisplayEnd": function () { var len = this._iDisplayLength, start = this._iDisplayStart, calc = start + len, records = this.aiDisplay.length, features = this.oFeatures, paginate = features.bPaginate; if ( features.bServerSide ) { return paginate === false || len === -1 ? start + records : Math.min( start+len, this._iRecordsDisplay ); } else { return ! paginate || calc>records || len===-1 ? records : calc; } }, /** * The DataTables object for this table * @type object * @default null */ "oInstance": null, /** * Unique identifier for each instance of the DataTables object. If there * is an ID on the table node, then it takes that value, otherwise an * incrementing internal counter is used. * @type string * @default null */ "sInstance": null, /** * tabindex attribute value that is added to DataTables control elements, allowing * keyboard navigation of the table and its controls. */ "iTabIndex": 0, /** * DIV container for the footer scrolling table if scrolling */ "nScrollHead": null, /** * DIV container for the footer scrolling table if scrolling */ "nScrollFoot": null, /** * Last applied sort * @type array * @default [] */ "aLastSort": [], /** * Stored plug-in instances * @type object * @default {} */ "oPlugins": {}, /** * Function used to get a row's id from the row's data * @type function * @default null */ "rowIdFn": null, /** * Data location where to store a row's id * @type string * @default null */ "rowId": null }; /** * Extension object for DataTables that is used to provide all extension * options. * * Note that the `DataTable.ext` object is available through * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is * also aliased to `jQuery.fn.dataTableExt` for historic reasons. * @namespace * @extends DataTable.models.ext */ /** * DataTables extensions * * This namespace acts as a collection area for plug-ins that can be used to * extend DataTables capabilities. Indeed many of the build in methods * use this method to provide their own capabilities (sorting methods for * example). * * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy * reasons * * @namespace */ DataTable.ext = _ext = { /** * Buttons. For use with the Buttons extension for DataTables. This is * defined here so other extensions can define buttons regardless of load * order. It is _not_ used by DataTables core. * * @type object * @default {} */ buttons: {}, /** * Element class names * * @type object * @default {} */ classes: {}, /** * DataTables build type (expanded by the download builder) * * @type string */ build:"bs/dt-1.10.21", /** * Error reporting. * * How should DataTables report an error. Can take the value 'alert', * 'throw', 'none' or a function. * * @type string|function * @default alert */ errMode: "alert", /** * Feature plug-ins. * * This is an array of objects which describe the feature plug-ins that are * available to DataTables. These feature plug-ins are then available for * use through the `dom` initialisation option. * * Each feature plug-in is described by an object which must have the * following properties: * * * `fnInit` - function that is used to initialise the plug-in, * * `cFeature` - a character so the feature can be enabled by the `dom` * instillation option. This is case sensitive. * * The `fnInit` function has the following input parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * * And the following return is expected: * * * {node|null} The element which contains your feature. Note that the * return may also be void if your plug-in does not require to inject any * DOM elements into DataTables control (`dom`) - for example this might * be useful when developing a plug-in which allows table control via * keyboard entry * * @type array * * @example * $.fn.dataTable.ext.features.push( { * "fnInit": function( oSettings ) { * return new TableTools( { "oDTSettings": oSettings } ); * }, * "cFeature": "T" * } ); */ feature: [], /** * Row searching. * * This method of searching is complimentary to the default type based * searching, and a lot more comprehensive as it allows you complete control * over the searching logic. Each element in this array is a function * (parameters described below) that is called for every row in the table, * and your logic decides if it should be included in the searching data set * or not. * * Searching functions have the following input parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * 2. `{array|object}` Data for the row to be processed (same as the * original format that was passed in as the data source, or an array * from a DOM data source * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which * can be useful to retrieve the `TR` element if you need DOM interaction. * * And the following return is expected: * * * {boolean} Include the row in the searched result set (true) or not * (false) * * Note that as with the main search ability in DataTables, technically this * is "filtering", since it is subtractive. However, for consistency in * naming we call it searching here. * * @type array * @default [] * * @example * // The following example shows custom search being applied to the * // fourth column (i.e. the data[3] index) based on two input values * // from the end-user, matching the data in a certain range. * $.fn.dataTable.ext.search.push( * function( settings, data, dataIndex ) { * var min = document.getElementById('min').value * 1; * var max = document.getElementById('max').value * 1; * var version = data[3] == "-" ? 0 : data[3]*1; * * if ( min == "" && max == "" ) { * return true; * } * else if ( min == "" && version < max ) { * return true; * } * else if ( min < version && "" == max ) { * return true; * } * else if ( min < version && version < max ) { * return true; * } * return false; * } * ); */ search: [], /** * Selector extensions * * The `selector` option can be used to extend the options available for the * selector modifier options (`selector-modifier` object data type) that * each of the three built in selector types offer (row, column and cell + * their plural counterparts). For example the Select extension uses this * mechanism to provide an option to select only rows, columns and cells * that have been marked as selected by the end user (`{selected: true}`), * which can be used in conjunction with the existing built in selector * options. * * Each property is an array to which functions can be pushed. The functions * take three attributes: * * * Settings object for the host table * * Options object (`selector-modifier` object type) * * Array of selected item indexes * * The return is an array of the resulting item indexes after the custom * selector has been applied. * * @type object */ selector: { cell: [], column: [], row: [] }, /** * Internal functions, exposed for used in plug-ins. * * Please note that you should not need to use the internal methods for * anything other than a plug-in (and even then, try to avoid if possible). * The internal function may change between releases. * * @type object * @default {} */ internal: {}, /** * Legacy configuration options. Enable and disable legacy options that * are available in DataTables. * * @type object */ legacy: { /** * Enable / disable DataTables 1.9 compatible server-side processing * requests * * @type boolean * @default null */ ajax: null }, /** * Pagination plug-in methods. * * Each entry in this object is a function and defines which buttons should * be shown by the pagination rendering method that is used for the table: * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the * buttons are displayed in the document, while the functions here tell it * what buttons to display. This is done by returning an array of button * descriptions (what each button will do). * * Pagination types (the four built in options and any additional plug-in * options defined here) can be used through the `paginationType` * initialisation parameter. * * The functions defined take two parameters: * * 1. `{int} page` The current page index * 2. `{int} pages` The number of pages in the table * * Each function is expected to return an array where each element of the * array can be one of: * * * `first` - Jump to first page when activated * * `last` - Jump to last page when activated * * `previous` - Show previous page when activated * * `next` - Show next page when activated * * `{int}` - Show page of the index given * * `{array}` - A nested array containing the above elements to add a * containing 'DIV' element (might be useful for styling). * * Note that DataTables v1.9- used this object slightly differently whereby * an object with two functions would be defined for each plug-in. That * ability is still supported by DataTables 1.10+ to provide backwards * compatibility, but this option of use is now decremented and no longer * documented in DataTables 1.10+. * * @type object * @default {} * * @example * // Show previous, next and current page buttons only * $.fn.dataTableExt.oPagination.current = function ( page, pages ) { * return [ 'previous', page, 'next' ]; * }; */ pager: {}, renderer: { pageButton: {}, header: {} }, /** * Ordering plug-ins - custom data source * * The extension options for ordering of data available here is complimentary * to the default type based ordering that DataTables typically uses. It * allows much greater control over the the data that is being used to * order a column, but is necessarily therefore more complex. * * This type of ordering is useful if you want to do ordering based on data * live from the DOM (for example the contents of an 'input' element) rather * than just the static string that DataTables knows of. * * The way these plug-ins work is that you create an array of the values you * wish to be ordering for the column in question and then return that * array. The data in the array much be in the index order of the rows in * the table (not the currently ordering order!). Which order data gathering * function is run here depends on the `dt-init columns.orderDataType` * parameter that is used for the column (if any). * * The functions defined take two parameters: * * 1. `{object}` DataTables settings object: see * {@link DataTable.models.oSettings} * 2. `{int}` Target column index * * Each function is expected to return an array: * * * `{array}` Data for the column to be ordering upon * * @type array * * @example * // Ordering using `input` node values * $.fn.dataTable.ext.order['dom-text'] = function ( settings, col ) * { * return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) { * return $('input', td).val(); * } ); * } */ order: {}, /** * Type based plug-ins. * * Each column in DataTables has a type assigned to it, either by automatic * detection or by direct assignment using the `type` option for the column. * The type of a column will effect how it is ordering and search (plug-ins * can also make use of the column type if required). * * @namespace */ type: { /** * Type detection functions. * * The functions defined in this object are used to automatically detect * a column's type, making initialisation of DataTables super easy, even * when complex data is in the table. * * The functions defined take two parameters: * * 1. `{*}` Data from the column cell to be analysed * 2. `{settings}` DataTables settings object. This can be used to * perform context specific type detection - for example detection * based on language settings such as using a comma for a decimal * place. Generally speaking the options from the settings will not * be required * * Each function is expected to return: * * * `{string|null}` Data type detected, or null if unknown (and thus * pass it on to the other type detection functions. * * @type array * * @example * // Currency type detection plug-in: * $.fn.dataTable.ext.type.detect.push( * function ( data, settings ) { * // Check the numeric part * if ( ! data.substring(1).match(/[0-9]/) ) { * return null; * } * * // Check prefixed by currency * if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) { * return 'currency'; * } * return null; * } * ); */ detect: [], /** * Type based search formatting. * * The type based searching functions can be used to pre-format the * data to be search on. For example, it can be used to strip HTML * tags or to de-format telephone numbers for numeric only searching. * * Note that is a search is not defined for a column of a given type, * no search formatting will be performed. * * Pre-processing of searching data plug-ins - When you assign the sType * for a column (or have it automatically detected for you by DataTables * or a type detection plug-in), you will typically be using this for * custom sorting, but it can also be used to provide custom searching * by allowing you to pre-processing the data and returning the data in * the format that should be searched upon. This is done by adding * functions this object with a parameter name which matches the sType * for that target column. This is the corollary of <i>afnSortData</i> * for searching data. * * The functions defined take a single parameter: * * 1. `{*}` Data from the column cell to be prepared for searching * * Each function is expected to return: * * * `{string|null}` Formatted string that will be used for the searching. * * @type object * @default {} * * @example * $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) { * return d.replace(/\n/g," ").replace( /<.*?>/g, "" ); * } */ search: {}, /** * Type based ordering. * * The column type tells DataTables what ordering to apply to the table * when a column is sorted upon. The order for each type that is defined, * is defined by the functions available in this object. * * Each ordering option can be described by three properties added to * this object: * * * `{type}-pre` - Pre-formatting function * * `{type}-asc` - Ascending order function * * `{type}-desc` - Descending order function * * All three can be used together, only `{type}-pre` or only * `{type}-asc` and `{type}-desc` together. It is generally recommended * that only `{type}-pre` is used, as this provides the optimal * implementation in terms of speed, although the others are provided * for compatibility with existing Javascript sort functions. * * `{type}-pre`: Functions defined take a single parameter: * * 1. `{*}` Data from the column cell to be prepared for ordering * * And return: * * * `{*}` Data to be sorted upon * * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort * functions, taking two parameters: * * 1. `{*}` Data to compare to the second parameter * 2. `{*}` Data to compare to the first parameter * * And returning: * * * `{*}` Ordering match: <0 if first parameter should be sorted lower * than the second parameter, ===0 if the two parameters are equal and * >0 if the first parameter should be sorted height than the second * parameter. * * @type object * @default {} * * @example * // Numeric ordering of formatted numbers with a pre-formatter * $.extend( $.fn.dataTable.ext.type.order, { * "string-pre": function(x) { * a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" ); * return parseFloat( a ); * } * } ); * * @example * // Case-sensitive string ordering, with no pre-formatting method * $.extend( $.fn.dataTable.ext.order, { * "string-case-asc": function(x,y) { * return ((x < y) ? -1 : ((x > y) ? 1 : 0)); * }, * "string-case-desc": function(x,y) { * return ((x < y) ? 1 : ((x > y) ? -1 : 0)); * } * } ); */ order: {} }, /** * Unique DataTables instance counter * * @type int * @private */ _unique: 0, // // Depreciated // The following properties are retained for backwards compatiblity only. // The should not be used in new projects and will be removed in a future // version // /** * Version check function. * @type function * @depreciated Since 1.10 */ fnVersionCheck: DataTable.fnVersionCheck, /** * Index for what 'this' index API functions should use * @type int * @deprecated Since v1.10 */ iApiIndex: 0, /** * jQuery UI class container * @type object * @deprecated Since v1.10 */ oJUIClasses: {}, /** * Software version * @type string * @deprecated Since v1.10 */ sVersion: DataTable.version }; // // Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts // $.extend( _ext, { afnFiltering: _ext.search, aTypes: _ext.type.detect, ofnSearch: _ext.type.search, oSort: _ext.type.order, afnSortData: _ext.order, aoFeatures: _ext.feature, oApi: _ext.internal, oStdClasses: _ext.classes, oPagination: _ext.pager } ); $.extend( DataTable.ext.classes, { "sTable": "dataTable", "sNoFooter": "no-footer", /* Paging buttons */ "sPageButton": "paginate_button", "sPageButtonActive": "current", "sPageButtonDisabled": "disabled", /* Striping classes */ "sStripeOdd": "odd", "sStripeEven": "even", /* Empty row */ "sRowEmpty": "dataTables_empty", /* Features */ "sWrapper": "dataTables_wrapper", "sFilter": "dataTables_filter", "sInfo": "dataTables_info", "sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */ "sLength": "dataTables_length", "sProcessing": "dataTables_processing", /* Sorting */ "sSortAsc": "sorting_asc", "sSortDesc": "sorting_desc", "sSortable": "sorting", /* Sortable in both directions */ "sSortableAsc": "sorting_asc_disabled", "sSortableDesc": "sorting_desc_disabled", "sSortableNone": "sorting_disabled", "sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */ /* Filtering */ "sFilterInput": "", /* Page length */ "sLengthSelect": "", /* Scrolling */ "sScrollWrapper": "dataTables_scroll", "sScrollHead": "dataTables_scrollHead", "sScrollHeadInner": "dataTables_scrollHeadInner", "sScrollBody": "dataTables_scrollBody", "sScrollFoot": "dataTables_scrollFoot", "sScrollFootInner": "dataTables_scrollFootInner", /* Misc */ "sHeaderTH": "", "sFooterTH": "", // Deprecated "sSortJUIAsc": "", "sSortJUIDesc": "", "sSortJUI": "", "sSortJUIAscAllowed": "", "sSortJUIDescAllowed": "", "sSortJUIWrapper": "", "sSortIcon": "", "sJUIHeader": "", "sJUIFooter": "" } ); var extPagination = DataTable.ext.pager; function _numbers ( page, pages ) { var numbers = [], buttons = extPagination.numbers_length, half = Math.floor( buttons / 2 ), i = 1; if ( pages <= buttons ) { numbers = _range( 0, pages ); } else if ( page <= half ) { numbers = _range( 0, buttons-2 ); numbers.push( 'ellipsis' ); numbers.push( pages-1 ); } else if ( page >= pages - 1 - half ) { numbers = _range( pages-(buttons-2), pages ); numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6 numbers.splice( 0, 0, 0 ); } else { numbers = _range( page-half+2, page+half-1 ); numbers.push( 'ellipsis' ); numbers.push( pages-1 ); numbers.splice( 0, 0, 'ellipsis' ); numbers.splice( 0, 0, 0 ); } numbers.DT_el = 'span'; return numbers; } $.extend( extPagination, { simple: function ( page, pages ) { return [ 'previous', 'next' ]; }, full: function ( page, pages ) { return [ 'first', 'previous', 'next', 'last' ]; }, numbers: function ( page, pages ) { return [ _numbers(page, pages) ]; }, simple_numbers: function ( page, pages ) { return [ 'previous', _numbers(page, pages), 'next' ]; }, full_numbers: function ( page, pages ) { return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ]; }, first_last_numbers: function (page, pages) { return ['first', _numbers(page, pages), 'last']; }, // For testing and plug-ins to use _numbers: _numbers, // Number of number buttons (including ellipsis) to show. _Must be odd!_ numbers_length: 7 } ); $.extend( true, DataTable.ext.renderer, { pageButton: { _: function ( settings, host, idx, buttons, page, pages ) { var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; var aria = settings.oLanguage.oAria.paginate || {}; var btnDisplay, btnClass, counter=0; var attach = function( container, buttons ) { var i, ien, node, button, tabIndex; var disabledClass = classes.sPageButtonDisabled; var clickHandler = function ( e ) { _fnPageChange( settings, e.data.action, true ); }; for ( i=0, ien=buttons.length ; i<ien ; i++ ) { button = buttons[i]; if ( $.isArray( button ) ) { var inner = $( '<'+(button.DT_el || 'div')+'/>' ) .appendTo( container ); attach( inner, button ); } else { btnDisplay = null; btnClass = button; tabIndex = settings.iTabIndex; switch ( button ) { case 'ellipsis': container.append('<span class="ellipsis">&#x2026;</span>'); break; case 'first': btnDisplay = lang.sFirst; if ( page === 0 ) { tabIndex = -1; btnClass += ' ' + disabledClass; } break; case 'previous': btnDisplay = lang.sPrevious; if ( page === 0 ) { tabIndex = -1; btnClass += ' ' + disabledClass; } break; case 'next': btnDisplay = lang.sNext; if ( pages === 0 || page === pages-1 ) { tabIndex = -1; btnClass += ' ' + disabledClass; } break; case 'last': btnDisplay = lang.sLast; if ( page === pages-1 ) { tabIndex = -1; btnClass += ' ' + disabledClass; } break; default: btnDisplay = button + 1; btnClass = page === button ? classes.sPageButtonActive : ''; break; } if ( btnDisplay !== null ) { node = $('<a>', { 'class': classes.sPageButton+' '+btnClass, 'aria-controls': settings.sTableId, 'aria-label': aria[ button ], 'data-dt-idx': counter, 'tabindex': tabIndex, 'id': idx === 0 && typeof button === 'string' ? settings.sTableId +'_'+ button : null } ) .html( btnDisplay ) .appendTo( container ); _fnBindAction( node, {action: button}, clickHandler ); counter++; } } } }; // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame. Try / catch the error. Not good for // accessibility, but neither are frames. var activeEl; try { // Because this approach is destroying and recreating the paging // elements, focus is lost on the select button which is bad for // accessibility. So we want to restore focus once the draw has // completed activeEl = $(host).find(document.activeElement).data('dt-idx'); } catch (e) {} attach( $(host).empty(), buttons ); if ( activeEl !== undefined ) { $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); } } } } ); // Built in type detection. See model.ext.aTypes for information about // what is required from this methods. $.extend( DataTable.ext.type.detect, [ // Plain numbers - first since V8 detects some plain numbers as dates // e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...). function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _isNumber( d, decimal ) ? 'num'+decimal : null; }, // Dates (only those recognised by the browser's Date.parse) function ( d, settings ) { // V8 tries _very_ hard to make a string passed into `Date.parse()` // valid, so we need to use a regex to restrict date formats. Use a // plug-in for anything other than ISO8601 style strings if ( d && !(d instanceof Date) && ! _re_date.test(d) ) { return null; } var parsed = Date.parse(d); return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null; }, // Formatted numbers function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null; }, // HTML numeric function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null; }, // HTML numeric, formatted function ( d, settings ) { var decimal = settings.oLanguage.sDecimal; return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null; }, // HTML (this is strict checking - there must be html) function ( d, settings ) { return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ? 'html' : null; } ] ); // Filter formatting functions. See model.ext.ofnSearch for information about // what is required from these methods. // // Note that additional search methods are added for the html numbers and // html formatted numbers by `_addNumericSort()` when we know what the decimal // place is $.extend( DataTable.ext.type.search, { html: function ( data ) { return _empty(data) ? data : typeof data === 'string' ? data .replace( _re_new_lines, " " ) .replace( _re_html, "" ) : ''; }, string: function ( data ) { return _empty(data) ? data : typeof data === 'string' ? data.replace( _re_new_lines, " " ) : data; } } ); var __numericReplace = function ( d, decimalPlace, re1, re2 ) { if ( d !== 0 && (!d || d === '-') ) { return -Infinity; } // If a decimal place other than `.` is used, it needs to be given to the // function so we can detect it and replace with a `.` which is the only // decimal place Javascript recognises - it is not locale aware. if ( decimalPlace ) { d = _numToDecimal( d, decimalPlace ); } if ( d.replace ) { if ( re1 ) { d = d.replace( re1, '' ); } if ( re2 ) { d = d.replace( re2, '' ); } } return d * 1; }; // Add the numeric 'deformatting' functions for sorting and search. This is done // in a function to provide an easy ability for the language options to add // additional methods if a non-period decimal place is used. function _addNumericSort ( decimalPlace ) { $.each( { // Plain numbers "num": function ( d ) { return __numericReplace( d, decimalPlace ); }, // Formatted numbers "num-fmt": function ( d ) { return __numericReplace( d, decimalPlace, _re_formatted_numeric ); }, // HTML numeric "html-num": function ( d ) { return __numericReplace( d, decimalPlace, _re_html ); }, // HTML numeric, formatted "html-num-fmt": function ( d ) { return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric ); } }, function ( key, fn ) { // Add the ordering method _ext.type.order[ key+decimalPlace+'-pre' ] = fn; // For HTML types add a search formatter that will strip the HTML if ( key.match(/^html\-/) ) { _ext.type.search[ key+decimalPlace ] = _ext.type.search.html; } } ); } // Default sort methods $.extend( _ext.type.order, { // Dates "date-pre": function ( d ) { var ts = Date.parse( d ); return isNaN(ts) ? -Infinity : ts; }, // html "html-pre": function ( a ) { return _empty(a) ? '' : a.replace ? a.replace( /<.*?>/g, "" ).toLowerCase() : a+''; }, // string "string-pre": function ( a ) { // This is a little complex, but faster than always calling toString, // http://jsperf.com/tostring-v-check return _empty(a) ? '' : typeof a === 'string' ? a.toLowerCase() : ! a.toString ? '' : a.toString(); }, // string-asc and -desc are retained only for compatibility with the old // sort methods "string-asc": function ( x, y ) { return ((x < y) ? -1 : ((x > y) ? 1 : 0)); }, "string-desc": function ( x, y ) { return ((x < y) ? 1 : ((x > y) ? -1 : 0)); } } ); // Numeric sorting types - order doesn't matter here _addNumericSort( '' ); $.extend( true, DataTable.ext.renderer, { header: { _: function ( settings, cell, column, classes ) { // No additional mark-up required // Attach a sort listener to update on sort - note that using the // `DT` namespace will allow the event to be removed automatically // on destroy, while the `dt` namespaced event is the one we are // listening for $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { if ( settings !== ctx ) { // need to check this this is the host return; // table, not a nested one } var colIdx = column.idx; cell .removeClass( column.sSortingClass +' '+ classes.sSortAsc +' '+ classes.sSortDesc ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortAsc : columns[ colIdx ] == 'desc' ? classes.sSortDesc : column.sSortingClass ); } ); }, jqueryui: function ( settings, cell, column, classes ) { $('<div/>') .addClass( classes.sSortJUIWrapper ) .append( cell.contents() ) .append( $('<span/>') .addClass( classes.sSortIcon+' '+column.sSortingClassJUI ) ) .appendTo( cell ); // Attach a sort listener to update on sort $(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) { if ( settings !== ctx ) { return; } var colIdx = column.idx; cell .removeClass( classes.sSortAsc +" "+classes.sSortDesc ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortAsc : columns[ colIdx ] == 'desc' ? classes.sSortDesc : column.sSortingClass ); cell .find( 'span.'+classes.sSortIcon ) .removeClass( classes.sSortJUIAsc +" "+ classes.sSortJUIDesc +" "+ classes.sSortJUI +" "+ classes.sSortJUIAscAllowed +" "+ classes.sSortJUIDescAllowed ) .addClass( columns[ colIdx ] == 'asc' ? classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ? classes.sSortJUIDesc : column.sSortingClassJUI ); } ); } } } ); /* * Public helper functions. These aren't used internally by DataTables, or * called by any of the options passed into DataTables, but they can be used * externally by developers working with DataTables. They are helper functions * to make working with DataTables a little bit easier. */ var __htmlEscapeEntities = function ( d ) { return typeof d === 'string' ? d .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') : d; }; /** * Helpers for `columns.render`. * * The options defined here can be used with the `columns.render` initialisation * option to provide a display renderer. The following functions are defined: * * * `number` - Will format numeric data (defined by `columns.data`) for * display, retaining the original unformatted data for sorting and filtering. * It takes 5 parameters: * * `string` - Thousands grouping separator * * `string` - Decimal point indicator * * `integer` - Number of decimal points to show * * `string` (optional) - Prefix. * * `string` (optional) - Postfix (/suffix). * * `text` - Escape HTML to help prevent XSS attacks. It has no optional * parameters. * * @example * // Column definition using the number renderer * { * data: "salary", * render: $.fn.dataTable.render.number( '\'', '.', 0, '$' ) * } * * @namespace */ DataTable.render = { number: function ( thousands, decimal, precision, prefix, postfix ) { return { display: function ( d ) { if ( typeof d !== 'number' && typeof d !== 'string' ) { return d; } var negative = d < 0 ? '-' : ''; var flo = parseFloat( d ); // If NaN then there isn't much formatting that we can do - just // return immediately, escaping any HTML (this was supposed to // be a number after all) if ( isNaN( flo ) ) { return __htmlEscapeEntities( d ); } flo = flo.toFixed( precision ); d = Math.abs( flo ); var intPart = parseInt( d, 10 ); var floatPart = precision ? decimal+(d - intPart).toFixed( precision ).substring( 2 ): ''; return negative + (prefix||'') + intPart.toString().replace( /\B(?=(\d{3})+(?!\d))/g, thousands ) + floatPart + (postfix||''); } }; }, text: function () { return { display: __htmlEscapeEntities, filter: __htmlEscapeEntities }; } }; /* * This is really a good bit rubbish this method of exposing the internal methods * publicly... - To be fixed in 2.0 using methods on the prototype */ /** * Create a wrapper function for exporting an internal functions to an external API. * @param {string} fn API function name * @returns {function} wrapped function * @memberof DataTable#internal */ function _fnExternApiFunc (fn) { return function() { var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat( Array.prototype.slice.call(arguments) ); return DataTable.ext.internal[fn].apply( this, args ); }; } /** * Reference to internal functions for use by plug-in developers. Note that * these methods are references to internal functions and are considered to be * private. If you use these methods, be aware that they are liable to change * between versions. * @namespace */ $.extend( DataTable.ext.internal, { _fnExternApiFunc: _fnExternApiFunc, _fnBuildAjax: _fnBuildAjax, _fnAjaxUpdate: _fnAjaxUpdate, _fnAjaxParameters: _fnAjaxParameters, _fnAjaxUpdateDraw: _fnAjaxUpdateDraw, _fnAjaxDataSrc: _fnAjaxDataSrc, _fnAddColumn: _fnAddColumn, _fnColumnOptions: _fnColumnOptions, _fnAdjustColumnSizing: _fnAdjustColumnSizing, _fnVisibleToColumnIndex: _fnVisibleToColumnIndex, _fnColumnIndexToVisible: _fnColumnIndexToVisible, _fnVisbleColumns: _fnVisbleColumns, _fnGetColumns: _fnGetColumns, _fnColumnTypes: _fnColumnTypes, _fnApplyColumnDefs: _fnApplyColumnDefs, _fnHungarianMap: _fnHungarianMap, _fnCamelToHungarian: _fnCamelToHungarian, _fnLanguageCompat: _fnLanguageCompat, _fnBrowserDetect: _fnBrowserDetect, _fnAddData: _fnAddData, _fnAddTr: _fnAddTr, _fnNodeToDataIndex: _fnNodeToDataIndex, _fnNodeToColumnIndex: _fnNodeToColumnIndex, _fnGetCellData: _fnGetCellData, _fnSetCellData: _fnSetCellData, _fnSplitObjNotation: _fnSplitObjNotation, _fnGetObjectDataFn: _fnGetObjectDataFn, _fnSetObjectDataFn: _fnSetObjectDataFn, _fnGetDataMaster: _fnGetDataMaster, _fnClearTable: _fnClearTable, _fnDeleteIndex: _fnDeleteIndex, _fnInvalidate: _fnInvalidate, _fnGetRowElements: _fnGetRowElements, _fnCreateTr: _fnCreateTr, _fnBuildHead: _fnBuildHead, _fnDrawHead: _fnDrawHead, _fnDraw: _fnDraw, _fnReDraw: _fnReDraw, _fnAddOptionsHtml: _fnAddOptionsHtml, _fnDetectHeader: _fnDetectHeader, _fnGetUniqueThs: _fnGetUniqueThs, _fnFeatureHtmlFilter: _fnFeatureHtmlFilter, _fnFilterComplete: _fnFilterComplete, _fnFilterCustom: _fnFilterCustom, _fnFilterColumn: _fnFilterColumn, _fnFilter: _fnFilter, _fnFilterCreateSearch: _fnFilterCreateSearch, _fnEscapeRegex: _fnEscapeRegex, _fnFilterData: _fnFilterData, _fnFeatureHtmlInfo: _fnFeatureHtmlInfo, _fnUpdateInfo: _fnUpdateInfo, _fnInfoMacros: _fnInfoMacros, _fnInitialise: _fnInitialise, _fnInitComplete: _fnInitComplete, _fnLengthChange: _fnLengthChange, _fnFeatureHtmlLength: _fnFeatureHtmlLength, _fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate, _fnPageChange: _fnPageChange, _fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing, _fnProcessingDisplay: _fnProcessingDisplay, _fnFeatureHtmlTable: _fnFeatureHtmlTable, _fnScrollDraw: _fnScrollDraw, _fnApplyToChildren: _fnApplyToChildren, _fnCalculateColumnWidths: _fnCalculateColumnWidths, _fnThrottle: _fnThrottle, _fnConvertToWidth: _fnConvertToWidth, _fnGetWidestNode: _fnGetWidestNode, _fnGetMaxLenString: _fnGetMaxLenString, _fnStringToCss: _fnStringToCss, _fnSortFlatten: _fnSortFlatten, _fnSort: _fnSort, _fnSortAria: _fnSortAria, _fnSortListener: _fnSortListener, _fnSortAttachListener: _fnSortAttachListener, _fnSortingClasses: _fnSortingClasses, _fnSortData: _fnSortData, _fnSaveState: _fnSaveState, _fnLoadState: _fnLoadState, _fnSettingsFromNode: _fnSettingsFromNode, _fnLog: _fnLog, _fnMap: _fnMap, _fnBindAction: _fnBindAction, _fnCallbackReg: _fnCallbackReg, _fnCallbackFire: _fnCallbackFire, _fnLengthOverflow: _fnLengthOverflow, _fnRenderer: _fnRenderer, _fnDataSource: _fnDataSource, _fnRowAttributes: _fnRowAttributes, _fnExtend: _fnExtend, _fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant // in 1.10, so this dead-end function is // added to prevent errors } ); // jQuery access $.fn.dataTable = DataTable; // Provide access to the host jQuery object (circular reference) DataTable.$ = $; // Legacy aliases $.fn.dataTableSettings = DataTable.settings; $.fn.dataTableExt = DataTable.ext; // With a capital `D` we return a DataTables API instance rather than a // jQuery object $.fn.DataTable = function ( opts ) { return $(this).dataTable( opts ).api(); }; // All properties that are available to $.fn.dataTable should also be // available on $.fn.DataTable $.each( DataTable, function ( prop, val ) { $.fn.DataTable[ prop ] = val; } ); // Information about events fired by DataTables - for documentation. /** * Draw event, fired whenever the table is redrawn on the page, at the same * point as fnDrawCallback. This may be useful for binding events or * performing calculations when the table is altered at all. * @name DataTable#draw.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * Search event, fired when the searching applied to the table (using the * built-in global search, or column filters) is altered. * @name DataTable#search.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * Page change event, fired when the paging of the table is altered. * @name DataTable#page.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * Order event, fired when the ordering applied to the table is altered. * @name DataTable#order.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * DataTables initialisation complete event, fired when the table is fully * drawn, including Ajax data loaded, if Ajax data is required. * @name DataTable#init.dt * @event * @param {event} e jQuery event object * @param {object} oSettings DataTables settings object * @param {object} json The JSON object request from the server - only * present if client-side Ajax sourced data is used</li></ol> */ /** * State save event, fired when the table has changed state a new state save * is required. This event allows modification of the state saving object * prior to actually doing the save, including addition or other state * properties (for plug-ins) or modification of a DataTables core property. * @name DataTable#stateSaveParams.dt * @event * @param {event} e jQuery event object * @param {object} oSettings DataTables settings object * @param {object} json The state information to be saved */ /** * State load event, fired when the table is loading state from the stored * data, but prior to the settings object being modified by the saved state * - allowing modification of the saved state is required or loading of * state for a plug-in. * @name DataTable#stateLoadParams.dt * @event * @param {event} e jQuery event object * @param {object} oSettings DataTables settings object * @param {object} json The saved state information */ /** * State loaded event, fired when state has been loaded from stored data and * the settings object has been modified by the loaded data. * @name DataTable#stateLoaded.dt * @event * @param {event} e jQuery event object * @param {object} oSettings DataTables settings object * @param {object} json The saved state information */ /** * Processing event, fired when DataTables is doing some kind of processing * (be it, order, search or anything else). It can be used to indicate to * the end user that there is something happening, or that something has * finished. * @name DataTable#processing.dt * @event * @param {event} e jQuery event object * @param {object} oSettings DataTables settings object * @param {boolean} bShow Flag for if DataTables is doing processing or not */ /** * Ajax (XHR) event, fired whenever an Ajax request is completed from a * request to made to the server for new data. This event is called before * DataTables processed the returned data, so it can also be used to pre- * process the data returned from the server, if needed. * * Note that this trigger is called in `fnServerData`, if you override * `fnServerData` and which to use this event, you need to trigger it in you * success function. * @name DataTable#xhr.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} * @param {object} json JSON returned from the server * * @example * // Use a custom property returned from the server in another DOM element * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { * $('#status').html( json.status ); * } ); * * @example * // Pre-process the data returned from the server * $('#table').dataTable().on('xhr.dt', function (e, settings, json) { * for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) { * json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two; * } * // Note no return - manipulate the data directly in the JSON object. * } ); */ /** * Destroy event, fired when the DataTable is destroyed by calling fnDestroy * or passing the bDestroy:true parameter in the initialisation object. This * can be used to remove bound events, added DOM nodes, etc. * @name DataTable#destroy.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * Page length change event, fired when number of records to show on each * page (the length) is changed. * @name DataTable#length.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} * @param {integer} len New length */ /** * Column sizing has changed. * @name DataTable#column-sizing.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} */ /** * Column visibility has changed. * @name DataTable#column-visibility.dt * @event * @param {event} e jQuery event object * @param {object} o DataTables settings object {@link DataTable.models.oSettings} * @param {int} column Column index * @param {bool} vis `false` if column now hidden, or `true` if visible */ return $.fn.dataTable; })); /*! DataTables Bootstrap 3 integration * ©2011-2015 SpryMedia Ltd - datatables.net/license */ /** * DataTables integration for Bootstrap 3. This requires Bootstrap 3 and * DataTables 1.10 or newer. * * This file sets the defaults and adds options to DataTables to style its * controls using Bootstrap. See http://datatables.net/manual/styling/bootstrap * for further information. */ (function( factory ){ if ( typeof define === 'function' && define.amd ) { // AMD define( ['jquery', 'datatables.net'], function ( $ ) { return factory( $, window, document ); } ); } else if ( typeof exports === 'object' ) { // CommonJS module.exports = function (root, $) { if ( ! root ) { root = window; } if ( ! $ || ! $.fn.dataTable ) { // Require DataTables, which attaches to jQuery, including // jQuery if needed and have a $ property so we can access the // jQuery object that is used $ = require('datatables.net')(root, $).$; } return factory( $, root, root.document ); }; } else { // Browser factory( jQuery, window, document ); } }(function( $, window, document, undefined ) { 'use strict'; var DataTable = $.fn.dataTable; /* Set the defaults for DataTables initialisation */ $.extend( true, DataTable.defaults, { dom: "<'row'<'col-sm-6'l><'col-sm-6'f>>" + "<'row'<'col-sm-12'tr>>" + "<'row'<'col-sm-5'i><'col-sm-7'p>>", renderer: 'bootstrap' } ); /* Default class modification */ $.extend( DataTable.ext.classes, { sWrapper: "dataTables_wrapper form-inline dt-bootstrap", sFilterInput: "form-control input-sm", sLengthSelect: "form-control input-sm", sProcessing: "dataTables_processing panel panel-default" } ); /* Bootstrap paging button renderer */ DataTable.ext.renderer.pageButton.bootstrap = function ( settings, host, idx, buttons, page, pages ) { var api = new DataTable.Api( settings ); var classes = settings.oClasses; var lang = settings.oLanguage.oPaginate; var aria = settings.oLanguage.oAria.paginate || {}; var btnDisplay, btnClass, counter=0; var attach = function( container, buttons ) { var i, ien, node, button; var clickHandler = function ( e ) { e.preventDefault(); if ( !$(e.currentTarget).hasClass('disabled') && api.page() != e.data.action ) { api.page( e.data.action ).draw( 'page' ); } }; for ( i=0, ien=buttons.length ; i<ien ; i++ ) { button = buttons[i]; if ( $.isArray( button ) ) { attach( container, button ); } else { btnDisplay = ''; btnClass = ''; switch ( button ) { case 'ellipsis': btnDisplay = '&#x2026;'; btnClass = 'disabled'; break; case 'first': btnDisplay = lang.sFirst; btnClass = button + (page > 0 ? '' : ' disabled'); break; case 'previous': btnDisplay = lang.sPrevious; btnClass = button + (page > 0 ? '' : ' disabled'); break; case 'next': btnDisplay = lang.sNext; btnClass = button + (page < pages-1 ? '' : ' disabled'); break; case 'last': btnDisplay = lang.sLast; btnClass = button + (page < pages-1 ? '' : ' disabled'); break; default: btnDisplay = button + 1; btnClass = page === button ? 'active' : ''; break; } if ( btnDisplay ) { node = $('<li>', { 'class': classes.sPageButton+' '+btnClass, 'id': idx === 0 && typeof button === 'string' ? settings.sTableId +'_'+ button : null } ) .append( $('<a>', { 'href': '#', 'aria-controls': settings.sTableId, 'aria-label': aria[ button ], 'data-dt-idx': counter, 'tabindex': settings.iTabIndex } ) .html( btnDisplay ) ) .appendTo( container ); settings.oApi._fnBindAction( node, {action: button}, clickHandler ); counter++; } } } }; // IE9 throws an 'unknown error' if document.activeElement is used // inside an iframe or frame. var activeEl; try { // Because this approach is destroying and recreating the paging // elements, focus is lost on the select button which is bad for // accessibility. So we want to restore focus once the draw has // completed activeEl = $(host).find(document.activeElement).data('dt-idx'); } catch (e) {} attach( $(host).empty().html('<ul class="pagination"/>').children('ul'), buttons ); if ( activeEl !== undefined ) { $(host).find( '[data-dt-idx='+activeEl+']' ).trigger('focus'); } }; return DataTable; })); ================================================ FILE: gui/static/js/domains.js ================================================ ;(function(window, document) { 'use strict'; function processDomainType(value) { var modal_body = $('.modal-body'); if (value == "msteams-nosub") { //console.log("Clicked on authtype " + value); modal_body.find(".domain_name").attr('disabled', true); modal_body.find(".pbx_list").attr('disabled', true); modal_body.find(".notes").attr('disabled', true); modal_body.find(":submit").attr('disabled', true); //modal_body.find(":submit").attr('disabled',true); } else { modal_body.find(".domain_name").attr('disabled', false); modal_body.find(".pbx_list").attr('disabled', false); modal_body.find(".notes").attr('disabled', false); modal_body.find(":submit").attr('disabled', false); } } /* validate domain form fields before submitting */ function validateDomainFormFields(fields) { var domainlist_obj = fields.get('domainlist'); // empty string will return [''] here var domains = domainlist_obj.val().split(','); for (var i=0; i<domains.length; i++) { if (domains[i].trim() === '') { return { result: false, err_node: domainlist_obj, err_msg: "Domain can not be an empty string" }; } } return { result: true }; } function updateModals() { $(document).ready(function() { // Updates the modal with domain to be deleted $('#domains #open-Delete').off('click').on('click', function () { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('domains'); var domain_id = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var domain_name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); /* update modal fields */ var modal_body = $('#delete .modal-body'); modal_body.find(".domain_id").val(domain_id); modal_body.find(".domain_name").val(domain_name); }); // Updates the modal with the values from the endpointgroup API $('#domains #open-Update').off('click').on('click', function () { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('domains'); var domain_id = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var domain_name = $(c).find('tr:eq(' + row_index + ') td:eq(2)').text(); var domain_type = $(c).find('tr:eq(' + row_index + ') td:eq(3)').text(); var pbx_name = $(c).find('tr:eq(' + row_index + ') td:eq(4)').text(); var authtype = $(c).find('tr:eq(' + row_index + ') td:eq(5)').text(); var pbx_list = $(c).find('tr:eq(' + row_index + ') td:eq(6)').text(); var notes = $(c).find('tr:eq(' + row_index + ') td:eq(7)').text(); /** Clear out and reset the modal */ var modal_body = $('#edit .modal-body'); modal_body.find(".domain_name").attr('disabled', false); modal_body.find(".pbx_list").attr('disabled', false); modal_body.find(".notes").attr('disabled', false); modal_body.find(":submit").attr('disabled', false); modal_body.find(".domain_id").val(''); modal_body.find(".domain_name").val(''); modal_body.find(".domain_type").val(''); modal_body.find(".pbx_name").val(''); modal_body.find(".pbx_list").val(''); modal_body.find(".notes").val(''); modal_body.find('.authtype').val([]); modal_body.find('.authtype').val(""); /* update modal fields */ modal_body.find(".domain_id").val(domain_id); modal_body.find(".domain_name").val(domain_name); modal_body.find(".domain_type").val(domain_type); modal_body.find(".pbx_name").val(pbx_name); modal_body.find(".pbx_list").val(pbx_list); modal_body.find(".notes").val(notes); if (authtype !== "") { /* Set the radio button if authtype is given */ //modal_body.find('.authtype option[value="' + authtype + '"]').attr('selected', 'selected').trigger("change"); modal_body.find('.authtype').val(authtype) } }); }); } $(document).ready(function() { // datatable init $('#domains') .on('page.dt', function() { updateModals(); } ) .DataTable({ "columnDefs": [ {"orderable": true, "targets": [1, 2, 3, 4, 5, 6, 7]}, {"orderable": false, "targets": [0, 8, 9]}, ], "order": [[1, 'asc']] }); // Update the modals on the first page load updateModals(); // Resets the Add Modal $('#open-DomainAdd').click(function() { /** Clear out and reset the modal */ var modal_body = $('#add .modal-body'); modal_body.find(".domain_name").attr('disabled', false); modal_body.find(".pbx_list").attr('disabled', false); modal_body.find(".notes").attr('disabled', false); modal_body.find(":submit").attr('disabled', false); modal_body.find(".domain_id").val(''); modal_body.find(".domain_name").val(''); modal_body.find(".domain_type").val(''); modal_body.find(".pbx_name").val(''); modal_body.find(".pbx_list").val(''); modal_body.find(".notes").val(''); modal_body.find('.authtype').val([]); modal_body.find('.authtype').val(""); }); $('#add .authtype').change(function() { var modal_body = $('#add .modal-body'); var type = modal_body.find('.authtype').val(); processDomainType(type); }); $('#edit .authtype').change(function() { var modal_body = $('#edit .modal-body'); var type = modal_body.find('.authtype').val(); processDomainType(type); }); $('#addDomainForm').submit(function(ev) { if (!validateFields(this, validateDomainFormFields)) { // prevent form from submitting if it failed ev.preventDefault(); // prevent jquery from propagating event return false; } }); $('#updateDomainForm').submit(function(ev) { if (!validateFields(this, validateDomainFormFields)) { ev.preventDefault(); return false; } }); }); })(window, document); ================================================ FILE: gui/static/js/endpointgroups.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof validateFields === "undefined") { throw new Error("validateFields() is required and is not defined"); } if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof toggleElemDisabled === "undefined") { throw new Error("toggleElemDisabled() is required and is not defined"); } if (typeof reloadKamRequired === "undefined") { throw new Error("reloadKamRequired() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } // global constants for this script const SIGNAL_OPTIONS = { "proxy": "Unaltered", "sip_udp": "SIP over UDP", "sip_tcp": "SIP over TCP", "sip_sctp": "SIP over SCTP", // "sip_ws": "SIP over WS", "sips_tls": "SIPS over TLS", "sips_sctp": "SIPS over SCTP", // "sips_wss": "SIPS over WSS" }; const SIGNAL_OPTIONS_STR = JSON.stringify(SIGNAL_OPTIONS); // TODO: think of a more user friendly description for these options const MEDIA_OPTIONS = { "proxy": "Proxy Media", "direct": "Direct Media", "rtp_avp": "RTP/AVP", "rtp_savp": "RTP/SAVP", "rtp_avpf": "RTP/AVPF", "rtp_savpf": "RTP/SAVPF", "rtp_avp_any": "UDP/TLS/RTP/SAVP", "rtp_avpf_any": "UDP/TLS/RTP/SAVPF", "udptl": "T.38 over UDPTL", "osrtp_avp": "OSRTP over RTP/AVP", "osrtp_avpf": "OSRTP over RTP/AVPF" }; const MEDIA_OPTIONS_STR = JSON.stringify(MEDIA_OPTIONS); const KEEPALIVE_OPTIONS = { 0: "disabled", 1: "enabled" }; const KEEPALIVE_OPTIONS_STR = JSON.stringify(KEEPALIVE_OPTIONS); // global variables/constants for this script // TODO: find a way to pass these values around gwgroupid instead of using global var gwgroupid; var endpoint_table1; var endpoint_table2; var gwgroup_table; function generateEndpointObject(row) { var jq_row = $(row); return { gwid: parseInt(jq_row.find('input[name="gwid"]').val(), 10), host: jq_row.find('input[name="host"]').val(), port: parseInt(jq_row.find('input[name="port"]').val(), 10), signalling: jq_row.find('select[name="signalling"]').val(), media: jq_row.find('select[name="media"]').val(), description: jq_row.find('input[name="description"]').val(), rweight: parseInt(jq_row.find('input[name="rweight"]').val(), 10), keepalive: parseInt(jq_row.find('select[name="keepalive"]').val(), 10), }; } /** * Generate the markup for an endpoint wrapped in a query object * @param endpoint * @returns jQuery */ function generateEndpointMarkup(endpoint = null) { if (endpoint === null) { return $('<tr class="endpoint">' + '<td name="gwid"></td>' + '<td name="host"></td>' + '<td name="port"></td>' + '<td name="signalling"></td>' + '<td name="media"></td>' + '<td name="description"></td>' + '<td name="rweight">1</td>' + '<td name="keepalive"></td>' + '</tr>'); } else { return $('<tr class="endpoint">' + '<td name="gwid">' + endpoint.gwid.toString() + '</td>' + '<td name="host">' + endpoint.host + '</td>' + '<td name="port">' + endpoint.port + '</td>' + '<td name="signalling">' + SIGNAL_OPTIONS[endpoint.signalling] + '</td>' + '<td name="media">' + MEDIA_OPTIONS[endpoint.media] + '</td>' + '<td name="description">' + endpoint.description + '</td>' + '<td name="rweight">' + endpoint.rweight.toString() + '</td>' + '<td name="keepalive">' + KEEPALIVE_OPTIONS[endpoint.keepalive] + '</td>' + '</tr>'); } } function generateEndpointTable(selector) { var endpoint_table = $(selector); endpoint_table.Tabledit({ columns: { identifier: [0, 'gwid'], editable: [ [1, 'host'], [2, 'port'], [3, 'signalling', SIGNAL_OPTIONS_STR], [4, 'media', MEDIA_OPTIONS_STR], [5, 'description'], [6, 'rweight'], [7, 'keepalive', KEEPALIVE_OPTIONS_STR], ], saveButton: true, }, ajaxDisabled: true, restoreButton: false }); return endpoint_table; } // Add EndpointGroup function addEndpointGroup(action) { var selector, modal_body, url, tmp; // The default action is a POST (creating a new EndpointGroup) if (typeof action === "undefined") { action = "POST"; } if (action === "POST") { action = "POST"; selector = "#add"; modal_body = $(selector + ' .modal-body'); url = API_BASE_URL + "endpointgroups"; } // Grab the Gateway Group ID if updating using a PUT else if (action === "PUT") { selector = "#edit"; modal_body = $(selector + ' .modal-body'); gwgroupid = modal_body.find(".gwgroupid").val(); url = API_BASE_URL + "endpointgroups/" + gwgroupid; } else { throw new Error("addEndpointGroup(): action must be either POST or PUT"); } var requestPayload = {}; requestPayload.name = modal_body.find(".name").val(); var call_settings = {}; tmp = modal_body.find(".call_limit").val(); call_settings.limit = tmp === '' ? null : tmp; tmp = modal_body.find(".call_timeout").val(); call_settings.timeout = tmp === '' ? null : tmp; requestPayload.call_settings = call_settings; var auth = {}; if (action === "POST") { if ($('input#ip.authtype').is(':checked')) { auth.type = "ip"; } else { auth.type = "userpwd"; auth.pass = modal_body.find("#auth_password").val(); } } else if (action === "PUT") { if ($('input#ip2.authtype').is(':checked')) { auth.type = "ip"; } else { auth.type = "userpwd"; auth.pass = modal_body.find("#auth_password2").val(); } } auth.user = modal_body.find(".auth_username").val(); auth.domain = modal_body.find(".auth_domain").val(); requestPayload.auth = auth; requestPayload.strip = modal_body.find(".strip").val(); requestPayload.prefix = modal_body.find(".prefix").val(); var notifications = {}; notifications.overmaxcalllimit = modal_body.find(".email_over_max_calls").val(); notifications.endpointfailure = modal_body.find(".email_endpoint_failure").val(); requestPayload.notifications = notifications; var cdr = {}; cdr.cdr_email = modal_body.find(".cdr_email").val(); cdr.cdr_send_interval = modal_body.find(".cdr_send_minute").val() + ' ' + modal_body.find(".cdr_send_hour").val() + ' ' + modal_body.find(".cdr_send_day").val() + ' ' + modal_body.find(".cdr_send_month").val() + ' ' + modal_body.find(".cdr_send_weekday").val(); requestPayload.cdr = cdr; var fusionpbx = {}; fusionpbx.enabled = modal_body.find(".fusionpbx_db_enabled").val(); fusionpbx.dbhost = modal_body.find(".fusionpbx_db_server").val(); fusionpbx.dbuser = modal_body.find(".fusionpbx_db_username").val(); fusionpbx.dbpass = modal_body.find(".fusionpbx_db_password").val(); fusionpbx.clustersupport = modal_body.find(".fusionpbx_clustersupport").val(); requestPayload.fusionpbx = fusionpbx; /* Process endpoints (empty endpoints are ignored) */ requestPayload.endpoints = $("tr.endpoint").map(function(idx, row) { return generateEndpointObject(row); }).get(); // set payload defaults for numbers // doing it here allows us to keep placeholder on the input if (requestPayload.strip.length === 0) { requestPayload.strip = 0; } // Put into JSON Message and send over $.ajax({ type: action, url: url, dataType: "json", contentType: "application/json; charset=utf-8", data: JSON.stringify(requestPayload), success: function(response, textStatus, jqXHR) { var btn; var gwgroupid_int = response.data[0].gwgroupid; // Update the Add Button and the table if (action === "POST") { btn = $('#add .modal-footer').find('#addButton'); btn.removeClass("btn-primary"); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.removeClass("btn-warning"); } btn.addClass("btn-success"); btn.html("<span class='glyphicon glyphicon-check'></span> Saved!"); btn.attr("disabled", true); // Update Reload buttons reloadKamRequired(true); if (action === "POST") { gwgroup_table.row.add({ "name": requestPayload.name, "gwgroupid": gwgroupid_int }).draw(); } else { gwgroup_table.row(function(idx, data, node) { return data.gwgroupid === gwgroupid_int; }).data({ "name": requestPayload.name, "gwgroupid": gwgroupid_int }).draw(); } } }) } function updateEndpointGroup() { addEndpointGroup("PUT"); } function clearEndpointGroupModal(modal_selector) { /** Clear out the modal */ var modal_body = $(modal_selector).find('.modal-body'); modal_body.find(".gwgroupid").val(''); modal_body.find(".name").val(''); modal_body.find(".ip_addr").val(''); modal_body.find(".strip").val(''); modal_body.find(".prefix").val(''); modal_body.find(".fusionpbx_db_server").val(''); modal_body.find(".fusionpbx_db_username").val('fusionpbx'); modal_body.find(".fusionpbx_db_password").val(''); modal_body.find(".authtype[value='ip']").trigger('click'); modal_body.find(".auth_username").val(''); modal_body.find(".auth_password").val(''); modal_body.find(".auth_domain").val(''); modal_body.find(".call_limit").val(''); modal_body.find(".call_timeout").val(''); modal_body.find(".email_over_max_calls").val(''); modal_body.find(".email_endpoint_failure").val(''); modal_body.find(".cdr_email").val(''); modal_body.find(".cdr_send_minute").val('*'); modal_body.find(".cdr_send_hour").val('*'); modal_body.find(".cdr_send_day").val('1'); modal_body.find(".cdr_send_month").val('*'); modal_body.find(".cdr_send_weekday").val('*'); modal_body.find('.FusionPBXDomainOptions').addClass("hidden"); modal_body.find('.updateButton').attr("disabled", false); // Clear out update button in add footer var modal_footer = modal_body.find('.modal-footer'); modal_footer.find("#addButton").attr("disabled", false); // Clear out update button in add footer modal_footer.find("#updateButton").attr("disabled", false); var btn; if (modal_selector == "#add") { btn = $('#add .modal-footer').find('#addButton'); btn.html("<span class='glyphicon glyphicon-ok-sign'></span> Add"); btn.removeClass("btn-success"); btn.addClass("btn-primary"); } else { btn = $('#edit .modal-footer').find('#updateButton'); btn.html("<span class='glyphicon glyphicon-ok-sign'></span> Update"); btn.removeClass("btn-success"); btn.addClass("btn-warning"); } btn.attr('disabled', false); // Remove Endpont Rows $("tr.endpoint").each(function(i, row) { $(this).remove(); }) /* start endpoint-nav on first tab */ modal_body.find('#endpoint-nav .nav-tabs > li').removeClass("active"); modal_body.find('#endpoint-nav > .nav-tabs a').first().trigger('click'); // make sure userpwd options not shown modal_body.find('.userpwd').addClass('hidden'); /* make sure ip_addr not disabled */ toggleElemDisabled(modal_body.find('.ip_addr'), false); } function displayEndpointGroup(gwgroup_data) { var modal_body = $('#edit .modal-body'); modal_body.find(".name").val(gwgroup_data.name); modal_body.find(".gwgroupid").val(gwgroup_data.gwgroupid); modal_body.find(".call_limit").val(gwgroup_data.call_settings.limit); modal_body.find(".call_timeout").val(gwgroup_data.call_settings.timeout); if (gwgroup_data.auth.type == "ip") { $('#ip2.authtype').prop('checked', true); $("#userpwd_enabled2").addClass('hidden'); $("#userpwd_enabled").addClass('hidden'); } else { $('#userpwd2.authtype').prop('checked', true); $("#userpwd_enabled2").removeClass('hidden'); $("#userpwd_enabled").removeClass('hidden'); } // parse the cdr_send_interval var send_interval = gwgroup_data.cdr.cdr_send_interval; modal_body.find(".auth_username").val(gwgroup_data.auth.user); modal_body.find("#auth_password2").val(gwgroup_data.auth.pass); modal_body.find("#auth_password").val(gwgroup_data.auth.pass); modal_body.find(".auth_domain").val(gwgroup_data.auth.domain); modal_body.find(".strip").val(gwgroup_data.strip); modal_body.find(".prefix").val(gwgroup_data.prefix); modal_body.find(".email_over_max_calls").val(gwgroup_data.notifications.overmaxcalllimit); modal_body.find(".email_endpoint_failure").val(gwgroup_data.notifications.endpointfailure); modal_body.find(".cdr_email").val(gwgroup_data.cdr.cdr_email); if (send_interval) { send_interval = send_interval.split(' '); modal_body.find(".cdr_send_minute").val(send_interval[0]); modal_body.find(".cdr_send_hour").val(send_interval[1]); modal_body.find(".cdr_send_day").val(send_interval[2]); modal_body.find(".cdr_send_month").val(send_interval[3]); modal_body.find(".cdr_send_weekday").val(send_interval[4]); } modal_body.find(".fusionpbx_db_enabled").val(gwgroup_data.fusionpbx.enabled); modal_body.find(".fusionpbx_db_server").val(gwgroup_data.fusionpbx.dbhost); modal_body.find(".fusionpbx_db_username").val(gwgroup_data.fusionpbx.dbuser); modal_body.find(".fusionpbx_db_password").val(gwgroup_data.fusionpbx.dbpass); modal_body.find(".fusionpbx_clustersupport").val(gwgroup_data.fusionpbx.clustersupport); /* reset the save button*/ var updatebtn = $('#edit .modal-footer').find("#updateButton"); updatebtn.removeClass("btn-success"); updatebtn.addClass("btn-warning"); updatebtn.html("<span class='glyphicon glyphicon-ok-sign'></span>Update"); if (gwgroup_data.endpoints) { for (var i = 0; i < gwgroup_data.endpoints.length; i++) { endpoint_table1.append(generateEndpointMarkup(gwgroup_data.endpoints[i])); } endpoint_table1.data('Tabledit').reload(); } if (gwgroup_data.fusionpbx.enabled) { modal_body.find(".toggleFusionPBXDomain").bootstrapToggle('on'); } else { modal_body.find(".toggleFusionPBXDomain").bootstrapToggle('off'); } if (gwgroup_data.auth.type == "userpwd") { /* userpwd auth enabled, Set the radio button to true */ modal_body.find('.authtype[data-toggle="userpwd_enabled"]').trigger('click'); } else { /* ip auth enabled, Set the radio button to true */ modal_body.find('.authtype[data-toggle="ip_enabled"]').trigger('click'); } } function deleteEndpointGroup() { var gwgroupid_int = parseInt(gwgroupid, 10); $.ajax({ type: "DELETE", url: API_BASE_URL + "endpointgroups/" + gwgroupid, dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, textStatus, jqXHR) { $('#delete').modal('hide'); $('#edit').modal('hide'); // Update Reload buttons reloadKamRequired(true); gwgroup_table.row(function(idx, data, node) { return data.gwgroupid === gwgroupid_int; }).remove().draw(); } }); } $(document).ready(function() { // datatable init gwgroup_table = $('#endpointgroups').DataTable({ "ajax": { "url": API_BASE_URL + "endpointgroups" }, "columns": [ {"data": "name"}, {"data": "gwgroupid"} //{ "data": "gwlist", visible: false }, ], "order": [[1, 'asc']] }); // table editing by clicking on the row $('#endpointgroups tbody').on('click', 'tr', function() { //Turn off selected on any other rows $('#endpointgroups').find('tr').removeClass('selected'); if ($(this).hasClass('selected')) { $(this).removeClass('selected'); } else { //table.$('tr.selected').removeClass('selected'); $(this).addClass('selected'); gwgroupid = $(this).find('td').eq(1).text() //console.log(gwgroupid); $('#edit').modal('show'); } }); /* edit modal tabledit init */ endpoint_table1 = generateEndpointTable('#endpoint-table'); /* add modal tabledit init */ endpoint_table2 = generateEndpointTable('#endpoint-table2'); $('#edit').on('show.bs.modal', function() { clearEndpointGroupModal('#edit'); // Show the auth tab by default when the modal shows var modal_body = $('#edit .modal-body'); modal_body.find("[name='auth-toggle']").trigger('click'); // Put into JSON Message and send over $.ajax({ type: "GET", url: API_BASE_URL + "endpointgroups/" + gwgroupid, dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, textStatus, jqXHR) { displayEndpointGroup(response.data[0]); } }) }); $('#addEndpointRow').on('click', function() { endpoint_table2.append(generateEndpointMarkup()); endpoint_table2.data('Tabledit').reload(); endpoint_table2.find("tbody tr:last td:last .tabledit-edit-button").trigger("click"); }); $('#updateEndpointRow').on('click', function() { endpoint_table1.append(generateEndpointMarkup()); endpoint_table1.data('Tabledit').reload(); endpoint_table1.find("tbody tr:last td:last .tabledit-edit-button").trigger("click"); }); $('.modal-body .fusionpbx_clustersupport').change(function() { var modal = $(this).closest('div.modal'); var modal_body = modal.find('.modal-body'); if ($(this).is(":checked") || $(this).prop("checked")) { modal_body.find('.fusionpbx_clustersupport').val(1); } else { modal_body.find('.fusionpbx_clustersupport').val(0); } }); /* listener for fusionPBX toggle */ $('.modal-body .toggleFusionPBXDomain').change(function() { var self = $(this); var modal = self.closest('div.modal'); var modal_body = modal.find('.modal-body'); if (self.is(":checked") || self.prop("checked")) { modal_body.find('.FusionPBXDomainOptions').removeClass("hidden"); modal_body.find('.fusionpbx_db_enabled').val(1); self.bootstrapToggle('on'); } else { modal_body.find('.FusionPBXDomainOptions').addClass("hidden"); modal_body.find('.fusionpbx_db_enabled').val(0); self.bootstrapToggle('off'); } }); /* listener for freePBX toggle */ $('.modal-body .toggleFreePBXDomain').change(function() { var self = $(this); var modal = self.closest('div.modal'); var modal_body = modal.find('.modal-body'); if (self.is(":checked") || self.prop("checked")) { modal_body.find('.FreePBXDomainOptions').removeClass("hidden"); modal_body.find('.freepbx_enabled').val(1); self.bootstrapToggle('on'); } else { modal_body.find('.FreePBXDomainOptions').addClass("hidden"); modal_body.find('.freepbx_enabled').val(0); self.bootstrapToggle('off'); } }); $(".toggle-password").on('click', function() { var input = $($(this).attr("toggle")); if (input.attr("type") == "password") { input.attr("type", "text"); $(this).removeClass("glyphicon glyphicon-eye-close"); $(this).addClass("glyphicon glyphicon-eye-open"); } else { input.attr("type", "password"); $(this).removeClass("glyphicon glyphicon-eye-open"); $(this).addClass("glyphicon glyphicon-eye-close"); } }); $("#authoptions :input").change(function() { var userpwd_div = $('#userpwd_enabled'); var authpwd_inp = $("#auth_password"); var togglepwd_span = $(".toggle-password"); if ($('#ip').is(':checked')) { userpwd_div.addClass('hidden'); } else { $.ajax({ type: "GET", url: API_BASE_URL + "sys/generatepassword", dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, textStatus, jqXHR) { authpwd_inp.attr("type", "text"); authpwd_inp.val(response.data[0]) togglepwd_span.removeClass("glyphicon glyphicon-eye-close"); togglepwd_span.addClass("glyphicon glyphicon-eye-open"); } }); userpwd_div.removeClass('hidden'); } }); $("#authoptions2 :input").change(function() { var userpwd_div = $('#userpwd_enabled2'); if ($('#ip2').is(':checked')) { userpwd_div.addClass('hidden'); } else { userpwd_div.removeClass('hidden'); } }); $('#open-EndpointGroupsAdd').click(function() { clearEndpointGroupModal('#add'); }); /* validate fields before submitting api request */ $('#addButton').click(function(ev) { /* prevent form default submit */ ev.preventDefault(); if (validateFields('#add')) { addEndpointGroup(); // hide the modal after 1.5 sec setTimeout(function() { var add_modal = $('#add'); if (add_modal.is(':visible')) { add_modal.modal('hide'); } }, 1500); } }); /* validate fields before submitting api request */ $('#updateButton').click(function(ev) { /* prevent form default submit */ ev.preventDefault(); if (validateFields('#edit')) { updateEndpointGroup(); // hide the modal after 1.5 sec setTimeout(function() { var edit_modal = $('#edit'); if (edit_modal.is(':visible')) { edit_modal.modal('hide'); } }, 1500); } /* prevent page reload */ return false; }); /* handler for deleting endpoint group */ $('#deleteButton').click(function() { deleteEndpointGroup(); }); /* validate fields before moving to next tab */ $('#endpoint-nav > .nav-tabs').click({tab_panes: $('div.tab-content > div.tab-pane')}, function(ev) { var current_tab = ev.data.tab_panes.filter(':not(:hidden)'); if (!validateFields(current_tab)) { return false; } }); }); })(window, document); ================================================ FILE: gui/static/js/highlight/LICENSE ================================================ Copyright (c) 2006, Ivan Sagalaev All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of highlight.js nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: gui/static/js/highlight/highlight.pack.js ================================================ /*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */ !function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset<r[0].offset?e:r:"start"===r[0].event?e:r:e.length?e:r}function o(e){function r(e){return" "+e.nodeName+'="'+n(e.value).replace('"',"&quot;")+'"'}s+="<"+t(e)+E.map.call(e.attributes,r).join("")+">"}function u(e){s+="</"+t(e)+">"}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='<span class="'+a,o=t?"":C;return i+=e+'">',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"<unnamed>")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"<br>":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="</span>",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/</,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("<!--","-->",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{name:"style"},c:[t],starts:{e:"</style>",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{name:"script"},c:[t],starts:{e:"</script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("markdown",function(e){return{aliases:["md","mkdown","mkd"],c:[{cN:"section",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"quote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"^```w*s*$",e:"^```s*$"},{b:"`.+?`"},{b:"^( {4}| )",e:"$",r:0}]},{b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].*?[\\)\\]]",rB:!0,c:[{cN:"string",b:"\\[",e:"\\]",eB:!0,rE:!0,r:0},{cN:"link",b:"\\]\\(",e:"\\)",eB:!0,eE:!0},{cN:"symbol",b:"\\]\\[",e:"\\]",eB:!0,eE:!0}],r:10},{b:/^\[[^\n]+\]:/,rB:!0,c:[{cN:"symbol",b:/\[/,e:/\]/,eB:!0,eE:!0},{cN:"link",b:/:\s*/,e:/$/,eB:!0}]}]}});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b:/</,e:/(\/\w+|\w+\/)>/,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("go",function(e){var t={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:t,i:"</",c:[e.CLCM,e.CBCM,{cN:"string",v:[e.QSM,{b:"'",e:"[^\\\\]'"},{b:"`",e:"`"}]},{cN:"number",v:[{b:e.CNR+"[dflsi]",r:1},e.CNM]},{b:/:=/},{cN:"function",bK:"func",e:/\s*\{/,eE:!0,c:[e.TM,{cN:"params",b:/\(/,e:/\)/,k:t,i:/["']/}]}]}});hljs.registerLanguage("lua",function(e){var t="\\[=*\\[",a="\\]=*\\]",r={b:t,e:a,c:["self"]},n=[e.C("--(?!"+t+")","$"),e.C("--"+t,a,{c:[r],r:10})];return{l:e.UIR,k:{literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstringmodule next pairs pcall print rawequal rawget rawset require select setfenvsetmetatable tonumber tostring type unpack xpcall arg selfcoroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},c:n.concat([{cN:"function",bK:"function",e:"\\)",c:[e.inherit(e.TM,{b:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{cN:"params",b:"\\(",eW:!0,c:n}].concat(n)},e.CNM,e.ASM,e.QSM,{cN:"string",b:t,e:a,c:[r],r:5}])}});hljs.registerLanguage("less",function(e){var r="[\\w-]+",t="("+r+"|@{"+r+"})",a=[],c=[],s=function(e){return{cN:"string",b:"~?"+e+".*?"+e}},b=function(e,r,t){return{cN:e,b:r,r:t}},n={b:"\\(",e:"\\)",c:c,r:0};c.push(e.CLCM,e.CBCM,s("'"),s('"'),e.CSSNM,{b:"(url|data-uri)\\(",starts:{cN:"string",e:"[\\)\\n]",eE:!0}},b("number","#[0-9A-Fa-f]+\\b"),n,b("variable","@@?"+r,10),b("variable","@{"+r+"}"),b("built_in","~?`[^`]*?`"),{cN:"attribute",b:r+"\\s*:",e:":",rB:!0,eE:!0},{cN:"meta",b:"!important"});var i=c.concat({b:"{",e:"}",c:a}),o={bK:"when",eW:!0,c:[{bK:"and not"}].concat(c)},u={b:t+"\\s*:",rB:!0,e:"[;}]",r:0,c:[{cN:"attribute",b:t,e:":",eE:!0,starts:{eW:!0,i:"[<=$]",r:0,c:c}}]},l={cN:"keyword",b:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{e:"[;{}]",rE:!0,c:c,r:0}},C={cN:"variable",v:[{b:"@"+r+"\\s*:",r:15},{b:"@"+r}],starts:{e:"[;}]",rE:!0,c:i}},p={v:[{b:"[\\.#:&\\[>]",e:"[;{}]"},{b:t,e:"{"}],rB:!0,rE:!0,i:"[<='$\"]",r:0,c:[e.CLCM,e.CBCM,o,b("keyword","all\\b"),b("variable","@{"+r+"}"),b("selector-tag",t+"%?",0),b("selector-id","#"+t),b("selector-class","\\."+t,0),b("selector-tag","&",0),{cN:"selector-attr",b:"\\[",e:"\\]"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"\\(",e:"\\)",c:i},{b:"!important"}]};return a.push(e.CLCM,e.CBCM,l,C,u,p),{cI:!0,i:"[=>'/<($\"]",c:a}});hljs.registerLanguage("cmake",function(e){return{aliases:["cmake.in"],cI:!0,k:{keyword:"add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_minimum_required cmake_policy configure_file create_test_sourcelist define_property else elseif enable_language enable_testing endforeach endfunction endif endmacro endwhile execute_process export find_file find_library find_package find_path find_program fltk_wrap_ui foreach function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property if include include_directories include_external_msproject include_regular_expression install link_directories load_cache load_command macro mark_as_advanced message option output_required_files project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_link_libraries try_compile try_run unset variable_watch while build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or equal less greater strless strgreater strequal matches"},c:[{cN:"variable",b:"\\${",e:"}"},e.HCM,e.QSM,e.NM]}});hljs.registerLanguage("diff",function(e){return{aliases:["patch"],c:[{cN:"meta",r:10,v:[{b:/^@@ +\-\d+,\d+ +\+\d+,\d+ +@@$/},{b:/^\*\*\* +\d+,\d+ +\*\*\*\*$/},{b:/^\-\-\- +\d+,\d+ +\-\-\-\-$/}]},{cN:"comment",v:[{b:/Index: /,e:/$/},{b:/={3,}/,e:/$/},{b:/^\-{3}/,e:/$/},{b:/^\*{3} /,e:/$/},{b:/^\+{3}/,e:/$/},{b:/\*{5}/,e:/\*{5}$/}]},{cN:"addition",b:"^\\+",e:"$"},{cN:"deletion",b:"^\\-",e:"$"},{cN:"addition",b:"^\\!",e:"$"}]}});hljs.registerLanguage("nginx",function(e){var r={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+e.UIR}]},b={eW:!0,l:"[a-z/_]+",k:{literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[e.HCM,{cN:"string",c:[e.BE,r],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{b:"([a-z]+):/",e:"\\s",eW:!0,eE:!0,c:[r]},{cN:"regexp",c:[e.BE,r],v:[{b:"\\s\\^",e:"\\s|{|;",rE:!0},{b:"~\\*?\\s+",e:"\\s|{|;",rE:!0},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},r]};return{aliases:["nginxconf"],c:[e.HCM,{b:e.UIR+"\\s+{",rB:!0,e:"{",c:[{cN:"section",b:e.UIR}],r:0},{b:e.UIR+"\\s",e:";|{",rB:!0,c:[{cN:"attribute",b:e.UIR,starts:b}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("perl",function(e){var t="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when",r={cN:"subst",b:"[$@]\\{",e:"\\}",k:t},s={b:"->{",e:"}"},n={v:[{b:/\$\d/},{b:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{b:/[\$%@][^\s\w{]/,r:0}]},i=[e.BE,r,n],o=[n,e.HCM,e.C("^\\=\\w","\\=cut",{eW:!0}),s,{cN:"string",c:i,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[e.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[e.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+e.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[e.HCM,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[e.BE],r:0}]},{cN:"function",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",eE:!0,r:5,c:[e.TM]},{b:"-\\w\\b",r:0},{b:"^__DATA__$",e:"^__END__$",sL:"mojolicious",c:[{b:"^@@.*",e:"$",cN:"comment"}]}];return r.c=o,s.c=o,{aliases:["pl","pm"],l:/[\w\.]+/,k:t,c:o}});hljs.registerLanguage("coffeescript",function(e){var c={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super yield import export from as default await then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",built_in:"npm require console print module global window document"},n="[A-Za-z$_][0-9A-Za-z$_]*",r={cN:"subst",b:/#\{/,e:/}/,k:c},i=[e.BNM,e.inherit(e.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[e.BE]},{b:/'/,e:/'/,c:[e.BE]},{b:/"""/,e:/"""/,c:[e.BE,r]},{b:/"/,e:/"/,c:[e.BE,r]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[r,e.HCM]},{b:"//[gim]*",r:0},{b:/\/(?![ *])(\\\/|.)*?\/[gim]*(?=\W|$)/}]},{b:"@"+n},{sL:"javascript",eB:!0,eE:!0,v:[{b:"```",e:"```"},{b:"`",e:"`"}]}];r.c=i;var s=e.inherit(e.TM,{b:n}),t="(\\(.*\\))?\\s*\\B[-=]>",o={cN:"params",b:"\\([^\\(]",rB:!0,c:[{b:/\(/,e:/\)/,k:c,c:["self"].concat(i)}]};return{aliases:["coffee","cson","iced"],k:c,i:/\/\*/,c:i.concat([e.C("###","###"),e.HCM,{cN:"function",b:"^\\s*"+n+"\\s*=\\s*"+t,e:"[-=]>",rB:!0,c:[s,o]},{b:/[:\(,=]\s*/,r:0,c:[{cN:"function",b:t,e:"[-=]>",rB:!0,c:[o]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:!0,i:/[:="\[\]]/,c:[s]},s]},{b:n+":",e:":",rB:!0,rE:!0,r:0}])}});hljs.registerLanguage("accesslog",function(T){return{c:[{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+\\b",r:0},{cN:"string",b:'"(GET|POST|HEAD|PUT|DELETE|CONNECT|OPTIONS|PATCH|TRACE)',e:'"',k:"GET POST HEAD PUT DELETE CONNECT OPTIONS PATCH TRACE",i:"\\n",r:10},{cN:"string",b:/\[/,e:/\]/,i:"\\n"},{cN:"string",b:'"',e:'"',i:"\\n"}]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("css",function(e){var c="[a-zA-Z-][a-zA-Z0-9_-]*",t={b:/[A-Z\_\.\-]+\s*:/,rB:!0,e:";",eW:!0,c:[{cN:"attribute",b:/\S/,e:":",eE:!0,starts:{eW:!0,eE:!0,c:[{b:/[\w-]+\(/,rB:!0,c:[{cN:"built_in",b:/[\w-]+/},{b:/\(/,e:/\)/,c:[e.ASM,e.QSM]}]},e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"number",b:"#[0-9A-Fa-f]+"},{cN:"meta",b:"!important"}]}}]};return{cI:!0,i:/[=\/|'\$]/,c:[e.CBCM,{cN:"selector-id",b:/#[A-Za-z0-9_-]+/},{cN:"selector-class",b:/\.[A-Za-z0-9_-]+/},{cN:"selector-attr",b:/\[/,e:/\]/,i:"$"},{cN:"selector-pseudo",b:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{b:"@",e:"[{;]",i:/:/,c:[{cN:"keyword",b:/\w+/},{b:/\s/,eW:!0,eE:!0,r:0,c:[e.ASM,e.QSM,e.CSSNM]}]},{cN:"selector-tag",b:c,r:0},{b:"{",e:"}",i:/\S/,c:[e.CBCM,t]}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"\\b[a-z\\d_]*_t\\b"},r={cN:"string",v:[{b:'(u8?|U)?L?"',e:'"',i:"\\n",c:[t.BE]},{b:'(u8?|U)?R"',e:'"',c:[t.BE]},{b:"'\\\\?.",e:"'",i:"."}]},s={cN:"number",v:[{b:"\\b(0b[01']+)"},{b:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{b:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],r:0},i={cN:"meta",b:/#\s*[a-z]+\b/,e:/$/,k:{"meta-keyword":"if else elif endif define undef warning error line pragma ifdef ifndef include"},c:[{b:/\\\n/,r:0},t.inherit(r,{cN:"meta-string"}),{cN:"meta-string",b:/<[^\n>]*>/,e:/$/,i:"\\n"},t.CLCM,t.CBCM]},a=t.IR+"\\s*\\(",c={keyword:"int float while private char catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and or not",built_in:"std string cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr",literal:"true false nullptr NULL"},n=[e,t.CLCM,t.CBCM,s,r];return{aliases:["c","cc","h","c++","h++","hpp"],k:c,i:"</",c:n.concat([i,{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:c,c:["self",e]},{b:t.IR+"::",k:c},{v:[{b:/=/,e:/;/},{b:/\(/,e:/\)/},{bK:"new throw return else",e:/;/}],k:c,c:n.concat([{b:/\(/,e:/\)/,k:c,c:n.concat(["self"]),r:0}]),r:0},{cN:"function",b:"("+t.IR+"[\\*&\\s]+)+"+a,rB:!0,e:/[{;=]/,eE:!0,k:c,i:/[^\w\s\*&]/,c:[{b:a,rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:c,r:0,c:[t.CLCM,t.CBCM,r,s,e]},t.CLCM,t.CBCM,i]},{cN:"class",bK:"class struct",e:/[{;:]/,c:[{b:/</,e:/>/,c:["self"]},t.TM]}]),exports:{preprocessor:i,strings:r,k:c}}});hljs.registerLanguage("awk",function(e){var r={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},b="BEGIN END if else while do for in break continue delete next nextfile function func exit|10",n={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,r:10},{b:/(u|b)?r?"""/,e:/"""/,r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},e.ASM,e.QSM]};return{k:{keyword:b},c:[r,n,e.RM,e.HCM,e.NM]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"\\b(AV|CA|CF|CG|CI|CL|CM|CN|CT|MK|MP|MTK|MTL|NS|SCN|SK|UI|WK|XC)\\w+"},_={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},i=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:_,l:i,i:"</",c:[t,e.CLCM,e.CBCM,e.CNM,e.QSM,{cN:"string",v:[{b:'@"',e:'"',i:"\\n",c:[e.BE]},{b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"}]},{cN:"meta",b:"#",e:"$",c:[{cN:"meta-string",v:[{b:'"',e:'"'},{b:"<",e:">"}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:i,c:[e.UTM]},{b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("ini",function(e){var b={cN:"string",c:[e.BE],v:[{b:"'''",e:"'''",r:10},{b:'"""',e:'"""',r:10},{b:'"',e:'"'},{b:"'",e:"'"}]};return{aliases:["toml"],cI:!0,i:/\S/,c:[e.C(";","$"),e.HCM,{cN:"section",b:/^\s*\[+/,e:/\]+/},{b:/^[a-z0-9\[\]_-]+\s*=\s*/,e:"$",rB:!0,c:[{cN:"attr",b:/[a-z0-9\[\]_-]+/},{b:/=/,eW:!0,r:0,c:[{cN:"literal",b:/\bon|off|true|false|yes|no\b/},{cN:"variable",v:[{b:/\$[\w\d"][\w\d_]*/},{b:/\$\{(.*?)}/}]},b,{cN:"number",b:/([\+\-]+)?[\d]+_[\d_]+/},e.NM]}]}]}});hljs.registerLanguage("makefile",function(e){var i={cN:"variable",v:[{b:"\\$\\("+e.UIR+"\\)",c:[e.BE]},{b:/\$[@%<?\^\+\*]/}]},r={cN:"string",b:/"/,e:/"/,c:[e.BE,i]},a={cN:"variable",b:/\$\([\w-]+\s/,e:/\)/,k:{built_in:"subst patsubst strip findstring filter filter-out sort word wordlist firstword lastword dir notdir suffix basename addsuffix addprefix join wildcard realpath abspath error warning shell origin flavor foreach if or and call eval file value"},c:[i]},n={b:"^"+e.UIR+"\\s*[:+?]?=",i:"\\n",rB:!0,c:[{b:"^"+e.UIR,e:"[:+?]?=",eE:!0}]},t={cN:"meta",b:/^\.PHONY:/,e:/$/,k:{"meta-keyword":".PHONY"},l:/[\.\w]+/},l={cN:"section",b:/^[^\s]+:/,e:/$/,c:[i]};return{aliases:["mk","mak"],k:"define endef undefine ifdef ifndef ifeq ifneq else endif include -include sinclude override export unexport private vpath",l:/[\w-]+/,c:[e.HCM,i,r,a,n,t,l]}});hljs.registerLanguage("python",function(e){var r={keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},b={cN:"meta",b:/^(>>>|\.\.\.) /},c={cN:"subst",b:/\{/,e:/\}/,k:r,i:/#/},a={cN:"string",c:[e.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[b],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[b],r:10},{b:/(fr|rf|f)'''/,e:/'''/,c:[b,c]},{b:/(fr|rf|f)"""/,e:/"""/,c:[b,c]},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},{b:/(fr|rf|f)'/,e:/'/,c:[c]},{b:/(fr|rf|f)"/,e:/"/,c:[c]},e.ASM,e.QSM]},s={cN:"number",r:0,v:[{b:e.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:e.CNR+"[lLjJ]?"}]},i={cN:"params",b:/\(/,e:/\)/,c:["self",b,s,a]};return c.c=[a,s,b],{aliases:["py","gyp"],k:r,i:/(<\/|->|\?)|=>/,c:[b,s,a,e.HCM,{v:[{cN:"function",bK:"def"},{cN:"class",bK:"class"}],e:/:/,i:/[${=;\n,]/,c:[e.UTM,i,{b:/->/,eW:!0,k:"None"}]},{cN:"meta",b:/^[\t ]*@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("apache",function(e){var r={cN:"number",b:"[\\$%]\\d+"};return{aliases:["apacheconf"],cI:!0,c:[e.HCM,{cN:"section",b:"</?",e:">"},{cN:"attribute",b:/\w+/,r:0,k:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"meta",b:"\\s\\[",e:"\\]$"},{cN:"variable",b:"[\\$%]\\{",e:"\\}",c:["self",r]},r,e.QSM]}}],i:/\S/}});hljs.registerLanguage("cs",function(e){var i={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long nameof object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let on orderby partial remove select set value var where yield",literal:"null false true"},t={cN:"string",b:'@"',e:'"',c:[{b:'""'}]},r=e.inherit(t,{i:/\n/}),a={cN:"subst",b:"{",e:"}",k:i},c=e.inherit(a,{i:/\n/}),n={cN:"string",b:/\$"/,e:'"',i:/\n/,c:[{b:"{{"},{b:"}}"},e.BE,c]},s={cN:"string",b:/\$@"/,e:'"',c:[{b:"{{"},{b:"}}"},{b:'""'},a]},o=e.inherit(s,{i:/\n/,c:[{b:"{{"},{b:"}}"},{b:'""'},c]});a.c=[s,n,t,e.ASM,e.QSM,e.CNM,e.CBCM],c.c=[o,n,r,e.ASM,e.QSM,e.CNM,e.inherit(e.CBCM,{i:/\n/})];var l={v:[s,n,t,e.ASM,e.QSM]},b=e.IR+"(<"+e.IR+"(\\s*,\\s*"+e.IR+")*>)?(\\[\\])?";return{aliases:["csharp"],k:i,i:/::/,c:[e.C("///","$",{rB:!0,c:[{cN:"doctag",v:[{b:"///",r:0},{b:"<!--|-->"},{b:"</?",e:">"}]}]}),e.CLCM,e.CBCM,{cN:"meta",b:"#",e:"$",k:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},l,e.CNM,{bK:"class interface",e:/[{;=]/,i:/[^\s:]/,c:[e.TM,e.CLCM,e.CBCM]},{bK:"namespace",e:/[{;=]/,i:/[^\s:]/,c:[e.inherit(e.TM,{b:"[a-zA-Z](\\.?\\w)*"}),e.CLCM,e.CBCM]},{cN:"meta",b:"^\\s*\\[",eB:!0,e:"\\]",eE:!0,c:[{cN:"meta-string",b:/"/,e:/"/}]},{bK:"new return throw await else",r:0},{cN:"function",b:"("+b+"\\s+)+"+e.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:i,c:[{b:e.IR+"\\s*\\(",rB:!0,c:[e.TM],r:0},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,k:i,r:0,c:[l,e.CNM,e.CBCM]},e.CLCM,e.CBCM]}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});hljs.registerLanguage("scss",function(e){var t="[a-zA-Z-][a-zA-Z0-9_-]*",i={cN:"variable",b:"(\\$"+t+")\\b"},r={cN:"number",b:"#[0-9A-Fa-f]+"};({cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:!0,i:"[^\\s]",starts:{eW:!0,eE:!0,c:[r,e.CSSNM,e.QSM,e.ASM,e.CBCM,{cN:"meta",b:"!important"}]}});return{cI:!0,i:"[=/|']",c:[e.CLCM,e.CBCM,{cN:"selector-id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"selector-class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"selector-attr",b:"\\[",e:"\\]",i:"$"},{cN:"selector-tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},i,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{b:":",e:";",c:[i,r,e.CSSNM,e.QSM,e.ASM,{cN:"meta",b:"!important"}]},{b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[i,e.QSM,e.ASM,r,e.CSSNM,{b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a="^[ \\-]*",r="[a-zA-Z_][\\w\\-]*",t={cN:"attr",v:[{b:a+r+":"},{b:a+'"'+r+'":'},{b:a+"'"+r+"':"}]},c={cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]},l={cN:"string",r:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,c]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[t,{cN:"meta",b:"^---s*$",r:10},{cN:"string",b:"[\\|>] *$",rE:!0,c:l.c,e:t.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},e.HCM,{bK:b,k:{literal:b}},e.CNM,l]}});hljs.registerLanguage("php",function(e){var c={b:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},i={cN:"meta",b:/<\?(php)?|\?>/},t={cN:"string",c:[e.BE,i],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},e.inherit(e.ASM,{i:null}),e.inherit(e.QSM,{i:null})]},a={v:[e.BNM,e.CNM]};return{aliases:["php3","php4","php5","php6"],cI:!0,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[e.HCM,e.C("//","$",{c:[i]}),e.C("/\\*","\\*/",{c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.C("__halt_compiler.+?;",!1,{eW:!0,k:"__halt_compiler",l:e.UIR}),{cN:"string",b:/<<<['"]?\w+['"]?$/,e:/^\w+;?$/,c:[e.BE,{cN:"subst",v:[{b:/\$\w+/},{b:/\{\$/,e:/\}/}]}]},i,{cN:"keyword",b:/\$this\b/},c,{b:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{cN:"function",bK:"function",e:/[;{]/,eE:!0,i:"\\$|\\[|%",c:[e.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",c,e.CBCM,t,a]}]},{cN:"class",bK:"class interface",e:"{",eE:!0,i:/[:\(\$"]/,c:[{bK:"extends implements"},e.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[e.UTM]},{bK:"use",e:";",c:[e.UTM]},{b:"=>"},t,a]}});hljs.registerLanguage("http",function(e){var t="HTTP/[0-9\\.]+";return{aliases:["https"],i:"\\S",c:[{b:"^"+t,e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{b:"^[A-Z]+ (.*?) "+t+"$",rB:!0,e:"$",c:[{cN:"string",b:" ",e:" ",eB:!0,eE:!0},{b:t},{cN:"keyword",b:"[A-Z]+"}]},{cN:"attribute",b:"^\\w",e:": ",eE:!0,i:"\\n|\\s|=",starts:{e:"$",r:0}},{b:"\\n\\n",starts:{sL:[],eW:!0}}]}}); ================================================ FILE: gui/static/js/inboundmapping.js ================================================ ;(function(window, document) { 'use strict'; /** * @global window scope * @namespace aria */ var aria = aria || {}; /** * @global script scope * @type {Array} */ var DID_LIST = DID_LIST || []; /** * Search DID_LIST for search_string * DID_LIST should be globally defined * @param search * @returns {Array} */ function searchDIDs(search) { var res = []; var num_dids = DID_LIST.length; for (var i = 0; i < num_dids; i++) { if (DID_LIST[i].indexOf(search.toLowerCase()) === 0) { res.push(DID_LIST[i]); } } return res; } /** * Wrapper for initializing * @param parent_selector * @returns {aria.ListboxCombobox} */ function comboboxInit(parent_selector) { var parent = $(parent_selector); var arrow = parent.find('.did-combobox-arrow > span'); /* create combobox */ new aria.ListboxCombobox( parent.find('.did-combobox').get(0), parent.find('.did-combobox-input').get(0), parent.find('.did-listbox').get(0), searchDIDs, false, function() { arrow.removeClass('icon-circle-down'); arrow.addClass('icon-circle-up'); }, function() { arrow.removeClass('icon-circle-up'); arrow.addClass('icon-circle-down'); } ) } /* any handlers depending on DOM elems go here */ $(document).ready(function() { /* only created if we have DID's */ if (DID_LIST.length > 0) { /* init the combobox's */ comboboxInit('#add .modal-body'); comboboxInit('#edit .modal-body'); } /* init datatable */ $('#inboundmapping').DataTable({ "columnDefs": [ {"orderable": true, "targets": [1, 2, 3, 4, 5]}, {"orderable": false, "targets": [0, 6, 7]}, ], "order": [[1, 'asc']] }); $('#open-Add').click(function() { /** Clear out the modal */ var modal_body = $('#add .modal-body'); modal_body.find("input.ruleid").val(''); modal_body.find("input.prefix").val(''); modal_body.find("input.rulename").val(''); modal_body.find("input.hf_ruleid").val(''); modal_body.find("input.hf_groupid").val(''); modal_body.find("input.hf_fwddid").val(''); modal_body.find("input.ff_ruleid").val(''); modal_body.find("input.ff_groupid").val(''); modal_body.find("input.ff_fwddid").val(''); /* reset options selected */ modal_body.find("select").val(''); /* reset toggle buttons */ modal_body.find("input.toggle-hardfwd").bootstrapToggle('off'); modal_body.find("input.toggle-failfwd").bootstrapToggle('off'); }); $('#inboundmapping').on('click', '#open-Update', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('inboundmapping'); var ruleid = $(c).find('tr:eq(' + row_index + ') td.ruleid').text(); var prefix = $(c).find('tr:eq(' + row_index + ') td.prefix').text(); var gwgroupid = $(c).find('tr:eq(' + row_index + ') td.gwgroupid').text(); var gwgroupname = $(c).find('tr:eq(' + row_index + ') td.gwgroupname').text(); var rulename = $(c).find('tr:eq(' + row_index + ') td.rulename').text(); var gwlistid = $(c).find('tr:eq(' + row_index + ') td.gwlistid').text().replace('#', ''); var lb_enabled = $(c).find('tr:eq(' + row_index + ') td.lb_enabled').text() === '1'; var hf_ruleid = $(c).find('tr:eq(' + row_index + ') td.hf_ruleid').text(); var hf_groupid = $(c).find('tr:eq(' + row_index + ') td.hf_groupid').text(); var hf_gwgroupid = $(c).find('tr:eq(' + row_index + ') td.hf_gwgroupid').text(); var hf_fwddid = $(c).find('tr:eq(' + row_index + ') td.hf_fwddid').text(); var ff_ruleid = $(c).find('tr:eq(' + row_index + ') td.ff_ruleid').text(); var ff_groupid = $(c).find('tr:eq(' + row_index + ') td.ff_groupid').text(); var ff_gwgroupid = $(c).find('tr:eq(' + row_index + ') td.ff_gwgroupid').text(); var ff_fwddid = $(c).find('tr:eq(' + row_index + ') td.ff_fwddid').text(); /** Clear out the modal */ var modal_body = $('#edit .modal-body'); modal_body.find("input.ruleid").val(''); modal_body.find("input.prefix").val(''); modal_body.find("input.rulename").val(''); modal_body.find("input.hf_ruleid").val(''); modal_body.find("input.hf_groupid").val(''); modal_body.find("input.hf_fwddid").val(''); modal_body.find("input.ff_ruleid").val(''); modal_body.find("input.ff_groupid").val(''); modal_body.find("input.ff_fwddid").val(''); /* update modal fields */ modal_body.find("input.ruleid").val(ruleid); modal_body.find("input.prefix").val(prefix); modal_body.find("input.rulename").val(rulename); modal_body.find("input.hf_ruleid").val(hf_ruleid); modal_body.find("input.hf_groupid").val(hf_groupid); modal_body.find("input.hf_fwddid").val(hf_fwddid); modal_body.find("input.ff_ruleid").val(ff_ruleid); modal_body.find("input.ff_groupid").val(ff_groupid); modal_body.find("input.ff_fwddid").val(ff_fwddid); /* update options selected */ modal_body.find("select").val(''); modal_body.find("select.gwgroupid").val(gwgroupid); modal_body.find("select.hf_gwgroupid").val(hf_gwgroupid); modal_body.find("select.ff_gwgroupid").val(ff_gwgroupid); if (lb_enabled) { modal_body.find("select.gwgroupid option").filter(function() { return this.value.indexOf('lb_'+gwgroupid) !== -1; }).prop("selected", true); } /* update toggle buttons */ if (hf_ruleid.length > 0) { modal_body.find("input.toggle-hardfwd").bootstrapToggle('on'); } else { modal_body.find("input.toggle-hardfwd").bootstrapToggle('off'); } if (ff_ruleid.length > 0) { modal_body.find("input.toggle-failfwd").bootstrapToggle('on'); } else { modal_body.find("input.toggle-failfwd").bootstrapToggle('off'); } }); $('#inboundmapping').on('click', '#open-Delete', function() { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('inboundmapping'); var ruleid = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); var hf_ruleid = $(c).find('tr:eq(' + row_index + ') td:eq(9)').text(); var hf_groupid = $(c).find('tr:eq(' + row_index + ') td:eq(10)').text(); var ff_ruleid = $(c).find('tr:eq(' + row_index + ') td:eq(13)').text(); var ff_groupid = $(c).find('tr:eq(' + row_index + ') td:eq(14)').text(); /* update modal fields */ var modal_body = $('#delete .modal-body'); modal_body.find("input.ruleid").val(ruleid); modal_body.find("input.hf_ruleid").val(hf_ruleid); modal_body.find("input.hf_groupid").val(hf_groupid); modal_body.find("input.ff_ruleid").val(ff_ruleid); modal_body.find("input.ff_groupid").val(ff_groupid); }); /* listener for hard forward toggle */ $('.modal-body .toggle-hardfwd').change(function() { var modal = $(this).closest('div.modal'); var modal_body = modal.find('.modal-body'); if ($(this).is(":checked") || $(this).prop("checked")) { modal_body.find('.hardfwd-options').removeClass("hidden"); modal_body.find('.hardfwd_enabled').val(1); } else { modal_body.find('.hardfwd-options').addClass("hidden"); modal_body.find('.hardfwd_enabled').val(0); } }); /* listener for failover forward toggle */ $('.modal-body .toggle-failfwd').change(function() { var modal = $(this).closest('div.modal'); var modal_body = modal.find('.modal-body'); if ($(this).is(":checked") || $(this).prop("checked")) { modal_body.find('.failfwd-options').removeClass("hidden"); modal_body.find('.failfwd_enabled').val(1); } else { modal_body.find('.failfwd-options').addClass("hidden"); modal_body.find('.failfwd_enabled').val(0); } }); }); })(window, document); ================================================ FILE: gui/static/js/jquery.js ================================================ /*! * jQuery JavaScript Library v3.3.1 * https://jquery.com/ * * Includes Sizzle.js * https://sizzlejs.com/ * * Copyright JS Foundation and other contributors * Released under the MIT license * https://jquery.org/license * * Date: 2018-01-20T17:24Z */ ( function( global, factory ) { "use strict"; if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper `window` // is present, execute the factory and get jQuery. // For environments that do not have a `window` with a `document` // (such as Node.js), expose a factory as module.exports. // This accentuates the need for the creation of a real `window`. // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info. module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet } )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { // Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 // throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode // arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common // enough that all such attempts are guarded in a try block. "use strict"; var arr = []; var document = window.document; var getProto = Object.getPrototypeOf; var slice = arr.slice; var concat = arr.concat; var push = arr.push; var indexOf = arr.indexOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var fnToString = hasOwn.toString; var ObjectFunctionString = fnToString.call( Object ); var support = {}; var isFunction = function isFunction( obj ) { // Support: Chrome <=57, Firefox <=52 // In some browsers, typeof returns "function" for HTML <object> elements // (i.e., `typeof document.createElement( "object" ) === "function"`). // We don't want to classify *any* DOM node as a function. return typeof obj === "function" && typeof obj.nodeType !== "number"; }; var isWindow = function isWindow( obj ) { return obj != null && obj === obj.window; }; var preservedScriptAttributes = { type: true, src: true, noModule: true }; function DOMEval( code, doc, node ) { doc = doc || document; var i, script = doc.createElement( "script" ); script.text = code; if ( node ) { for ( i in preservedScriptAttributes ) { if ( node[ i ] ) { script[ i ] = node[ i ]; } } } doc.head.appendChild( script ).parentNode.removeChild( script ); } function toType( obj ) { if ( obj == null ) { return obj + ""; } // Support: Android <=2.3 only (functionish RegExp) return typeof obj === "object" || typeof obj === "function" ? class2type[ toString.call( obj ) ] || "object" : typeof obj; } /* global Symbol */ // Defining this global in .eslintrc.json would create a danger of using the global // unguarded in another place, it seems safer to define global only for this module var version = "3.3.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); }, // Support: Android <=4.0 only // Make sure we trim BOM and NBSP rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g; jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, constructor: jQuery, // The default length of a jQuery object is 0 length: 0, toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { // Return all the elements in a clean array if ( num == null ) { return slice.call( this ); } // Return just the one element from the set return num < 0 ? this[ num + this.length ] : this[ num ]; }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. each: function( callback ) { return jQuery.each( this, callback ); }, map: function( callback ) { return this.pushStack( jQuery.map( this, function( elem, i ) { return callback.call( elem, i, elem ); } ) ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); }, end: function() { return this.prevObject || this.constructor(); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: arr.sort, splice: arr.splice }; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[ 0 ] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; // Skip the boolean and the target target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !isFunction( target ) ) { target = {}; } // Extend jQuery itself if only one argument is passed if ( i === length ) { target = this; i--; } for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( ( options = arguments[ i ] ) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject( copy ) || ( copyIsArray = Array.isArray( copy ) ) ) ) { if ( copyIsArray ) { copyIsArray = false; clone = src && Array.isArray( src ) ? src : []; } else { clone = src && jQuery.isPlainObject( src ) ? src : {}; } // Never move original objects, clone them target[ name ] = jQuery.extend( deep, clone, copy ); // Don't bring in undefined values } else if ( copy !== undefined ) { target[ name ] = copy; } } } } // Return the modified object return target; }; jQuery.extend( { // Unique for each copy of jQuery on the page expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready without the ready module isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, isPlainObject: function( obj ) { var proto, Ctor; // Detect obvious negatives // Use toString instead of jQuery.type to catch host objects if ( !obj || toString.call( obj ) !== "[object Object]" ) { return false; } proto = getProto( obj ); // Objects with no prototype (e.g., `Object.create( null )`) are plain if ( !proto ) { return true; } // Objects with prototype are plain iff they were constructed by a global Object function Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; }, isEmptyObject: function( obj ) { /* eslint-disable no-unused-vars */ // See https://github.com/eslint/eslint/issues/6125 var name; for ( name in obj ) { return false; } return true; }, // Evaluates a script in a global context globalEval: function( code ) { DOMEval( code ); }, each: function( obj, callback ) { var length, i = 0; if ( isArrayLike( obj ) ) { length = obj.length; for ( ; i < length; i++ ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } else { for ( i in obj ) { if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { break; } } } return obj; }, // Support: Android <=4.0 only trim: function( text ) { return text == null ? "" : ( text + "" ).replace( rtrim, "" ); }, // results is for internal usage only makeArray: function( arr, results ) { var ret = results || []; if ( arr != null ) { if ( isArrayLike( Object( arr ) ) ) { jQuery.merge( ret, typeof arr === "string" ? [ arr ] : arr ); } else { push.call( ret, arr ); } } return ret; }, inArray: function( elem, arr, i ) { return arr == null ? -1 : indexOf.call( arr, elem, i ); }, // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }, grep: function( elems, callback, invert ) { var callbackInverse, matches = [], i = 0, length = elems.length, callbackExpect = !invert; // Go through the array, only saving the items // that pass the validator function for ( ; i < length; i++ ) { callbackInverse = !callback( elems[ i ], i ); if ( callbackInverse !== callbackExpect ) { matches.push( elems[ i ] ); } } return matches; }, // arg is for internal usage only map: function( elems, callback, arg ) { var length, value, i = 0, ret = []; // Go through the array, translating each of the items to their new values if ( isArrayLike( elems ) ) { length = elems.length; for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } // Go through every key on the object, } else { for ( i in elems ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret.push( value ); } } } // Flatten any nested arrays return concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // jQuery.support is not used in Core but other projects attach their // properties to it so it needs to exist. support: support } ); if ( typeof Symbol === "function" ) { jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; } // Populate the class2type map jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), function( i, name ) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); } ); function isArrayLike( obj ) { // Support: real iOS 8.2 only (not reproducible in simulator) // `in` check used to prevent JIT error (gh-2145) // hasOwn isn't used here due to false negatives // regarding Nodelist length in IE var length = !!obj && "length" in obj && obj.length, type = toType( obj ); if ( isFunction( obj ) || isWindow( obj ) ) { return false; } return type === "array" || length === 0 || typeof length === "number" && length > 0 && ( length - 1 ) in obj; } var Sizzle = /*! * Sizzle CSS Selector Engine v2.3.3 * https://sizzlejs.com/ * * Copyright jQuery Foundation and other contributors * Released under the MIT license * http://jquery.org/license * * Date: 2016-08-08 */ (function( window ) { var i, support, Expr, getText, isXML, tokenize, compile, select, outermostContext, sortInput, hasDuplicate, // Local document vars setDocument, document, docElem, documentIsHTML, rbuggyQSA, rbuggyMatches, matches, contains, // Instance-specific data expando = "sizzle" + 1 * new Date(), preferredDoc = window.document, dirruns = 0, done = 0, classCache = createCache(), tokenCache = createCache(), compilerCache = createCache(), sortOrder = function( a, b ) { if ( a === b ) { hasDuplicate = true; } return 0; }, // Instance methods hasOwn = ({}).hasOwnProperty, arr = [], pop = arr.pop, push_native = arr.push, push = arr.push, slice = arr.slice, // Use a stripped-down indexOf as it's faster than native // https://jsperf.com/thor-indexof-vs-for/5 indexOf = function( list, elem ) { var i = 0, len = list.length; for ( ; i < len; i++ ) { if ( list[i] === elem ) { return i; } } return -1; }, booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", // Regular expressions // http://www.w3.org/TR/css3-selectors/#whitespace whitespace = "[\\x20\\t\\r\\n\\f]", // http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier identifier = "(?:\\\\.|[\\w-]|[^\0-\\xa0])+", // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + // Operator (capture 2) "*([*^$|!~]?=)" + whitespace + // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + "*\\]", pseudos = ":(" + identifier + ")(?:\\((" + // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: // 1. quoted (capture 3; capture 4 or capture 5) "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + // 2. simple (capture 6) "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + // 3. anything else (capture 2) ".*" + ")\\)|)", // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter rwhitespace = new RegExp( whitespace + "+", "g" ), rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), rpseudo = new RegExp( pseudos ), ridentifier = new RegExp( "^" + identifier + "$" ), matchExpr = { "ID": new RegExp( "^#(" + identifier + ")" ), "CLASS": new RegExp( "^\\.(" + identifier + ")" ), "TAG": new RegExp( "^(" + identifier + "|[*])" ), "ATTR": new RegExp( "^" + attributes ), "PSEUDO": new RegExp( "^" + pseudos ), "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), // For use in libraries implementing .is() // We use this for POS matching in `select` "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) }, rinputs = /^(?:input|select|textarea|button)$/i, rheader = /^h\d$/i, rnative = /^[^{]+\{\s*\[native \w/, // Easily-parseable/retrievable ID or TAG or CLASS selectors rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, rsibling = /[+~]/, // CSS escapes // http://www.w3.org/TR/CSS21/syndata.html#escaped-characters runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), funescape = function( _, escaped, escapedWhitespace ) { var high = "0x" + escaped - 0x10000; // NaN means non-codepoint // Support: Firefox<24 // Workaround erroneous numeric interpretation of +"0x" return high !== high || escapedWhitespace ? escaped : high < 0 ? // BMP codepoint String.fromCharCode( high + 0x10000 ) : // Supplemental Plane codepoint (surrogate pair) String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); }, // CSS string/identifier serialization // https://drafts.csswg.org/cssom/#common-serializing-idioms rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g, fcssescape = function( ch, asCodePoint ) { if ( asCodePoint ) { // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER if ( ch === "\0" ) { return "\uFFFD"; } // Control characters and (dependent upon position) numbers get escaped as code points return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; } // Other potentially-special ASCII characters get backslash-escaped return "\\" + ch; }, // Used for iframes // See setDocument() // Removing the function wrapper causes a "Permission Denied" // error in IE unloadHandler = function() { setDocument(); }, disabledAncestor = addCombinator( function( elem ) { return elem.disabled === true && ("form" in elem || "label" in elem); }, { dir: "parentNode", next: "legend" } ); // Optimize for push.apply( _, NodeList ) try { push.apply( (arr = slice.call( preferredDoc.childNodes )), preferredDoc.childNodes ); // Support: Android<4.0 // Detect silently failing push.apply arr[ preferredDoc.childNodes.length ].nodeType; } catch ( e ) { push = { apply: arr.length ? // Leverage slice if possible function( target, els ) { push_native.apply( target, slice.call(els) ); } : // Support: IE<9 // Otherwise append directly function( target, els ) { var j = target.length, i = 0; // Can't trust NodeList.length while ( (target[j++] = els[i++]) ) {} target.length = j - 1; } }; } function Sizzle( selector, context, results, seed ) { var m, i, elem, nid, match, groups, newSelector, newContext = context && context.ownerDocument, // nodeType defaults to 9, since context defaults to document nodeType = context ? context.nodeType : 9; results = results || []; // Return early from calls with invalid selector or context if ( typeof selector !== "string" || !selector || nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { return results; } // Try to shortcut find operations (as opposed to filters) in HTML documents if ( !seed ) { if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { setDocument( context ); } context = context || document; if ( documentIsHTML ) { // If the selector is sufficiently simple, try using a "get*By*" DOM method // (excepting DocumentFragment context, where the methods don't exist) if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) { // ID selector if ( (m = match[1]) ) { // Document context if ( nodeType === 9 ) { if ( (elem = context.getElementById( m )) ) { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( elem.id === m ) { results.push( elem ); return results; } } else { return results; } // Element context } else { // Support: IE, Opera, Webkit // TODO: identify versions // getElementById can match elements by name instead of ID if ( newContext && (elem = newContext.getElementById( m )) && contains( context, elem ) && elem.id === m ) { results.push( elem ); return results; } } // Type selector } else if ( match[2] ) { push.apply( results, context.getElementsByTagName( selector ) ); return results; // Class selector } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { push.apply( results, context.getElementsByClassName( m ) ); return results; } } // Take advantage of querySelectorAll if ( support.qsa && !compilerCache[ selector + " " ] && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { if ( nodeType !== 1 ) { newContext = context; newSelector = selector; // qSA looks outside Element context, which is not what we want // Thanks to Andrew Dupont for this workaround technique // Support: IE <=8 // Exclude object elements } else if ( context.nodeName.toLowerCase() !== "object" ) { // Capture the context ID, setting it first if necessary if ( (nid = context.getAttribute( "id" )) ) { nid = nid.replace( rcssescape, fcssescape ); } else { context.setAttribute( "id", (nid = expando) ); } // Prefix every selector in the list groups = tokenize( selector ); i = groups.length; while ( i-- ) { groups[i] = "#" + nid + " " + toSelector( groups[i] ); } newSelector = groups.join( "," ); // Expand context for sibling selectors newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; } if ( newSelector ) { try { push.apply( results, newContext.querySelectorAll( newSelector ) ); return results; } catch ( qsaError ) { } finally { if ( nid === expando ) { context.removeAttribute( "id" ); } } } } } } // All others return select( selector.replace( rtrim, "$1" ), context, results, seed ); } /** * Create key-value caches of limited size * @returns {function(string, object)} Returns the Object data after storing it on itself with * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) * deleting the oldest entry */ function createCache() { var keys = []; function cache( key, value ) { // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) if ( keys.push( key + " " ) > Expr.cacheLength ) { // Only keep the most recent entries delete cache[ keys.shift() ]; } return (cache[ key + " " ] = value); } return cache; } /** * Mark a function for special use by Sizzle * @param {Function} fn The function to mark */ function markFunction( fn ) { fn[ expando ] = true; return fn; } /** * Support testing using an element * @param {Function} fn Passed the created element and returns a boolean result */ function assert( fn ) { var el = document.createElement("fieldset"); try { return !!fn( el ); } catch (e) { return false; } finally { // Remove from its parent by default if ( el.parentNode ) { el.parentNode.removeChild( el ); } // release memory in IE el = null; } } /** * Adds the same handler for all of the specified attrs * @param {String} attrs Pipe-separated list of attributes * @param {Function} handler The method that will be applied */ function addHandle( attrs, handler ) { var arr = attrs.split("|"), i = arr.length; while ( i-- ) { Expr.attrHandle[ arr[i] ] = handler; } } /** * Checks document order of two siblings * @param {Element} a * @param {Element} b * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b */ function siblingCheck( a, b ) { var cur = b && a, diff = cur && a.nodeType === 1 && b.nodeType === 1 && a.sourceIndex - b.sourceIndex; // Use IE sourceIndex if available on both nodes if ( diff ) { return diff; } // Check if b follows a if ( cur ) { while ( (cur = cur.nextSibling) ) { if ( cur === b ) { return -1; } } } return a ? 1 : -1; } /** * Returns a function to use in pseudos for input types * @param {String} type */ function createInputPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === type; }; } /** * Returns a function to use in pseudos for buttons * @param {String} type */ function createButtonPseudo( type ) { return function( elem ) { var name = elem.nodeName.toLowerCase(); return (name === "input" || name === "button") && elem.type === type; }; } /** * Returns a function to use in pseudos for :enabled/:disabled * @param {Boolean} disabled true for :disabled; false for :enabled */ function createDisabledPseudo( disabled ) { // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable return function( elem ) { // Only certain elements can match :enabled or :disabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled if ( "form" in elem ) { // Check for inherited disabledness on relevant non-disabled elements: // * listed form-associated elements in a disabled fieldset // https://html.spec.whatwg.org/multipage/forms.html#category-listed // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled // * option elements in a disabled optgroup // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled // All such elements have a "form" property. if ( elem.parentNode && elem.disabled === false ) { // Option elements defer to a parent optgroup if present if ( "label" in elem ) { if ( "label" in elem.parentNode ) { return elem.parentNode.disabled === disabled; } else { return elem.disabled === disabled; } } // Support: IE 6 - 11 // Use the isDisabled shortcut property to check for disabled fieldset ancestors return elem.isDisabled === disabled || // Where there is no isDisabled, check manually /* jshint -W018 */ elem.isDisabled !== !disabled && disabledAncestor( elem ) === disabled; } return elem.disabled === disabled; // Try to winnow out elements that can't be disabled before trusting the disabled property. // Some victims get caught in our net (label, legend, menu, track), but it shouldn't // even exist on them, let alone have a boolean value. } else if ( "label" in elem ) { return elem.disabled === disabled; } // Remaining elements are neither :enabled nor :disabled return false; }; } /** * Returns a function to use in pseudos for positionals * @param {Function} fn */ function createPositionalPseudo( fn ) { return markFunction(function( argument ) { argument = +argument; return markFunction(function( seed, matches ) { var j, matchIndexes = fn( [], seed.length, argument ), i = matchIndexes.length; // Match elements found at the specified indexes while ( i-- ) { if ( seed[ (j = matchIndexes[i]) ] ) { seed[j] = !(matches[j] = seed[j]); } } }); }); } /** * Checks a node for validity as a Sizzle context * @param {Element|Object=} context * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value */ function testContext( context ) { return context && typeof context.getElementsByTagName !== "undefined" && context; } // Expose support vars for convenience support = Sizzle.support = {}; /** * Detects XML nodes * @param {Element|Object} elem An element or a document * @returns {Boolean} True iff elem is a non-HTML XML node */ isXML = Sizzle.isXML = function( elem ) { // documentElement is verified for cases where it doesn't yet exist // (such as loading iframes in IE - #4833) var documentElement = elem && (elem.ownerDocument || elem).documentElement; return documentElement ? documentElement.nodeName !== "HTML" : false; }; /** * Sets document-related variables once based on the current document * @param {Element|Object} [doc] An element or document object to use to set the document * @returns {Object} Returns the current document */ setDocument = Sizzle.setDocument = function( node ) { var hasCompare, subWindow, doc = node ? node.ownerDocument || node : preferredDoc; // Return early if doc is invalid or already selected if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { return document; } // Update global variables document = doc; docElem = document.documentElement; documentIsHTML = !isXML( document ); // Support: IE 9-11, Edge // Accessing iframe documents after unload throws "permission denied" errors (jQuery #13936) if ( preferredDoc !== document && (subWindow = document.defaultView) && subWindow.top !== subWindow ) { // Support: IE 11, Edge if ( subWindow.addEventListener ) { subWindow.addEventListener( "unload", unloadHandler, false ); // Support: IE 9 - 10 only } else if ( subWindow.attachEvent ) { subWindow.attachEvent( "onunload", unloadHandler ); } } /* Attributes ---------------------------------------------------------------------- */ // Support: IE<8 // Verify that getAttribute really returns attributes and not properties // (excepting IE8 booleans) support.attributes = assert(function( el ) { el.className = "i"; return !el.getAttribute("className"); }); /* getElement(s)By* ---------------------------------------------------------------------- */ // Check if getElementsByTagName("*") returns only elements support.getElementsByTagName = assert(function( el ) { el.appendChild( document.createComment("") ); return !el.getElementsByTagName("*").length; }); // Support: IE<9 support.getElementsByClassName = rnative.test( document.getElementsByClassName ); // Support: IE<10 // Check if getElementById returns elements by name // The broken getElementById methods don't pick up programmatically-set names, // so use a roundabout getElementsByName test support.getById = assert(function( el ) { docElem.appendChild( el ).id = expando; return !document.getElementsByName || !document.getElementsByName( expando ).length; }); // ID filter and find if ( support.getById ) { Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { return elem.getAttribute("id") === attrId; }; }; Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var elem = context.getElementById( id ); return elem ? [ elem ] : []; } }; } else { Expr.filter["ID"] = function( id ) { var attrId = id.replace( runescape, funescape ); return function( elem ) { var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); return node && node.value === attrId; }; }; // Support: IE 6 - 7 only // getElementById is not reliable as a find shortcut Expr.find["ID"] = function( id, context ) { if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { var node, i, elems, elem = context.getElementById( id ); if ( elem ) { // Verify the id attribute node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } // Fall back on getElementsByName elems = context.getElementsByName( id ); i = 0; while ( (elem = elems[i++]) ) { node = elem.getAttributeNode("id"); if ( node && node.value === id ) { return [ elem ]; } } } return []; } }; } // Tag Expr.find["TAG"] = support.getElementsByTagName ? function( tag, context ) { if ( typeof context.getElementsByTagName !== "undefined" ) { return context.getElementsByTagName( tag ); // DocumentFragment nodes don't have gEBTN } else if ( support.qsa ) { return context.querySelectorAll( tag ); } } : function( tag, context ) { var elem, tmp = [], i = 0, // By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too results = context.getElementsByTagName( tag ); // Filter out possible comments if ( tag === "*" ) { while ( (elem = results[i++]) ) { if ( elem.nodeType === 1 ) { tmp.push( elem ); } } return tmp; } return results; }; // Class Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { return context.getElementsByClassName( className ); } }; /* QSA/matchesSelector ---------------------------------------------------------------------- */ // QSA and matchesSelector support // matchesSelector(:active) reports false when true (IE9/Opera 11.5) rbuggyMatches = []; // qSa(:focus) reports false when true (Chrome 21) // We allow this because of a bug in IE8/9 that throws an error // whenever `document.activeElement` is accessed on an iframe // So, we allow :focus to pass through QSA all the time to avoid the IE error // See https://bugs.jquery.com/ticket/13378 rbuggyQSA = []; if ( (support.qsa = rnative.test( document.querySelectorAll )) ) { // Build QSA regex // Regex strategy adopted from Diego Perini assert(function( el ) { // Select is set to empty string on purpose // This is to test IE's treatment of not explicitly // setting a boolean content attribute, // since its presence should be enough // https://bugs.jquery.com/ticket/12359 docElem.appendChild( el ).innerHTML = "<a id='" + expando + "'></a>" + "<select id='" + expando + "-\r\\' msallowcapture=''>" + "<option selected=''></option></select>"; // Support: IE8, Opera 11-12.16 // Nothing should be selected when empty strings follow ^= or $= or *= // The test attribute must be unknown in Opera but "safe" for WinRT // https://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section if ( el.querySelectorAll("[msallowcapture^='']").length ) { rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); } // Support: IE8 // Boolean attributes and "value" are not treated correctly if ( !el.querySelectorAll("[selected]").length ) { rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); } // Support: Chrome<29, Android<4.4, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.8+ if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { rbuggyQSA.push("~="); } // Webkit/Opera - :checked should return selected option elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked // IE8 throws error here and will not see later tests if ( !el.querySelectorAll(":checked").length ) { rbuggyQSA.push(":checked"); } // Support: Safari 8+, iOS 8+ // https://bugs.webkit.org/show_bug.cgi?id=136851 // In-page `selector#id sibling-combinator selector` fails if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { rbuggyQSA.push(".#.+[+~]"); } }); assert(function( el ) { el.innerHTML = "<a href='' disabled='disabled'></a>" + "<select disabled='disabled'><option/></select>"; // Support: Windows 8 Native Apps // The type and name attributes are restricted during .innerHTML assignment var input = document.createElement("input"); input.setAttribute( "type", "hidden" ); el.appendChild( input ).setAttribute( "name", "D" ); // Support: IE8 // Enforce case-sensitivity of name attribute if ( el.querySelectorAll("[name=d]").length ) { rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); } // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) // IE8 throws error here and will not see later tests if ( el.querySelectorAll(":enabled").length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Support: IE9-11+ // IE's :disabled selector does not pick up the children of disabled fieldsets docElem.appendChild( el ).disabled = true; if ( el.querySelectorAll(":disabled").length !== 2 ) { rbuggyQSA.push( ":enabled", ":disabled" ); } // Opera 10-11 does not throw on post-comma invalid pseudos el.querySelectorAll("*,:x"); rbuggyQSA.push(",.*:"); }); } if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || docElem.webkitMatchesSelector || docElem.mozMatchesSelector || docElem.oMatchesSelector || docElem.msMatchesSelector) )) ) { assert(function( el ) { // Check to see if it's possible to do matchesSelector // on a disconnected node (IE 9) support.disconnectedMatch = matches.call( el, "*" ); // This should fail with an exception // Gecko does not error, returns false instead matches.call( el, "[s!='']:x" ); rbuggyMatches.push( "!=", pseudos ); }); } rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); /* Contains ---------------------------------------------------------------------- */ hasCompare = rnative.test( docElem.compareDocumentPosition ); // Element contains another // Purposefully self-exclusive // As in, an element does not contain itself contains = hasCompare || rnative.test( docElem.contains ) ? function( a, b ) { var adown = a.nodeType === 9 ? a.documentElement : a, bup = b && b.parentNode; return a === bup || !!( bup && bup.nodeType === 1 && ( adown.contains ? adown.contains( bup ) : a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 )); } : function( a, b ) { if ( b ) { while ( (b = b.parentNode) ) { if ( b === a ) { return true; } } } return false; }; /* Sorting ---------------------------------------------------------------------- */ // Document order sorting sortOrder = hasCompare ? function( a, b ) { // Flag for duplicate removal if ( a === b ) { hasDuplicate = true; return 0; } // Sort on method existence if only one input has compareDocumentPosition var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; if ( compare ) { return compare; } // Calculate position if both inputs belong to the same document compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? a.compareDocumentPosition( b ) : // Otherwise we know they are disconnected 1; // Disconnected nodes if ( compare & 1 || (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { // Choose the first element that is related to our preferred document if ( a === document || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { return -1; } if ( b === document || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { return 1; } // Maintain original order return sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; } return compare & 4 ? -1 : 1; } : function( a, b ) { // Exit early if the nodes are identical if ( a === b ) { hasDuplicate = true; return 0; } var cur, i = 0, aup = a.parentNode, bup = b.parentNode, ap = [ a ], bp = [ b ]; // Parentless nodes are either documents or disconnected if ( !aup || !bup ) { return a === document ? -1 : b === document ? 1 : aup ? -1 : bup ? 1 : sortInput ? ( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) : 0; // If the nodes are siblings, we can do a quick check } else if ( aup === bup ) { return siblingCheck( a, b ); } // Otherwise we need full lists of their ancestors for comparison cur = a; while ( (cur = cur.parentNode) ) { ap.unshift( cur ); } cur = b; while ( (cur = cur.parentNode) ) { bp.unshift( cur ); } // Walk down the tree looking for a discrepancy while ( ap[i] === bp[i] ) { i++; } return i ? // Do a sibling check if the nodes have a common ancestor siblingCheck( ap[i], bp[i] ) : // Otherwise nodes in our document sort first ap[i] === preferredDoc ? -1 : bp[i] === preferredDoc ? 1 : 0; }; return document; }; Sizzle.matches = function( expr, elements ) { return Sizzle( expr, null, null, elements ); }; Sizzle.matchesSelector = function( elem, expr ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } // Make sure that attribute selectors are quoted expr = expr.replace( rattributeQuotes, "='$1']" ); if ( support.matchesSelector && documentIsHTML && !compilerCache[ expr + " " ] && ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { try { var ret = matches.call( elem, expr ); // IE 9's matchesSelector returns false on disconnected nodes if ( ret || support.disconnectedMatch || // As well, disconnected nodes are said to be in a document // fragment in IE 9 elem.document && elem.document.nodeType !== 11 ) { return ret; } } catch (e) {} } return Sizzle( expr, document, null, [ elem ] ).length > 0; }; Sizzle.contains = function( context, elem ) { // Set document vars if needed if ( ( context.ownerDocument || context ) !== document ) { setDocument( context ); } return contains( context, elem ); }; Sizzle.attr = function( elem, name ) { // Set document vars if needed if ( ( elem.ownerDocument || elem ) !== document ) { setDocument( elem ); } var fn = Expr.attrHandle[ name.toLowerCase() ], // Don't get fooled by Object.prototype properties (jQuery #13807) val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? fn( elem, name, !documentIsHTML ) : undefined; return val !== undefined ? val : support.attributes || !documentIsHTML ? elem.getAttribute( name ) : (val = elem.getAttributeNode(name)) && val.specified ? val.value : null; }; Sizzle.escape = function( sel ) { return (sel + "").replace( rcssescape, fcssescape ); }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Document sorting and removing duplicates * @param {ArrayLike} results */ Sizzle.uniqueSort = function( results ) { var elem, duplicates = [], j = 0, i = 0; // Unless we *know* we can detect duplicates, assume their presence hasDuplicate = !support.detectDuplicates; sortInput = !support.sortStable && results.slice( 0 ); results.sort( sortOrder ); if ( hasDuplicate ) { while ( (elem = results[i++]) ) { if ( elem === results[ i ] ) { j = duplicates.push( i ); } } while ( j-- ) { results.splice( duplicates[ j ], 1 ); } } // Clear input after sorting to release objects // See https://github.com/jquery/sizzle/pull/225 sortInput = null; return results; }; /** * Utility function for retrieving the text value of an array of DOM nodes * @param {Array|Element} elem */ getText = Sizzle.getText = function( elem ) { var node, ret = "", i = 0, nodeType = elem.nodeType; if ( !nodeType ) { // If no nodeType, this is expected to be an array while ( (node = elem[i++]) ) { // Do not traverse comment nodes ret += getText( node ); } } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent for elements // innerText usage removed for consistency of new lines (jQuery #11153) if ( typeof elem.textContent === "string" ) { return elem.textContent; } else { // Traverse its children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } // Do not include comment or processing instruction nodes return ret; }; Expr = Sizzle.selectors = { // Can be adjusted by the user cacheLength: 50, createPseudo: markFunction, match: matchExpr, attrHandle: {}, find: {}, relative: { ">": { dir: "parentNode", first: true }, " ": { dir: "parentNode" }, "+": { dir: "previousSibling", first: true }, "~": { dir: "previousSibling" } }, preFilter: { "ATTR": function( match ) { match[1] = match[1].replace( runescape, funescape ); // Move the given value to match[3] whether quoted or unquoted match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); if ( match[2] === "~=" ) { match[3] = " " + match[3] + " "; } return match.slice( 0, 4 ); }, "CHILD": function( match ) { /* matches from matchExpr["CHILD"] 1 type (only|nth|...) 2 what (child|of-type) 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) 4 xn-component of xn+y argument ([+-]?\d*n|) 5 sign of xn-component 6 x of xn-component 7 sign of y-component 8 y of y-component */ match[1] = match[1].toLowerCase(); if ( match[1].slice( 0, 3 ) === "nth" ) { // nth-* requires argument if ( !match[3] ) { Sizzle.error( match[0] ); } // numeric x and y parameters for Expr.filter.CHILD // remember that false/true cast respectively to 0/1 match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); // other types prohibit arguments } else if ( match[3] ) { Sizzle.error( match[0] ); } return match; }, "PSEUDO": function( match ) { var excess, unquoted = !match[6] && match[2]; if ( matchExpr["CHILD"].test( match[0] ) ) { return null; } // Accept quoted arguments as-is if ( match[3] ) { match[2] = match[4] || match[5] || ""; // Strip excess characters from unquoted arguments } else if ( unquoted && rpseudo.test( unquoted ) && // Get excess from tokenize (recursively) (excess = tokenize( unquoted, true )) && // advance to the next closing parenthesis (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { // excess is a negative index match[0] = match[0].slice( 0, excess ); match[2] = unquoted.slice( 0, excess ); } // Return only captures needed by the pseudo filter method (type and argument) return match.slice( 0, 3 ); } }, filter: { "TAG": function( nodeNameSelector ) { var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); return nodeNameSelector === "*" ? function() { return true; } : function( elem ) { return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; }; }, "CLASS": function( className ) { var pattern = classCache[ className + " " ]; return pattern || (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && classCache( className, function( elem ) { return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" ); }); }, "ATTR": function( name, operator, check ) { return function( elem ) { var result = Sizzle.attr( elem, name ); if ( result == null ) { return operator === "!="; } if ( !operator ) { return true; } result += ""; return operator === "=" ? result === check : operator === "!=" ? result !== check : operator === "^=" ? check && result.indexOf( check ) === 0 : operator === "*=" ? check && result.indexOf( check ) > -1 : operator === "$=" ? check && result.slice( -check.length ) === check : operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 : operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : false; }; }, "CHILD": function( type, what, argument, first, last ) { var simple = type.slice( 0, 3 ) !== "nth", forward = type.slice( -4 ) !== "last", ofType = what === "of-type"; return first === 1 && last === 0 ? // Shortcut for :nth-*(n) function( elem ) { return !!elem.parentNode; } : function( elem, context, xml ) { var cache, uniqueCache, outerCache, node, nodeIndex, start, dir = simple !== forward ? "nextSibling" : "previousSibling", parent = elem.parentNode, name = ofType && elem.nodeName.toLowerCase(), useCache = !xml && !ofType, diff = false; if ( parent ) { // :(first|last|only)-(child|of-type) if ( simple ) { while ( dir ) { node = elem; while ( (node = node[ dir ]) ) { if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { return false; } } // Reverse direction for :only-* (if we haven't yet done so) start = dir = type === "only" && !start && "nextSibling"; } return true; } start = [ forward ? parent.firstChild : parent.lastChild ]; // non-xml :nth-child(...) stores cache data on `parent` if ( forward && useCache ) { // Seek `elem` from a previously-cached index // ...in a gzip-friendly way node = parent; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex && cache[ 2 ]; node = nodeIndex && parent.childNodes[ nodeIndex ]; while ( (node = ++nodeIndex && node && node[ dir ] || // Fallback to seeking `elem` from the start (diff = nodeIndex = 0) || start.pop()) ) { // When found, cache indexes on `parent` and break if ( node.nodeType === 1 && ++diff && node === elem ) { uniqueCache[ type ] = [ dirruns, nodeIndex, diff ]; break; } } } else { // Use previously-cached element index if available if ( useCache ) { // ...in a gzip-friendly way node = elem; outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); cache = uniqueCache[ type ] || []; nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; diff = nodeIndex; } // xml :nth-child(...) // or :nth-last-child(...) or :nth(-last)?-of-type(...) if ( diff === false ) { // Use the same loop as above to seek `elem` from the start while ( (node = ++nodeIndex && node && node[ dir ] || (diff = nodeIndex = 0) || start.pop()) ) { if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { // Cache the index of each encountered element if ( useCache ) { outerCache = node[ expando ] || (node[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ node.uniqueID ] || (outerCache[ node.uniqueID ] = {}); uniqueCache[ type ] = [ dirruns, diff ]; } if ( node === elem ) { break; } } } } } // Incorporate the offset, then check against cycle size diff -= last; return diff === first || ( diff % first === 0 && diff / first >= 0 ); } }; }, "PSEUDO": function( pseudo, argument ) { // pseudo-class names are case-insensitive // http://www.w3.org/TR/selectors/#pseudo-classes // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters // Remember that setFilters inherits from pseudos var args, fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || Sizzle.error( "unsupported pseudo: " + pseudo ); // The user may use createPseudo to indicate that // arguments are needed to create the filter function // just as Sizzle does if ( fn[ expando ] ) { return fn( argument ); } // But maintain support for old signatures if ( fn.length > 1 ) { args = [ pseudo, pseudo, "", argument ]; return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? markFunction(function( seed, matches ) { var idx, matched = fn( seed, argument ), i = matched.length; while ( i-- ) { idx = indexOf( seed, matched[i] ); seed[ idx ] = !( matches[ idx ] = matched[i] ); } }) : function( elem ) { return fn( elem, 0, args ); }; } return fn; } }, pseudos: { // Potentially complex pseudos "not": markFunction(function( selector ) { // Trim the selector passed to compile // to avoid treating leading and trailing // spaces as combinators var input = [], results = [], matcher = compile( selector.replace( rtrim, "$1" ) ); return matcher[ expando ] ? markFunction(function( seed, matches, context, xml ) { var elem, unmatched = matcher( seed, null, xml, [] ), i = seed.length; // Match elements unmatched by `matcher` while ( i-- ) { if ( (elem = unmatched[i]) ) { seed[i] = !(matches[i] = elem); } } }) : function( elem, context, xml ) { input[0] = elem; matcher( input, null, xml, results ); // Don't keep the element (issue #299) input[0] = null; return !results.pop(); }; }), "has": markFunction(function( selector ) { return function( elem ) { return Sizzle( selector, elem ).length > 0; }; }), "contains": markFunction(function( text ) { text = text.replace( runescape, funescape ); return function( elem ) { return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; }; }), // "Whether an element is represented by a :lang() selector // is based solely on the element's language value // being equal to the identifier C, // or beginning with the identifier C immediately followed by "-". // The matching of C against the element's language value is performed case-insensitively. // The identifier C does not have to be a valid language name." // http://www.w3.org/TR/selectors/#lang-pseudo "lang": markFunction( function( lang ) { // lang value must be a valid identifier if ( !ridentifier.test(lang || "") ) { Sizzle.error( "unsupported lang: " + lang ); } lang = lang.replace( runescape, funescape ).toLowerCase(); return function( elem ) { var elemLang; do { if ( (elemLang = documentIsHTML ? elem.lang : elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { elemLang = elemLang.toLowerCase(); return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; } } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); return false; }; }), // Miscellaneous "target": function( elem ) { var hash = window.location && window.location.hash; return hash && hash.slice( 1 ) === elem.id; }, "root": function( elem ) { return elem === docElem; }, "focus": function( elem ) { return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); }, // Boolean properties "enabled": createDisabledPseudo( false ), "disabled": createDisabledPseudo( true ), "checked": function( elem ) { // In CSS3, :checked should return both checked and selected elements // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked var nodeName = elem.nodeName.toLowerCase(); return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); }, "selected": function( elem ) { // Accessing this property makes selected-by-default // options in Safari work properly if ( elem.parentNode ) { elem.parentNode.selectedIndex; } return elem.selected === true; }, // Contents "empty": function( elem ) { // http://www.w3.org/TR/selectors/#empty-pseudo // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), // but not by others (comment: 8; processing instruction: 7; etc.) // nodeType < 6 works because attributes (2) do not appear as children for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { if ( elem.nodeType < 6 ) { return false; } } return true; }, "parent": function( elem ) { return !Expr.pseudos["empty"]( elem ); }, // Element/input types "header": function( elem ) { return rheader.test( elem.nodeName ); }, "input": function( elem ) { return rinputs.test( elem.nodeName ); }, "button": function( elem ) { var name = elem.nodeName.toLowerCase(); return name === "input" && elem.type === "button" || name === "button"; }, "text": function( elem ) { var attr; return elem.nodeName.toLowerCase() === "input" && elem.type === "text" && // Support: IE<8 // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); }, // Position-in-collection "first": createPositionalPseudo(function() { return [ 0 ]; }), "last": createPositionalPseudo(function( matchIndexes, length ) { return [ length - 1 ]; }), "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { return [ argument < 0 ? argument + length : argument ]; }), "even": createPositionalPseudo(function( matchIndexes, length ) { var i = 0; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "odd": createPositionalPseudo(function( matchIndexes, length ) { var i = 1; for ( ; i < length; i += 2 ) { matchIndexes.push( i ); } return matchIndexes; }), "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; --i >= 0; ) { matchIndexes.push( i ); } return matchIndexes; }), "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { var i = argument < 0 ? argument + length : argument; for ( ; ++i < length; ) { matchIndexes.push( i ); } return matchIndexes; }) } }; Expr.pseudos["nth"] = Expr.pseudos["eq"]; // Add button/input type pseudos for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { Expr.pseudos[ i ] = createInputPseudo( i ); } for ( i in { submit: true, reset: true } ) { Expr.pseudos[ i ] = createButtonPseudo( i ); } // Easy API for creating new setFilters function setFilters() {} setFilters.prototype = Expr.filters = Expr.pseudos; Expr.setFilters = new setFilters(); tokenize = Sizzle.tokenize = function( selector, parseOnly ) { var matched, match, tokens, type, soFar, groups, preFilters, cached = tokenCache[ selector + " " ]; if ( cached ) { return parseOnly ? 0 : cached.slice( 0 ); } soFar = selector; groups = []; preFilters = Expr.preFilter; while ( soFar ) { // Comma and first run if ( !matched || (match = rcomma.exec( soFar )) ) { if ( match ) { // Don't consume trailing commas as valid soFar = soFar.slice( match[0].length ) || soFar; } groups.push( (tokens = []) ); } matched = false; // Combinators if ( (match = rcombinators.exec( soFar )) ) { matched = match.shift(); tokens.push({ value: matched, // Cast descendant combinators to space type: match[0].replace( rtrim, " " ) }); soFar = soFar.slice( matched.length ); } // Filters for ( type in Expr.filter ) { if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || (match = preFilters[ type ]( match ))) ) { matched = match.shift(); tokens.push({ value: matched, type: type, matches: match }); soFar = soFar.slice( matched.length ); } } if ( !matched ) { break; } } // Return the length of the invalid excess // if we're just parsing // Otherwise, throw an error or return tokens return parseOnly ? soFar.length : soFar ? Sizzle.error( selector ) : // Cache the tokens tokenCache( selector, groups ).slice( 0 ); }; function toSelector( tokens ) { var i = 0, len = tokens.length, selector = ""; for ( ; i < len; i++ ) { selector += tokens[i].value; } return selector; } function addCombinator( matcher, combinator, base ) { var dir = combinator.dir, skip = combinator.next, key = skip || dir, checkNonElements = base && key === "parentNode", doneName = done++; return combinator.first ? // Check against closest ancestor/preceding element function( elem, context, xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { return matcher( elem, context, xml ); } } return false; } : // Check against all ancestor/preceding elements function( elem, context, xml ) { var oldCache, uniqueCache, outerCache, newCache = [ dirruns, doneName ]; // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching if ( xml ) { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { if ( matcher( elem, context, xml ) ) { return true; } } } } else { while ( (elem = elem[ dir ]) ) { if ( elem.nodeType === 1 || checkNonElements ) { outerCache = elem[ expando ] || (elem[ expando ] = {}); // Support: IE <9 only // Defend against cloned attroperties (jQuery gh-1709) uniqueCache = outerCache[ elem.uniqueID ] || (outerCache[ elem.uniqueID ] = {}); if ( skip && skip === elem.nodeName.toLowerCase() ) { elem = elem[ dir ] || elem; } else if ( (oldCache = uniqueCache[ key ]) && oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { // Assign to newCache so results back-propagate to previous elements return (newCache[ 2 ] = oldCache[ 2 ]); } else { // Reuse newcache so results back-propagate to previous elements uniqueCache[ key ] = newCache; // A match means we're done; a fail means we have to keep checking if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { return true; } } } } } return false; }; } function elementMatcher( matchers ) { return matchers.length > 1 ? function( elem, context, xml ) { var i = matchers.length; while ( i-- ) { if ( !matchers[i]( elem, context, xml ) ) { return false; } } return true; } : matchers[0]; } function multipleContexts( selector, contexts, results ) { var i = 0, len = contexts.length; for ( ; i < len; i++ ) { Sizzle( selector, contexts[i], results ); } return results; } function condense( unmatched, map, filter, context, xml ) { var elem, newUnmatched = [], i = 0, len = unmatched.length, mapped = map != null; for ( ; i < len; i++ ) { if ( (elem = unmatched[i]) ) { if ( !filter || filter( elem, context, xml ) ) { newUnmatched.push( elem ); if ( mapped ) { map.push( i ); } } } } return newUnmatched; } function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { if ( postFilter && !postFilter[ expando ] ) { postFilter = setMatcher( postFilter ); } if ( postFinder && !postFinder[ expando ] ) { postFinder = setMatcher( postFinder, postSelector ); } return markFunction(function( seed, results, context, xml ) { var temp, i, elem, preMap = [], postMap = [], preexisting = results.length, // Get initial elements from seed or context elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), // Prefilter to get matcher input, preserving a map for seed-results synchronization matcherIn = preFilter && ( seed || !selector ) ? condense( elems, preMap, preFilter, context, xml ) : elems, matcherOut = matcher ? // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, postFinder || ( seed ? preFilter : preexisting || postFilter ) ? // ...intermediate processing is necessary [] : // ...otherwise use results directly results : matcherIn; // Find primary matches if ( matcher ) { matcher( matcherIn, matcherOut, context, xml ); } // Apply postFilter if ( postFilter ) { temp = condense( matcherOut, postMap ); postFilter( temp, [], context, xml ); // Un-match failing elements by moving them back to matcherIn i = temp.length; while ( i-- ) { if ( (elem = temp[i]) ) { matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); } } } if ( seed ) { if ( postFinder || preFilter ) { if ( postFinder ) { // Get the final matcherOut by condensing this intermediate into postFinder contexts temp = []; i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) ) { // Restore matcherIn since elem is not yet a final match temp.push( (matcherIn[i] = elem) ); } } postFinder( null, (matcherOut = []), temp, xml ); } // Move matched elements from seed to results to keep them synchronized i = matcherOut.length; while ( i-- ) { if ( (elem = matcherOut[i]) && (temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) { seed[temp] = !(results[temp] = elem); } } } // Add elements to results, through postFinder if defined } else { matcherOut = condense( matcherOut === results ? matcherOut.splice( preexisting, matcherOut.length ) : matcherOut ); if ( postFinder ) { postFinder( null, results, matcherOut, xml ); } else { push.apply( results, matcherOut ); } } }); } function matcherFromTokens( tokens ) { var checkContext, matcher, j, len = tokens.length, leadingRelative = Expr.relative[ tokens[0].type ], implicitRelative = leadingRelative || Expr.relative[" "], i = leadingRelative ? 1 : 0, // The foundational matcher ensures that elements are reachable from top-level context(s) matchContext = addCombinator( function( elem ) { return elem === checkContext; }, implicitRelative, true ), matchAnyContext = addCombinator( function( elem ) { return indexOf( checkContext, elem ) > -1; }, implicitRelative, true ), matchers = [ function( elem, context, xml ) { var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( (checkContext = context).nodeType ? matchContext( elem, context, xml ) : matchAnyContext( elem, context, xml ) ); // Avoid hanging onto element (issue #299) checkContext = null; return ret; } ]; for ( ; i < len; i++ ) { if ( (matcher = Expr.relative[ tokens[i].type ]) ) { matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; } else { matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); // Return special upon seeing a positional matcher if ( matcher[ expando ] ) { // Find the next relative operator (if any) for proper handling j = ++i; for ( ; j < len; j++ ) { if ( Expr.relative[ tokens[j].type ] ) { break; } } return setMatcher( i > 1 && elementMatcher( matchers ), i > 1 && toSelector( // If the preceding token was a descendant combinator, insert an implicit any-element `*` tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) ).replace( rtrim, "$1" ), matcher, i < j && matcherFromTokens( tokens.slice( i, j ) ), j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), j < len && toSelector( tokens ) ); } matchers.push( matcher ); } } return elementMatcher( matchers ); } function matcherFromGroupMatchers( elementMatchers, setMatchers ) { var bySet = setMatchers.length > 0, byElement = elementMatchers.length > 0, superMatcher = function( seed, context, xml, results, outermost ) { var elem, j, matcher, matchedCount = 0, i = "0", unmatched = seed && [], setMatched = [], contextBackup = outermostContext, // We must always have either seed elements or outermost context elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), // Use integer dirruns iff this is the outermost matcher dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), len = elems.length; if ( outermost ) { outermostContext = context === document || context || outermost; } // Add elements passing elementMatchers directly to results // Support: IE<9, Safari // Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id for ( ; i !== len && (elem = elems[i]) != null; i++ ) { if ( byElement && elem ) { j = 0; if ( !context && elem.ownerDocument !== document ) { setDocument( elem ); xml = !documentIsHTML; } while ( (matcher = elementMatchers[j++]) ) { if ( matcher( elem, context || document, xml) ) { results.push( elem ); break; } } if ( outermost ) { dirruns = dirrunsUnique; } } // Track unmatched elements for set filters if ( bySet ) { // They will have gone through all possible matchers if ( (elem = !matcher && elem) ) { matchedCount--; } // Lengthen the array for every element, matched or not if ( seed ) { unmatched.push( elem ); } } } // `i` is now the count of elements visited above, and adding it to `matchedCount` // makes the latter nonnegative. matchedCount += i; // Apply set filters to unmatched elements // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` // equals `i`), unless we didn't visit _any_ elements in the above loop because we have // no element matchers and no seed. // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that // case, which will result in a "00" `matchedCount` that differs from `i` but is also // numerically zero. if ( bySet && i !== matchedCount ) { j = 0; while ( (matcher = setMatchers[j++]) ) { matcher( unmatched, setMatched, context, xml ); } if ( seed ) { // Reintegrate element matches to eliminate the need for sorting if ( matchedCount > 0 ) { while ( i-- ) { if ( !(unmatched[i] || setMatched[i]) ) { setMatched[i] = pop.call( results ); } } } // Discard index placeholder values to get only actual matches setMatched = condense( setMatched ); } // Add matches to results push.apply( results, setMatched ); // Seedless set matches succeeding multiple successful matchers stipulate sorting if ( outermost && !seed && setMatched.length > 0 && ( matchedCount + setMatchers.length ) > 1 ) { Sizzle.uniqueSort( results ); } } // Override manipulation of globals by nested matchers if ( outermost ) { dirruns = dirrunsUnique; outermostContext = contextBackup; } return unmatched; }; return bySet ? markFunction( superMatcher ) : superMatcher; } compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { var i, setMatchers = [], elementMatchers = [], cached = compilerCache[ selector + " " ]; if ( !cached ) { // Generate a function of recursive functions that can be used to check each element if ( !match ) { match = tokenize( selector ); } i = match.length; while ( i-- ) { cached = matcherFromTokens( match[i] ); if ( cached[ expando ] ) { setMatchers.push( cached ); } else { elementMatchers.push( cached ); } } // Cache the compiled function cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); // Save selector and tokenization cached.selector = selector; } return cached; }; /** * A low-level selection function that works with Sizzle's compiled * selector functions * @param {String|Function} selector A selector or a pre-compiled * selector function built with Sizzle.compile * @param {Element} context * @param {Array} [results] * @param {Array} [seed] A set of elements to match against */ select = Sizzle.select = function( selector, context, results, seed ) { var i, tokens, token, type, find, compiled = typeof selector === "function" && selector, match = !seed && tokenize( (selector = compiled.selector || selector) ); results = results || []; // Try to minimize operations if there is only one selector in the list and no seed // (the latter of which guarantees us context) if ( match.length === 1 ) { // Reduce context if the leading compound selector is an ID tokens = match[0] = match[0].slice( 0 ); if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[1].type ] ) { context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; if ( !context ) { return results; // Precompiled matchers will still verify ancestry, so step up a level } else if ( compiled ) { context = context.parentNode; } selector = selector.slice( tokens.shift().value.length ); } // Fetch a seed set for right-to-left matching i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; while ( i-- ) { token = tokens[i]; // Abort if we hit a combinator if ( Expr.relative[ (type = token.type) ] ) { break; } if ( (find = Expr.find[ type ]) ) { // Search, expanding context for leading sibling combinators if ( (seed = find( token.matches[0].replace( runescape, funescape ), rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context )) ) { // If seed is empty or no tokens remain, we can return early tokens.splice( i, 1 ); selector = seed.length && toSelector( tokens ); if ( !selector ) { push.apply( results, seed ); return results; } break; } } } } // Compile and execute a filtering function if one is not provided // Provide `match` to avoid retokenization if we modified the selector above ( compiled || compile( selector, match ) )( seed, context, !documentIsHTML, results, !context || rsibling.test( selector ) && testContext( context.parentNode ) || context ); return results; }; // One-time assignments // Sort stability support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; // Support: Chrome 14-35+ // Always assume duplicates if they aren't passed to the comparison function support.detectDuplicates = !!hasDuplicate; // Initialize against the default document setDocument(); // Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) // Detached nodes confoundingly follow *each other* support.sortDetached = assert(function( el ) { // Should return 1, but returns 4 (following) return el.compareDocumentPosition( document.createElement("fieldset") ) & 1; }); // Support: IE<8 // Prevent attribute/property "interpolation" // https://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx if ( !assert(function( el ) { el.innerHTML = "<a href='#'></a>"; return el.firstChild.getAttribute("href") === "#" ; }) ) { addHandle( "type|href|height|width", function( elem, name, isXML ) { if ( !isXML ) { return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); } }); } // Support: IE<9 // Use defaultValue in place of getAttribute("value") if ( !support.attributes || !assert(function( el ) { el.innerHTML = "<input/>"; el.firstChild.setAttribute( "value", "" ); return el.firstChild.getAttribute( "value" ) === ""; }) ) { addHandle( "value", function( elem, name, isXML ) { if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { return elem.defaultValue; } }); } // Support: IE<9 // Use getAttributeNode to fetch booleans when getAttribute lies if ( !assert(function( el ) { return el.getAttribute("disabled") == null; }) ) { addHandle( booleans, function( elem, name, isXML ) { var val; if ( !isXML ) { return elem[ name ] === true ? name.toLowerCase() : (val = elem.getAttributeNode( name )) && val.specified ? val.value : null; } }); } return Sizzle; })( window ); jQuery.find = Sizzle; jQuery.expr = Sizzle.selectors; // Deprecated jQuery.expr[ ":" ] = jQuery.expr.pseudos; jQuery.uniqueSort = jQuery.unique = Sizzle.uniqueSort; jQuery.text = Sizzle.getText; jQuery.isXMLDoc = Sizzle.isXML; jQuery.contains = Sizzle.contains; jQuery.escapeSelector = Sizzle.escape; var dir = function( elem, dir, until ) { var matched = [], truncate = until !== undefined; while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { if ( elem.nodeType === 1 ) { if ( truncate && jQuery( elem ).is( until ) ) { break; } matched.push( elem ); } } return matched; }; var siblings = function( n, elem ) { var matched = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { matched.push( n ); } } return matched; }; var rneedsContext = jQuery.expr.match.needsContext; function nodeName( elem, name ) { return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); }; var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); // Implement the identical functionality for filter and not function winnow( elements, qualifier, not ) { if ( isFunction( qualifier ) ) { return jQuery.grep( elements, function( elem, i ) { return !!qualifier.call( elem, i, elem ) !== not; } ); } // Single element if ( qualifier.nodeType ) { return jQuery.grep( elements, function( elem ) { return ( elem === qualifier ) !== not; } ); } // Arraylike of elements (jQuery, arguments, Array) if ( typeof qualifier !== "string" ) { return jQuery.grep( elements, function( elem ) { return ( indexOf.call( qualifier, elem ) > -1 ) !== not; } ); } // Filtered directly for both simple and complex selectors return jQuery.filter( qualifier, elements, not ); } jQuery.filter = function( expr, elems, not ) { var elem = elems[ 0 ]; if ( not ) { expr = ":not(" + expr + ")"; } if ( elems.length === 1 && elem.nodeType === 1 ) { return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; } return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { return elem.nodeType === 1; } ) ); }; jQuery.fn.extend( { find: function( selector ) { var i, ret, len = this.length, self = this; if ( typeof selector !== "string" ) { return this.pushStack( jQuery( selector ).filter( function() { for ( i = 0; i < len; i++ ) { if ( jQuery.contains( self[ i ], this ) ) { return true; } } } ) ); } ret = this.pushStack( [] ); for ( i = 0; i < len; i++ ) { jQuery.find( selector, self[ i ], ret ); } return len > 1 ? jQuery.uniqueSort( ret ) : ret; }, filter: function( selector ) { return this.pushStack( winnow( this, selector || [], false ) ); }, not: function( selector ) { return this.pushStack( winnow( this, selector || [], true ) ); }, is: function( selector ) { return !!winnow( this, // If this is a positional/relative selector, check membership in the returned set // so $("p:first").is("p:last") won't return true for a doc with two "p". typeof selector === "string" && rneedsContext.test( selector ) ? jQuery( selector ) : selector || [], false ).length; } } ); // Initialize a jQuery object // A central reference to the root jQuery(document) var rootjQuery, // A simple way to check for HTML strings // Prioritize #id over <tag> to avoid XSS via location.hash (#9521) // Strict HTML recognition (#11290: must start with <) // Shortcut simple #id case for speed rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, init = jQuery.fn.init = function( selector, context, root ) { var match, elem; // HANDLE: $(""), $(null), $(undefined), $(false) if ( !selector ) { return this; } // Method init() accepts an alternate rootjQuery // so migrate can support jQuery.sub (gh-2101) root = root || rootjQuery; // Handle HTML strings if ( typeof selector === "string" ) { if ( selector[ 0 ] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = rquickExpr.exec( selector ); } // Match html or make sure no context is specified for #id if ( match && ( match[ 1 ] || !context ) ) { // HANDLE: $(html) -> $(array) if ( match[ 1 ] ) { context = context instanceof jQuery ? context[ 0 ] : context; // Option to run scripts is true for back-compat // Intentionally let the error be thrown if parseHTML is not present jQuery.merge( this, jQuery.parseHTML( match[ 1 ], context && context.nodeType ? context.ownerDocument || context : document, true ) ); // HANDLE: $(html, props) if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { for ( match in context ) { // Properties of context are called as methods if possible if ( isFunction( this[ match ] ) ) { this[ match ]( context[ match ] ); // ...and otherwise set as attributes } else { this.attr( match, context[ match ] ); } } } return this; // HANDLE: $(#id) } else { elem = document.getElementById( match[ 2 ] ); if ( elem ) { // Inject the element directly into the jQuery object this[ 0 ] = elem; this.length = 1; } return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || root ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(DOMElement) } else if ( selector.nodeType ) { this[ 0 ] = selector; this.length = 1; return this; // HANDLE: $(function) // Shortcut for document ready } else if ( isFunction( selector ) ) { return root.ready !== undefined ? root.ready( selector ) : // Execute immediately if ready is not present selector( jQuery ); } return jQuery.makeArray( selector, this ); }; // Give the init function the jQuery prototype for later instantiation init.prototype = jQuery.fn; // Initialize central reference rootjQuery = jQuery( document ); var rparentsprev = /^(?:parents|prev(?:Until|All))/, // Methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend( { has: function( target ) { var targets = jQuery( target, this ), l = targets.length; return this.filter( function() { var i = 0; for ( ; i < l; i++ ) { if ( jQuery.contains( this, targets[ i ] ) ) { return true; } } } ); }, closest: function( selectors, context ) { var cur, i = 0, l = this.length, matched = [], targets = typeof selectors !== "string" && jQuery( selectors ); // Positional selectors never match, since there's no _selection_ context if ( !rneedsContext.test( selectors ) ) { for ( ; i < l; i++ ) { for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { // Always skip document fragments if ( cur.nodeType < 11 && ( targets ? targets.index( cur ) > -1 : // Don't pass non-elements to Sizzle cur.nodeType === 1 && jQuery.find.matchesSelector( cur, selectors ) ) ) { matched.push( cur ); break; } } } } return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); }, // Determine the position of an element within the set index: function( elem ) { // No argument, return index in parent if ( !elem ) { return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; } // Index in selector if ( typeof elem === "string" ) { return indexOf.call( jQuery( elem ), this[ 0 ] ); } // Locate the position of the desired element return indexOf.call( this, // If it receives a jQuery object, the first element is used elem.jquery ? elem[ 0 ] : elem ); }, add: function( selector, context ) { return this.pushStack( jQuery.uniqueSort( jQuery.merge( this.get(), jQuery( selector, context ) ) ) ); }, addBack: function( selector ) { return this.add( selector == null ? this.prevObject : this.prevObject.filter( selector ) ); } } ); function sibling( cur, dir ) { while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} return cur; } jQuery.each( { parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { return dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { return dir( elem, "parentNode", until ); }, next: function( elem ) { return sibling( elem, "nextSibling" ); }, prev: function( elem ) { return sibling( elem, "previousSibling" ); }, nextAll: function( elem ) { return dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return siblings( ( elem.parentNode || {} ).firstChild, elem ); }, children: function( elem ) { return siblings( elem.firstChild ); }, contents: function( elem ) { if ( nodeName( elem, "iframe" ) ) { return elem.contentDocument; } // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only // Treat the template element as a regular one in browsers that // don't support it. if ( nodeName( elem, "template" ) ) { elem = elem.content || elem; } return jQuery.merge( [], elem.childNodes ); } }, function( name, fn ) { jQuery.fn[ name ] = function( until, selector ) { var matched = jQuery.map( this, fn, until ); if ( name.slice( -5 ) !== "Until" ) { selector = until; } if ( selector && typeof selector === "string" ) { matched = jQuery.filter( selector, matched ); } if ( this.length > 1 ) { // Remove duplicates if ( !guaranteedUnique[ name ] ) { jQuery.uniqueSort( matched ); } // Reverse order for parents* and prev-derivatives if ( rparentsprev.test( name ) ) { matched.reverse(); } } return this.pushStack( matched ); }; } ); var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); // Convert String-formatted options into Object-formatted ones function createOptions( options ) { var object = {}; jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { object[ flag ] = true; } ); return object; } /* * Create a callback list using the following parameters: * * options: an optional list of space-separated options that will change how * the callback list behaves or a more traditional option object * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible options: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( options ) { // Convert options from String-formatted to Object-formatted if needed // (we check in cache first) options = typeof options === "string" ? createOptions( options ) : jQuery.extend( {}, options ); var // Flag to know if list is currently firing firing, // Last fire value for non-forgettable lists memory, // Flag to know if list was already fired fired, // Flag to prevent firing locked, // Actual callback list list = [], // Queue of execution data for repeatable lists queue = [], // Index of currently firing callback (modified by add/remove as needed) firingIndex = -1, // Fire callbacks fire = function() { // Enforce single-firing locked = locked || options.once; // Execute callbacks for all pending executions, // respecting firingIndex overrides and runtime changes fired = firing = true; for ( ; queue.length; firingIndex = -1 ) { memory = queue.shift(); while ( ++firingIndex < list.length ) { // Run callback and check for early termination if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && options.stopOnFalse ) { // Jump to end and forget the data so .add doesn't re-fire firingIndex = list.length; memory = false; } } } // Forget the data if we're done with it if ( !options.memory ) { memory = false; } firing = false; // Clean up if we're done firing for good if ( locked ) { // Keep an empty list if we have data for future add calls if ( memory ) { list = []; // Otherwise, this object is spent } else { list = ""; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { // If we have memory from a past run, we should fire after adding if ( memory && !firing ) { firingIndex = list.length - 1; queue.push( memory ); } ( function add( args ) { jQuery.each( args, function( _, arg ) { if ( isFunction( arg ) ) { if ( !options.unique || !self.has( arg ) ) { list.push( arg ); } } else if ( arg && arg.length && toType( arg ) !== "string" ) { // Inspect recursively add( arg ); } } ); } )( arguments ); if ( memory && !firing ) { fire(); } } return this; }, // Remove a callback from the list remove: function() { jQuery.each( arguments, function( _, arg ) { var index; while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { list.splice( index, 1 ); // Handle firing indexes if ( index <= firingIndex ) { firingIndex--; } } } ); return this; }, // Check if a given callback is in the list. // If no argument is given, return whether or not list has callbacks attached. has: function( fn ) { return fn ? jQuery.inArray( fn, list ) > -1 : list.length > 0; }, // Remove all callbacks from the list empty: function() { if ( list ) { list = []; } return this; }, // Disable .fire and .add // Abort any current/pending executions // Clear all callbacks and values disable: function() { locked = queue = []; list = memory = ""; return this; }, disabled: function() { return !list; }, // Disable .fire // Also disable .add unless we have memory (since it would have no effect) // Abort any pending executions lock: function() { locked = queue = []; if ( !memory && !firing ) { list = memory = ""; } return this; }, locked: function() { return !!locked; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( !locked ) { args = args || []; args = [ context, args.slice ? args.slice() : args ]; queue.push( args ); if ( !firing ) { fire(); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; function Identity( v ) { return v; } function Thrower( ex ) { throw ex; } function adoptValue( value, resolve, reject, noValue ) { var method; try { // Check for promise aspect first to privilege synchronous behavior if ( value && isFunction( ( method = value.promise ) ) ) { method.call( value ).done( resolve ).fail( reject ); // Other thenables } else if ( value && isFunction( ( method = value.then ) ) ) { method.call( value, resolve, reject ); // Other non-thenables } else { // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: // * false: [ value ].slice( 0 ) => resolve( value ) // * true: [ value ].slice( 1 ) => resolve() resolve.apply( undefined, [ value ].slice( noValue ) ); } // For Promises/A+, convert exceptions into rejections // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in // Deferred#then to conditionally suppress rejection. } catch ( value ) { // Support: Android 4.0 only // Strict mode functions invoked without .call/.apply get global-object context reject.apply( undefined, [ value ] ); } } jQuery.extend( { Deferred: function( func ) { var tuples = [ // action, add listener, callbacks, // ... .then handlers, argument index, [final state] [ "notify", "progress", jQuery.Callbacks( "memory" ), jQuery.Callbacks( "memory" ), 2 ], [ "resolve", "done", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 0, "resolved" ], [ "reject", "fail", jQuery.Callbacks( "once memory" ), jQuery.Callbacks( "once memory" ), 1, "rejected" ] ], state = "pending", promise = { state: function() { return state; }, always: function() { deferred.done( arguments ).fail( arguments ); return this; }, "catch": function( fn ) { return promise.then( null, fn ); }, // Keep pipe for back-compat pipe: function( /* fnDone, fnFail, fnProgress */ ) { var fns = arguments; return jQuery.Deferred( function( newDefer ) { jQuery.each( tuples, function( i, tuple ) { // Map tuples (progress, done, fail) to arguments (done, fail, progress) var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; // deferred.progress(function() { bind to newDefer or newDefer.notify }) // deferred.done(function() { bind to newDefer or newDefer.resolve }) // deferred.fail(function() { bind to newDefer or newDefer.reject }) deferred[ tuple[ 1 ] ]( function() { var returned = fn && fn.apply( this, arguments ); if ( returned && isFunction( returned.promise ) ) { returned.promise() .progress( newDefer.notify ) .done( newDefer.resolve ) .fail( newDefer.reject ); } else { newDefer[ tuple[ 0 ] + "With" ]( this, fn ? [ returned ] : arguments ); } } ); } ); fns = null; } ).promise(); }, then: function( onFulfilled, onRejected, onProgress ) { var maxDepth = 0; function resolve( depth, deferred, handler, special ) { return function() { var that = this, args = arguments, mightThrow = function() { var returned, then; // Support: Promises/A+ section 2.3.3.3.3 // https://promisesaplus.com/#point-59 // Ignore double-resolution attempts if ( depth < maxDepth ) { return; } returned = handler.apply( that, args ); // Support: Promises/A+ section 2.3.1 // https://promisesaplus.com/#point-48 if ( returned === deferred.promise() ) { throw new TypeError( "Thenable self-resolution" ); } // Support: Promises/A+ sections 2.3.3.1, 3.5 // https://promisesaplus.com/#point-54 // https://promisesaplus.com/#point-75 // Retrieve `then` only once then = returned && // Support: Promises/A+ section 2.3.4 // https://promisesaplus.com/#point-64 // Only check objects and functions for thenability ( typeof returned === "object" || typeof returned === "function" ) && returned.then; // Handle a returned thenable if ( isFunction( then ) ) { // Special processors (notify) just wait for resolution if ( special ) { then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ) ); // Normal processors (resolve) also hook into progress } else { // ...and disregard older resolution values maxDepth++; then.call( returned, resolve( maxDepth, deferred, Identity, special ), resolve( maxDepth, deferred, Thrower, special ), resolve( maxDepth, deferred, Identity, deferred.notifyWith ) ); } // Handle all other returned values } else { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Identity ) { that = undefined; args = [ returned ]; } // Process the value(s) // Default process is resolve ( special || deferred.resolveWith )( that, args ); } }, // Only normal processors (resolve) catch and reject exceptions process = special ? mightThrow : function() { try { mightThrow(); } catch ( e ) { if ( jQuery.Deferred.exceptionHook ) { jQuery.Deferred.exceptionHook( e, process.stackTrace ); } // Support: Promises/A+ section 2.3.3.3.4.1 // https://promisesaplus.com/#point-61 // Ignore post-resolution exceptions if ( depth + 1 >= maxDepth ) { // Only substitute handlers pass on context // and multiple values (non-spec behavior) if ( handler !== Thrower ) { that = undefined; args = [ e ]; } deferred.rejectWith( that, args ); } } }; // Support: Promises/A+ section 2.3.3.3.1 // https://promisesaplus.com/#point-57 // Re-resolve promises immediately to dodge false rejection from // subsequent errors if ( depth ) { process(); } else { // Call an optional hook to record the stack, in case of exception // since it's otherwise lost when execution goes async if ( jQuery.Deferred.getStackHook ) { process.stackTrace = jQuery.Deferred.getStackHook(); } window.setTimeout( process ); } }; } return jQuery.Deferred( function( newDefer ) { // progress_handlers.add( ... ) tuples[ 0 ][ 3 ].add( resolve( 0, newDefer, isFunction( onProgress ) ? onProgress : Identity, newDefer.notifyWith ) ); // fulfilled_handlers.add( ... ) tuples[ 1 ][ 3 ].add( resolve( 0, newDefer, isFunction( onFulfilled ) ? onFulfilled : Identity ) ); // rejected_handlers.add( ... ) tuples[ 2 ][ 3 ].add( resolve( 0, newDefer, isFunction( onRejected ) ? onRejected : Thrower ) ); } ).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { return obj != null ? jQuery.extend( obj, promise ) : promise; } }, deferred = {}; // Add list-specific methods jQuery.each( tuples, function( i, tuple ) { var list = tuple[ 2 ], stateString = tuple[ 5 ]; // promise.progress = list.add // promise.done = list.add // promise.fail = list.add promise[ tuple[ 1 ] ] = list.add; // Handle state if ( stateString ) { list.add( function() { // state = "resolved" (i.e., fulfilled) // state = "rejected" state = stateString; }, // rejected_callbacks.disable // fulfilled_callbacks.disable tuples[ 3 - i ][ 2 ].disable, // rejected_handlers.disable // fulfilled_handlers.disable tuples[ 3 - i ][ 3 ].disable, // progress_callbacks.lock tuples[ 0 ][ 2 ].lock, // progress_handlers.lock tuples[ 0 ][ 3 ].lock ); } // progress_handlers.fire // fulfilled_handlers.fire // rejected_handlers.fire list.add( tuple[ 3 ].fire ); // deferred.notify = function() { deferred.notifyWith(...) } // deferred.resolve = function() { deferred.resolveWith(...) } // deferred.reject = function() { deferred.rejectWith(...) } deferred[ tuple[ 0 ] ] = function() { deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); return this; }; // deferred.notifyWith = list.fireWith // deferred.resolveWith = list.fireWith // deferred.rejectWith = list.fireWith deferred[ tuple[ 0 ] + "With" ] = list.fireWith; } ); // Make the deferred a promise promise.promise( deferred ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( singleValue ) { var // count of uncompleted subordinates remaining = arguments.length, // count of unprocessed arguments i = remaining, // subordinate fulfillment data resolveContexts = Array( i ), resolveValues = slice.call( arguments ), // the master Deferred master = jQuery.Deferred(), // subordinate callback factory updateFunc = function( i ) { return function( value ) { resolveContexts[ i ] = this; resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; if ( !( --remaining ) ) { master.resolveWith( resolveContexts, resolveValues ); } }; }; // Single- and empty arguments are adopted like Promise.resolve if ( remaining <= 1 ) { adoptValue( singleValue, master.done( updateFunc( i ) ).resolve, master.reject, !remaining ); // Use .then() to unwrap secondary thenables (cf. gh-3000) if ( master.state() === "pending" || isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { return master.then(); } } // Multiple arguments are aggregated like Promise.all array elements while ( i-- ) { adoptValue( resolveValues[ i ], updateFunc( i ), master.reject ); } return master.promise(); } } ); // These usually indicate a programmer mistake during development, // warn about them ASAP rather than swallowing them by default. var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; jQuery.Deferred.exceptionHook = function( error, stack ) { // Support: IE 8 - 9 only // Console exists when dev tools are open, which can happen at any time if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { window.console.warn( "jQuery.Deferred exception: " + error.message, error.stack, stack ); } }; jQuery.readyException = function( error ) { window.setTimeout( function() { throw error; } ); }; // The deferred used on DOM ready var readyList = jQuery.Deferred(); jQuery.fn.ready = function( fn ) { readyList .then( fn ) // Wrap jQuery.readyException in a function so that the lookup // happens at the time of error handling instead of callback // registration. .catch( function( error ) { jQuery.readyException( error ); } ); return this; }; jQuery.extend( { // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Handle when the DOM is ready ready: function( wait ) { // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); } } ); jQuery.ready.then = readyList.then; // The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } // Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); } // Multifunctional method to get and set values of a collection // The value/s can optionally be executed if it's a function var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { var i = 0, len = elems.length, bulk = key == null; // Sets many values if ( toType( key ) === "object" ) { chainable = true; for ( i in key ) { access( elems, fn, i, key[ i ], true, emptyGet, raw ); } // Sets one value } else if ( value !== undefined ) { chainable = true; if ( !isFunction( value ) ) { raw = true; } if ( bulk ) { // Bulk operations run against the entire set if ( raw ) { fn.call( elems, value ); fn = null; // ...except when executing function values } else { bulk = fn; fn = function( elem, key, value ) { return bulk.call( jQuery( elem ), value ); }; } } if ( fn ) { for ( ; i < len; i++ ) { fn( elems[ i ], key, raw ? value : value.call( elems[ i ], i, fn( elems[ i ], key ) ) ); } } } if ( chainable ) { return elems; } // Gets if ( bulk ) { return fn.call( elems ); } return len ? fn( elems[ 0 ], key ) : emptyGet; }; // Matches dashed string for camelizing var rmsPrefix = /^-ms-/, rdashAlpha = /-([a-z])/g; // Used by camelCase as callback to replace() function fcamelCase( all, letter ) { return letter.toUpperCase(); } // Convert dashed to camelCase; used by the css and data modules // Support: IE <=9 - 11, Edge 12 - 15 // Microsoft forgot to hump their vendor prefix (#9572) function camelCase( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); } var acceptData = function( owner ) { // Accepts only: // - Node // - Node.ELEMENT_NODE // - Node.DOCUMENT_NODE // - Object // - Any return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); }; function Data() { this.expando = jQuery.expando + Data.uid++; } Data.uid = 1; Data.prototype = { cache: function( owner ) { // Check if the owner object already has a cache var value = owner[ this.expando ]; // If not, create one if ( !value ) { value = {}; // We can accept data for non-element nodes in modern browsers, // but we should not, see #8335. // Always return an empty object. if ( acceptData( owner ) ) { // If it is a node unlikely to be stringify-ed or looped over // use plain assignment if ( owner.nodeType ) { owner[ this.expando ] = value; // Otherwise secure it in a non-enumerable property // configurable must be true to allow the property to be // deleted when data is removed } else { Object.defineProperty( owner, this.expando, { value: value, configurable: true } ); } } } return value; }, set: function( owner, data, value ) { var prop, cache = this.cache( owner ); // Handle: [ owner, key, value ] args // Always use camelCase key (gh-2257) if ( typeof data === "string" ) { cache[ camelCase( data ) ] = value; // Handle: [ owner, { properties } ] args } else { // Copy the properties one-by-one to the cache object for ( prop in data ) { cache[ camelCase( prop ) ] = data[ prop ]; } } return cache; }, get: function( owner, key ) { return key === undefined ? this.cache( owner ) : // Always use camelCase key (gh-2257) owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; }, access: function( owner, key, value ) { // In cases where either: // // 1. No key was specified // 2. A string key was specified, but no value provided // // Take the "read" path and allow the get method to determine // which value to return, respectively either: // // 1. The entire cache object // 2. The data stored at the key // if ( key === undefined || ( ( key && typeof key === "string" ) && value === undefined ) ) { return this.get( owner, key ); } // When the key is not a string, or both a key and value // are specified, set or extend (existing objects) with either: // // 1. An object of properties // 2. A key and value // this.set( owner, key, value ); // Since the "set" path can have two possible entry points // return the expected data based on which path was taken[*] return value !== undefined ? value : key; }, remove: function( owner, key ) { var i, cache = owner[ this.expando ]; if ( cache === undefined ) { return; } if ( key !== undefined ) { // Support array or space separated string of keys if ( Array.isArray( key ) ) { // If key is an array of keys... // We always set camelCase keys, so remove that. key = key.map( camelCase ); } else { key = camelCase( key ); // If a key with the spaces exists, use it. // Otherwise, create an array by matching non-whitespace key = key in cache ? [ key ] : ( key.match( rnothtmlwhite ) || [] ); } i = key.length; while ( i-- ) { delete cache[ key[ i ] ]; } } // Remove the expando if there's no more data if ( key === undefined || jQuery.isEmptyObject( cache ) ) { // Support: Chrome <=35 - 45 // Webkit & Blink performance suffers when deleting properties // from DOM nodes, so set to undefined instead // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) if ( owner.nodeType ) { owner[ this.expando ] = undefined; } else { delete owner[ this.expando ]; } } }, hasData: function( owner ) { var cache = owner[ this.expando ]; return cache !== undefined && !jQuery.isEmptyObject( cache ); } }; var dataPriv = new Data(); var dataUser = new Data(); // Implementation Summary // // 1. Enforce API surface and semantic compatibility with 1.9.x branch // 2. Improve the module's maintainability by reducing the storage // paths to a single mechanism. // 3. Use the same single mechanism to support "private" and "user" data. // 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) // 5. Avoid exposing implementation details on user objects (eg. expando properties) // 6. Provide a clear path for implementation upgrade to WeakMap in 2014 var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, rmultiDash = /[A-Z]/g; function getData( data ) { if ( data === "true" ) { return true; } if ( data === "false" ) { return false; } if ( data === "null" ) { return null; } // Only convert to a number if it doesn't change the string if ( data === +data + "" ) { return +data; } if ( rbrace.test( data ) ) { return JSON.parse( data ); } return data; } function dataAttr( elem, key, data ) { var name; // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = getData( data ); } catch ( e ) {} // Make sure we set the data so it isn't changed later dataUser.set( elem, key, data ); } else { data = undefined; } } return data; } jQuery.extend( { hasData: function( elem ) { return dataUser.hasData( elem ) || dataPriv.hasData( elem ); }, data: function( elem, name, data ) { return dataUser.access( elem, name, data ); }, removeData: function( elem, name ) { dataUser.remove( elem, name ); }, // TODO: Now that all calls to _data and _removeData have been replaced // with direct calls to dataPriv methods, these can be deprecated. _data: function( elem, name, data ) { return dataPriv.access( elem, name, data ); }, _removeData: function( elem, name ) { dataPriv.remove( elem, name ); } } ); jQuery.fn.extend( { data: function( key, value ) { var i, name, data, elem = this[ 0 ], attrs = elem && elem.attributes; // Gets all values if ( key === undefined ) { if ( this.length ) { data = dataUser.get( elem ); if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { i = attrs.length; while ( i-- ) { // Support: IE 11 only // The attrs elements can be null (#14894) if ( attrs[ i ] ) { name = attrs[ i ].name; if ( name.indexOf( "data-" ) === 0 ) { name = camelCase( name.slice( 5 ) ); dataAttr( elem, name, data[ name ] ); } } } dataPriv.set( elem, "hasDataAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each( function() { dataUser.set( this, key ); } ); } return access( this, function( value ) { var data; // The calling jQuery object (element matches) is not empty // (and therefore has an element appears at this[ 0 ]) and the // `value` parameter was not undefined. An empty jQuery object // will result in `undefined` for elem = this[ 0 ] which will // throw an exception if an attempt to read a data cache is made. if ( elem && value === undefined ) { // Attempt to get data from the cache // The key will always be camelCased in Data data = dataUser.get( elem, key ); if ( data !== undefined ) { return data; } // Attempt to "discover" the data in // HTML5 custom data-* attrs data = dataAttr( elem, key ); if ( data !== undefined ) { return data; } // We tried really hard, but the data doesn't exist. return; } // Set the data... this.each( function() { // We always store the camelCased key dataUser.set( this, key, value ); } ); }, null, value, arguments.length > 1, null, true ); }, removeData: function( key ) { return this.each( function() { dataUser.remove( this, key ); } ); } } ); jQuery.extend( { queue: function( elem, type, data ) { var queue; if ( elem ) { type = ( type || "fx" ) + "queue"; queue = dataPriv.get( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !queue || Array.isArray( data ) ) { queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); } else { queue.push( data ); } } return queue || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), startLength = queue.length, fn = queue.shift(), hooks = jQuery._queueHooks( elem, type ), next = function() { jQuery.dequeue( elem, type ); }; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); startLength--; } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } // Clear up the last queue stop function delete hooks.stop; fn.call( elem, next, hooks ); } if ( !startLength && hooks ) { hooks.empty.fire(); } }, // Not public - generate a queueHooks object, or return the current one _queueHooks: function( elem, type ) { var key = type + "queueHooks"; return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { empty: jQuery.Callbacks( "once memory" ).add( function() { dataPriv.remove( elem, [ type + "queue", key ] ); } ) } ); } } ); jQuery.fn.extend( { queue: function( type, data ) { var setter = 2; if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } if ( arguments.length < setter ) { return jQuery.queue( this[ 0 ], type ); } return data === undefined ? this : this.each( function() { var queue = jQuery.queue( this, type, data ); // Ensure a hooks for this queue jQuery._queueHooks( this, type ); if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { jQuery.dequeue( this, type ); } } ); }, dequeue: function( type ) { return this.each( function() { jQuery.dequeue( this, type ); } ); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // Get a promise resolved when queues of a certain type // are emptied (fx is the type by default) promise: function( type, obj ) { var tmp, count = 1, defer = jQuery.Deferred(), elements = this, i = this.length, resolve = function() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } }; if ( typeof type !== "string" ) { obj = type; type = undefined; } type = type || "fx"; while ( i-- ) { tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); if ( tmp && tmp.empty ) { count++; tmp.empty.add( resolve ); } } resolve(); return defer.promise( obj ); } } ); var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; var isHiddenWithinTree = function( elem, el ) { // isHiddenWithinTree might be called from jQuery#filter function; // in that case, element will be second argument elem = el || elem; // Inline style trumps all return elem.style.display === "none" || elem.style.display === "" && // Otherwise, check computed style // Support: Firefox <=43 - 45 // Disconnected elements can have computed display: none, so first confirm that elem is // in the document. jQuery.contains( elem.ownerDocument, elem ) && jQuery.css( elem, "display" ) === "none"; }; var swap = function( elem, options, callback, args ) { var ret, name, old = {}; // Remember the old values, and insert the new ones for ( name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } ret = callback.apply( elem, args || [] ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } return ret; }; function adjustCSS( elem, prop, valueParts, tween ) { var adjusted, scale, maxIterations = 20, currentValue = tween ? function() { return tween.cur(); } : function() { return jQuery.css( elem, prop, "" ); }, initial = currentValue(), unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), // Starting value computation is required for potential unit mismatches initialInUnit = ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && rcssNum.exec( jQuery.css( elem, prop ) ); if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { // Support: Firefox <=54 // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) initial = initial / 2; // Trust units reported by jQuery.css unit = unit || initialInUnit[ 3 ]; // Iteratively approximate from a nonzero starting point initialInUnit = +initial || 1; while ( maxIterations-- ) { // Evaluate and update our best guess (doubling guesses that zero out). // Finish if the scale equals or crosses 1 (making the old*new product non-positive). jQuery.style( elem, prop, initialInUnit + unit ); if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { maxIterations = 0; } initialInUnit = initialInUnit / scale; } initialInUnit = initialInUnit * 2; jQuery.style( elem, prop, initialInUnit + unit ); // Make sure we update the tween properties later on valueParts = valueParts || []; } if ( valueParts ) { initialInUnit = +initialInUnit || +initial || 0; // Apply relative offset (+=/-=) if specified adjusted = valueParts[ 1 ] ? initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : +valueParts[ 2 ]; if ( tween ) { tween.unit = unit; tween.start = initialInUnit; tween.end = adjusted; } } return adjusted; } var defaultDisplayMap = {}; function getDefaultDisplay( elem ) { var temp, doc = elem.ownerDocument, nodeName = elem.nodeName, display = defaultDisplayMap[ nodeName ]; if ( display ) { return display; } temp = doc.body.appendChild( doc.createElement( nodeName ) ); display = jQuery.css( temp, "display" ); temp.parentNode.removeChild( temp ); if ( display === "none" ) { display = "block"; } defaultDisplayMap[ nodeName ] = display; return display; } function showHide( elements, show ) { var display, elem, values = [], index = 0, length = elements.length; // Determine new display value for elements that need to change for ( ; index < length; index++ ) { elem = elements[ index ]; if ( !elem.style ) { continue; } display = elem.style.display; if ( show ) { // Since we force visibility upon cascade-hidden elements, an immediate (and slow) // check is required in this first loop unless we have a nonempty display value (either // inline or about-to-be-restored) if ( display === "none" ) { values[ index ] = dataPriv.get( elem, "display" ) || null; if ( !values[ index ] ) { elem.style.display = ""; } } if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { values[ index ] = getDefaultDisplay( elem ); } } else { if ( display !== "none" ) { values[ index ] = "none"; // Remember what we're overwriting dataPriv.set( elem, "display", display ); } } } // Set the display of the elements in a second loop to avoid constant reflow for ( index = 0; index < length; index++ ) { if ( values[ index ] != null ) { elements[ index ].style.display = values[ index ]; } } return elements; } jQuery.fn.extend( { show: function() { return showHide( this, true ); }, hide: function() { return showHide( this ); }, toggle: function( state ) { if ( typeof state === "boolean" ) { return state ? this.show() : this.hide(); } return this.each( function() { if ( isHiddenWithinTree( this ) ) { jQuery( this ).show(); } else { jQuery( this ).hide(); } } ); } } ); var rcheckableType = ( /^(?:checkbox|radio)$/i ); var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]+)/i ); var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); // We have to close these tags to support XHTML (#13200) var wrapMap = { // Support: IE <=9 only option: [ 1, "<select multiple='multiple'>", "</select>" ], // XHTML parsers do not magically insert elements in the // same way that tag soup parsers do. So we cannot shorten // this by omitting <tbody> or other required elements. thead: [ 1, "<table>", "</table>" ], col: [ 2, "<table><colgroup>", "</colgroup></table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], _default: [ 0, "", "" ] }; // Support: IE <=9 only wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; function getAll( context, tag ) { // Support: IE <=9 - 11 only // Use typeof to avoid zero-argument method invocation on host objects (#15151) var ret; if ( typeof context.getElementsByTagName !== "undefined" ) { ret = context.getElementsByTagName( tag || "*" ); } else if ( typeof context.querySelectorAll !== "undefined" ) { ret = context.querySelectorAll( tag || "*" ); } else { ret = []; } if ( tag === undefined || tag && nodeName( context, tag ) ) { return jQuery.merge( [ context ], ret ); } return ret; } // Mark scripts as having already been evaluated function setGlobalEval( elems, refElements ) { var i = 0, l = elems.length; for ( ; i < l; i++ ) { dataPriv.set( elems[ i ], "globalEval", !refElements || dataPriv.get( refElements[ i ], "globalEval" ) ); } } var rhtml = /<|&#?\w+;/; function buildFragment( elems, context, scripts, selection, ignored ) { var elem, tmp, tag, wrap, contains, j, fragment = context.createDocumentFragment(), nodes = [], i = 0, l = elems.length; for ( ; i < l; i++ ) { elem = elems[ i ]; if ( elem || elem === 0 ) { // Add nodes directly if ( toType( elem ) === "object" ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); // Convert non-html into a text node } else if ( !rhtml.test( elem ) ) { nodes.push( context.createTextNode( elem ) ); // Convert html into DOM nodes } else { tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); // Deserialize a standard representation tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); wrap = wrapMap[ tag ] || wrapMap._default; tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; // Descend through wrappers to the right content j = wrap[ 0 ]; while ( j-- ) { tmp = tmp.lastChild; } // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( nodes, tmp.childNodes ); // Remember the top-level container tmp = fragment.firstChild; // Ensure the created nodes are orphaned (#12392) tmp.textContent = ""; } } } // Remove wrapper from fragment fragment.textContent = ""; i = 0; while ( ( elem = nodes[ i++ ] ) ) { // Skip elements already in the context collection (trac-4087) if ( selection && jQuery.inArray( elem, selection ) > -1 ) { if ( ignored ) { ignored.push( elem ); } continue; } contains = jQuery.contains( elem.ownerDocument, elem ); // Append to fragment tmp = getAll( fragment.appendChild( elem ), "script" ); // Preserve script evaluation history if ( contains ) { setGlobalEval( tmp ); } // Capture executables if ( scripts ) { j = 0; while ( ( elem = tmp[ j++ ] ) ) { if ( rscriptType.test( elem.type || "" ) ) { scripts.push( elem ); } } } } return fragment; } ( function() { var fragment = document.createDocumentFragment(), div = fragment.appendChild( document.createElement( "div" ) ), input = document.createElement( "input" ); // Support: Android 4.0 - 4.3 only // Check state lost if the name is set (#11217) // Support: Windows Web Apps (WWA) // `name` and `type` must use .setAttribute for WWA (#14901) input.setAttribute( "type", "radio" ); input.setAttribute( "checked", "checked" ); input.setAttribute( "name", "t" ); div.appendChild( input ); // Support: Android <=4.1 only // Older WebKit doesn't clone checked state correctly in fragments support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; // Support: IE <=11 only // Make sure textarea (and checkbox) defaultValue is properly cloned div.innerHTML = "<textarea>x</textarea>"; support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; } )(); var documentElement = document.documentElement; var rkeyEvent = /^key/, rmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/, rtypenamespace = /^([^.]*)(?:\.(.+)|)/; function returnTrue() { return true; } function returnFalse() { return false; } // Support: IE <=9 only // See #13393 for more info function safeActiveElement() { try { return document.activeElement; } catch ( err ) { } } function on( elem, types, selector, data, fn, one ) { var origFn, type; // Types can be a map of types/handlers if ( typeof types === "object" ) { // ( types-Object, selector, data ) if ( typeof selector !== "string" ) { // ( types-Object, data ) data = data || selector; selector = undefined; } for ( type in types ) { on( elem, type, selector, data, types[ type ], one ); } return elem; } if ( data == null && fn == null ) { // ( types, fn ) fn = selector; data = selector = undefined; } else if ( fn == null ) { if ( typeof selector === "string" ) { // ( types, selector, fn ) fn = data; data = undefined; } else { // ( types, data, fn ) fn = data; data = selector; selector = undefined; } } if ( fn === false ) { fn = returnFalse; } else if ( !fn ) { return elem; } if ( one === 1 ) { origFn = fn; fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // Use same guid so caller can remove using origFn fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } return elem.each( function() { jQuery.event.add( this, types, fn, data, selector ); } ); } /* * Helper functions for managing events -- not part of the public interface. * Props to Dean Edwards' addEvent library for many of the ideas. */ jQuery.event = { global: {}, add: function( elem, types, handler, data, selector ) { var handleObjIn, eventHandle, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.get( elem ); // Don't attach events to noData or text/comment nodes (but allow plain objects) if ( !elemData ) { return; } // Caller can pass in an object of custom data in lieu of the handler if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // Ensure that invalid selectors throw exceptions at attach time // Evaluate against documentElement in case elem is a non-element node (e.g., document) if ( selector ) { jQuery.find.matchesSelector( documentElement, selector ); } // Make sure that the handler has a unique ID, used to find/remove it later if ( !handler.guid ) { handler.guid = jQuery.guid++; } // Init the element's event structure and main handler, if this is the first if ( !( events = elemData.events ) ) { events = elemData.events = {}; } if ( !( eventHandle = elemData.handle ) ) { eventHandle = elemData.handle = function( e ) { // Discard the second event of a jQuery.event.trigger() and // when an event is called after a page has unloaded return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? jQuery.event.dispatch.apply( elem, arguments ) : undefined; }; } // Handle multiple events separated by a space types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // There *must* be a type, no attaching namespace-only handlers if ( !type ) { continue; } // If event changes its type, use the special event handlers for the changed type special = jQuery.event.special[ type ] || {}; // If selector defined, determine special event api type, otherwise given type type = ( selector ? special.delegateType : special.bindType ) || type; // Update special based on newly reset type special = jQuery.event.special[ type ] || {}; // handleObj is passed to all event handlers handleObj = jQuery.extend( { type: type, origType: origType, data: data, handler: handler, guid: handler.guid, selector: selector, needsContext: selector && jQuery.expr.match.needsContext.test( selector ), namespace: namespaces.join( "." ) }, handleObjIn ); // Init the event handler queue if we're the first if ( !( handlers = events[ type ] ) ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // Only use addEventListener if the special events handler returns false if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // Add to the element's handler list, delegates in front if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // Keep track of which events have ever been used, for event optimization jQuery.event.global[ type ] = true; } }, // Detach an event or set of events from an element remove: function( elem, types, handler, selector, mappedTypes ) { var j, origCount, tmp, events, t, handleObj, special, handlers, type, namespaces, origType, elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); if ( !elemData || !( events = elemData.events ) ) { return; } // Once for each type.namespace in types; type may be omitted types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; t = types.length; while ( t-- ) { tmp = rtypenamespace.exec( types[ t ] ) || []; type = origType = tmp[ 1 ]; namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); // Unbind all events (on this namespace, if provided) for the element if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector ? special.delegateType : special.bindType ) || type; handlers = events[ type ] || []; tmp = tmp[ 2 ] && new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); // Remove matching events origCount = j = handlers.length; while ( j-- ) { handleObj = handlers[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !tmp || tmp.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { handlers.splice( j, 1 ); if ( handleObj.selector ) { handlers.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // Remove generic event handler if we removed something and no more handlers exist // (avoids potential for endless recursion during removal of special event handlers) if ( origCount && !handlers.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // Remove data and the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { dataPriv.remove( elem, "handle events" ); } }, dispatch: function( nativeEvent ) { // Make a writable jQuery.Event from the native event object var event = jQuery.event.fix( nativeEvent ); var i, j, ret, matched, handleObj, handlerQueue, args = new Array( arguments.length ), handlers = ( dataPriv.get( this, "events" ) || {} )[ event.type ] || [], special = jQuery.event.special[ event.type ] || {}; // Use the fix-ed jQuery.Event rather than the (read-only) native event args[ 0 ] = event; for ( i = 1; i < arguments.length; i++ ) { args[ i ] = arguments[ i ]; } event.delegateTarget = this; // Call the preDispatch hook for the mapped type, and let it bail if desired if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // Determine handlers handlerQueue = jQuery.event.handlers.call( this, event, handlers ); // Run delegates first; they may want to stop propagation beneath us i = 0; while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { event.currentTarget = matched.elem; j = 0; while ( ( handleObj = matched.handlers[ j++ ] ) && !event.isImmediatePropagationStopped() ) { // Triggered event must either 1) have no namespace, or 2) have namespace(s) // a subset or equal to those in the bound event (both can have no namespace). if ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) { event.handleObj = handleObj; event.data = handleObj.data; ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || handleObj.handler ).apply( matched.elem, args ); if ( ret !== undefined ) { if ( ( event.result = ret ) === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // Call the postDispatch hook for the mapped type if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, handlers: function( event, handlers ) { var i, handleObj, sel, matchedHandlers, matchedSelectors, handlerQueue = [], delegateCount = handlers.delegateCount, cur = event.target; // Find delegate handlers if ( delegateCount && // Support: IE <=9 // Black-hole SVG <use> instance trees (trac-13180) cur.nodeType && // Support: Firefox <=42 // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click // Support: IE 11 only // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) !( event.type === "click" && event.button >= 1 ) ) { for ( ; cur !== this; cur = cur.parentNode || this ) { // Don't check non-elements (#13208) // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { matchedHandlers = []; matchedSelectors = {}; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; // Don't conflict with Object.prototype properties (#13203) sel = handleObj.selector + " "; if ( matchedSelectors[ sel ] === undefined ) { matchedSelectors[ sel ] = handleObj.needsContext ? jQuery( sel, this ).index( cur ) > -1 : jQuery.find( sel, this, null, [ cur ] ).length; } if ( matchedSelectors[ sel ] ) { matchedHandlers.push( handleObj ); } } if ( matchedHandlers.length ) { handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); } } } } // Add the remaining (directly-bound) handlers cur = this; if ( delegateCount < handlers.length ) { handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); } return handlerQueue; }, addProp: function( name, hook ) { Object.defineProperty( jQuery.Event.prototype, name, { enumerable: true, configurable: true, get: isFunction( hook ) ? function() { if ( this.originalEvent ) { return hook( this.originalEvent ); } } : function() { if ( this.originalEvent ) { return this.originalEvent[ name ]; } }, set: function( value ) { Object.defineProperty( this, name, { enumerable: true, configurable: true, writable: true, value: value } ); } } ); }, fix: function( originalEvent ) { return originalEvent[ jQuery.expando ] ? originalEvent : new jQuery.Event( originalEvent ); }, special: { load: { // Prevent triggered image.load events from bubbling to window.load noBubble: true }, focus: { // Fire native event if possible so blur/focus sequence is correct trigger: function() { if ( this !== safeActiveElement() && this.focus ) { this.focus(); return false; } }, delegateType: "focusin" }, blur: { trigger: function() { if ( this === safeActiveElement() && this.blur ) { this.blur(); return false; } }, delegateType: "focusout" }, click: { // For checkbox, fire native event so checked state will be right trigger: function() { if ( this.type === "checkbox" && this.click && nodeName( this, "input" ) ) { this.click(); return false; } }, // For cross-browser consistency, don't fire native .click() on links _default: function( event ) { return nodeName( event.target, "a" ); } }, beforeunload: { postDispatch: function( event ) { // Support: Firefox 20+ // Firefox doesn't alert if the returnValue field is not set. if ( event.result !== undefined && event.originalEvent ) { event.originalEvent.returnValue = event.result; } } } } }; jQuery.removeEvent = function( elem, type, handle ) { // This "if" is needed for plain objects if ( elem.removeEventListener ) { elem.removeEventListener( type, handle ); } }; jQuery.Event = function( src, props ) { // Allow instantiation without the 'new' keyword if ( !( this instanceof jQuery.Event ) ) { return new jQuery.Event( src, props ); } // Event object if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = src.defaultPrevented || src.defaultPrevented === undefined && // Support: Android <=2.3 only src.returnValue === false ? returnTrue : returnFalse; // Create target properties // Support: Safari <=6 - 7 only // Target should not be a text node (#504, #13143) this.target = ( src.target && src.target.nodeType === 3 ) ? src.target.parentNode : src.target; this.currentTarget = src.currentTarget; this.relatedTarget = src.relatedTarget; // Event type } else { this.type = src; } // Put explicitly provided properties onto the event object if ( props ) { jQuery.extend( this, props ); } // Create a timestamp if incoming event doesn't have one this.timeStamp = src && src.timeStamp || Date.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html jQuery.Event.prototype = { constructor: jQuery.Event, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse, isSimulated: false, preventDefault: function() { var e = this.originalEvent; this.isDefaultPrevented = returnTrue; if ( e && !this.isSimulated ) { e.preventDefault(); } }, stopPropagation: function() { var e = this.originalEvent; this.isPropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopPropagation(); } }, stopImmediatePropagation: function() { var e = this.originalEvent; this.isImmediatePropagationStopped = returnTrue; if ( e && !this.isSimulated ) { e.stopImmediatePropagation(); } this.stopPropagation(); } }; // Includes all common event props including KeyEvent and MouseEvent specific props jQuery.each( { altKey: true, bubbles: true, cancelable: true, changedTouches: true, ctrlKey: true, detail: true, eventPhase: true, metaKey: true, pageX: true, pageY: true, shiftKey: true, view: true, "char": true, charCode: true, key: true, keyCode: true, button: true, buttons: true, clientX: true, clientY: true, offsetX: true, offsetY: true, pointerId: true, pointerType: true, screenX: true, screenY: true, targetTouches: true, toElement: true, touches: true, which: function( event ) { var button = event.button; // Add which for key events if ( event.which == null && rkeyEvent.test( event.type ) ) { return event.charCode != null ? event.charCode : event.keyCode; } // Add which for click: 1 === left; 2 === middle; 3 === right if ( !event.which && button !== undefined && rmouseEvent.test( event.type ) ) { if ( button & 1 ) { return 1; } if ( button & 2 ) { return 3; } if ( button & 4 ) { return 2; } return 0; } return event.which; } }, jQuery.event.addProp ); // Create mouseenter/leave events using mouseover/out and event-time checks // so that event delegation works in jQuery. // Do the same for pointerenter/pointerleave and pointerover/pointerout // // Support: Safari 7 only // Safari sends mouseenter too often; see: // https://bugs.chromium.org/p/chromium/issues/detail?id=470258 // for the description of the bug (it existed in older Chrome versions as well). jQuery.each( { mouseenter: "mouseover", mouseleave: "mouseout", pointerenter: "pointerover", pointerleave: "pointerout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var ret, target = this, related = event.relatedTarget, handleObj = event.handleObj; // For mouseenter/leave call the handler if related is outside the target. // NB: No relatedTarget if the mouse left/entered the browser window if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; } ); jQuery.fn.extend( { on: function( types, selector, data, fn ) { return on( this, types, selector, data, fn ); }, one: function( types, selector, data, fn ) { return on( this, types, selector, data, fn, 1 ); }, off: function( types, selector, fn ) { var handleObj, type; if ( types && types.preventDefault && types.handleObj ) { // ( event ) dispatched jQuery.Event handleObj = types.handleObj; jQuery( types.delegateTarget ).off( handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } if ( typeof types === "object" ) { // ( types-object [, selector] ) for ( type in types ) { this.off( type, selector, types[ type ] ); } return this; } if ( selector === false || typeof selector === "function" ) { // ( types [, fn] ) fn = selector; selector = undefined; } if ( fn === false ) { fn = returnFalse; } return this.each( function() { jQuery.event.remove( this, types, fn, selector ); } ); } } ); var /* eslint-disable max-len */ // See https://github.com/eslint/eslint/issues/3229 rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\/\0>\x20\t\r\n\f]*)[^>]*)\/>/gi, /* eslint-enable */ // Support: IE <=10 - 11, Edge 12 - 13 only // In IE/Edge using regex groups here causes severe slowdowns. // See https://connect.microsoft.com/IE/feedback/details/1736512/ rnoInnerhtml = /<script|<style|<link/i, // checked="checked" or checked rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g; // Prefer a tbody over its parent table for containing new rows function manipulationTarget( elem, content ) { if ( nodeName( elem, "table" ) && nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { return jQuery( elem ).children( "tbody" )[ 0 ] || elem; } return elem; } // Replace/restore the type attribute of script elements for safe DOM manipulation function disableScript( elem ) { elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; return elem; } function restoreScript( elem ) { if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { elem.type = elem.type.slice( 5 ); } else { elem.removeAttribute( "type" ); } return elem; } function cloneCopyEvent( src, dest ) { var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; if ( dest.nodeType !== 1 ) { return; } // 1. Copy private data: events, handlers, etc. if ( dataPriv.hasData( src ) ) { pdataOld = dataPriv.access( src ); pdataCur = dataPriv.set( dest, pdataOld ); events = pdataOld.events; if ( events ) { delete pdataCur.handle; pdataCur.events = {}; for ( type in events ) { for ( i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ] ); } } } } // 2. Copy user data if ( dataUser.hasData( src ) ) { udataOld = dataUser.access( src ); udataCur = jQuery.extend( {}, udataOld ); dataUser.set( dest, udataCur ); } } // Fix IE bugs, see support tests function fixInput( src, dest ) { var nodeName = dest.nodeName.toLowerCase(); // Fails to persist the checked state of a cloned checkbox or radio button. if ( nodeName === "input" && rcheckableType.test( src.type ) ) { dest.checked = src.checked; // Fails to return the selected option to the default selected state when cloning options } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } } function domManip( collection, args, callback, ignored ) { // Flatten any nested arrays args = concat.apply( [], args ); var fragment, first, scripts, hasScripts, node, doc, i = 0, l = collection.length, iNoClone = l - 1, value = args[ 0 ], valueIsFunction = isFunction( value ); // We can't cloneNode fragments that contain checked, in WebKit if ( valueIsFunction || ( l > 1 && typeof value === "string" && !support.checkClone && rchecked.test( value ) ) ) { return collection.each( function( index ) { var self = collection.eq( index ); if ( valueIsFunction ) { args[ 0 ] = value.call( this, index, self.html() ); } domManip( self, args, callback, ignored ); } ); } if ( l ) { fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); first = fragment.firstChild; if ( fragment.childNodes.length === 1 ) { fragment = first; } // Require either new content or an interest in ignored elements to invoke the callback if ( first || ignored ) { scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); hasScripts = scripts.length; // Use the original fragment for the last item // instead of the first because it can end up // being emptied incorrectly in certain situations (#8070). for ( ; i < l; i++ ) { node = fragment; if ( i !== iNoClone ) { node = jQuery.clone( node, true, true ); // Keep references to cloned scripts for later restoration if ( hasScripts ) { // Support: Android <=4.0 only, PhantomJS 1 only // push.apply(_, arraylike) throws on ancient WebKit jQuery.merge( scripts, getAll( node, "script" ) ); } } callback.call( collection[ i ], node, i ); } if ( hasScripts ) { doc = scripts[ scripts.length - 1 ].ownerDocument; // Reenable scripts jQuery.map( scripts, restoreScript ); // Evaluate executable scripts on first document insertion for ( i = 0; i < hasScripts; i++ ) { node = scripts[ i ]; if ( rscriptType.test( node.type || "" ) && !dataPriv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { // Optional AJAX dependency, but won't run scripts if not present if ( jQuery._evalUrl ) { jQuery._evalUrl( node.src ); } } else { DOMEval( node.textContent.replace( rcleanScript, "" ), doc, node ); } } } } } } return collection; } function remove( elem, selector, keepData ) { var node, nodes = selector ? jQuery.filter( selector, elem ) : elem, i = 0; for ( ; ( node = nodes[ i ] ) != null; i++ ) { if ( !keepData && node.nodeType === 1 ) { jQuery.cleanData( getAll( node ) ); } if ( node.parentNode ) { if ( keepData && jQuery.contains( node.ownerDocument, node ) ) { setGlobalEval( getAll( node, "script" ) ); } node.parentNode.removeChild( node ); } } return elem; } jQuery.extend( { htmlPrefilter: function( html ) { return html.replace( rxhtmlTag, "<$1></$2>" ); }, clone: function( elem, dataAndEvents, deepDataAndEvents ) { var i, l, srcElements, destElements, clone = elem.cloneNode( true ), inPage = jQuery.contains( elem.ownerDocument, elem ); // Fix IE cloning issues if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && !jQuery.isXMLDoc( elem ) ) { // We eschew Sizzle here for performance reasons: https://jsperf.com/getall-vs-sizzle/2 destElements = getAll( clone ); srcElements = getAll( elem ); for ( i = 0, l = srcElements.length; i < l; i++ ) { fixInput( srcElements[ i ], destElements[ i ] ); } } // Copy the events from the original to the clone if ( dataAndEvents ) { if ( deepDataAndEvents ) { srcElements = srcElements || getAll( elem ); destElements = destElements || getAll( clone ); for ( i = 0, l = srcElements.length; i < l; i++ ) { cloneCopyEvent( srcElements[ i ], destElements[ i ] ); } } else { cloneCopyEvent( elem, clone ); } } // Preserve script evaluation history destElements = getAll( clone, "script" ); if ( destElements.length > 0 ) { setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); } // Return the cloned set return clone; }, cleanData: function( elems ) { var data, elem, type, special = jQuery.event.special, i = 0; for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { if ( acceptData( elem ) ) { if ( ( data = elem[ dataPriv.expando ] ) ) { if ( data.events ) { for ( type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } } // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataPriv.expando ] = undefined; } if ( elem[ dataUser.expando ] ) { // Support: Chrome <=35 - 45+ // Assign undefined instead of using delete, see Data#remove elem[ dataUser.expando ] = undefined; } } } } } ); jQuery.fn.extend( { detach: function( selector ) { return remove( this, selector, true ); }, remove: function( selector ) { return remove( this, selector ); }, text: function( value ) { return access( this, function( value ) { return value === undefined ? jQuery.text( this ) : this.empty().each( function() { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { this.textContent = value; } } ); }, null, value, arguments.length ); }, append: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.appendChild( elem ); } } ); }, prepend: function() { return domManip( this, arguments, function( elem ) { if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { var target = manipulationTarget( this, elem ); target.insertBefore( elem, target.firstChild ); } } ); }, before: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this ); } } ); }, after: function() { return domManip( this, arguments, function( elem ) { if ( this.parentNode ) { this.parentNode.insertBefore( elem, this.nextSibling ); } } ); }, empty: function() { var elem, i = 0; for ( ; ( elem = this[ i ] ) != null; i++ ) { if ( elem.nodeType === 1 ) { // Prevent memory leaks jQuery.cleanData( getAll( elem, false ) ); // Remove any remaining nodes elem.textContent = ""; } } return this; }, clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? false : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function() { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); } ); }, html: function( value ) { return access( this, function( value ) { var elem = this[ 0 ] || {}, i = 0, l = this.length; if ( value === undefined && elem.nodeType === 1 ) { return elem.innerHTML; } // See if we can take a shortcut and just use innerHTML if ( typeof value === "string" && !rnoInnerhtml.test( value ) && !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { value = jQuery.htmlPrefilter( value ); try { for ( ; i < l; i++ ) { elem = this[ i ] || {}; // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( getAll( elem, false ) ); elem.innerHTML = value; } } elem = 0; // If using innerHTML throws an exception, use the fallback method } catch ( e ) {} } if ( elem ) { this.empty().append( value ); } }, null, value, arguments.length ); }, replaceWith: function() { var ignored = []; // Make the changes, replacing each non-ignored context element with the new content return domManip( this, arguments, function( elem ) { var parent = this.parentNode; if ( jQuery.inArray( this, ignored ) < 0 ) { jQuery.cleanData( getAll( this ) ); if ( parent ) { parent.replaceChild( elem, this ); } } // Force callback invocation }, ignored ); } } ); jQuery.each( { appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var elems, ret = [], insert = jQuery( selector ), last = insert.length - 1, i = 0; for ( ; i <= last; i++ ) { elems = i === last ? this : this.clone( true ); jQuery( insert[ i ] )[ original ]( elems ); // Support: Android <=4.0 only, PhantomJS 1 only // .get() because push.apply(_, arraylike) throws on ancient WebKit push.apply( ret, elems.get() ); } return this.pushStack( ret ); }; } ); var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); var getStyles = function( elem ) { // Support: IE <=11 only, Firefox <=30 (#15098, #14150) // IE throws on elements created in popups // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" var view = elem.ownerDocument.defaultView; if ( !view || !view.opener ) { view = window; } return view.getComputedStyle( elem ); }; var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); ( function() { // Executing both pixelPosition & boxSizingReliable tests require only one layout // so they're executed at the same time to save the second computation. function computeStyleTests() { // This is a singleton, we need to execute it only once if ( !div ) { return; } container.style.cssText = "position:absolute;left:-11111px;width:60px;" + "margin-top:1px;padding:0;border:0"; div.style.cssText = "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + "margin:auto;border:1px;padding:1px;" + "width:60%;top:1%"; documentElement.appendChild( container ).appendChild( div ); var divStyle = window.getComputedStyle( div ); pixelPositionVal = divStyle.top !== "1%"; // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 // Some styles come back with percentage values, even though they shouldn't div.style.right = "60%"; pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; // Support: IE 9 - 11 only // Detect misreporting of content dimensions for box-sizing:border-box elements boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; // Support: IE 9 only // Detect overflow:scroll screwiness (gh-3699) div.style.position = "absolute"; scrollboxSizeVal = div.offsetWidth === 36 || "absolute"; documentElement.removeChild( container ); // Nullify the div so it wouldn't be stored in the memory and // it will also be a sign that checks already performed div = null; } function roundPixelMeasures( measure ) { return Math.round( parseFloat( measure ) ); } var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, reliableMarginLeftVal, container = document.createElement( "div" ), div = document.createElement( "div" ); // Finish early in limited (non-browser) environments if ( !div.style ) { return; } // Support: IE <=9 - 11 only // Style of cloned element affects source element cloned (#8908) div.style.backgroundClip = "content-box"; div.cloneNode( true ).style.backgroundClip = ""; support.clearCloneStyle = div.style.backgroundClip === "content-box"; jQuery.extend( support, { boxSizingReliable: function() { computeStyleTests(); return boxSizingReliableVal; }, pixelBoxStyles: function() { computeStyleTests(); return pixelBoxStylesVal; }, pixelPosition: function() { computeStyleTests(); return pixelPositionVal; }, reliableMarginLeft: function() { computeStyleTests(); return reliableMarginLeftVal; }, scrollboxSize: function() { computeStyleTests(); return scrollboxSizeVal; } } ); } )(); function curCSS( elem, name, computed ) { var width, minWidth, maxWidth, ret, // Support: Firefox 51+ // Retrieving style before computed somehow // fixes an issue with getting wrong values // on detached elements style = elem.style; computed = computed || getStyles( elem ); // getPropertyValue is needed for: // .css('filter') (IE 9 only, #12537) // .css('--customProperty) (#3144) if ( computed ) { ret = computed.getPropertyValue( name ) || computed[ name ]; if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) { ret = jQuery.style( elem, name ); } // A tribute to the "awesome hack by Dean Edwards" // Android Browser returns percentage for some values, // but width seems to be reliably pixels. // This is against the CSSOM draft spec: // https://drafts.csswg.org/cssom/#resolved-values if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { // Remember the original values width = style.width; minWidth = style.minWidth; maxWidth = style.maxWidth; // Put in the new values to get a computed value out style.minWidth = style.maxWidth = style.width = ret; ret = computed.width; // Revert the changed values style.width = width; style.minWidth = minWidth; style.maxWidth = maxWidth; } } return ret !== undefined ? // Support: IE <=9 - 11 only // IE returns zIndex value as an integer. ret + "" : ret; } function addGetHookIf( conditionFn, hookFn ) { // Define the hook, we'll check on the first run if it's really needed. return { get: function() { if ( conditionFn() ) { // Hook not needed (or it's not possible to use it due // to missing dependency), remove it. delete this.get; return; } // Hook needed; redefine it so that the support test is not executed again. return ( this.get = hookFn ).apply( this, arguments ); } }; } var // Swappable if display is none or starts with table // except "table", "table-cell", or "table-caption" // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display rdisplayswap = /^(none|table(?!-c[ea]).+)/, rcustomProp = /^--/, cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssNormalTransform = { letterSpacing: "0", fontWeight: "400" }, cssPrefixes = [ "Webkit", "Moz", "ms" ], emptyStyle = document.createElement( "div" ).style; // Return a css property mapped to a potentially vendor prefixed property function vendorPropName( name ) { // Shortcut for names that are not vendor prefixed if ( name in emptyStyle ) { return name; } // Check for vendor prefixed names var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), i = cssPrefixes.length; while ( i-- ) { name = cssPrefixes[ i ] + capName; if ( name in emptyStyle ) { return name; } } } // Return a property mapped along what jQuery.cssProps suggests or to // a vendor prefixed property. function finalPropName( name ) { var ret = jQuery.cssProps[ name ]; if ( !ret ) { ret = jQuery.cssProps[ name ] = vendorPropName( name ) || name; } return ret; } function setPositiveNumber( elem, value, subtract ) { // Any relative (+/-) values have already been // normalized at this point var matches = rcssNum.exec( value ); return matches ? // Guard against undefined "subtract", e.g., when used as in cssHooks Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : value; } function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { var i = dimension === "width" ? 1 : 0, extra = 0, delta = 0; // Adjustment may not be necessary if ( box === ( isBorderBox ? "border" : "content" ) ) { return 0; } for ( ; i < 4; i += 2 ) { // Both box models exclude margin if ( box === "margin" ) { delta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); } // If we get here with a content-box, we're seeking "padding" or "border" or "margin" if ( !isBorderBox ) { // Add padding delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); // For "border" or "margin", add border if ( box !== "padding" ) { delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); // But still keep track of it otherwise } else { extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } // If we get here with a border-box (content + padding + border), we're seeking "content" or // "padding" or "margin" } else { // For "content", subtract padding if ( box === "content" ) { delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); } // For "content" or "padding", subtract border if ( box !== "margin" ) { delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); } } } // Account for positive content-box scroll gutter when requested by providing computedVal if ( !isBorderBox && computedVal >= 0 ) { // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border // Assuming integer scroll gutter, subtract the rest and round down delta += Math.max( 0, Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - computedVal - delta - extra - 0.5 ) ); } return delta; } function getWidthOrHeight( elem, dimension, extra ) { // Start with computed style var styles = getStyles( elem ), val = curCSS( elem, dimension, styles ), isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", valueIsBorderBox = isBorderBox; // Support: Firefox <=54 // Return a confounding non-pixel value or feign ignorance, as appropriate. if ( rnumnonpx.test( val ) ) { if ( !extra ) { return val; } val = "auto"; } // Check for style in case a browser which returns unreliable values // for getComputedStyle silently falls back to the reliable elem.style valueIsBorderBox = valueIsBorderBox && ( support.boxSizingReliable() || val === elem.style[ dimension ] ); // Fall back to offsetWidth/offsetHeight when value is "auto" // This happens for inline elements with no explicit setting (gh-3571) // Support: Android <=4.1 - 4.3 only // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) if ( val === "auto" || !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) { val = elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ]; // offsetWidth/offsetHeight provide border-box values valueIsBorderBox = true; } // Normalize "" and auto val = parseFloat( val ) || 0; // Adjust for the element's box model return ( val + boxModelAdjustment( elem, dimension, extra || ( isBorderBox ? "border" : "content" ), valueIsBorderBox, styles, // Provide the current computed size to request scroll gutter calculation (gh-3589) val ) ) + "px"; } jQuery.extend( { // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity" ); return ret === "" ? "1" : ret; } } } }, // Don't automatically add "px" to these possibly-unitless properties cssNumber: { "animationIterationCount": true, "columnCount": true, "fillOpacity": true, "flexGrow": true, "flexShrink": true, "fontWeight": true, "lineHeight": true, "opacity": true, "order": true, "orphans": true, "widows": true, "zIndex": true, "zoom": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: {}, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, type, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ), style = elem.style; // Make sure that we're working with the right name. We don't // want to query the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Gets hook for the prefixed version, then unprefixed version hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // Check if we're setting a value if ( value !== undefined ) { type = typeof value; // Convert "+=" or "-=" to relative numbers (#7345) if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { value = adjustCSS( elem, name, ret ); // Fixes bug #9237 type = "number"; } // Make sure that null and NaN values aren't set (#7116) if ( value == null || value !== value ) { return; } // If a number was passed in, add the unit (except for certain CSS properties) if ( type === "number" ) { value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); } // background-* props affect original clone's values if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { style[ name ] = "inherit"; } // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !( "set" in hooks ) || ( value = hooks.set( elem, value, extra ) ) !== undefined ) { if ( isCustomProp ) { style.setProperty( name, value ); } else { style[ name ] = value; } } } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { return ret; } // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra, styles ) { var val, num, hooks, origName = camelCase( name ), isCustomProp = rcustomProp.test( name ); // Make sure that we're working with the right name. We don't // want to modify the value if it is a CSS custom property // since they are user-defined. if ( !isCustomProp ) { name = finalPropName( origName ); } // Try prefixed name followed by the unprefixed name hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; // If a hook was provided get the computed value from there if ( hooks && "get" in hooks ) { val = hooks.get( elem, true, extra ); } // Otherwise, if a way to get the computed value exists, use that if ( val === undefined ) { val = curCSS( elem, name, styles ); } // Convert "normal" to computed value if ( val === "normal" && name in cssNormalTransform ) { val = cssNormalTransform[ name ]; } // Make numeric if forced or a qualifier was provided and val looks numeric if ( extra === "" || extra ) { num = parseFloat( val ); return extra === true || isFinite( num ) ? num || 0 : val; } return val; } } ); jQuery.each( [ "height", "width" ], function( i, dimension ) { jQuery.cssHooks[ dimension ] = { get: function( elem, computed, extra ) { if ( computed ) { // Certain elements can have dimension info if we invisibly show them // but it must have a current display style that would benefit return rdisplayswap.test( jQuery.css( elem, "display" ) ) && // Support: Safari 8+ // Table columns in Safari have non-zero offsetWidth & zero // getBoundingClientRect().width unless display is changed. // Support: IE <=11 only // Running getBoundingClientRect on a disconnected node // in IE throws an error. ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? swap( elem, cssShow, function() { return getWidthOrHeight( elem, dimension, extra ); } ) : getWidthOrHeight( elem, dimension, extra ); } }, set: function( elem, value, extra ) { var matches, styles = getStyles( elem ), isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box", subtract = extra && boxModelAdjustment( elem, dimension, extra, isBorderBox, styles ); // Account for unreliable border-box dimensions by comparing offset* to computed and // faking a content-box to get border and padding (gh-3699) if ( isBorderBox && support.scrollboxSize() === styles.position ) { subtract -= Math.ceil( elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - parseFloat( styles[ dimension ] ) - boxModelAdjustment( elem, dimension, "border", false, styles ) - 0.5 ); } // Convert to pixels if value adjustment is needed if ( subtract && ( matches = rcssNum.exec( value ) ) && ( matches[ 3 ] || "px" ) !== "px" ) { elem.style[ dimension ] = value; value = jQuery.css( elem, dimension ); } return setPositiveNumber( elem, value, subtract ); } }; } ); jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, function( elem, computed ) { if ( computed ) { return ( parseFloat( curCSS( elem, "marginLeft" ) ) || elem.getBoundingClientRect().left - swap( elem, { marginLeft: 0 }, function() { return elem.getBoundingClientRect().left; } ) ) + "px"; } } ); // These hooks are used by animate to expand properties jQuery.each( { margin: "", padding: "", border: "Width" }, function( prefix, suffix ) { jQuery.cssHooks[ prefix + suffix ] = { expand: function( value ) { var i = 0, expanded = {}, // Assumes a single number if not a string parts = typeof value === "string" ? value.split( " " ) : [ value ]; for ( ; i < 4; i++ ) { expanded[ prefix + cssExpand[ i ] + suffix ] = parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; } return expanded; } }; if ( prefix !== "margin" ) { jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; } } ); jQuery.fn.extend( { css: function( name, value ) { return access( this, function( elem, name, value ) { var styles, len, map = {}, i = 0; if ( Array.isArray( name ) ) { styles = getStyles( elem ); len = name.length; for ( ; i < len; i++ ) { map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); } return map; } return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }, name, value, arguments.length > 1 ); } } ); function Tween( elem, options, prop, end, easing ) { return new Tween.prototype.init( elem, options, prop, end, easing ); } jQuery.Tween = Tween; Tween.prototype = { constructor: Tween, init: function( elem, options, prop, end, easing, unit ) { this.elem = elem; this.prop = prop; this.easing = easing || jQuery.easing._default; this.options = options; this.start = this.now = this.cur(); this.end = end; this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); }, cur: function() { var hooks = Tween.propHooks[ this.prop ]; return hooks && hooks.get ? hooks.get( this ) : Tween.propHooks._default.get( this ); }, run: function( percent ) { var eased, hooks = Tween.propHooks[ this.prop ]; if ( this.options.duration ) { this.pos = eased = jQuery.easing[ this.easing ]( percent, this.options.duration * percent, 0, 1, this.options.duration ); } else { this.pos = eased = percent; } this.now = ( this.end - this.start ) * eased + this.start; if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } if ( hooks && hooks.set ) { hooks.set( this ); } else { Tween.propHooks._default.set( this ); } return this; } }; Tween.prototype.init.prototype = Tween.prototype; Tween.propHooks = { _default: { get: function( tween ) { var result; // Use a property on the element directly when it is not a DOM element, // or when there is no matching style property that exists. if ( tween.elem.nodeType !== 1 || tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { return tween.elem[ tween.prop ]; } // Passing an empty string as a 3rd parameter to .css will automatically // attempt a parseFloat and fallback to a string if the parse fails. // Simple values such as "10px" are parsed to Float; // complex values such as "rotate(1rad)" are returned as-is. result = jQuery.css( tween.elem, tween.prop, "" ); // Empty strings, null, undefined and "auto" are converted to 0. return !result || result === "auto" ? 0 : result; }, set: function( tween ) { // Use step hook for back compat. // Use cssHook if its there. // Use .style if available and use plain properties where available. if ( jQuery.fx.step[ tween.prop ] ) { jQuery.fx.step[ tween.prop ]( tween ); } else if ( tween.elem.nodeType === 1 && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) { jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); } else { tween.elem[ tween.prop ] = tween.now; } } } }; // Support: IE <=9 only // Panic based approach to setting things on disconnected nodes Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { set: function( tween ) { if ( tween.elem.nodeType && tween.elem.parentNode ) { tween.elem[ tween.prop ] = tween.now; } } }; jQuery.easing = { linear: function( p ) { return p; }, swing: function( p ) { return 0.5 - Math.cos( p * Math.PI ) / 2; }, _default: "swing" }; jQuery.fx = Tween.prototype.init; // Back compat <1.8 extension point jQuery.fx.step = {}; var fxNow, inProgress, rfxtypes = /^(?:toggle|show|hide)$/, rrun = /queueHooks$/; function schedule() { if ( inProgress ) { if ( document.hidden === false && window.requestAnimationFrame ) { window.requestAnimationFrame( schedule ); } else { window.setTimeout( schedule, jQuery.fx.interval ); } jQuery.fx.tick(); } } // Animations created synchronously will run synchronously function createFxNow() { window.setTimeout( function() { fxNow = undefined; } ); return ( fxNow = Date.now() ); } // Generate parameters to create a standard animation function genFx( type, includeWidth ) { var which, i = 0, attrs = { height: type }; // If we include width, step value is 1 to do all cssExpand values, // otherwise step value is 2 to skip over Left and Right includeWidth = includeWidth ? 1 : 0; for ( ; i < 4; i += 2 - includeWidth ) { which = cssExpand[ i ]; attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; } if ( includeWidth ) { attrs.opacity = attrs.width = type; } return attrs; } function createTween( value, prop, animation ) { var tween, collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), index = 0, length = collection.length; for ( ; index < length; index++ ) { if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { // We're done with this property return tween; } } } function defaultPrefilter( elem, props, opts ) { var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, isBox = "width" in props || "height" in props, anim = this, orig = {}, style = elem.style, hidden = elem.nodeType && isHiddenWithinTree( elem ), dataShow = dataPriv.get( elem, "fxshow" ); // Queue-skipping animations hijack the fx hooks if ( !opts.queue ) { hooks = jQuery._queueHooks( elem, "fx" ); if ( hooks.unqueued == null ) { hooks.unqueued = 0; oldfire = hooks.empty.fire; hooks.empty.fire = function() { if ( !hooks.unqueued ) { oldfire(); } }; } hooks.unqueued++; anim.always( function() { // Ensure the complete handler is called before this completes anim.always( function() { hooks.unqueued--; if ( !jQuery.queue( elem, "fx" ).length ) { hooks.empty.fire(); } } ); } ); } // Detect show/hide animations for ( prop in props ) { value = props[ prop ]; if ( rfxtypes.test( value ) ) { delete props[ prop ]; toggle = toggle || value === "toggle"; if ( value === ( hidden ? "hide" : "show" ) ) { // Pretend to be hidden if this is a "show" and // there is still data from a stopped show/hide if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { hidden = true; // Ignore all other no-op show/hide data } else { continue; } } orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); } } // Bail out if this is a no-op like .hide().hide() propTween = !jQuery.isEmptyObject( props ); if ( !propTween && jQuery.isEmptyObject( orig ) ) { return; } // Restrict "overflow" and "display" styles during box animations if ( isBox && elem.nodeType === 1 ) { // Support: IE <=9 - 11, Edge 12 - 15 // Record all 3 overflow attributes because IE does not infer the shorthand // from identically-valued overflowX and overflowY and Edge just mirrors // the overflowX value there. opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; // Identify a display type, preferring old show/hide data over the CSS cascade restoreDisplay = dataShow && dataShow.display; if ( restoreDisplay == null ) { restoreDisplay = dataPriv.get( elem, "display" ); } display = jQuery.css( elem, "display" ); if ( display === "none" ) { if ( restoreDisplay ) { display = restoreDisplay; } else { // Get nonempty value(s) by temporarily forcing visibility showHide( [ elem ], true ); restoreDisplay = elem.style.display || restoreDisplay; display = jQuery.css( elem, "display" ); showHide( [ elem ] ); } } // Animate inline elements as inline-block if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { if ( jQuery.css( elem, "float" ) === "none" ) { // Restore the original display value at the end of pure show/hide animations if ( !propTween ) { anim.done( function() { style.display = restoreDisplay; } ); if ( restoreDisplay == null ) { display = style.display; restoreDisplay = display === "none" ? "" : display; } } style.display = "inline-block"; } } } if ( opts.overflow ) { style.overflow = "hidden"; anim.always( function() { style.overflow = opts.overflow[ 0 ]; style.overflowX = opts.overflow[ 1 ]; style.overflowY = opts.overflow[ 2 ]; } ); } // Implement show/hide animations propTween = false; for ( prop in orig ) { // General show/hide setup for this element animation if ( !propTween ) { if ( dataShow ) { if ( "hidden" in dataShow ) { hidden = dataShow.hidden; } } else { dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); } // Store hidden/visible for toggle so `.stop().toggle()` "reverses" if ( toggle ) { dataShow.hidden = !hidden; } // Show elements before animating them if ( hidden ) { showHide( [ elem ], true ); } /* eslint-disable no-loop-func */ anim.done( function() { /* eslint-enable no-loop-func */ // The final step of a "hide" animation is actually hiding the element if ( !hidden ) { showHide( [ elem ] ); } dataPriv.remove( elem, "fxshow" ); for ( prop in orig ) { jQuery.style( elem, prop, orig[ prop ] ); } } ); } // Per-property setup propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); if ( !( prop in dataShow ) ) { dataShow[ prop ] = propTween.start; if ( hidden ) { propTween.end = propTween.start; propTween.start = 0; } } } } function propFilter( props, specialEasing ) { var index, name, easing, value, hooks; // camelCase, specialEasing and expand cssHook pass for ( index in props ) { name = camelCase( index ); easing = specialEasing[ name ]; value = props[ index ]; if ( Array.isArray( value ) ) { easing = value[ 1 ]; value = props[ index ] = value[ 0 ]; } if ( index !== name ) { props[ name ] = value; delete props[ index ]; } hooks = jQuery.cssHooks[ name ]; if ( hooks && "expand" in hooks ) { value = hooks.expand( value ); delete props[ name ]; // Not quite $.extend, this won't overwrite existing keys. // Reusing 'index' because we have the correct "name" for ( index in value ) { if ( !( index in props ) ) { props[ index ] = value[ index ]; specialEasing[ index ] = easing; } } } else { specialEasing[ name ] = easing; } } } function Animation( elem, properties, options ) { var result, stopped, index = 0, length = Animation.prefilters.length, deferred = jQuery.Deferred().always( function() { // Don't match elem in the :animated selector delete tick.elem; } ), tick = function() { if ( stopped ) { return false; } var currentTime = fxNow || createFxNow(), remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), // Support: Android 2.3 only // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497) temp = remaining / animation.duration || 0, percent = 1 - temp, index = 0, length = animation.tweens.length; for ( ; index < length; index++ ) { animation.tweens[ index ].run( percent ); } deferred.notifyWith( elem, [ animation, percent, remaining ] ); // If there's more to do, yield if ( percent < 1 && length ) { return remaining; } // If this was an empty animation, synthesize a final progress notification if ( !length ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); } // Resolve the animation and report its conclusion deferred.resolveWith( elem, [ animation ] ); return false; }, animation = deferred.promise( { elem: elem, props: jQuery.extend( {}, properties ), opts: jQuery.extend( true, { specialEasing: {}, easing: jQuery.easing._default }, options ), originalProperties: properties, originalOptions: options, startTime: fxNow || createFxNow(), duration: options.duration, tweens: [], createTween: function( prop, end ) { var tween = jQuery.Tween( elem, animation.opts, prop, end, animation.opts.specialEasing[ prop ] || animation.opts.easing ); animation.tweens.push( tween ); return tween; }, stop: function( gotoEnd ) { var index = 0, // If we are going to the end, we want to run all the tweens // otherwise we skip this part length = gotoEnd ? animation.tweens.length : 0; if ( stopped ) { return this; } stopped = true; for ( ; index < length; index++ ) { animation.tweens[ index ].run( 1 ); } // Resolve when we played the last frame; otherwise, reject if ( gotoEnd ) { deferred.notifyWith( elem, [ animation, 1, 0 ] ); deferred.resolveWith( elem, [ animation, gotoEnd ] ); } else { deferred.rejectWith( elem, [ animation, gotoEnd ] ); } return this; } } ), props = animation.props; propFilter( props, animation.opts.specialEasing ); for ( ; index < length; index++ ) { result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); if ( result ) { if ( isFunction( result.stop ) ) { jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = result.stop.bind( result ); } return result; } } jQuery.map( props, createTween, animation ); if ( isFunction( animation.opts.start ) ) { animation.opts.start.call( elem, animation ); } // Attach callbacks from options animation .progress( animation.opts.progress ) .done( animation.opts.done, animation.opts.complete ) .fail( animation.opts.fail ) .always( animation.opts.always ); jQuery.fx.timer( jQuery.extend( tick, { elem: elem, anim: animation, queue: animation.opts.queue } ) ); return animation; } jQuery.Animation = jQuery.extend( Animation, { tweeners: { "*": [ function( prop, value ) { var tween = this.createTween( prop, value ); adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); return tween; } ] }, tweener: function( props, callback ) { if ( isFunction( props ) ) { callback = props; props = [ "*" ]; } else { props = props.match( rnothtmlwhite ); } var prop, index = 0, length = props.length; for ( ; index < length; index++ ) { prop = props[ index ]; Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; Animation.tweeners[ prop ].unshift( callback ); } }, prefilters: [ defaultPrefilter ], prefilter: function( callback, prepend ) { if ( prepend ) { Animation.prefilters.unshift( callback ); } else { Animation.prefilters.push( callback ); } } } ); jQuery.speed = function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { complete: fn || !fn && easing || isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !isFunction( easing ) && easing }; // Go to the end state if fx are off if ( jQuery.fx.off ) { opt.duration = 0; } else { if ( typeof opt.duration !== "number" ) { if ( opt.duration in jQuery.fx.speeds ) { opt.duration = jQuery.fx.speeds[ opt.duration ]; } else { opt.duration = jQuery.fx.speeds._default; } } } // Normalize opt.queue - true/undefined/null -> "fx" if ( opt.queue == null || opt.queue === true ) { opt.queue = "fx"; } // Queueing opt.old = opt.complete; opt.complete = function() { if ( isFunction( opt.old ) ) { opt.old.call( this ); } if ( opt.queue ) { jQuery.dequeue( this, opt.queue ); } }; return opt; }; jQuery.fn.extend( { fadeTo: function( speed, to, easing, callback ) { // Show any hidden elements after setting opacity to 0 return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() // Animate to the value specified .end().animate( { opacity: to }, speed, easing, callback ); }, animate: function( prop, speed, easing, callback ) { var empty = jQuery.isEmptyObject( prop ), optall = jQuery.speed( speed, easing, callback ), doAnimation = function() { // Operate on a copy of prop so per-property easing won't be lost var anim = Animation( this, jQuery.extend( {}, prop ), optall ); // Empty animations, or finishing resolves immediately if ( empty || dataPriv.get( this, "finish" ) ) { anim.stop( true ); } }; doAnimation.finish = doAnimation; return empty || optall.queue === false ? this.each( doAnimation ) : this.queue( optall.queue, doAnimation ); }, stop: function( type, clearQueue, gotoEnd ) { var stopQueue = function( hooks ) { var stop = hooks.stop; delete hooks.stop; stop( gotoEnd ); }; if ( typeof type !== "string" ) { gotoEnd = clearQueue; clearQueue = type; type = undefined; } if ( clearQueue && type !== false ) { this.queue( type || "fx", [] ); } return this.each( function() { var dequeue = true, index = type != null && type + "queueHooks", timers = jQuery.timers, data = dataPriv.get( this ); if ( index ) { if ( data[ index ] && data[ index ].stop ) { stopQueue( data[ index ] ); } } else { for ( index in data ) { if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { stopQueue( data[ index ] ); } } } for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && ( type == null || timers[ index ].queue === type ) ) { timers[ index ].anim.stop( gotoEnd ); dequeue = false; timers.splice( index, 1 ); } } // Start the next in the queue if the last step wasn't forced. // Timers currently will call their complete callbacks, which // will dequeue but only if they were gotoEnd. if ( dequeue || !gotoEnd ) { jQuery.dequeue( this, type ); } } ); }, finish: function( type ) { if ( type !== false ) { type = type || "fx"; } return this.each( function() { var index, data = dataPriv.get( this ), queue = data[ type + "queue" ], hooks = data[ type + "queueHooks" ], timers = jQuery.timers, length = queue ? queue.length : 0; // Enable finishing flag on private data data.finish = true; // Empty the queue first jQuery.queue( this, type, [] ); if ( hooks && hooks.stop ) { hooks.stop.call( this, true ); } // Look for any active animations, and finish them for ( index = timers.length; index--; ) { if ( timers[ index ].elem === this && timers[ index ].queue === type ) { timers[ index ].anim.stop( true ); timers.splice( index, 1 ); } } // Look for any animations in the old queue and finish them for ( index = 0; index < length; index++ ) { if ( queue[ index ] && queue[ index ].finish ) { queue[ index ].finish.call( this ); } } // Turn off finishing flag delete data.finish; } ); } } ); jQuery.each( [ "toggle", "show", "hide" ], function( i, name ) { var cssFn = jQuery.fn[ name ]; jQuery.fn[ name ] = function( speed, easing, callback ) { return speed == null || typeof speed === "boolean" ? cssFn.apply( this, arguments ) : this.animate( genFx( name, true ), speed, easing, callback ); }; } ); // Generate shortcuts for custom animations jQuery.each( { slideDown: genFx( "show" ), slideUp: genFx( "hide" ), slideToggle: genFx( "toggle" ), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; } ); jQuery.timers = []; jQuery.fx.tick = function() { var timer, i = 0, timers = jQuery.timers; fxNow = Date.now(); for ( ; i < timers.length; i++ ) { timer = timers[ i ]; // Run the timer and safely remove it when done (allowing for external removal) if ( !timer() && timers[ i ] === timer ) { timers.splice( i--, 1 ); } } if ( !timers.length ) { jQuery.fx.stop(); } fxNow = undefined; }; jQuery.fx.timer = function( timer ) { jQuery.timers.push( timer ); jQuery.fx.start(); }; jQuery.fx.interval = 13; jQuery.fx.start = function() { if ( inProgress ) { return; } inProgress = true; schedule(); }; jQuery.fx.stop = function() { inProgress = null; }; jQuery.fx.speeds = { slow: 600, fast: 200, // Default speed _default: 400 }; // Based off of the plugin by Clint Helfers, with permission. // https://web.archive.org/web/20100324014747/http://blindsignals.com/index.php/2009/07/jquery-delay/ jQuery.fn.delay = function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; return this.queue( type, function( next, hooks ) { var timeout = window.setTimeout( next, time ); hooks.stop = function() { window.clearTimeout( timeout ); }; } ); }; ( function() { var input = document.createElement( "input" ), select = document.createElement( "select" ), opt = select.appendChild( document.createElement( "option" ) ); input.type = "checkbox"; // Support: Android <=4.3 only // Default value for a checkbox should be "on" support.checkOn = input.value !== ""; // Support: IE <=11 only // Must access selectedIndex to make default options select support.optSelected = opt.selected; // Support: IE <=11 only // An input loses its value after becoming a radio input = document.createElement( "input" ); input.value = "t"; input.type = "radio"; support.radioValue = input.value === "t"; } )(); var boolHook, attrHandle = jQuery.expr.attrHandle; jQuery.fn.extend( { attr: function( name, value ) { return access( this, jQuery.attr, name, value, arguments.length > 1 ); }, removeAttr: function( name ) { return this.each( function() { jQuery.removeAttr( this, name ); } ); } } ); jQuery.extend( { attr: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set attributes on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } // Fallback to prop when attributes are not supported if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } // Attribute hooks are determined by the lowercase version // Grab necessary hook if one is defined if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { hooks = jQuery.attrHooks[ name.toLowerCase() ] || ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); return; } if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } elem.setAttribute( name, value + "" ); return value; } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } ret = jQuery.find.attr( elem, name ); // Non-existent attributes return null, we normalize to undefined return ret == null ? undefined : ret; }, attrHooks: { type: { set: function( elem, value ) { if ( !support.radioValue && value === "radio" && nodeName( elem, "input" ) ) { var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } } }, removeAttr: function( elem, value ) { var name, i = 0, // Attribute names can contain non-HTML whitespace characters // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 attrNames = value && value.match( rnothtmlwhite ); if ( attrNames && elem.nodeType === 1 ) { while ( ( name = attrNames[ i++ ] ) ) { elem.removeAttribute( name ); } } } } ); // Hooks for boolean attributes boolHook = { set: function( elem, value, name ) { if ( value === false ) { // Remove boolean attributes when set to false jQuery.removeAttr( elem, name ); } else { elem.setAttribute( name, name ); } return name; } }; jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) { var getter = attrHandle[ name ] || jQuery.find.attr; attrHandle[ name ] = function( elem, name, isXML ) { var ret, handle, lowercaseName = name.toLowerCase(); if ( !isXML ) { // Avoid an infinite loop by temporarily removing this function from the getter handle = attrHandle[ lowercaseName ]; attrHandle[ lowercaseName ] = ret; ret = getter( elem, name, isXML ) != null ? lowercaseName : null; attrHandle[ lowercaseName ] = handle; } return ret; }; } ); var rfocusable = /^(?:input|select|textarea|button)$/i, rclickable = /^(?:a|area)$/i; jQuery.fn.extend( { prop: function( name, value ) { return access( this, jQuery.prop, name, value, arguments.length > 1 ); }, removeProp: function( name ) { return this.each( function() { delete this[ jQuery.propFix[ name ] || name ]; } ); } } ); jQuery.extend( { prop: function( elem, name, value ) { var ret, hooks, nType = elem.nodeType; // Don't get/set properties on text, comment and attribute nodes if ( nType === 3 || nType === 8 || nType === 2 ) { return; } if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { // Fix name and attach hooks name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && ( ret = hooks.set( elem, value, name ) ) !== undefined ) { return ret; } return ( elem[ name ] = value ); } if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { return ret; } return elem[ name ]; }, propHooks: { tabIndex: { get: function( elem ) { // Support: IE <=9 - 11 only // elem.tabIndex doesn't always return the // correct value when it hasn't been explicitly set // https://web.archive.org/web/20141116233347/http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // Use proper attribute retrieval(#12072) var tabindex = jQuery.find.attr( elem, "tabindex" ); if ( tabindex ) { return parseInt( tabindex, 10 ); } if ( rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ) { return 0; } return -1; } } }, propFix: { "for": "htmlFor", "class": "className" } } ); // Support: IE <=11 only // Accessing the selectedIndex property // forces the browser to respect setting selected // on the option // The getter ensures a default option is selected // when in an optgroup // eslint rule "no-unused-expressions" is disabled for this code // since it considers such accessions noop if ( !support.optSelected ) { jQuery.propHooks.selected = { get: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent && parent.parentNode ) { parent.parentNode.selectedIndex; } return null; }, set: function( elem ) { /* eslint no-unused-expressions: "off" */ var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } }; } jQuery.each( [ "tabIndex", "readOnly", "maxLength", "cellSpacing", "cellPadding", "rowSpan", "colSpan", "useMap", "frameBorder", "contentEditable" ], function() { jQuery.propFix[ this.toLowerCase() ] = this; } ); // Strip and collapse whitespace according to HTML spec // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace function stripAndCollapse( value ) { var tokens = value.match( rnothtmlwhite ) || []; return tokens.join( " " ); } function getClass( elem ) { return elem.getAttribute && elem.getAttribute( "class" ) || ""; } function classesToArray( value ) { if ( Array.isArray( value ) ) { return value; } if ( typeof value === "string" ) { return value.match( rnothtmlwhite ) || []; } return []; } jQuery.fn.extend( { addClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); } ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { if ( cur.indexOf( " " + clazz + " " ) < 0 ) { cur += clazz + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, removeClass: function( value ) { var classes, elem, cur, curValue, clazz, j, finalValue, i = 0; if ( isFunction( value ) ) { return this.each( function( j ) { jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); } ); } if ( !arguments.length ) { return this.attr( "class", "" ); } classes = classesToArray( value ); if ( classes.length ) { while ( ( elem = this[ i++ ] ) ) { curValue = getClass( elem ); // This expression is here for better compressibility (see addClass) cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { j = 0; while ( ( clazz = classes[ j++ ] ) ) { // Remove *all* instances while ( cur.indexOf( " " + clazz + " " ) > -1 ) { cur = cur.replace( " " + clazz + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { elem.setAttribute( "class", finalValue ); } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isValidValue = type === "string" || Array.isArray( value ); if ( typeof stateVal === "boolean" && isValidValue ) { return stateVal ? this.addClass( value ) : this.removeClass( value ); } if ( isFunction( value ) ) { return this.each( function( i ) { jQuery( this ).toggleClass( value.call( this, i, getClass( this ), stateVal ), stateVal ); } ); } return this.each( function() { var className, i, self, classNames; if ( isValidValue ) { // Toggle individual class names i = 0; self = jQuery( this ); classNames = classesToArray( value ); while ( ( className = classNames[ i++ ] ) ) { // Check each className given, space separated list if ( self.hasClass( className ) ) { self.removeClass( className ); } else { self.addClass( className ); } } // Toggle whole class name } else if ( value === undefined || type === "boolean" ) { className = getClass( this ); if ( className ) { // Store className if set dataPriv.set( this, "__className__", className ); } // If the element has a class name or if we're passed `false`, // then remove the whole classname (if there was one, the above saved it). // Otherwise bring back whatever was previously saved (if anything), // falling back to the empty string if nothing was stored. if ( this.setAttribute ) { this.setAttribute( "class", className || value === false ? "" : dataPriv.get( this, "__className__" ) || "" ); } } } ); }, hasClass: function( selector ) { var className, elem, i = 0; className = " " + selector + " "; while ( ( elem = this[ i++ ] ) ) { if ( elem.nodeType === 1 && ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { return true; } } return false; } } ); var rreturn = /\r/g; jQuery.fn.extend( { val: function( value ) { var hooks, ret, valueIsFunction, elem = this[ 0 ]; if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && ( ret = hooks.get( elem, "value" ) ) !== undefined ) { return ret; } ret = elem.value; // Handle most common string cases if ( typeof ret === "string" ) { return ret.replace( rreturn, "" ); } // Handle cases where value is null/undef or number return ret == null ? "" : ret; } return; } valueIsFunction = isFunction( value ); return this.each( function( i ) { var val; if ( this.nodeType !== 1 ) { return; } if ( valueIsFunction ) { val = value.call( this, i, jQuery( this ).val() ); } else { val = value; } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( Array.isArray( val ) ) { val = jQuery.map( val, function( value ) { return value == null ? "" : value + ""; } ); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // If set returns undefined, fall back to normal setting if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } } ); } } ); jQuery.extend( { valHooks: { option: { get: function( elem ) { var val = jQuery.find.attr( elem, "value" ); return val != null ? val : // Support: IE <=10 - 11 only // option.text throws exceptions (#14686, #14858) // Strip and collapse whitespace // https://html.spec.whatwg.org/#strip-and-collapse-whitespace stripAndCollapse( jQuery.text( elem ) ); } }, select: { get: function( elem ) { var value, option, i, options = elem.options, index = elem.selectedIndex, one = elem.type === "select-one", values = one ? null : [], max = one ? index + 1 : options.length; if ( index < 0 ) { i = max; } else { i = one ? index : 0; } // Loop through all the selected options for ( ; i < max; i++ ) { option = options[ i ]; // Support: IE <=9 only // IE8-9 doesn't update selected after form reset (#2551) if ( ( option.selected || i === index ) && // Don't return options that are disabled or in a disabled optgroup !option.disabled && ( !option.parentNode.disabled || !nodeName( option.parentNode, "optgroup" ) ) ) { // Get the specific value for the option value = jQuery( option ).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; }, set: function( elem, value ) { var optionSet, option, options = elem.options, values = jQuery.makeArray( value ), i = options.length; while ( i-- ) { option = options[ i ]; /* eslint-disable no-cond-assign */ if ( option.selected = jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 ) { optionSet = true; } /* eslint-enable no-cond-assign */ } // Force browsers to behave consistently when non-matching value is set if ( !optionSet ) { elem.selectedIndex = -1; } return values; } } } } ); // Radios and checkboxes getter/setter jQuery.each( [ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { set: function( elem, value ) { if ( Array.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); } } }; if ( !support.checkOn ) { jQuery.valHooks[ this ].get = function( elem ) { return elem.getAttribute( "value" ) === null ? "on" : elem.value; }; } } ); // Return jQuery for attributes-only inclusion support.focusin = "onfocusin" in window; var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, stopPropagationCallback = function( e ) { e.stopPropagation(); }; jQuery.extend( jQuery.event, { trigger: function( event, data, elem, onlyHandlers ) { var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, eventPath = [ elem || document ], type = hasOwn.call( event, "type" ) ? event.type : event, namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; cur = lastElement = tmp = elem = elem || document; // Don't do events on text and comment nodes if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; } // focus/blur morphs to focusin/out; ensure we're not firing them right now if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "." ) > -1 ) { // Namespaced trigger; create a regexp to match event type in handle() namespaces = type.split( "." ); type = namespaces.shift(); namespaces.sort(); } ontype = type.indexOf( ":" ) < 0 && "on" + type; // Caller can pass in a jQuery.Event object, Object, or just an event type string event = event[ jQuery.expando ] ? event : new jQuery.Event( type, typeof event === "object" && event ); // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) event.isTrigger = onlyHandlers ? 2 : 3; event.namespace = namespaces.join( "." ); event.rnamespace = event.namespace ? new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : null; // Clean up the event in case it is being reused event.result = undefined; if ( !event.target ) { event.target = elem; } // Clone any incoming data and prepend the event, creating the handler arg list data = data == null ? [ event ] : jQuery.makeArray( data, [ event ] ); // Allow special events to draw outside the lines special = jQuery.event.special[ type ] || {}; if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // Determine event propagation path in advance, per W3C events spec (#9951) // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { bubbleType = special.delegateType || type; if ( !rfocusMorph.test( bubbleType + type ) ) { cur = cur.parentNode; } for ( ; cur; cur = cur.parentNode ) { eventPath.push( cur ); tmp = cur; } // Only add window if we got to document (e.g., not plain obj or detached DOM) if ( tmp === ( elem.ownerDocument || document ) ) { eventPath.push( tmp.defaultView || tmp.parentWindow || window ); } } // Fire handlers on the event path i = 0; while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { lastElement = cur; event.type = i > 1 ? bubbleType : special.bindType || type; // jQuery handler handle = ( dataPriv.get( cur, "events" ) || {} )[ event.type ] && dataPriv.get( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // Native handler handle = ontype && cur[ ontype ]; if ( handle && handle.apply && acceptData( cur ) ) { event.result = handle.apply( cur, data ); if ( event.result === false ) { event.preventDefault(); } } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( ( !special._default || special._default.apply( eventPath.pop(), data ) === false ) && acceptData( elem ) ) { // Call a native DOM method on the target with the same name as the event. // Don't do default actions on window, that's where global variables be (#6170) if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method tmp = elem[ ontype ]; if ( tmp ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; if ( event.isPropagationStopped() ) { lastElement.addEventListener( type, stopPropagationCallback ); } elem[ type ](); if ( event.isPropagationStopped() ) { lastElement.removeEventListener( type, stopPropagationCallback ); } jQuery.event.triggered = undefined; if ( tmp ) { elem[ ontype ] = tmp; } } } } return event.result; }, // Piggyback on a donor event to simulate a different one // Used only for `focus(in | out)` events simulate: function( type, elem, event ) { var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true } ); jQuery.event.trigger( e, null, elem ); } } ); jQuery.fn.extend( { trigger: function( type, data ) { return this.each( function() { jQuery.event.trigger( type, data, this ); } ); }, triggerHandler: function( type, data ) { var elem = this[ 0 ]; if ( elem ) { return jQuery.event.trigger( type, data, elem, true ); } } } ); // Support: Firefox <=44 // Firefox doesn't have focus(in | out) events // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 // // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 // focus(in | out) events fire after focus & blur events, // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 if ( !support.focusin ) { jQuery.each( { focus: "focusin", blur: "focusout" }, function( orig, fix ) { // Attach a single capturing handler on the document while someone wants focusin/focusout var handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ) ); }; jQuery.event.special[ fix ] = { setup: function() { var doc = this.ownerDocument || this, attaches = dataPriv.access( doc, fix ); if ( !attaches ) { doc.addEventListener( orig, handler, true ); } dataPriv.access( doc, fix, ( attaches || 0 ) + 1 ); }, teardown: function() { var doc = this.ownerDocument || this, attaches = dataPriv.access( doc, fix ) - 1; if ( !attaches ) { doc.removeEventListener( orig, handler, true ); dataPriv.remove( doc, fix ); } else { dataPriv.access( doc, fix, attaches ); } } }; } ); } var location = window.location; var nonce = Date.now(); var rquery = ( /\?/ ); // Cross-browser xml parsing jQuery.parseXML = function( data ) { var xml; if ( !data || typeof data !== "string" ) { return null; } // Support: IE 9 - 11 only // IE throws on parseFromString with invalid input. try { xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); } catch ( e ) { xml = undefined; } if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }; var rbracket = /\[\]$/, rCRLF = /\r?\n/g, rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, rsubmittable = /^(?:input|select|textarea|keygen)/i; function buildParams( prefix, obj, traditional, add ) { var name; if ( Array.isArray( obj ) ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", v, traditional, add ); } } ); } else if ( !traditional && toType( obj ) === "object" ) { // Serialize object item. for ( name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string jQuery.param = function( a, traditional ) { var prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value var value = isFunction( valueOrFunction ) ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ); }; jQuery.fn.extend( { serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map( function() { // Can add propHook for "elements" to filter or add form elements var elements = jQuery.prop( this, "elements" ); return elements ? jQuery.makeArray( elements ) : this; } ) .filter( function() { var type = this.type; // Use .is( ":disabled" ) so that fieldset[disabled] works return this.name && !jQuery( this ).is( ":disabled" ) && rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && ( this.checked || !rcheckableType.test( type ) ); } ) .map( function( i, elem ) { var val = jQuery( this ).val(); if ( val == null ) { return null; } if ( Array.isArray( val ) ) { return jQuery.map( val, function( val ) { return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ); } return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; } ).get(); } } ); var r20 = /%20/g, rhash = /#.*$/, rantiCache = /([?&])_=[^&]*/, rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, // #7653, #8125, #8152: local protocol detection rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}, // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression allTypes = "*/".concat( "*" ), // Anchor tag for parsing the document origin originAnchor = document.createElement( "a" ); originAnchor.href = location.href; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport function addToPrefiltersOrTransports( structure ) { // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } var dataType, i = 0, dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; if ( isFunction( func ) ) { // For each dataType in the dataTypeExpression while ( ( dataType = dataTypes[ i++ ] ) ) { // Prepend if requested if ( dataType[ 0 ] === "+" ) { dataType = dataType.slice( 1 ) || "*"; ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); // Otherwise append } else { ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); } } } }; } // Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { var inspected = {}, seekingTransport = ( structure === transports ); function inspect( dataType ) { var selected; inspected[ dataType ] = true; jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) { options.dataTypes.unshift( dataTypeOrTransport ); inspect( dataTypeOrTransport ); return false; } else if ( seekingTransport ) { return !( selected = dataTypeOrTransport ); } } ); return selected; } return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); } // A special extend for ajax options // that takes "flat" options (not to be deep extended) // Fixes #9887 function ajaxExtend( target, src ) { var key, deep, flatOptions = jQuery.ajaxSettings.flatOptions || {}; for ( key in src ) { if ( src[ key ] !== undefined ) { ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; } } if ( deep ) { jQuery.extend( true, target, deep ); } return target; } /* Handles responses to an ajax request: * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jqXHR, responses ) { var ct, type, finalDataType, firstDataType, contents = s.contents, dataTypes = s.dataTypes; // Remove auto dataType and get content-type in the process while ( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } /* Chain conversions given the request and the original response * Also sets the responseXXX fields on the jqXHR instance */ function ajaxConvert( s, response, jqXHR, isSuccess ) { var conv2, current, conv, tmp, prev, converters = {}, // Work with a copy of dataTypes in case we need to modify it for conversion dataTypes = s.dataTypes.slice(); // Create converters map with lowercased keys if ( dataTypes[ 1 ] ) { for ( conv in s.converters ) { converters[ conv.toLowerCase() ] = s.converters[ conv ]; } } current = dataTypes.shift(); // Convert to each sequential dataType while ( current ) { if ( s.responseFields[ current ] ) { jqXHR[ s.responseFields[ current ] ] = response; } // Apply the dataFilter if provided if ( !prev && isSuccess && s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } prev = current; current = dataTypes.shift(); if ( current ) { // There's only work to do if current dataType is non-auto if ( current === "*" ) { current = prev; // Convert response if prev dataType is non-auto and differs from current } else if ( prev !== "*" && prev !== current ) { // Seek a direct converter conv = converters[ prev + " " + current ] || converters[ "* " + current ]; // If none found, seek a pair if ( !conv ) { for ( conv2 in converters ) { // If conv2 outputs current tmp = conv2.split( " " ); if ( tmp[ 1 ] === current ) { // If prev can be converted to accepted input conv = converters[ prev + " " + tmp[ 0 ] ] || converters[ "* " + tmp[ 0 ] ]; if ( conv ) { // Condense equivalence converters if ( conv === true ) { conv = converters[ conv2 ]; // Otherwise, insert the intermediate dataType } else if ( converters[ conv2 ] !== true ) { current = tmp[ 0 ]; dataTypes.unshift( tmp[ 1 ] ); } break; } } } } // Apply converter (if not an equivalence) if ( conv !== true ) { // Unless errors are allowed to bubble, catch and return them if ( conv && s.throws ) { response = conv( response ); } else { try { response = conv( response ); } catch ( e ) { return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current }; } } } } } } return { state: "success", data: response }; } jQuery.extend( { // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {}, ajaxSettings: { url: location.href, type: "GET", isLocal: rlocalProtocol.test( location.protocol ), global: true, processData: true, async: true, contentType: "application/x-www-form-urlencoded; charset=UTF-8", /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, throws: false, traditional: false, headers: {}, */ accepts: { "*": allTypes, text: "text/plain", html: "text/html", xml: "application/xml, text/xml", json: "application/json, text/javascript" }, contents: { xml: /\bxml\b/, html: /\bhtml/, json: /\bjson\b/ }, responseFields: { xml: "responseXML", text: "responseText", json: "responseJSON" }, // Data converters // Keys separate source (or catchall "*") and destination types with a single space converters: { // Convert anything to text "* text": String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": JSON.parse, // Parse text as xml "text xml": jQuery.parseXML }, // For options that shouldn't be deep extended: // you can add your own custom options here if // and when you create one that shouldn't be // deep extended (see ajaxExtend) flatOptions: { url: true, context: true } }, // Creates a full fledged settings object into target // with both ajaxSettings and settings fields. // If target is omitted, writes into ajaxSettings. ajaxSetup: function( target, settings ) { return settings ? // Building a settings object ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : // Extending ajaxSettings ajaxExtend( jQuery.ajaxSettings, target ); }, ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), ajaxTransport: addToPrefiltersOrTransports( transports ), // Main method ajax: function( url, options ) { // If url is an object, simulate pre-1.5 signature if ( typeof url === "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var transport, // URL without anti-cache param cacheURL, // Response headers responseHeadersString, responseHeaders, // timeout handle timeoutTimer, // Url cleanup var urlAnchor, // Request state (becomes false upon send and true upon completion) completed, // To know if global events are to be dispatched fireGlobals, // Loop variable i, // uncached part of the url uncached, // Create the final options object s = jQuery.ajaxSetup( {}, options ), // Callbacks context callbackContext = s.context || s, // Context for global events is callbackContext if it is a DOM node or jQuery collection globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ? jQuery( callbackContext ) : jQuery.event, // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery.Callbacks( "once memory" ), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, requestHeadersNames = {}, // Default abort message strAbort = "canceled", // Fake xhr jqXHR = { readyState: 0, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( completed ) { if ( !responseHeaders ) { responseHeaders = {}; while ( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[ 1 ].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match == null ? null : match; }, // Raw string getAllResponseHeaders: function() { return completed ? responseHeadersString : null; }, // Caches the header setRequestHeader: function( name, value ) { if ( completed == null ) { name = requestHeadersNames[ name.toLowerCase() ] = requestHeadersNames[ name.toLowerCase() ] || name; requestHeaders[ name ] = value; } return this; }, // Overrides response content-type header overrideMimeType: function( type ) { if ( completed == null ) { s.mimeType = type; } return this; }, // Status-dependent callbacks statusCode: function( map ) { var code; if ( map ) { if ( completed ) { // Execute the appropriate callbacks jqXHR.always( map[ jqXHR.status ] ); } else { // Lazy-add the new callbacks in a way that preserves old ones for ( code in map ) { statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; } } } return this; }, // Cancel the request abort: function( statusText ) { var finalText = statusText || strAbort; if ( transport ) { transport.abort( finalText ); } done( 0, finalText ); return this; } }; // Attach deferreds deferred.promise( jqXHR ); // Add protocol if not provided (prefilters might expect it) // Handle falsy url in the settings object (#10093: consistency with old signature) // We also use the url parameter if available s.url = ( ( url || s.url || location.href ) + "" ) .replace( rprotocol, location.protocol + "//" ); // Alias method option to type as per ticket #12004 s.type = options.method || options.type || s.method || s.type; // Extract dataTypes list s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; // A cross-domain request is in order when the origin doesn't match the current origin. if ( s.crossDomain == null ) { urlAnchor = document.createElement( "a" ); // Support: IE <=8 - 11, Edge 12 - 15 // IE throws exception on accessing the href property if url is malformed, // e.g. http://example.com:80x/ try { urlAnchor.href = s.url; // Support: IE <=8 - 11 only // Anchor's host property isn't correctly set when s.url is relative urlAnchor.href = urlAnchor.href; s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== urlAnchor.protocol + "//" + urlAnchor.host; } catch ( e ) { // If there is an error parsing the URL, assume it is crossDomain, // it can be rejected by the transport if it is invalid s.crossDomain = true; } } // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { s.data = jQuery.param( s.data, s.traditional ); } // Apply prefilters inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); // If request was aborted inside a prefilter, stop there if ( completed ) { return jqXHR; } // We can fire global events as of now if asked to // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118) fireGlobals = jQuery.event && s.global; // Watch for a new set of requests if ( fireGlobals && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); } // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content s.hasContent = !rnoContent.test( s.type ); // Save the URL in case we're toying with the If-Modified-Since // and/or If-None-Match header later on // Remove hash to simplify url manipulation cacheURL = s.url.replace( rhash, "" ); // More options handling for requests with no content if ( !s.hasContent ) { // Remember the hash so we can put it back uncached = s.url.slice( cacheURL.length ); // If data is available and should be processed, append data to url if ( s.data && ( s.processData || typeof s.data === "string" ) ) { cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; // #9682: remove data so that it's not used in an eventual retry delete s.data; } // Add or update anti-cache param if needed if ( s.cache === false ) { cacheURL = cacheURL.replace( rantiCache, "$1" ); uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce++ ) + uncached; } // Put hash and anti-cache on the URL that will be requested (gh-1732) s.url = cacheURL + uncached; // Change '%20' to '+' if this is encoded form body content (gh-2658) } else if ( s.data && s.processData && ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { s.data = s.data.replace( r20, "+" ); } // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ cacheURL ] ) { jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); } if ( jQuery.etag[ cacheURL ] ) { jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); } } // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { jqXHR.setRequestHeader( "Content-Type", s.contentType ); } // Set the Accepts header for the server, depending on the dataType jqXHR.setRequestHeader( "Accept", s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? s.accepts[ s.dataTypes[ 0 ] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : s.accepts[ "*" ] ); // Check for headers option for ( i in s.headers ) { jqXHR.setRequestHeader( i, s.headers[ i ] ); } // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { // Abort if not done already and return return jqXHR.abort(); } // Aborting is no longer a cancellation strAbort = "abort"; // Install callbacks on deferreds completeDeferred.add( s.complete ); jqXHR.done( s.success ); jqXHR.fail( s.error ); // Get transport transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { jqXHR.readyState = 1; // Send global event if ( fireGlobals ) { globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); } // If request was aborted inside ajaxSend, stop there if ( completed ) { return jqXHR; } // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = window.setTimeout( function() { jqXHR.abort( "timeout" ); }, s.timeout ); } try { completed = false; transport.send( requestHeaders, done ); } catch ( e ) { // Rethrow post-completion exceptions if ( completed ) { throw e; } // Propagate others as results done( -1, e ); } } // Callback for when everything is done function done( status, nativeStatusText, responses, headers ) { var isSuccess, success, error, response, modified, statusText = nativeStatusText; // Ignore repeat invocations if ( completed ) { return; } completed = true; // Clear timeout if it exists if ( timeoutTimer ) { window.clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jqXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jqXHR.readyState = status > 0 ? 4 : 0; // Determine if successful isSuccess = status >= 200 && status < 300 || status === 304; // Get response data if ( responses ) { response = ajaxHandleResponses( s, jqXHR, responses ); } // Convert no matter what (that way responseXXX fields are always set) response = ajaxConvert( s, response, jqXHR, isSuccess ); // If successful, handle type chaining if ( isSuccess ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { modified = jqXHR.getResponseHeader( "Last-Modified" ); if ( modified ) { jQuery.lastModified[ cacheURL ] = modified; } modified = jqXHR.getResponseHeader( "etag" ); if ( modified ) { jQuery.etag[ cacheURL ] = modified; } } // if no content if ( status === 204 || s.type === "HEAD" ) { statusText = "nocontent"; // if not modified } else if ( status === 304 ) { statusText = "notmodified"; // If we have data, let's convert it } else { statusText = response.state; success = response.data; error = response.error; isSuccess = !error; } } else { // Extract error from statusText and normalize for non-aborts error = statusText; if ( status || !statusText ) { statusText = "error"; if ( status < 0 ) { status = 0; } } } // Set data for the fake xhr object jqXHR.status = status; jqXHR.statusText = ( nativeStatusText || statusText ) + ""; // Success/Error if ( isSuccess ) { deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); } else { deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); } // Status-dependent callbacks jqXHR.statusCode( statusCode ); statusCode = undefined; if ( fireGlobals ) { globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", [ jqXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); if ( fireGlobals ) { globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } } return jqXHR; }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, getScript: function( url, callback ) { return jQuery.get( url, undefined, callback, "script" ); } } ); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // Shift arguments if data argument was omitted if ( isFunction( data ) ) { type = type || callback; callback = data; data = undefined; } // The url can be an options object (which then must have .url) return jQuery.ajax( jQuery.extend( { url: url, type: method, dataType: type, data: data, success: callback }, jQuery.isPlainObject( url ) && url ) ); }; } ); jQuery._evalUrl = function( url ) { return jQuery.ajax( { url: url, // Make this explicit, since user can override this through ajaxSetup (#11264) type: "GET", dataType: "script", cache: true, async: false, global: false, "throws": true } ); }; jQuery.fn.extend( { wrapAll: function( html ) { var wrap; if ( this[ 0 ] ) { if ( isFunction( html ) ) { html = html.call( this[ 0 ] ); } // The elements to wrap the target around wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); if ( this[ 0 ].parentNode ) { wrap.insertBefore( this[ 0 ] ); } wrap.map( function() { var elem = this; while ( elem.firstElementChild ) { elem = elem.firstElementChild; } return elem; } ).append( this ); } return this; }, wrapInner: function( html ) { if ( isFunction( html ) ) { return this.each( function( i ) { jQuery( this ).wrapInner( html.call( this, i ) ); } ); } return this.each( function() { var self = jQuery( this ), contents = self.contents(); if ( contents.length ) { contents.wrapAll( html ); } else { self.append( html ); } } ); }, wrap: function( html ) { var htmlIsFunction = isFunction( html ); return this.each( function( i ) { jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); } ); }, unwrap: function( selector ) { this.parent( selector ).not( "body" ).each( function() { jQuery( this ).replaceWith( this.childNodes ); } ); return this; } } ); jQuery.expr.pseudos.hidden = function( elem ) { return !jQuery.expr.pseudos.visible( elem ); }; jQuery.expr.pseudos.visible = function( elem ) { return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); }; jQuery.ajaxSettings.xhr = function() { try { return new window.XMLHttpRequest(); } catch ( e ) {} }; var xhrSuccessStatus = { // File protocol always yields status code 0, assume 200 0: 200, // Support: IE <=9 only // #1450: sometimes IE returns 1223 when it should be 204 1223: 204 }, xhrSupported = jQuery.ajaxSettings.xhr(); support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); support.ajax = xhrSupported = !!xhrSupported; jQuery.ajaxTransport( function( options ) { var callback, errorCallback; // Cross domain only allowed if supported through XMLHttpRequest if ( support.cors || xhrSupported && !options.crossDomain ) { return { send: function( headers, complete ) { var i, xhr = options.xhr(); xhr.open( options.type, options.url, options.async, options.username, options.password ); // Apply custom fields if provided if ( options.xhrFields ) { for ( i in options.xhrFields ) { xhr[ i ] = options.xhrFields[ i ]; } } // Override mime type if needed if ( options.mimeType && xhr.overrideMimeType ) { xhr.overrideMimeType( options.mimeType ); } // X-Requested-With header // For cross-domain requests, seeing as conditions for a preflight are // akin to a jigsaw puzzle, we simply never set it to be sure. // (it can always be set on a per-request basis or even using ajaxSetup) // For same-domain requests, won't change header if already provided. if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { headers[ "X-Requested-With" ] = "XMLHttpRequest"; } // Set headers for ( i in headers ) { xhr.setRequestHeader( i, headers[ i ] ); } // Callback callback = function( type ) { return function() { if ( callback ) { callback = errorCallback = xhr.onload = xhr.onerror = xhr.onabort = xhr.ontimeout = xhr.onreadystatechange = null; if ( type === "abort" ) { xhr.abort(); } else if ( type === "error" ) { // Support: IE <=9 only // On a manual native abort, IE9 throws // errors on any property access that is not readyState if ( typeof xhr.status !== "number" ) { complete( 0, "error" ); } else { complete( // File: protocol always yields status 0; see #8605, #14207 xhr.status, xhr.statusText ); } } else { complete( xhrSuccessStatus[ xhr.status ] || xhr.status, xhr.statusText, // Support: IE <=9 only // IE9 has no XHR2 but throws on binary (trac-11426) // For XHR2 non-text, let the caller handle it (gh-2498) ( xhr.responseType || "text" ) !== "text" || typeof xhr.responseText !== "string" ? { binary: xhr.response } : { text: xhr.responseText }, xhr.getAllResponseHeaders() ); } } }; }; // Listen to events xhr.onload = callback(); errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); // Support: IE 9 only // Use onreadystatechange to replace onabort // to handle uncaught aborts if ( xhr.onabort !== undefined ) { xhr.onabort = errorCallback; } else { xhr.onreadystatechange = function() { // Check readyState before timeout as it changes if ( xhr.readyState === 4 ) { // Allow onerror to be called first, // but that will not handle a native abort // Also, save errorCallback to a variable // as xhr.onerror cannot be accessed window.setTimeout( function() { if ( callback ) { errorCallback(); } } ); } }; } // Create the abort callback callback = callback( "abort" ); try { // Do send the request (this may raise an exception) xhr.send( options.hasContent && options.data || null ); } catch ( e ) { // #14683: Only rethrow if this hasn't been notified as an error yet if ( callback ) { throw e; } } }, abort: function() { if ( callback ) { callback(); } } }; } } ); // Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) jQuery.ajaxPrefilter( function( s ) { if ( s.crossDomain ) { s.contents.script = false; } } ); // Install script dataType jQuery.ajaxSetup( { accepts: { script: "text/javascript, application/javascript, " + "application/ecmascript, application/x-ecmascript" }, contents: { script: /\b(?:java|ecma)script\b/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } } ); // Handle cache's special case and crossDomain jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; } } ); // Bind script tag hack transport jQuery.ajaxTransport( "script", function( s ) { // This transport only deals with cross domain requests if ( s.crossDomain ) { var script, callback; return { send: function( _, complete ) { script = jQuery( "<script>" ).prop( { charset: s.scriptCharset, src: s.url } ).on( "load error", callback = function( evt ) { script.remove(); callback = null; if ( evt ) { complete( evt.type === "error" ? 404 : 200, evt.type ); } } ); // Use native DOM manipulation to avoid our domManip AJAX trickery document.head.appendChild( script[ 0 ] ); }, abort: function() { if ( callback ) { callback(); } } }; } } ); var oldCallbacks = [], rjsonp = /(=)\?(?=&|$)|\?\?/; // Default jsonp settings jQuery.ajaxSetup( { jsonp: "callback", jsonpCallback: function() { var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) ); this[ callback ] = true; return callback; } } ); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) { var callbackName, overwritten, responseContainer, jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ? "url" : typeof s.data === "string" && ( s.contentType || "" ) .indexOf( "application/x-www-form-urlencoded" ) === 0 && rjsonp.test( s.data ) && "data" ); // Handle iff the expected data type is "jsonp" or we have a parameter to set if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) { // Get callback name, remembering preexisting value associated with it callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback; // Insert callback into url or form data if ( jsonProp ) { s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName ); } else if ( s.jsonp !== false ) { s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName; } // Use data converter to retrieve json after script execution s.converters[ "script json" ] = function() { if ( !responseContainer ) { jQuery.error( callbackName + " was not called" ); } return responseContainer[ 0 ]; }; // Force json dataType s.dataTypes[ 0 ] = "json"; // Install callback overwritten = window[ callbackName ]; window[ callbackName ] = function() { responseContainer = arguments; }; // Clean-up function (fires after converters) jqXHR.always( function() { // If previous value didn't exist - remove it if ( overwritten === undefined ) { jQuery( window ).removeProp( callbackName ); // Otherwise restore preexisting value } else { window[ callbackName ] = overwritten; } // Save back as free if ( s[ callbackName ] ) { // Make sure that re-using the options doesn't screw things around s.jsonpCallback = originalSettings.jsonpCallback; // Save the callback name for future use oldCallbacks.push( callbackName ); } // Call if it was a function and we have a response if ( responseContainer && isFunction( overwritten ) ) { overwritten( responseContainer[ 0 ] ); } responseContainer = overwritten = undefined; } ); // Delegate to script return "script"; } } ); // Support: Safari 8 only // In Safari 8 documents created via document.implementation.createHTMLDocument // collapse sibling forms: the second one becomes a child of the first one. // Because of that, this security measure has to be disabled in Safari 8. // https://bugs.webkit.org/show_bug.cgi?id=137337 support.createHTMLDocument = ( function() { var body = document.implementation.createHTMLDocument( "" ).body; body.innerHTML = "<form></form><form></form>"; return body.childNodes.length === 2; } )(); // Argument "data" should be string of html // context (optional): If specified, the fragment will be created in this context, // defaults to document // keepScripts (optional): If true, will include scripts passed in the html string jQuery.parseHTML = function( data, context, keepScripts ) { if ( typeof data !== "string" ) { return []; } if ( typeof context === "boolean" ) { keepScripts = context; context = false; } var base, parsed, scripts; if ( !context ) { // Stop scripts or inline event handlers from being executed immediately // by using document.implementation if ( support.createHTMLDocument ) { context = document.implementation.createHTMLDocument( "" ); // Set the base href for the created document // so any parsed elements with URLs // are based on the document's URL (gh-2965) base = context.createElement( "base" ); base.href = document.location.href; context.head.appendChild( base ); } else { context = document; } } parsed = rsingleTag.exec( data ); scripts = !keepScripts && []; // Single tag if ( parsed ) { return [ context.createElement( parsed[ 1 ] ) ]; } parsed = buildFragment( [ data ], context, scripts ); if ( scripts && scripts.length ) { jQuery( scripts ).remove(); } return jQuery.merge( [], parsed.childNodes ); }; /** * Load a url into a page */ jQuery.fn.load = function( url, params, callback ) { var selector, type, response, self = this, off = url.indexOf( " " ); if ( off > -1 ) { selector = stripAndCollapse( url.slice( off ) ); url = url.slice( 0, off ); } // If it's a function if ( isFunction( params ) ) { // We assume that it's the callback callback = params; params = undefined; // Otherwise, build a param string } else if ( params && typeof params === "object" ) { type = "POST"; } // If we have elements to modify, make the request if ( self.length > 0 ) { jQuery.ajax( { url: url, // If "type" variable is undefined, then "GET" method will be used. // Make value of this field explicit since // user can override it through ajaxSetup method type: type || "GET", dataType: "html", data: params } ).done( function( responseText ) { // Save response for use in complete callback response = arguments; self.html( selector ? // If a selector was specified, locate the right elements in a dummy div // Exclude scripts to avoid IE 'Permission Denied' errors jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) : // Otherwise use the full result responseText ); // If the request succeeds, this function gets "data", "status", "jqXHR" // but they are ignored because response was set above. // If it fails, this function gets "jqXHR", "status", "error" } ).always( callback && function( jqXHR, status ) { self.each( function() { callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] ); } ); } ); } return this; }; // Attach a bunch of functions for handling common AJAX events jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) { jQuery.fn[ type ] = function( fn ) { return this.on( type, fn ); }; } ); jQuery.expr.pseudos.animated = function( elem ) { return jQuery.grep( jQuery.timers, function( fn ) { return elem === fn.elem; } ).length; }; jQuery.offset = { setOffset: function( elem, options, i ) { var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition, position = jQuery.css( elem, "position" ), curElem = jQuery( elem ), props = {}; // Set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; } curOffset = curElem.offset(); curCSSTop = jQuery.css( elem, "top" ); curCSSLeft = jQuery.css( elem, "left" ); calculatePosition = ( position === "absolute" || position === "fixed" ) && ( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1; // Need to be able to calculate position if either // top or left is auto and position is either absolute or fixed if ( calculatePosition ) { curPosition = curElem.position(); curTop = curPosition.top; curLeft = curPosition.left; } else { curTop = parseFloat( curCSSTop ) || 0; curLeft = parseFloat( curCSSLeft ) || 0; } if ( isFunction( options ) ) { // Use jQuery.extend here to allow modification of coordinates argument (gh-1848) options = options.call( elem, i, jQuery.extend( {}, curOffset ) ); } if ( options.top != null ) { props.top = ( options.top - curOffset.top ) + curTop; } if ( options.left != null ) { props.left = ( options.left - curOffset.left ) + curLeft; } if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend( { // offset() relates an element's border box to the document origin offset: function( options ) { // Preserve chaining for setter if ( arguments.length ) { return options === undefined ? this : this.each( function( i ) { jQuery.offset.setOffset( this, options, i ); } ); } var rect, win, elem = this[ 0 ]; if ( !elem ) { return; } // Return zeros for disconnected and hidden (display: none) elements (gh-2310) // Support: IE <=11 only // Running getBoundingClientRect on a // disconnected node in IE throws an error if ( !elem.getClientRects().length ) { return { top: 0, left: 0 }; } // Get document-relative position by adding viewport scroll to viewport-relative gBCR rect = elem.getBoundingClientRect(); win = elem.ownerDocument.defaultView; return { top: rect.top + win.pageYOffset, left: rect.left + win.pageXOffset }; }, // position() relates an element's margin box to its offset parent's padding box // This corresponds to the behavior of CSS absolute positioning position: function() { if ( !this[ 0 ] ) { return; } var offsetParent, offset, doc, elem = this[ 0 ], parentOffset = { top: 0, left: 0 }; // position:fixed elements are offset from the viewport, which itself always has zero offset if ( jQuery.css( elem, "position" ) === "fixed" ) { // Assume position:fixed implies availability of getBoundingClientRect offset = elem.getBoundingClientRect(); } else { offset = this.offset(); // Account for the *real* offset parent, which can be the document or its root element // when a statically positioned element is identified doc = elem.ownerDocument; offsetParent = elem.offsetParent || doc.documentElement; while ( offsetParent && ( offsetParent === doc.body || offsetParent === doc.documentElement ) && jQuery.css( offsetParent, "position" ) === "static" ) { offsetParent = offsetParent.parentNode; } if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) { // Incorporate borders into its offset, since they are outside its content origin parentOffset = jQuery( offsetParent ).offset(); parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true ); parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true ); } } // Subtract parent offsets and element margins return { top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ), left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true ) }; }, // This method will return documentElement in the following cases: // 1) For the element inside the iframe without offsetParent, this method will return // documentElement of the parent window // 2) For the hidden or detached element // 3) For body or html element, i.e. in case of the html node - it will return itself // // but those exceptions were never presented as a real life use-cases // and might be considered as more preferable results. // // This logic, however, is not guaranteed and can change at any point in the future offsetParent: function() { return this.map( function() { var offsetParent = this.offsetParent; while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) { offsetParent = offsetParent.offsetParent; } return offsetParent || documentElement; } ); } } ); // Create scrollLeft and scrollTop methods jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) { var top = "pageYOffset" === prop; jQuery.fn[ method ] = function( val ) { return access( this, function( elem, method, val ) { // Coalesce documents and windows var win; if ( isWindow( elem ) ) { win = elem; } else if ( elem.nodeType === 9 ) { win = elem.defaultView; } if ( val === undefined ) { return win ? win[ prop ] : elem[ method ]; } if ( win ) { win.scrollTo( !top ? val : win.pageXOffset, top ? val : win.pageYOffset ); } else { elem[ method ] = val; } }, method, val, arguments.length ); }; } ); // Support: Safari <=7 - 9.1, Chrome <=37 - 49 // Add the top/left cssHooks using jQuery.fn.position // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084 // Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347 // getComputedStyle returns percent when specified for top/left/bottom/right; // rather than make the css module depend on the offset module, just check for it here jQuery.each( [ "top", "left" ], function( i, prop ) { jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition, function( elem, computed ) { if ( computed ) { computed = curCSS( elem, prop ); // If curCSS returns percentage, fallback to offset return rnumnonpx.test( computed ) ? jQuery( elem ).position()[ prop ] + "px" : computed; } } ); } ); // Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods jQuery.each( { Height: "height", Width: "width" }, function( name, type ) { jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) { // Margin is only for outerHeight, outerWidth jQuery.fn[ funcName ] = function( margin, value ) { var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ), extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" ); return access( this, function( elem, type, value ) { var doc; if ( isWindow( elem ) ) { // $( window ).outerWidth/Height return w/h including scrollbars (gh-1729) return funcName.indexOf( "outer" ) === 0 ? elem[ "inner" + name ] : elem.document.documentElement[ "client" + name ]; } // Get document width or height if ( elem.nodeType === 9 ) { doc = elem.documentElement; // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], // whichever is greatest return Math.max( elem.body[ "scroll" + name ], doc[ "scroll" + name ], elem.body[ "offset" + name ], doc[ "offset" + name ], doc[ "client" + name ] ); } return value === undefined ? // Get width or height on the element, requesting but not forcing parseFloat jQuery.css( elem, type, extra ) : // Set width or height on the element jQuery.style( elem, type, value, extra ); }, type, chainable ? margin : undefined, chainable ); }; } ); } ); jQuery.each( ( "blur focus focusin focusout resize scroll click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup contextmenu" ).split( " " ), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { return arguments.length > 0 ? this.on( name, null, data, fn ) : this.trigger( name ); }; } ); jQuery.fn.extend( { hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } } ); jQuery.fn.extend( { bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, unbind: function( types, fn ) { return this.off( types, null, fn ); }, delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn ); } } ); // Bind a function to a context, optionally partially applying any // arguments. // jQuery.proxy is deprecated to promote standards (specifically Function#bind) // However, it is not slated for removal any time soon jQuery.proxy = function( fn, context ) { var tmp, args, proxy; if ( typeof context === "string" ) { tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !isFunction( fn ) ) { return undefined; } // Simulated bind args = slice.call( arguments, 2 ); proxy = function() { return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || jQuery.guid++; return proxy; }; jQuery.holdReady = function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }; jQuery.isArray = Array.isArray; jQuery.parseJSON = JSON.parse; jQuery.nodeName = nodeName; jQuery.isFunction = isFunction; jQuery.isWindow = isWindow; jQuery.camelCase = camelCase; jQuery.type = toType; jQuery.now = Date.now; jQuery.isNumeric = function( obj ) { // As of jQuery 3.0, isNumeric is limited to // strings and numbers (primitives or objects) // that can be coerced to finite numbers (gh-2662) var type = jQuery.type( obj ); return ( type === "number" || type === "string" ) && // parseFloat NaNs numeric-cast false positives ("") // ...but misinterprets leading-number strings, particularly hex literals ("0x...") // subtraction forces infinities to NaN !isNaN( obj - parseFloat( obj ) ); }; // Register as a named AMD module, since jQuery can be concatenated with other // files that may use define, but not via a proper concatenation script that // understands anonymous AMD modules. A named AMD is safest and most robust // way to register. Lowercase jquery is used because AMD module names are // derived from file names, and jQuery is normally delivered in a lowercase // file name. Do this after creating the global so that if an AMD module wants // to call noConflict to hide this version of jQuery, it will work. // Note that for maximum portability, libraries that are not jQuery should // declare themselves as anonymous modules, and avoid setting a global if an // AMD loader is present. jQuery is a special case. For more information, see // https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon if ( typeof define === "function" && define.amd ) { define( "jquery", [], function() { return jQuery; } ); } var // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$; jQuery.noConflict = function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }; // Expose jQuery and $ identifiers, even in AMD // (#7102#comment:10, https://github.com/jquery/jquery/pull/557) // and CommonJS for browser emulators (#13566) if ( !noGlobal ) { window.jQuery = window.$ = jQuery; } return jQuery; } ); /*# sourceMappingURL=jquery.js.map */ ================================================ FILE: gui/static/js/jquery.tabledit.js ================================================ /*! * Tabledit v1.2.3 (https://github.com/markcell/jQuery-Tabledit) * Copyright (c) 2015 Celso Marques * Copyright (c) 2020 dOpenSource * Licensed under MIT (https://github.com/markcell/jQuery-Tabledit/blob/master/LICENSE) * * Modified draw to be public, and plugin can now be accessed via $(table).data('Tabledit') so new rows can be added dynamically * Modified settings to be public, plugin settings can be accessed / modified after initialization * Created globals to access some basic globals from outside the library * Allowed disabling of ajax requests, using new setting ajaxDisabled * Delete row permanently if restore button not enabled */ /** * @description Inline editor for HTML tables compatible with Bootstrap * @version 1.2.3 * @author Celso Marques * @author Tyler Moore */ if (typeof jQuery === 'undefined') { throw new Error('Tabledit requires jQuery library.'); } (function($) { 'use strict'; $.Tabledit = function(element, options) { if (!$(element).is('table')) { throw new Error('Tabledit only works when applied to a table.'); } var plugin = this; var defaults = { url: window.location.href, inputClass: 'form-control input-sm', toolbarClass: 'btn-toolbar', groupClass: 'btn-group btn-group-sm', dangerClass: 'danger', warningClass: 'warning', mutedClass: 'text-muted', eventType: 'click', rowIdentifier: 'id', ajaxDisabled: false, removeOnDelete: false, hideIdentifier: false, autoFocus: true, editButton: true, deleteButton: true, saveButton: true, restoreButton: true, buttons: { edit: { class: 'btn btn-sm btn-default', html: '<span class="glyphicon glyphicon-pencil"></span>', action: 'edit' }, delete: { class: 'btn btn-sm btn-default', html: '<span class="glyphicon glyphicon-trash"></span>', action: 'delete' }, save: { class: 'btn btn-sm btn-success', html: 'Save' }, restore: { class: 'btn btn-sm btn-warning', html: 'Restore', action: 'restore' }, confirm: { class: 'btn btn-sm btn-danger', html: 'Confirm' } }, onDraw: function() { return; }, onSuccess: function() { return; }, onFail: function() { return; }, onAlways: function() { return; }, onAjax: function() { return; } }; plugin.settings = $.extend(true, defaults, options); plugin.globals = { "tableObject": $(element), "lastEditedRow": $(), "lastDeletedRow": $(), "lastRestoredRow": $() }; /** * Draw Tabledit structure (identifier column, editable columns, toolbar column). * * @type {object} */ plugin.Draw = { columns: { identifier: function() { // Hide identifier column. if (plugin.settings.hideIdentifier) { plugin.globals.tableObject.find('th:nth-child(' + parseInt(plugin.settings.columns.identifier[0]) + 1 + '), tbody td:nth-child(' + parseInt(plugin.settings.columns.identifier[0]) + 1 + ')').hide(); } var $td = plugin.globals.tableObject.find('tbody tr:not([data-tabledit-done]) td:nth-child(' + (parseInt(plugin.settings.columns.identifier[0]) + 1) + ')'); $td.each(function() { // Create hidden input with row identifier. var span = '<span class="tabledit-span tabledit-identifier">' + $(this).text() + '</span>'; var input = '<input class="tabledit-input tabledit-identifier" type="hidden" name="' + plugin.settings.columns.identifier[1] + '" value="' + $(this).text() + '" disabled>'; // Add elements to table cell. $(this).html(span + input); // Add attribute "id" to table row. $(this).parent('tr').attr(plugin.settings.rowIdentifier, $(this).text()); }); }, editable: function() { for (var i = 0; i < plugin.settings.columns.editable.length; i++) { var $td = plugin.globals.tableObject.find('tbody tr:not([data-tabledit-done]) td:nth-child(' + (parseInt(plugin.settings.columns.editable[i][0]) + 1) + ')'); $td.each(function() { // Get text of this cell. var text = $(this).text(); // Add pointer as cursor. if (!plugin.settings.editButton) { $(this).css('cursor', 'pointer'); } // Create span element. var span = '<span class="tabledit-span">' + text + '</span>'; // Check if exists the third parameter of editable array. if (typeof plugin.settings.columns.editable[i][2] !== 'undefined') { // Create select element. var input = '<select class="tabledit-input ' + plugin.settings.inputClass + '" name="' + plugin.settings.columns.editable[i][1] + '" style="display: none;" disabled>'; // Create options for select element. $.each(jQuery.parseJSON(plugin.settings.columns.editable[i][2]), function(index, value) { if (text === value) { input += '<option value="' + index + '" selected>' + value + '</option>'; } else { input += '<option value="' + index + '">' + value + '</option>'; } }); // Create last piece of select element. input += '</select>'; } else { // Create text input element. var input = '<input class="tabledit-input ' + plugin.settings.inputClass + '" type="text" name="' + plugin.settings.columns.editable[i][1] + '" value="' + $(this).text() + '" style="display: none;" disabled>'; } // Add elements and class "view" to table cell. $(this).html(span + input); $(this).addClass('tabledit-view-mode'); }); } }, toolbar: function() { if (plugin.settings.editButton || plugin.settings.deleteButton) { var editButton = ''; var deleteButton = ''; var saveButton = ''; var restoreButton = ''; var confirmButton = ''; // Add toolbar column header if not exists. if (plugin.globals.tableObject.find('th.tabledit-toolbar-column').length === 0) { plugin.globals.tableObject.find('tr:first').append('<th class="tabledit-toolbar-column"></th>'); } // Create edit button. if (plugin.settings.editButton) { editButton = '<button type="button" class="tabledit-edit-button ' + plugin.settings.buttons.edit.class + '" style="float: none;">' + plugin.settings.buttons.edit.html + '</button>'; } // Create delete button. if (plugin.settings.deleteButton) { deleteButton = '<button type="button" class="tabledit-delete-button ' + plugin.settings.buttons.delete.class + '" style="float: none;">' + plugin.settings.buttons.delete.html + '</button>'; confirmButton = '<button type="button" class="tabledit-confirm-button ' + plugin.settings.buttons.confirm.class + '" style="display: none; float: none;">' + plugin.settings.buttons.confirm.html + '</button>'; } // Create save button. if (plugin.settings.editButton && plugin.settings.saveButton) { saveButton = '<button type="button" class="tabledit-save-button ' + plugin.settings.buttons.save.class + '" style="display: none; float: none;">' + plugin.settings.buttons.save.html + '</button>'; } // Create restore button. if (plugin.settings.deleteButton && plugin.settings.restoreButton) { restoreButton = '<button type="button" class="tabledit-restore-button ' + plugin.settings.buttons.restore.class + '" style="display: none; float: none;">' + plugin.settings.buttons.restore.html + '</button>'; } var toolbar = '<div class="tabledit-toolbar ' + plugin.settings.toolbarClass + '" style="text-align: left;">\n\ <div class="' + plugin.settings.groupClass + '" style="float: none;">' + editButton + deleteButton + '</div>\n\ ' + saveButton + '\n\ ' + confirmButton + '\n\ ' + restoreButton + '\n\ </div></div>'; // Add toolbar column cells. plugin.globals.tableObject.find('tr:gt(0):not([data-tabledit-done])').append('<td style="white-space: nowrap; width: 1%;">' + toolbar + '</td>').attr('data-tabledit-done', 1); } } } }; /** * Change to view mode or edit mode with table td element as parameter. * * @type object */ var Mode = { view: function(td) { // Get table row. var $tr = $(td).parent('tr'); // Disable identifier. $(td).parent('tr').find('.tabledit-input.tabledit-identifier').prop('disabled', true); // Hide and disable input element. $(td).find('.tabledit-input').blur().hide().prop('disabled', true); // Show span element. $(td).find('.tabledit-span').show(); // Add "view" class and remove "edit" class in td element. $(td).addClass('tabledit-view-mode').removeClass('tabledit-edit-mode'); // Update toolbar buttons. if (plugin.settings.editButton) { $tr.find('button.tabledit-save-button').hide(); $tr.find('button.tabledit-edit-button').removeClass('active').blur(); } }, edit: function(td) { Delete.reset(td); // Get table row. var $tr = $(td).parent('tr'); // Enable identifier. $tr.find('.tabledit-input.tabledit-identifier').prop('disabled', false); // Hide span element. $(td).find('.tabledit-span').hide(); // Get input element. var $input = $(td).find('.tabledit-input'); // Enable and show input element. $input.prop('disabled', false).show(); // Focus on input element. if (plugin.settings.autoFocus) { $input.focus(); } // Add "edit" class and remove "view" class in td element. $(td).addClass('tabledit-edit-mode').removeClass('tabledit-view-mode'); // Update toolbar buttons. if (plugin.settings.editButton) { $tr.find('button.tabledit-edit-button').addClass('active'); $tr.find('button.tabledit-save-button').show(); } } }; /** * Available actions for edit function, with table td element as parameter or set of td elements. * * @type object */ var Edit = { reset: function(td) { $(td).each(function() { // Get input element. var $input = $(this).find('.tabledit-input'); // Get span text. var text = $(this).find('.tabledit-span').text(); // Set input/select value with span text. if ($input.is('select')) { $input.find('option').filter(function() { return $.trim($(this).text()) === text; }).attr('selected', true); } else { $input.val(text); } // Change to view mode. Mode.view(this); }); }, submit: function(td) { // Send AJAX request to server. var ajaxResult = ajax(plugin.settings.buttons.edit.action); if (ajaxResult === false) { return; } $(td).each(function() { // Get input element. var $input = $(this).find('.tabledit-input'); // Set span text with input/select new value. if ($input.is('select')) { $(this).find('.tabledit-span').text($input.find('option:selected').text()); } else { $(this).find('.tabledit-span').text($input.val()); } // Change to view mode. Mode.view(this); }); // Set last edited column and row. plugin.globals.lastEditedRow = $(td).parent('tr'); } }; /** * Available actions for delete function, with button as parameter. * * @type object */ var Delete = { reset: function(td) { // Reset delete button to initial status. plugin.globals.tableObject.find('.tabledit-confirm-button').hide(); // Remove "active" class in delete button. plugin.globals.tableObject.find('.tabledit-delete-button').removeClass('active').blur(); }, submit: function(td) { Delete.reset(td); // Enable identifier hidden input. $(td).parent('tr').find('input.tabledit-identifier').attr('disabled', false); // Send AJAX request to server. var ajaxResult = ajax(plugin.settings.buttons.delete.action); // Disable identifier hidden input. $(td).parents('tr').find('input.tabledit-identifier').attr('disabled', true); if (ajaxResult === false) { return; } // delete permanently if restore button not enabled if (plugin.settings.restoreButton === false) { $(td).parent('tr').remove(); } else { // Add class "deleted" to row. $(td).parent('tr').addClass('tabledit-deleted-row'); // Hide table row. $(td).parent('tr').addClass(plugin.settings.mutedClass).find('.tabledit-toolbar button:not(.tabledit-restore-button)').attr('disabled', true); // Show restore button. $(td).find('.tabledit-restore-button').show(); // Set last deleted row. plugin.globals.lastDeletedRow = $(td).parent('tr'); } }, confirm: function(td) { // Reset all cells in edit mode. plugin.globals.tableObject.find('td.tabledit-edit-mode').each(function() { Edit.reset(this); }); // Add "active" class in delete button. $(td).find('.tabledit-delete-button').addClass('active'); // Show confirm button. $(td).find('.tabledit-confirm-button').show(); }, restore: function(td) { // Enable identifier hidden input. $(td).parent('tr').find('input.tabledit-identifier').attr('disabled', false); // Send AJAX request to server. var ajaxResult = ajax(plugin.settings.buttons.restore.action); // Disable identifier hidden input. $(td).parents('tr').find('input.tabledit-identifier').attr('disabled', true); if (ajaxResult === false) { return; } // Remove class "deleted" to row. $(td).parent('tr').removeClass('tabledit-deleted-row'); // Hide table row. $(td).parent('tr').removeClass(plugin.settings.mutedClass).find('.tabledit-toolbar button').attr('disabled', false); // Hide restore button. $(td).find('.tabledit-restore-button').hide(); // Set last restored row. plugin.globals.lastRestoredRow = $(td).parent('tr'); } }; /** * Send AJAX request to server. * * @param {string} action */ function ajax(action) { if (plugin.settings.ajaxDisabled) { try { if (action === plugin.settings.buttons.edit.action) { plugin.globals.lastEditedRow.removeClass(plugin.settings.dangerClass).addClass(plugin.settings.warningClass); setTimeout(function() { plugin.globals.tableObject.find('tr.' + plugin.settings.warningClass).removeClass(plugin.settings.warningClass); }, 1400); } return true; } catch(error) { if (action === plugin.settings.buttons.delete.action) { plugin.globals.lastDeletedRow.removeClass(plugin.settings.mutedClass).addClass(plugin.settings.dangerClass); plugin.globals.lastDeletedRow.find('.tabledit-toolbar button').attr('disabled', false); plugin.globals.lastDeletedRow.find('.tabledit-toolbar .tabledit-restore-button').hide(); } else if (action === plugin.settings.buttons.edit.action) { plugin.globals.lastEditedRow.addClass(plugin.settings.dangerClass); } return false; } } var serialize = plugin.globals.tableObject.find('.tabledit-input').serialize() + '&action=' + action; var result = plugin.settings.onAjax(action, serialize); if (result === false) { return false; } var jqXHR = $.post(plugin.settings.url, serialize, function(data, textStatus, jqXHR) { if (action === plugin.settings.buttons.edit.action) { plugin.globals.lastEditedRow.removeClass(plugin.settings.dangerClass).addClass(plugin.settings.warningClass); setTimeout(function() { //plugin.globals.lastEditedRow.removeClass(plugin.settings.warningClass); plugin.globals.tableObject.find('tr.' + plugin.settings.warningClass).removeClass(plugin.settings.warningClass); }, 1400); } plugin.settings.onSuccess(data, textStatus, jqXHR); }, 'json'); jqXHR.fail(function(jqXHR, textStatus, errorThrown) { if (action === plugin.settings.buttons.delete.action) { plugin.globals.lastDeletedRow.removeClass(plugin.settings.mutedClass).addClass(plugin.settings.dangerClass); plugin.globals.lastDeletedRow.find('.tabledit-toolbar button').attr('disabled', false); plugin.globals.lastDeletedRow.find('.tabledit-toolbar .tabledit-restore-button').hide(); } else if (action === plugin.settings.buttons.edit.action) { plugin.globals.lastEditedRow.addClass(plugin.settings.dangerClass); } plugin.settings.onFail(jqXHR, textStatus, errorThrown); }); jqXHR.always(function() { plugin.settings.onAlways(); }); return jqXHR; } plugin.Draw.columns.identifier(); plugin.Draw.columns.editable(); plugin.Draw.columns.toolbar(); plugin.settings.onDraw(); plugin.reload = function() { plugin.globals.lastEditedRow = $(); plugin.globals.lastDeletedRow = $(); plugin.globals.lastRestoredRow = $(); plugin.Draw.columns.identifier(); plugin.Draw.columns.editable(); plugin.Draw.columns.toolbar(); plugin.settings.onDraw(); }; if (plugin.settings.deleteButton) { /** * Delete one row. * * @param {object} event */ plugin.globals.tableObject.on('click', 'button.tabledit-delete-button', function(event) { if (event.handled !== true) { event.preventDefault(); // Get current state before reset to view mode. var activated = $(this).hasClass('active'); var $td = $(this).parents('td'); Delete.reset($td); if (!activated) { Delete.confirm($td); } event.handled = true; } }); /** * Delete one row (confirm). * * @param {object} event */ plugin.globals.tableObject.on('click', 'button.tabledit-confirm-button', function(event) { if (event.handled !== true) { event.preventDefault(); var $td = $(this).parents('td'); Delete.submit($td); event.handled = true; } }); } if (plugin.settings.restoreButton) { /** * Restore one row. * * @param {object} event */ plugin.globals.tableObject.on('click', 'button.tabledit-restore-button', function(event) { if (event.handled !== true) { event.preventDefault(); Delete.restore($(this).parents('td')); event.handled = true; } }); } if (plugin.settings.editButton) { /** * Activate edit mode on all columns. * * @param {object} event */ plugin.globals.tableObject.on('click', 'button.tabledit-edit-button', function(event) { if (event.handled !== true) { event.preventDefault(); var $button = $(this); // Get current state before reset to view mode. var activated = $button.hasClass('active'); // Change to view mode columns that are in edit mode. Edit.reset(plugin.globals.tableObject.find('td.tabledit-edit-mode')); if (!activated) { // Change to edit mode for all columns in reverse way. $($button.parents('tr').find('td.tabledit-view-mode').get().reverse()).each(function() { Mode.edit(this); }); } event.handled = true; } }); /** * Save edited row. * * @param {object} event */ plugin.globals.tableObject.on('click', 'button.tabledit-save-button', function(event) { if (event.handled !== true) { event.preventDefault(); // Submit and update all columns. Edit.submit($(this).parents('tr').find('td.tabledit-edit-mode')); event.handled = true; } }); } else { /** * Change to edit mode on table td element. * * @param {object} event */ plugin.globals.tableObject.on(plugin.settings.eventType, 'tr:not(.tabledit-deleted-row) td.tabledit-view-mode', function(event) { if (event.handled !== true) { event.preventDefault(); // Reset all td's in edit mode. Edit.reset(plugin.globals.tableObject.find('td.tabledit-edit-mode')); // Change to edit mode. Mode.edit(this); event.handled = true; } }); /** * Change event when input is a select element. */ plugin.globals.tableObject.on('change', 'select.tabledit-input:visible', function() { if (event.handled !== true) { // Submit and update the column. Edit.submit($(this).parent('td')); event.handled = true; } }); /** * Click event on document element. * * @param {object} event */ $(document).on('click', function(event) { var $editMode = plugin.globals.tableObject.find('.tabledit-edit-mode'); // Reset visible edit mode column. if (!$editMode.is(event.target) && $editMode.has(event.target).length === 0) { Edit.reset(plugin.globals.tableObject.find('.tabledit-input:visible').parent('td')); } }); } /** * Keyup event on document element. * * @param {object} event */ $(document).on('keyup', function(event) { // Get input element with focus or confirmation button. var $input = plugin.globals.tableObject.find('.tabledit-input:visible'); var $button = plugin.globals.tableObject.find('.tabledit-confirm-button'); if ($input.length > 0) { var $td = $input.parents('td'); } else if ($button.length > 0 && event.keyCode == 27) { var $td = $button.parents('td'); } else { return; } // Key? switch (event.keyCode) { case 9: // Tab. if (!plugin.settings.editButton) { Edit.submit($td); Mode.edit($td.closest('td').next()); } break; case 13: // Enter. Edit.submit($td); break; case 27: // Escape. Edit.reset($td); Delete.reset($td); break; } }); }; $.fn.Tabledit = function (options) { return this.each(function () { if (undefined == $(this).data('Tabledit')) { var plugin = new $.Tabledit(this, options); $(this).data('Tabledit', plugin); } }); } }(jQuery)); ================================================ FILE: gui/static/js/license_manager.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof validateFields === "undefined") { throw new Error("validateFields() is required and is not defined"); } if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof toggleElemDisabled === "undefined") { throw new Error("toggleElemDisabled() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } // global variables/constants for this script var license_table = {}; var loading_spinner = $('#loading-spinner'); // TODO: add laading animation while waiting on woocommerce query to finish function activateLicense() { var selector = "#add"; var modal_body = $(selector + ' .modal-body'); var payload = { "license_key": modal_body.find(".key").val(), "key_encrypted": false, }; $.ajax({ type: "PUT", url: API_BASE_URL + "licensing/activate", dataType: "json", contentType: "application/json; charset=utf-8", data: JSON.stringify(payload), success: function(response, textStatus, jqXHR) { if (response.error.length !== 0) { showNotification(response.msg, true); return; } hideModal('#add'); showNotification(response.msg); license_table.row.add({ "tags": response.data[0].tags, "license_key": response.data[0].license_key, "active": response.data[0].active, "valid": response.data[0].valid, "expires": response.data[0].expires, }).draw(); }, error: function(xhr, msg, error_msg) { error_msg = JSON.parse(xhr.responseText)["msg"]; showNotification(error_msg, true); }, }) } // TODO: add laading animation while waiting on woocommerce query to finish function deactivateLicense() { var modal_body = $('#delete .modal-body'); var license_key = modal_body.find(".key").val().trim(); var payload = { "license_key": license_key, "key_encrypted": false, }; $.ajax({ type: "PUT", url: API_BASE_URL + "licensing/deactivate", dataType: "json", contentType: "application/json; charset=utf-8", data: JSON.stringify(payload), success: function(response, textStatus, jqXHR) { if (response.error.length !== 0) { showNotification(response.msg, true); return; } hideModal('#delete'); showNotification(response.msg); license_table.row(function(idx, data, node) { return data.license_key === license_key; }).remove().draw(); }, error: function(xhr, msg, error_msg) { error_msg = JSON.parse(xhr.responseText)["msg"]; showNotification(error_msg, true); }, }); } // noinspection JSUnusedLocalSymbols function updateDeleteModal(self=null) { // attached via vanilla js event listener or called outside an event listener if (self === null) { self = $(this); } // attached via jQuery event listener else if (self instanceof jQuery.Event) { self = $(self.currentTarget); } // calling node passed ref to itself else { self = $(self); } var modal_body = $('#delete .modal-body'); var license_key = self.closest('tr').find('.key').val().trim(); modal_body.find(".key").val(license_key); } // export function to make it available in scope when called via dataTable widgets window.updateDeleteModal = updateDeleteModal; // noinspection JSUnusedLocalSymbols function togglePasswordHidden(self=null) { // attached via vanilla js event listener or called outside an event listener if (self === null) { self = $(this); } // attached via jQuery event listener else if (self instanceof jQuery.Event) { self = $(self.currentTarget); } // calling node passed ref to itself else { self = $(self); } var input = $(self.attr("data-toggle")); if (input.attr("type") === "password") { input.attr("type", "text"); self.removeClass("glyphicon glyphicon-eye-close"); self.addClass("glyphicon glyphicon-eye-open"); } else { input.attr("type", "password"); self.removeClass("glyphicon glyphicon-eye-open"); self.addClass("glyphicon glyphicon-eye-close"); } } // export function to make it available in scope when called via dataTable widgets window.togglePasswordHidden = togglePasswordHidden; function createDeleteButton() { return '' + '<div class="dt-resize-height">' + ' <button class="open-Delete btn btn-danger btn-xs" data-title="Deactivate" data-toggle="modal" data-target="#delete" onclick="updateDeleteModal(this)">' + ' <span class="glyphicon glyphicon-trash"></span>' + ' </button>' + '</div>'; } function createLicenseKeyField(data, type, row, meta) { var unique_key_id = "key-" + meta.row; return '' + '<div class="wrapper-fieldicon-right dt-resize-height">' + ' <input id="' + unique_key_id + '" class="key" type="password" name="key" value="' + data + '" readonly>' + ' <span class="field-icon toggle-password glyphicon glyphicon-eye-close" data-toggle="#' + unique_key_id + '" onclick="togglePasswordHidden(this)"></span>' + '</div>'; } function createReadonlyInputField(data, type, row, meta) { return '' + '<div class="dt-resize-height">' + ' <input type="text" value="' + data + '" readonly>' + '</div>'; } function createReadonlyTextArea(data, type, row, meta) { return '' + '<div class="dt-resize-height">' + ' <textarea readonly>' + data.join('\n') + '</textarea>' + '</div>'; } function dtDrawCallback(settings) { var rows = $('#licensing > tbody > tr'); var row_height = rows.eq(0).find('td:eq(0)').get(0).clientHeight; rows.find('td .dt-resize-height').css({'height': row_height}); } function hideModal(selector) { var modal_elem = $(selector); // do nothing if not visible if (modal_elem.is(':visible')) { // hide the modal after 1.5 sec setTimeout(function() { modal_elem.modal('hide'); }, 1500); } } $(document).ready(function() { // datatable init license_table = $('#licensing').DataTable({ "ajax": { "url": API_BASE_URL + "licensing/list", "type": "GET", "error": function(xhr, error, code) { var response = JSON.parse(xhr.responseText); requestErrorHandler(xhr.status, response.msg, response.error); } }, "columns": [ {"data": "tags", "render": createReadonlyTextArea}, {"data": "license_key", "render": createLicenseKeyField}, {"data": "active", "render": createReadonlyInputField}, {"data": "valid", "render": createReadonlyInputField}, {"data": "expires", "render": createReadonlyInputField}, {"data": null, "render": createDeleteButton, "searchable": false, "orderable": false}, ], "order": [[0, 'asc']], "drawCallback": dtDrawCallback, }); // make license key a password hidden field $(".toggle-password").on('click', togglePasswordHidden); // reset add modal before displaying $('#open-LicenseAdd').click(function() { var modal_body = $('#add .modal-body'); var key = modal_body.find(".key"); var toggle = modal_body.find(".toggle-password"); // clear fields key.val(''); // reset toggles if (key.attr("type") !== "password") { key.attr("type", "password"); toggle.removeClass("glyphicon glyphicon-eye-open"); toggle.addClass("glyphicon glyphicon-eye-close"); } }); // submit activation request to api $('#addButton').click(function(ev) { /* prevent form default submit */ ev.preventDefault(); if (validateFields('#add')) { activateLicense(); } }); // submit deactivation request to api $('#deleteButton').click(function(ev) { ev.preventDefault(); deactivateLicense(); }); }); })(window, document); ================================================ FILE: gui/static/js/main.js ================================================ /* TODO: decouple scope like other js scripts */ // throw an error if required functions not defined if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } if (typeof descendingSearch === "undefined") { throw new Error("descendingSearch() is required and is not defined"); } if (typeof toggleElemDisabled === "undefined") { throw new Error("toggleElemDisabled() is required and is not defined"); } if (typeof runUntilTimeout === "undefined") { throw new Error("runUntilTimeout() is required and is not defined"); } if (typeof reloadKamRequired === "undefined") { throw new Error("reloadKamRequired() is required and is not defined"); } if (typeof reloadDsipRequired === "undefined") { throw new Error("reloadDsipRequired() is required and is not defined"); } // throw an error if required globals not defined if (typeof API_BASE_URL === "undefined") { throw new Error("API_BASE_URL is required and is not defined"); } /* TODO: replace shorthands with $(document).ready(...) its more verbose */ $(function() { var accordionActive = false; $(window).on('resize', function() { var windowWidth = $(window).width(); var $topMenu = $('#top-menu'); var $sideMenu = $('#side-menu'); var top_bar = $('.top-bar'); var msg_bar = $('.message-bar'); if (windowWidth < 768) { top_bar.show(); msg_bar.hide(); if ($topMenu.hasClass("active")) { $topMenu.removeClass("active"); $sideMenu.addClass("active"); var $ddl = $('#top-menu .movable.dropdown'); $ddl.detach(); $ddl.removeClass('dropdown'); $ddl.addClass('nav-header'); $ddl.find('.dropdown-toggle').removeClass('dropdown-toggle').addClass('link'); $ddl.find('.dropdown-menu').removeClass('dropdown-menu').addClass('submenu'); $ddl.prependTo($sideMenu.find('.accordion')); $('#top-menu #qform').detach().removeClass('navbar-form').prependTo($sideMenu); if (!accordionActive) { var Accordion2 = function(el, multiple) { this.el = el || {}; this.multiple = multiple || false; // Variables privadas var links = this.el.find('.movable .link'); // Evento links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown); }; Accordion2.prototype.dropdown = function(e) { var $el = e.data.el; $this = $(this); $next = $this.next(); $next.slideToggle(); $this.parent().toggleClass('open'); if (!e.data.multiple) { $el.find('.movable .submenu').not($next).slideUp().parent().removeClass('open'); } }; var accordion = new Accordion2($('ul.accordion'), false); accordionActive = true; } } } else { top_bar.hide(); msg_bar.show(); if ($sideMenu.hasClass("active")) { $sideMenu.removeClass('active'); $topMenu.addClass('active'); var $ddl = $('#side-menu .movable.nav-header'); $ddl.detach(); $ddl.removeClass('nav-header'); $ddl.addClass('dropdown'); $ddl.find('.link').removeClass('link').addClass('dropdown-toggle'); $ddl.find('.submenu').removeClass('submenu').addClass('dropdown-menu'); $('#side-menu #qform').detach().addClass('navbar-form').appendTo($topMenu.find('.nav')); $ddl.appendTo($topMenu.find('.nav')); } } }); /**/ var $menulink = $('.side-menu-link'), $wrap = $('.wrap'); $menulink.click(function() { $menulink.toggleClass('active'); $wrap.toggleClass('active'); return false; }); /*Accordion*/ var Accordion = function(el, multiple) { this.el = el || {}; this.multiple = multiple || false; // Variables privadas var links = this.el.find('.link'); // Evento links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown); }; Accordion.prototype.dropdown = function(e) { var $el = e.data.el; var $this = $(this); var $next = $this.next(); var anchor = $this.find('a') $next.slideToggle(); $this.parent().toggleClass('open'); if (!e.data.multiple) { $el.find('.submenu').not($next).slideUp().parent().removeClass('open'); } }; var accordion = new Accordion($('ul.accordion'), false); }); $(function() { /* styling links */ $('a').each(function() { if ($(this).prop('href') === window.location.href) { $(this).removeClass('navlink'); $(this).addClass('currentlink'); } }); /* prevent empty links from jumping to top of page */ $('a[href$=\\#]').on('click', function(event) { event.preventDefault(); }); }); /* TODO: do we have a use for this anymore? */ /* Update an attribute of an endpoint /* row - Javascript DOM that contains the row of the PBX * attr - is the attribute that we want to update */ // function updateEndpoint(row, attr, attrvalue) { // checkbox = row.cells[0].getElementsByClassName('checkthis'); // pbxid = checkbox[0].value; // requestPayload = '{"maintmode":' + attrvalue + '}'; // // $.ajax({ // type: "POST", // url: "/api/v1/endpoint/" + pbxid, // dataType: "json", // contentType: "application/json; charset=utf-8", // success: function(response, text_status, xhr) { // // uncheck the Checkbox // if (attr === 'maintmode') { // $('#checkbox_' + pbxid)[0].checked = false; // if (attrvalue == 1) { // $('#maintmode_' + pbxid).html("<span class='glyphicon glyphicon-wrench'>"); // } // else { // $('#maintmode_' + pbxid).html(""); // } // } // }, // data: requestPayload // }); // } /* TODO: update to work with endpoint groups */ // function enableMaintenanceMode() { // var table = document.getElementById("pbxs"); // // r = 1; // while (row = table.rows[r++]) { // checkbox = row.cells[0].getElementsByClassName('checkthis'); // if (checkbox[0].checked) { // updateEndpoint(row, 'maintmode', 1); // } // } // } /* TODO: update to work with endpoint groups */ // function disableMaintenanceMode() { // var table = document.getElementById("pbxs"); // // r = 1; // while (row = table.rows[r++]) { // checkbox = row.cells[0].getElementsByClassName('checkthis'); // if (checkbox[0].checked) { // updateEndpoint(row, 'maintmode', 0); // } // } // } $(document).ready(function() { var reloading_overlay = $('#reloading_overlay'); /* query param actions */ if (getQueryString('action') === 'add') { $('#add').modal('show'); } /* kam reload button listener */ $('#reload_kam').click(function() { reloading_overlay.removeClass('hidden'); $.ajax({ type: "POST", url: API_BASE_URL + "reload/kamailio", dataType: "json", global: false, success: function(response, text_status, xhr) { reloadKamRequired(false); reloading_overlay.addClass('hidden'); showNotification("Kamailio was reloaded"); }, error: function(xhr, text_status, error_msg) { error_msg = JSON.parse(xhr.responseText)["msg"]; reloading_overlay.addClass('hidden'); showNotification("Kamailio was NOT reloaded: " + error_msg, true); } }); }); /* dsiprouter reload button listener */ $('#reload_dsip').click(function() { var url = API_BASE_URL + "reload/dsiprouter"; reloading_overlay.removeClass('hidden'); $.ajax({ type: "POST", url: url, dataType: "json", global: false, success: function(response, text_status, xhr) { // we started a reload in the background showNotification(response.msg, false, 3000); // poll the server until up or timeout runUntilTimeout( async function() { try { var response = await fetch( url, { method: "GET", cache: "no-cache", signal: AbortSignal.timeout(1500), } ); if (response.status === 200) { var json = await response.json(); return json.data[0] === true; } return false; } catch { return false; } }, 60000, 2000, function() { showNotification("dSIPRouter was reloaded, reloading current session..", false, 5000); // refresh the page and bypass the cache setTimeout(function() { window.location.reload(true); }, 3000) }, function() { reloading_overlay.addClass('hidden'); showNotification("dSIPRouter reload timed out. Check the logs for more information.", true); } ) }, error: function(xhr, text_status, error_msg) { error_msg = JSON.parse(xhr.responseText)["msg"]; reloading_overlay.addClass('hidden'); showNotification("dSIPRouter reload failed to start: " + error_msg, true); } }); }); /* clicks on "reload" button go to the drowdon instead */ $('#reload').click(function(ev){ ev.stopPropagation(); $('#reload-split').trigger('click'); }); /* listener for authtype radio buttons */ $('.authoptions.radio').get().forEach(function(elem) { elem.addEventListener('click', function(e) { var target_radio = $(e.target); /* keep descending down DOM tree until input hit */ target_radio = descendingSearch($(e.target), function(node) { return node.get(0).nodeName.toLowerCase() === "input" }); if (target_radio === null) { return false; } var auth_radios = $(e.currentTarget).find('input[type="radio"]'); var modal_body = $(this).closest('.modal-body'); var hide_show_ids = []; $.each(auth_radios, function() { hide_show_ids.push('#' + $(this).data('toggle')); }); var hide_show_divs = modal_body.find(hide_show_ids.join(', ')); if (target_radio.is(":checked") || target_radio.prop("checked")) { /* enable ip_addr on ip auth in #edit modal only */ if ($(this).closest('div.modal').attr('id').toLowerCase().indexOf('edit') > -1) { if (target_radio.data('toggle') === "ip_enabled") { toggleElemDisabled(modal_body.find('input.ip_addr'), false); } else { toggleElemDisabled(modal_body.find('input.ip_addr'), true); } } /* change value of authtype inputs */ modal_body.find('.authtype').val(target_radio.data('toggle').split('_')[0]); /* show correct div's */ $.each(hide_show_divs, function(i, elem) { if (target_radio.data('toggle') === $(elem).attr('name')) { $(elem).removeClass("hidden"); } else { $(elem).addClass("hidden"); } }); } else { /* change value of authtype inputs */ modal_body.find('.authtype').val(''); /* show correct div's */ $.each(hide_show_divs, function(i, elem) { if (target_radio.data('toggle') === $(elem).attr('name')) { $(elem).addClass("hidden"); } else { $(elem).removeClass("hidden"); } }); } /* trickle down DOM tree (capture event) */ }, true); }); /* remove non-printable ascii chars on paste */ $('form input[type!="hidden"]').on("paste", function() { $(this).val(this.value.replace(/[^\x20-\x7E]+/g, '')) }); /* make sure autofocus is honored on loaded modals */ $('.modal').on('shown.bs.modal', function() { $(this).find('[autofocus]').focus(); }); /* enable bootstrap tooltips */ $('[data-toggle="tooltip"]').tooltip(); }); /* handle multiple modal stacking */ $(window).on('show.bs.modal', function(e) { modal = $(e.target); zIndexTop = Math.max.apply(null, $('.modal').map(function() { var z = parseInt($(this).css('z-index')); return isNaN(z, 10) ? 0 : z; })); modal.css('z-index', zIndexTop + 10); modal.addClass('modal-open'); }); $(window).on('hide.bs.modal', function(e) { modal = $(e.target); modal.css('z-index', '1050'); }); ================================================ FILE: gui/static/js/msteams.js ================================================ ;(function(window, document) { 'use strict'; /* Update the display with the status of the tests */ function updateConnectivtyStatus(msg) { var hostcheck_obj = $("#hostname_check"); var tlscheck_obj = $("#tls_check"); var tlscheckrow_obj = $("#tls_check_row"); var optioncheck_obj = $("#option_check"); var tlscheck_msg = ""; hostcheck_obj.removeClass(); tlscheck_obj.removeClass(); optioncheck_obj.removeClass(); //Hostname Check if (msg.hostname_check == true) { hostcheck_obj.addClass("glyphicon glyphicon-ok"); hostcheck_obj.css("color", "green"); } else { hostcheck_obj.addClass("glyphicon glyphicon-remove"); hostcheck_obj.css("color", "red"); } if (msg.tls_check.tls_cert_valid == true) { tlscheck_obj.addClass("glyphicon glyphicon-ok"); tlscheck_obj.css("color", "green"); } else { tlscheck_obj.addClass("glyphicon glyphicon-remove"); tlscheck_obj.css("color", "red"); if (msg.tls_check.tls_error.length > 0) { tlscheck_msg = msg.tls_check.tls_error; } else { tlscheck_msg = "Cert commonname doesn't match the domain:" + JSON.stringify(msg.tls_check.tls_cert_details); } tlscheckrow_obj.tooltip({ 'title': tlscheck_msg, 'placement': 'right', 'trigger': 'manual', 'tooltipClass': 'tooltipclass' }); tlscheckrow_obj.tooltip('show'); } //Option Check if (msg.option_check == true) { optioncheck_obj.addClass("glyphicon glyphicon-ok"); optioncheck_obj.css("color", "green"); } else { optioncheck_obj.addClass("glyphicon glyphicon-remove"); optioncheck_obj.css("color", "red"); } } /* Set the width of the sidebar to 250px (show it) */ function openNav() { document.getElementById("configurationPanel").style.width = "auto"; } /* Set the width of the sidebar to 0 (hide it) */ function closeNav() { document.getElementById("configurationPanel").style.width = "0"; } function runTests() { var testconn_obj = $('#testConnectivity'); var domain = testconn_obj.val(); //Disable Test Connectivity Button testconn_obj.prop('disabled', true); //Run Test using API $.ajax({ type: "GET", url: "/api/v1/domains/msteams/test/" + domain, dataType: "json", contentType: "application/json; charset=utf-8", success: function(response, text_status, xhr) { // Update the display updateConnectivtyStatus(response.data[0]) } }); //Enable Test Connectivity Button testconn_obj.prop('disabled', false); } /* once DOM is ready init variables and listeners */ $(document).ready(function() { runTests(); $('#testConnectivity').click(function() { runTests(); }); }); })(window, document); ================================================ FILE: gui/static/js/npm.js ================================================ // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. require('../../js/transition.js'); require('../../js/alert.js'); require('../../js/button.js'); require('../../js/carousel.js'); require('../../js/collapse.js'); require('../../js/dropdown.js'); require('../../js/modal.js'); require('../../js/tooltip.js'); require('../../js/popover.js'); require('../../js/scrollspy.js'); require('../../js/tab.js'); require('../../js/affix.js'); ================================================ FILE: gui/static/js/outboundroutes.js ================================================ ;(function(window, document) { 'use strict'; $(document).ready(function () { /* data tables init */ $('#outboundmapping').DataTable({ "columnDefs": [ {"orderable": true, "targets": [1, 3, 4, 5, 6, 7, 8, 9]}, {"orderable": false, "targets": [0, 2, 10, 11]}, ], "order": [[1, 'asc']] }); /* validator init */ $('#addOutboundRoutes').validator({ custom: { tocheck: function ($el) { return $el.length > 0 && $('prefix').length > 0; } }, errors: { tocheck: "You must enter a To Prefix as well. Entering just a From prefix is not supported" } }); /* listeners */ $('#outboundmapping').on('click', '#open-Update', function () { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('outboundmapping'); var ruleid = $(c).find('tr:eq(' + row_index + ') > td.ruleid').text(); var groupid = $(c).find('tr:eq(' + row_index + ') > td.groupid').text(); var prefix = $(c).find('tr:eq(' + row_index + ') > td.prefix').text(); var from_prefix = $(c).find('tr:eq(' + row_index + ') > td.from_prefix').text(); var timerec = $(c).find('tr:eq(' + row_index + ') > td.timerec').text(); var priority = $(c).find('tr:eq(' + row_index + ') > td.priority').text(); var routeid = $(c).find('tr:eq(' + row_index + ') > td.routeid').text(); var gwgroupid = $(c).find('tr:eq(' + row_index + ') > td.gwgroupid').text(); var name = $(c).find('tr:eq(' + row_index + ') > td.description').text(); /** Clear out the modal */ var modal_body = $('#edit .modal-body'); modal_body.find("input.ruleid").val(''); modal_body.find("input.groupid").val(''); modal_body.find("input.prefix").val(''); modal_body.find("input.from_prefix").val(''); modal_body.find("input.timerec").val(''); modal_body.find("input.priority").val(''); modal_body.find("input.gwgroupid").val(''); modal_body.find("input.name").val(''); /* update modal fields */ modal_body.find("input.ruleid").val(ruleid); modal_body.find("input.groupid").val(groupid); modal_body.find("input.prefix").val(prefix); modal_body.find("input.from_prefix").val(from_prefix); modal_body.find("input.timerec").val(timerec); modal_body.find("input.priority").val(priority); modal_body.find("input.name").val(name); /* update options selected */ modal_body.find("select").val(''); modal_body.find("select.gwgroupid").val(gwgroupid); modal_body.find("select.routeid").val(routeid); }); $('#outboundmapping').on('click','#open-Delete', function () { var row_index = $(this).parent().parent().parent().index() + 1; var c = document.getElementById('outboundmapping'); var ruleid = $(c).find('tr:eq(' + row_index + ') td:eq(1)').text(); /* update modal fields */ var modal_body = $('#delete .modal-body'); modal_body.find(".ruleid").val(ruleid); }); }); })(window, document); ================================================ FILE: gui/static/js/stirshaken.js ================================================ ;(function (window, document) { 'use strict'; $(document).ready(function() { var toggle = $('#toggleStirShaken'); function updateToggle() { if (toggle.is(":checked") || toggle.prop("checked")) { $('#stirShakenOptions').removeClass("hidden"); $(this).val("1"); $(this).bootstrapToggle('on'); } else { $('#stirShakenOptions').addClass("hidden"); $(this).val("0"); $(this).bootstrapToggle('off'); } } /* update toggle on page load */ updateToggle(); /* listener for toggle changes */ toggle.change(updateToggle); }); })(window, document); ================================================ FILE: gui/static/js/teleblock.js ================================================ ;(function (window, document) { 'use strict'; $(document).ready(function() { /* listener for teleblock toggle */ $('#toggleTeleblock').change(function () { if ($(this).is(":checked") || $(this).prop("checked")) { $('#teleblockOptions').removeClass("hidden"); $(this).val("1"); $(this).bootstrapToggle('on'); } else { $('#teleblockOptions').addClass("hidden"); $(this).val("0"); $(this).bootstrapToggle('off'); } }); }); })(window, document); ================================================ FILE: gui/static/js/transnexus.js ================================================ ;(function (window, document) { 'use strict'; function updateToggle(toggle_node, settings_node) { if (toggle_node.is(":checked") || toggle_node.prop("checked")) { settings_node.removeClass("hidden"); toggle_node.val("1"); toggle_node.bootstrapToggle('on'); } else { settings_node.addClass("hidden"); toggle_node.val("0"); toggle_node.bootstrapToggle('off'); } } $(document).ready(function() { var auth_toggle = $('#toggle_auth_settings'); var auth_settings = $('#authservice_settings'); var verify_toggle = $('#toggle_verify_settings'); var verify_settings = $('#verifyservice_settings'); /* update toggle on page load */ updateToggle(auth_toggle, auth_settings); updateToggle(verify_toggle, verify_settings); /* listener for toggle changes */ auth_toggle.change(function() { updateToggle(auth_toggle, auth_settings); }); verify_toggle.change(function() { updateToggle(verify_toggle, verify_settings); }); }); })(window, document); ================================================ FILE: gui/static/js/upgrade.js ================================================ ;(function(window, document) { 'use strict'; // throw an error if required functions not defined if (typeof reloadDsipRequired === "undefined") { throw new Error("reloadDsipRequired() is required and is not defined"); } var upgrade_form = $('#upgrade_form'); var upgrade_output_row = $('#upgrade_output_row'); var upgrade_output = $('#upgrade_output'); function displayUpgradeLog() { upgrade_form.hide(); upgrade_output.html(""); upgrade_output_row.show(); } function streamLog() { var source = new EventSource("/upgrade/log"); source.onmessage = function(event) { upgrade_output.append(event.data); }; source.onerror = function(event) { source.close(); }; } function dumpLog() { $.ajax({ type: "GET", url: "/upgrade/log", success: function(response, textStatus, jqXHR) { upgrade_output.append(response); } }); } function monitorUpgrade() { var source = new EventSource("/upgrade/status"); source.onmessage = function(event) { if (event.data === '0') { reloadDsipRequired(true); } }; source.onerror = function(event) { source.close(); }; } $(document).ready(function() { $('#btnShowLog').click(function() { displayUpgradeLog(); dumpLog(); }); upgrade_form.submit(function(e) { e.preventDefault(); var formData = $(this).serialize(); $.ajax({ type: "POST", url: "/upgrade/start", async: true, data: formData, success: function(response, text_status, xhr) { showNotification("Upgrade started. See log below for more details..", 5000); displayUpgradeLog(); streamLog(); monitorUpgrade(); }, error: function(xhr, text_status, error_msg) { showNotification("Could not start upgrade: " + error_msg, true); } }); }); }); })(window, document); ================================================ FILE: gui/static/js/util.js ================================================ ;(function(window, document) { 'use strict'; /** * Get the value of a querystring * @param {String} field The field to get the value of * @param {String} url The URL to get the value from (optional) * @return {String} The field value */ window.getQueryString = function(field, url) { var href = url ? url : window.location.href; var reg = new RegExp('[?&]' + field + '=([^&#]*)', 'i'); var string = reg.exec(href); return string ? string[1] : null; }; /** * Disable / Re-enable a form submittable element<br> * Use this instead of the HTML5 disabled prop * @param {String|jQuery|Object} selector The selector for element to toggle * @param {Boolean} disable Whether to disable or re-enable * @param {Boolean} child Whether to change cursor on child instead */ window.toggleElemDisabled = function(selector, disable, child) { var select_elem = null; if (typeof selector === 'string' || selector instanceof String) { select_elem = $(selector); } else if (selector instanceof jQuery) { select_elem = selector; } else { console.err("toggleElemDisabled(): invalid selector argument"); return; } /* by default change cursor on parent not child */ child = child || false; if (disable) { if (!child) { select_elem.parent().css({'cursor': 'not-allowed'}); } else { select_elem.css({'cursor': 'not-allowed'}); } select_elem.css({ 'background-color': '#EEEEEE', 'opacity': '0.7', 'pointer-events': 'none' }); select_elem.prop('readonly', true); select_elem.prop('tabindex', -1); select_elem.prop('disabled', true); } else { if (!child) { select_elem.parent().removeAttr('style'); } select_elem.removeAttr('style'); select_elem.prop('readonly', false); select_elem.prop('tabindex', 0); select_elem.prop('disabled', false); } }; /** * Recursively search DOM tree until test is true<br> * Starts at and includes selected node, tests each descendant<br> * Note that test callback applies to jQuery objects throughout * @param {String|jQuery|Object} selector The selector for start node * @param {Function} test Test to apply to each node * @return {jQuery|null} Returns found node or null */ window.descendingSearch = function(selector, test) { var select_node = null; if (typeof selector === 'string' || selector instanceof String) { select_node = $(selector); } else if (selector instanceof jQuery) { select_node = selector; } else { return null; } var num_nodes = select_node.length || 0; if (num_nodes > 1) { for (var i = 0; i < num_nodes; i++) { if (test(select_node[i])) { return select_node[i]; } } } else { if (test(select_node)) { return select_node; } } var node_list = select_node.children(); if (node_list.length <= 0) { return null; } descendingSearch(node_list, test) }; /** * Validate form fields in a selected node<br> * The optional test function is passed a an array of fields found<br> * Note that in this project our forms are usually the 1st child in a modal body<br> * If provided the test function should return an object of this structure:<br> * <pre> * { * result: boolean - whether the test passed or failed * err_node: element|jQuery|Object - the node that caused the failure * err_msg: String - the error message to display * } * </pre> * @param {String|jQuery|Object} selector Selector for a node with fields * @param {Function} test Custom validation test to run * @returns {Boolean} Whether the validation passed * @throws {Error} If selector is invalid */ window.validateFields = function(selector, test) { var select_node = null; if (typeof selector === 'string' || selector instanceof String) { select_node = $(selector); } else if (selector instanceof jQuery) { select_node = selector; } else if (typeof selector === "object" || selector instanceof Object) { select_node = $(selector); } else { throw new Error("validateFields(): invalid selector argument"); } /* grab the fields as a jquery object */ var field_elem_list = select_node.find('input,textarea,select,output').filter(':not(:hidden)').get(); /* check the builtin form validation */ for (var i = 0; i < field_elem_list.length; i++) { if (!field_elem_list[i].reportValidity()) { return false; } } /* if supplied, run the custom test function */ if (typeof test === 'function') { // convert the array of DOM elements into a Map of "name" => jQuery(elem) key pairs var fields = new Map(field_elem_list.map(function(elem) { return [elem.getAttribute('name'), $(elem)]; })); var resp = test(fields); if (resp.result === false) { var err_elem = null; if (resp.err_node instanceof jQuery) { err_elem = resp.err_node.get(0); } else { err_elem = resp.err_node; } err_elem.setCustomValidity(resp.err_msg); err_elem.reportValidity(); setTimeout(function() { err_elem.setCustomValidity(''); }, 2500); return false; } } return true; }; /** * Checks if selected element has a particular event listener<br> * Works for on<event> DOM events, and dynamic events added using jquery<br> * Events added dynamically using addEventListener will not be found * @param {String|jQuery|Object} selector Selector for a node with fields * @param {String} event Event listener type to check for * @returns {Boolean} * @throws {Error} If selector is invalid */ function hasEventListener(selector, event) { var element = undefined; if (typeof selector === 'string' || selector instanceof String) { element = $(selector).get(0); } else if (selector instanceof jQuery) { element = selector.get(0); } else if (typeof selector === "object" || selector instanceof Object) { element = $(selector).get(0); } if (element === undefined) { throw new Error("validateFields(): invalid selector argument"); } if (element.hasAttribute('on' + event)) { return true; } else if ($._data(element, "events").hasOwnProperty(event)) { return true; } return false; } /** * Show notification in top notification bar * @param {String} msg message to display * @param {Boolean} error whether the notification is an error */ window.showNotification = function(msg, error = false, duration = 10000) { var top_bar = $('.top-bar'); var msg_bar = $('.message-bar'); var visible_modals = $('.modal').filter(':not(:hidden)'); // hide modals if shown visible_modals.modal('hide'); // stop the animation if already running top_bar.stop(true, true); // change the notification accordingly if (error === true) { msg_bar.removeClass("alert-success"); msg_bar.addClass("alert alert-danger"); msg_bar.html("<strong>Failed!</strong> " + msg); } else { msg_bar.removeClass("alert-danger"); msg_bar.addClass("alert alert-success"); msg_bar.html("<strong>Success!</strong> " + msg); } // start the animation showing the notification top_bar.show(); top_bar.slideUp(duration, function() { top_bar.hide(); }); }; /** * Update reload kamailio button to indicate if reload is required * @param {Boolean} required whether a reload is required */ window.reloadKamRequired = function(required = true) { var reload_btn = $('#reload'); var split_btn = $('#reload-split'); var kamailio_btn = $('#reload_kam'); if (required) { reload_btn.removeClass('btn-primary'); split_btn.removeClass('btn-primary'); kamailio_btn.removeClass('btn-secondary'); reload_btn.addClass('btn-warning'); split_btn.addClass('btn-warning'); kamailio_btn.addClass('btn-warning'); } else { reload_btn.removeClass('btn-warning'); split_btn.removeClass('btn-warning'); kamailio_btn.removeClass('btn-warning'); reload_btn.addClass('btn-primary'); split_btn.addClass('btn-primary'); kamailio_btn.addClass('btn-secondary'); } }; /** * Update reload dsiprouter button to indicate if reload is required * @param {Boolean} required whether a reload is required */ window.reloadDsipRequired = function(required = true) { var reload_btn = $('#reload'); var split_btn = $('#reload-split'); var dsiprouter_btn = $('#reload_dsip'); if (required) { reload_btn.removeClass('btn-primary'); split_btn.removeClass('btn-primary'); dsiprouter_btn.removeClass('btn-secondary'); reload_btn.addClass('btn-warning'); split_btn.addClass('btn-warning'); dsiprouter_btn.addClass('btn-warning'); } else { reload_btn.removeClass('btn-warning'); split_btn.removeClass('btn-warning'); dsiprouter_btn.removeClass('btn-warning'); reload_btn.addClass('btn-primary'); split_btn.addClass('btn-primary'); dsiprouter_btn.addClass('btn-secondary'); } }; /** * Run a function periodically until successful or timed out * @param {Function} run_fn Function to run (must return true or false in a Promise) * @param {Number} timeout Timeout (ms) until failure * @param {Number} interval Period (ms) between attempting run_fn again * @param {Function} success_fn Function to run on successful completion * @param {Function} timeout_fn Function to run on timeout failure */ window.runUntilTimeout = function( run_fn, timeout, interval = 1000, success_fn = null, timeout_fn = null ) { var timeout_timer = setTimeout(function() { // ran unsuccessfully until timeout clearInterval(interval_timer); if (timeout_fn !== null) { timeout_fn(); } }, timeout); var interval_timer = setInterval(function() { run_fn().then((result) => { if (result === true) { // ran successfully without timeout clearInterval(interval_timer); clearTimeout(timeout_timer); if (success_fn !== null) { success_fn(); } } }); }, interval); } /** * Queued delay of a callback * @param fn The callback function * @param ms The timeout in milliseconds * @returns {(function(...[*]): void)|*} */ window.delayedCallback = function(fn, ms) { let timer = 0 return function(...args) { clearTimeout(timer) timer = setTimeout(fn.bind(this, ...args), ms || 0) } }; })(window, document); ================================================ FILE: gui/static/js/validator.js ================================================ /*! * Validator v0.11.9 for Bootstrap 3, by @1000hz * Copyright 2017 Cina Saffary * Licensed under http://opensource.org/licenses/MIT * * https://github.com/1000hz/bootstrap-validator */ +function ($) { 'use strict'; // VALIDATOR CLASS DEFINITION // ========================== function getValue($el) { return $el.is('[type="checkbox"]') ? $el.prop('checked') : $el.is('[type="radio"]') ? !!$('[name="' + $el.attr('name') + '"]:checked').length : $el.is('select[multiple]') ? ($el.val() || []).length : $el.val() } var Validator = function (element, options) { this.options = options this.validators = $.extend({}, Validator.VALIDATORS, options.custom) this.$element = $(element) this.$btn = $('button[type="submit"], input[type="submit"]') .filter('[form="' + this.$element.attr('id') + '"]') .add(this.$element.find('input[type="submit"], button[type="submit"]')) this.update() this.$element.on('input.bs.validator change.bs.validator focusout.bs.validator', $.proxy(this.onInput, this)) this.$element.on('submit.bs.validator', $.proxy(this.onSubmit, this)) this.$element.on('reset.bs.validator', $.proxy(this.reset, this)) this.$element.find('[data-match]').each(function () { var $this = $(this) var target = $this.attr('data-match') $(target).on('input.bs.validator', function (e) { getValue($this) && $this.trigger('input.bs.validator') }) }) // run validators for fields with values, but don't clobber server-side errors this.$inputs.filter(function () { return getValue($(this)) && !$(this).closest('.has-error').length }).trigger('focusout') this.$element.attr('novalidate', true) // disable automatic native validation } Validator.VERSION = '0.11.9' Validator.INPUT_SELECTOR = ':input:not([type="hidden"], [type="submit"], [type="reset"], button)' Validator.FOCUS_OFFSET = 20 Validator.DEFAULTS = { delay: 500, html: false, disable: true, focus: true, custom: {}, errors: { match: 'Does not match', minlength: 'Not long enough' }, feedback: { success: 'glyphicon-ok', error: 'glyphicon-remove' } } Validator.VALIDATORS = { 'native': function ($el) { var el = $el[0] if (el.checkValidity) { return !el.checkValidity() && !el.validity.valid && (el.validationMessage || "error!") } }, 'match': function ($el) { var target = $el.attr('data-match') return $el.val() !== $(target).val() && Validator.DEFAULTS.errors.match }, 'minlength': function ($el) { var minlength = $el.attr('data-minlength') return $el.val().length < minlength && Validator.DEFAULTS.errors.minlength } } Validator.prototype.update = function () { var self = this this.$inputs = this.$element.find(Validator.INPUT_SELECTOR) .add(this.$element.find('[data-validate="true"]')) .not(this.$element.find('[data-validate="false"]') .each(function () { self.clearErrors($(this)) }) ) this.toggleSubmit() return this } Validator.prototype.onInput = function (e) { var self = this var $el = $(e.target) var deferErrors = e.type !== 'focusout' if (!this.$inputs.is($el)) return this.validateInput($el, deferErrors).done(function () { self.toggleSubmit() }) } Validator.prototype.validateInput = function ($el, deferErrors) { var value = getValue($el) var prevErrors = $el.data('bs.validator.errors') if ($el.is('[type="radio"]')) $el = this.$element.find('input[name="' + $el.attr('name') + '"]') var e = $.Event('validate.bs.validator', {relatedTarget: $el[0]}) this.$element.trigger(e) if (e.isDefaultPrevented()) return var self = this return this.runValidators($el).done(function (errors) { $el.data('bs.validator.errors', errors) errors.length ? deferErrors ? self.defer($el, self.showErrors) : self.showErrors($el) : self.clearErrors($el) if (!prevErrors || errors.toString() !== prevErrors.toString()) { e = errors.length ? $.Event('invalid.bs.validator', {relatedTarget: $el[0], detail: errors}) : $.Event('valid.bs.validator', {relatedTarget: $el[0], detail: prevErrors}) self.$element.trigger(e) } self.toggleSubmit() self.$element.trigger($.Event('validated.bs.validator', {relatedTarget: $el[0]})) }) } Validator.prototype.runValidators = function ($el) { var errors = [] var deferred = $.Deferred() $el.data('bs.validator.deferred') && $el.data('bs.validator.deferred').reject() $el.data('bs.validator.deferred', deferred) function getValidatorSpecificError(key) { return $el.attr('data-' + key + '-error') } function getValidityStateError() { var validity = $el[0].validity return validity.typeMismatch ? $el.attr('data-type-error') : validity.patternMismatch ? $el.attr('data-pattern-error') : validity.stepMismatch ? $el.attr('data-step-error') : validity.rangeOverflow ? $el.attr('data-max-error') : validity.rangeUnderflow ? $el.attr('data-min-error') : validity.valueMissing ? $el.attr('data-required-error') : null } function getGenericError() { return $el.attr('data-error') } function getErrorMessage(key) { return getValidatorSpecificError(key) || getValidityStateError() || getGenericError() } $.each(this.validators, $.proxy(function (key, validator) { var error = null if ((getValue($el) || $el.attr('required')) && ($el.attr('data-' + key) !== undefined || key == 'native') && (error = validator.call(this, $el))) { error = getErrorMessage(key) || error !~errors.indexOf(error) && errors.push(error) } }, this)) if (!errors.length && getValue($el) && $el.attr('data-remote')) { this.defer($el, function () { var data = {} data[$el.attr('name')] = getValue($el) $.get($el.attr('data-remote'), data) .fail(function (jqXHR, textStatus, error) { errors.push(getErrorMessage('remote') || error) }) .always(function () { deferred.resolve(errors)}) }) } else deferred.resolve(errors) return deferred.promise() } Validator.prototype.validate = function () { var self = this $.when(this.$inputs.map(function (el) { return self.validateInput($(this), false) })).then(function () { self.toggleSubmit() self.focusError() }) return this } Validator.prototype.focusError = function () { if (!this.options.focus) return var $input = this.$element.find(".has-error:first :input") if ($input.length === 0) return $('html, body').animate({scrollTop: $input.offset().top - Validator.FOCUS_OFFSET}, 250) $input.focus() } Validator.prototype.showErrors = function ($el) { var method = this.options.html ? 'html' : 'text' var errors = $el.data('bs.validator.errors') var $group = $el.closest('.form-group') var $block = $group.find('.help-block.with-errors') var $feedback = $group.find('.form-control-feedback') if (!errors.length) return errors = $('<ul/>') .addClass('list-unstyled') .append($.map(errors, function (error) { return $('<li/>')[method](error) })) $block.data('bs.validator.originalContent') === undefined && $block.data('bs.validator.originalContent', $block.html()) $block.empty().append(errors) $group.addClass('has-error has-danger') $group.hasClass('has-feedback') && $feedback.removeClass(this.options.feedback.success) && $feedback.addClass(this.options.feedback.error) && $group.removeClass('has-success') } Validator.prototype.clearErrors = function ($el) { var $group = $el.closest('.form-group') var $block = $group.find('.help-block.with-errors') var $feedback = $group.find('.form-control-feedback') $block.html($block.data('bs.validator.originalContent')) $group.removeClass('has-error has-danger has-success') $group.hasClass('has-feedback') && $feedback.removeClass(this.options.feedback.error) && $feedback.removeClass(this.options.feedback.success) && getValue($el) && $feedback.addClass(this.options.feedback.success) && $group.addClass('has-success') } Validator.prototype.hasErrors = function () { function fieldErrors() { return !!($(this).data('bs.validator.errors') || []).length } return !!this.$inputs.filter(fieldErrors).length } Validator.prototype.isIncomplete = function () { function fieldIncomplete() { var value = getValue($(this)) return !(typeof value == "string" ? $.trim(value) : value) } return !!this.$inputs.filter('[required]').filter(fieldIncomplete).length } Validator.prototype.onSubmit = function (e) { this.validate() if (this.isIncomplete() || this.hasErrors()) e.preventDefault() } Validator.prototype.toggleSubmit = function () { if (!this.options.disable) return this.$btn.toggleClass('disabled', this.isIncomplete() || this.hasErrors()) } Validator.prototype.defer = function ($el, callback) { callback = $.proxy(callback, this, $el) if (!this.options.delay) return callback() window.clearTimeout($el.data('bs.validator.timeout')) $el.data('bs.validator.timeout', window.setTimeout(callback, this.options.delay)) } Validator.prototype.reset = function () { this.$element.find('.form-control-feedback') .removeClass(this.options.feedback.error) .removeClass(this.options.feedback.success) this.$inputs .removeData(['bs.validator.errors', 'bs.validator.deferred']) .each(function () { var $this = $(this) var timeout = $this.data('bs.validator.timeout') window.clearTimeout(timeout) && $this.removeData('bs.validator.timeout') }) this.$element.find('.help-block.with-errors') .each(function () { var $this = $(this) var originalContent = $this.data('bs.validator.originalContent') $this .removeData('bs.validator.originalContent') .html(originalContent) }) this.$btn.removeClass('disabled') this.$element.find('.has-error, .has-danger, .has-success').removeClass('has-error has-danger has-success') return this } Validator.prototype.destroy = function () { this.reset() this.$element .removeAttr('novalidate') .removeData('bs.validator') .off('.bs.validator') this.$inputs .off('.bs.validator') this.options = null this.validators = null this.$element = null this.$btn = null this.$inputs = null return this } // VALIDATOR PLUGIN DEFINITION // =========================== function Plugin(option) { return this.each(function () { var $this = $(this) var options = $.extend({}, Validator.DEFAULTS, $this.data(), typeof option == 'object' && option) var data = $this.data('bs.validator') if (!data && option == 'destroy') return if (!data) $this.data('bs.validator', (data = new Validator(this, options))) if (typeof option == 'string') data[option]() }) } var old = $.fn.validator $.fn.validator = Plugin $.fn.validator.Constructor = Validator // VALIDATOR NO CONFLICT // ===================== $.fn.validator.noConflict = function () { $.fn.validator = old return this } // VALIDATOR DATA-API // ================== $(window).on('load', function () { $('form[data-toggle="validator"]').each(function () { var $form = $(this) Plugin.call($form, $form.data()) }) }) }(jQuery); ================================================ FILE: gui/static/template/DID_example.csv ================================================ DID,EndpointGroupID,Name 13134860409,52,Inbound1 13134860410,53,Inbound2 13134860411,34,Inbound3 ================================================ FILE: gui/sysloginit.py ================================================ import logging import settings from logging.handlers import SysLogHandler from copy import copy def initSyslogLogger(): """ Configure syslog loggers :return: syslog log handler """ # close any zombie handlers for root logger for handler in copy(logging.getLogger().handlers): logging.getLogger().removeHandler(handler) handler.close() # create our own custom formatter and handler for syslog log_formatter = logging.Formatter('[%(asctime)s] [%(process)d->%(thread)d] [%(levelname)s]: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') log_handler = logging.handlers.SysLogHandler(address="/dev/log", facility=settings.DSIP_LOG_FACILITY) log_handler.setLevel(settings.DSIP_LOG_LEVEL) log_handler.setFormatter(log_formatter) # set log handler for our dsiprouter app logging.getLogger().setLevel(settings.DSIP_LOG_LEVEL) logging.getLogger().addHandler(log_handler) # redirect stderr and stdout to syslog # if not settings.DEBUG: # sys.stderr = StreamLogger(level=logging.WARNING) # sys.stdout = StreamLogger(level=logging.DEBUG) return log_handler # import this file to setup logging # syslog handler created globally # syslog_handler = initSyslogLogger() ================================================ FILE: gui/templates/backupandrestore.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}Backup and Restore{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="wrapper-horizontal edge-centered children-align-inherit content-header"> <div> <h3>Backup and Restore</h3> </div> {% if reloadstatus %} <h3>Kamailio reload: {{ reloadstatus }}</h3> {% endif %} </div> <div class="content-section"> <!-- begin backup/restore section --> <div class="col-md-12"> <div id="endpoint-nav" class="navbar"> <!-- begin nav tabs --> <ul class="nav nav-tabs"> <li role="presentation" class="auth-tab active"> <a href="#backup" name="backup-toggle" data-toggle="tab">Backup</a> </li> <li role="presentation"> <a href="#restore" name="restore-toggle" data-toggle="tab">Restore</a> </li> </ul> </div><!-- end nav tabs --> <div class="tab-content"> <!-- begin tab content --> <div id="backup" class="tab-pane fade in active" name="backup-toggle"> <div class="form-group"> <button id="start-Backup" class="btn btn-success btn-md">Download Backup</button> </div> </div> <div id="restore" class="tab-pane fade in" name="restore-toggle"> <form id="restore-backup" action="#"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group" style="position:relative;"> <a class='btn btn-primary' href='javascript:;'> Choose Backup File... <input type="file" style='position:absolute;z-index:2;top:0;left:0;filter: alpha(opacity=0);-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";opacity:0;background-color:transparent;color:transparent;' name="file" size="40" onchange='$("#upload-file-info").html($(this).val());$("#start-Restore").show();'> </a> &nbsp; <span class='label label-info' id="upload-file-info"></span> </div> <div id="start-Restore" style="display:none"> <div class="form-group"> <input type="submit" class="btn btn-success btn-md" value="Start Restore"/> </div> </div> </form> </div> </div> <!-- end tab content --> </div> </div> <!-- end backup/restore section --> {% endblock %} {% block custom_js %} {{ script_tag('backupandrestore') }} {% endblock %} ================================================ FILE: gui/templates/carriergroups.html ================================================ {% extends 'table_layout.html' %} {% block title %}Carrier Groups{% endblock %} {% block custom_css %} {{ link_tag('carriergroups') }} {% endblock %} {% block table_headers %} <div> <h3>List of Carrier Groups</h3> </div> <div class="tableAddButton"> <button id='open-CarrierGroupAdd' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add-group"> Add </button> </div> {% endblock %} {% block table %} <!-- carrier-groups table --> <table id="carrier-groups" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th><input type="checkbox" class="checkall"/></th> <th data-field="gwgroup">ID</th> <th data-field="name">Name</th> <th data-field="gwlist">Carriers</th> <th class="hidden"></th> <th class="hidden"></th> <th class="hidden"></th> <th class="hidden"></th> <th class="hidden"></th> <th class="hidden"></th> <th class="hidden"></th> <th></th> <th></th> </tr> </thead> <tbody> {% for row in rows %} <tr class='element-row' data-gwgroup="{{ row.id }}"> <td><input type="checkbox" class="checkthis" value="1"/></td> <td class='gwgroup'>{{ row.id|noneFilter() }}</td> <td class='name'>{{ row.description|attrFilter('name')|noneFilter() }}</td> <td class='gwlist'>{{ row.gwlist|noneFilter() }}</td> <td class='authtype hide'>{{ row.r_username|noneFilter() }}</td> <td class='r_username hide'>{{ row.r_username|noneFilter() }}</td> <td class='auth_password hide'>{{ row.auth_password|noneFilter() }}</td> <td class='auth_domain hide'>{{ row.r_domain|noneFilter() }}</td> <td class='auth_username hide'>{{ row.auth_username|noneFilter() }}</td> <td class='auth_proxy hide'>{{ row.auth_proxy|noneFilter() }}</td> <td class='lb_enabled hide'>{{ row.lb_enabled|noneFilter() }}</td> <td> <p data-placement="top" data-toggle="tooltip" title="Edit"> <button id="open-Update" class="open-Update btn btn-primary btn-xs" data-title="Edit" data-toggle="modal" data-target="#edit-group"><span class="glyphicon glyphicon-pencil"></span></button> </p> </td> <td> <p data-placement="top" data-toggle="tooltip" title="Delete"> <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete-group"><span class="glyphicon glyphicon-trash"></span></button> </p> </td> </tr> {% endfor %} </tbody> </table> {% endblock %} {% block edit_modal %} <!-- edit-endpoint modal --> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Your Carrier Detail</h4> </div> <div class="modal-body"> <form class="gwform" action="/carriers" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwid" name="gwid"> <input type="hidden" class="gwgroup" name="gwgroup" value="{{ gwgroup|noneFilter() }}"> <div class="form-group"> <input class="form-control name" type="text" name="name" placeholder="Friendly Name(Optional)" autofocus="autofocus"> </div> <div class="form-group"> <input class="form-control ip_addr" type="text" name="ip_addr" placeholder="Hostname/IP"> </div> <div class="form-group"> <input class="form-control strip" type="text" name="strip" placeholder="# of characters to strip from RURI (outbound calls)"> </div> <div class="form-group"> <input class="form-control prefix" type="text" name="prefix" placeholder="The characters to prefix to a RURI (outbound calls)"> </div> <div class="form-group"> <input class="form-control rweight" type="text" name="rweight" placeholder="The amount of traffic allocated to the carrier endpoint (outbound calls)"> </div> <div class="modal-footer "> <button type="submit" id="updateButton" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div> {% endblock %} {% block add_modal %} <!-- add-endpoint modal --> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add New Carrier Detail</h4> </div> <div class="modal-body"> <form class="gwform" action="/carriers" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwid" name="gwid"> <input type="hidden" class="gwgroup" name="gwgroup" value="{{ gwgroup|noneFilter() }}"> <div class="form-group"> <input class="form-control name" type="text" name="name" placeholder="Friendly Name(Optional)" autofocus="autofocus"> </div> <div class="form-group"> <input class="form-control ip_addr" type="text" name="ip_addr" placeholder="Hostname/IP"> </div> <div class="form-group"> <input class="form-control strip" type="text" name="strip" placeholder="# of characters to strip from RURI (outbound calls)"> </div> <div class="form-group"> <input class="form-control prefix" type="text" name="prefix" placeholder="The characters to prefix to a RURI (outbound calls)"> </div> <div class="form-group"> <input class="form-control rweight" type="text" name="rweight" placeholder="The amount of traffic allocated to the carrier endpoint (outbound calls)"> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <!-- delete-endpoint modal --> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form class="gwform" action="/carrierdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwid" name="gwid"> <input type="hidden" class="name" name="name"> <input type="hidden" class="ip_addr" name="ip_addr"> <input type="hidden" class="gwgroup" name="gwgroup" value="{{ gwgroup|noneFilter() }}"> <input type="hidden" class="related_rules" name="related_rules"> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Record? </div> <div class="modal-footer "> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes</button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_modals %} <!-- edit-group modal --> <div class="modal fade" id="edit-group" tabindex="-1" role="dialog" aria-labelledby="Edit" aria-hidden="true"> <div class="modal-dialog resizable"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Your Carrier Group</h4> </div> <div class="modal-body"> <form class="gwgroupform" action="/carriergroups" method="POST" onsubmit="event.preventDefault();" role="form"> <!-- nav tabs --> <div id="carrier-nav" class="navbar"> <ul class="nav nav-tabs"> <li role="presentation" class="nav-item"> <a href="#" name="auth-toggle" data-type="toggle" class="nav-link">Auth</a> </li> <li role="presentation" class="nav-item"> <a href="#" name="config-toggle" data-type="toggle" class="nav-link">Config</a> </li> <li role="presentation" class="nav-item"> <a href="#" name="carriers-toggle" data-type="toggle" class="nav-link">Endpoints</a> </li> {# <li role="presentation" class="nav-item">#} {# <a href="/carriers" name="carriers-link" data-type="link" class="nav-link">Endpoints</a>#} {# </li>#} </ul> </div> <!-- config form --> <div class="hidden" name="config-toggle" style="padding-top: 15px;"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwgroup" name="gwgroup"> <input type="hidden" class="name" name="name"> <input type="hidden" class="gwlist" name="gwlist"> <div class="form-group"> <input class="form-control new_name" type="text" name="new_name" placeholder="Group Name" autofocus="autofocus" required="required"> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-loadbalancing" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-load_balance'></span> Enabled" data-off="<span class='icon-load_balance'></span> Disabled" data-width="125px"> Load Balancing </label> </div> <input class="lb_enabled" type="hidden" name="lb_enabled" value="0"> </div> </div> <!-- auth form --> <div class="hidden" name="auth-toggle"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwgroup" name="gwgroup"> <input type="hidden" class="name" name="name"> <input type="hidden" class="gwlist" name="gwlist"> <div class="form-group"> <div class="authoptions radio"> <label><input type="radio" data-toggle="ip_enabled" class="authtype" name="authtype" value="ip" checked>IP Auth</label> <label><input type="radio" data-toggle="userpwd_enabled" class="authtype" name="authtype" value="userpwd">Username/Password Auth</label> </div> </div> <div class="form-group hidden" id="userpwd_enabled" name="userpwd_enabled" value="0"> <p>Please enter the registration username and password provided by the carrier.</p> <div class="form-group"> <input class="form-control r_username" type="text" name="r_username" placeholder="Username" autofocus="autofocus"> </div> <div class="form-group"> <input class="form-control auth_password" type="password" name="auth_password" placeholder="Auth Password"> </div> <div class="form-group"> <input class="form-control auth_domain" type="text" name="auth_domain" placeholder="Registration Server or Domain"> </div> <div class="form-group"> <h5 class="modal-title custom_align" id="Heading">Optional Credentials</h5> </div> <div class="form-group"> <input class="form-control auth_username" type="text" name="auth_username" placeholder="Auth Username"> </div> <div class="form-group"> <input class="form-control auth_proxy" type="text" name="auth_proxy" placeholder="Outbound Proxy"> </div> </div> </div> <!-- carriers table --> <div class="hidden" name="carriers-toggle"> <div class="wrapper-horizontal edge-centered"> <div class="tableAddButton"> <button id='open-CarrierAdd' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add"> Add </button> </div> <div class="navBarButtons"> </div> </div> <div id="carriers-table" class="table-responsive"> <div id="carriers-loading wrapper-vertical centered"> <div class="spinner spinner-circle"></div> <h5>Loading Your Carriers..</h5> </div> </div> </div> <div class="modal-footer "> <button type="submit" id="updateGroupButton" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div><!-- /.modal-body --> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <!-- add-group modal --> <div class="modal fade" id="add-group" tabindex="-1" role="dialog" aria-labelledby="Add" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add New Carrier Group</h4> </div> <div class="modal-body"> <form class="gwgroupform" action="/carriergroups" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwgroup" name="gwgroup"> <input type="hidden" class="gwlist" name="gwlist"> <div class="form-group"> <input class="form-control name" type="text" id="name" name="name" placeholder="Group Name" autofocus="autofocus" required="required"> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-loadbalancing" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-load_balance'></span> Enabled" data-off="<span class='icon-load_balance'></span> Disabled" data-width="125px"> Load Balancing </label> </div> <input class="lb_enabled" type="hidden" name="lb_enabled" value="0"> </div> <div class="form-group"> <select class="form-control plugin_name" id="plugin_name" name="plugin_name" title="plugin_name"> <option value="" selected="">Select Carrier Plugin (Optional)</option> <option value="Twilio">Twilio</option> </select> </div> <div class="form-group plugin_creds hidden" id="plugin_creds" name="plugin_creds" value="0"> <p>Please enter the plugin credentials.</p> <div class="form-group"> <input class="form-control plugin_account_sid" id="plugin_account_sid" type="text" name="plugin_account_sid" placeholder="Account SID" autofocus="autofocus"> </div> <div class="form-group"> <input class="form-control plugin_account_token" id="plugin_account_token" type="password" name="plugin_account_token" placeholder="Account Token" autofocus="autofocus"> </div> </div> <div class="form-group"> <div class="authoptions radio"> <label><input type="radio" data-toggle="ip_enabled" class="authtype" name="authtype" value="ip" checked>IP Auth</label> <label><input type="radio" data-toggle="userpwd_enabled" class="authtype" name="authtype" value="userpwd">Username/Password Auth</label> </div> </div> <div class="form-group hidden" id="userpwd_enabled" name="userpwd_enabled" value="0"> <p>Please enter the registration username and password provided by the carrier.</p> <div class="form-group"> <input class="form-control r_username" type="text" name="r_username" placeholder="Username" autofocus="autofocus"> </div> <div class="form-group"> <input class="form-control auth_password" type="password" name="auth_password" placeholder="Auth Password"> </div> <div class="form-group"> <input class="form-control auth_domain" type="text" name="auth_domain" placeholder="Registration Server or Domain"> </div> <div class="form-group"> <h5 class="modal-title custom_align" id="Heading">Optional Credentials</h5> </div> <div class="form-group"> <input class="form-control auth_username" type="text" name="auth_username" placeholder="Auth Username"> </div> <div class="form-group"> <input class="form-control auth_proxy" type="text" name="auth_proxy" placeholder="Outbound Proxy"> </div> </div> <div class="modal-footer "> <button type="submit" id="addButton" class="btn btn-lg btn-primary" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <!-- delete-group modal --> <div class="modal fade" id="delete-group" tabindex="-1" role="dialog" aria-labelledby="Delete" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form class="gwgroupform" action="/carriergroupdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" class="gwgroup" name="gwgroup"> <input type="hidden" class="name" name="name"> <input type="hidden" class="gwlist" name="gwlist"> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete all endpoints in this group? </div> <div class="modal-footer "> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes </button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> {% endblock %} {% block custom_js %} {{ script_tag('carriergroups') }} {% endblock %} ================================================ FILE: gui/templates/carriers.html ================================================ <script type="application/javascript"> {% if state.kam_reload_required == True %} reloadKamRequired(true); {% else %} reloadKamRequired(false); {% endif %} </script> <table id="carriers" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th><input type="checkbox" class="checkall"/></th> <th data-field="id">Carrier ID</th> <th data-field="name">Name</th> <th data-field="ip_addr">Hostname/IP</th> <th data-field="strip">Strip</th> <th data-field="prefix">Prefix</th> <th data-field="rweight"> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="relative weight (load balancing only) out of sum of carrier weights in this group. value must be 1-100 or 0 to disable load balancing on this carrier"> Weight </span> </th> <th colspan="2"></th> </tr> </thead> <tbody> {% for row,related_rules in zip(rows,routes) %} {% if new_gwid is not none and row.gwid == new_gwid %} <tr class='element-row new_gw' data-gwid="{{ row.gwid }}"> {% else %} <tr class='element-row' data-gwid="{{ row.gwid }}"> {% endif %} <td><input type="checkbox" class="checkthis" value="1"/></td> <td class='gwid'>{{ row.gwid }}</td> <td class='name'>{{ row.description|attrFilter('name') }}</td> <td class='ip_addr'>{{ row.address }}</td> <td class='strip'>{{ row.strip }}</td> <td class='prefix'>{{ row.pri_prefix }}</td> <td class='rweight'>{{ row.dispatcher_attrs|attrFilter('rweight', delims=(';', '=')) }}</td> <td class='related_rules hidden'>{{ related_rules }}</td> <td> <p data-placement="top" data-toggle="tooltip" title="Edit"> <button id="open-Update" class="open-Update btn btn-primary btn-xs" data-title="Edit" data-toggle="modal" data-target="#edit"><span class="glyphicon glyphicon-pencil"></span> </button> </p> </td> <td> <p data-placement="top" data-toggle="tooltip" title="Delete"> <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span> </button> </p> </td> </tr> {% endfor %} </tbody> </table> ================================================ FILE: gui/templates/cdrs.html ================================================ {% extends 'table_layout.html' %} {% block title %}Call Detail Records{% endblock %} {% block custom_css %} {{ link_tag('cdrs') }} {% endblock %} {% block table_headers %} <div> <h3>Call Detail Records</h3> </div> <div class="btn-group col-sm-3" id="menu"> <select class="btn-primary btn-md" id="endpointgroups"> <option class="hidden" value="0" selected disabled>Select Endpoint Group</option> </select> <i class="glyphicon glyphicon glyphicon-refresh" id="refreshCDR"></i> <i class="glyphicon glyphicon-download-alt" id="downloadCDR"></i> </div> <div> <label for="toggle_completed_calls"> Filter Calls </label> <input id="toggle_completed_calls" type="checkbox" data-toggle="toggle" value="1" data-on="All Calls <span class='icon-phone_enabled'></span>" data-off="Billable Calls <span class='icon-money'></span>" data-width="125px"> </div> {% endblock %} {% block table %} <table id="cdrs" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th data-field="cdr_id">CDR ID</th> <th data-field="call_start_time">Call Start</th> <th data-field="call_duration">Call Duration</th> <th data-field="call_direction">Call Direction</th> <th data-field="src_gwgroupid" class="hidden"></th> <th data-field="src_gwgroupname">Source Gateway Group</th> <th data-field="dst_gwgroupid" class="hidden"></th> <th data-field="dst_gwgroupname">Destination Gateway Group</th> <th data-field="src_username">Source Username</th> <th data-field="dst_username">Destination Username</th> <th data-field="src_address">Source Address</th> <th data-field="dst_address">Destination Address</th> <th data-field="call_id">Call ID</th> </tr> </thead> </table> {% endblock %} {% block custom_js %} {{ script_tag('jquery.tabledit') }} {{ script_tag('cdrs') }} {% endblock %} ================================================ FILE: gui/templates/certificates.html ================================================ {% extends 'table_layout.html' %} {% block title %}Certificates{% endblock %} {% block custom_css %} {{ link_tag('certificates') }} {% endblock %} {% block table_headers %} <div> <h3>Certificates</h3> </div> <div class="tableAddButton"> <button id='open-Add' class='btn btn-primary btn-md' data-title="Add" data-toggle="modal" data-target="#add"> Add </button> </div> {% endblock %} {% block table %} <table id="certificates" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th data-field="id">ID</th> <th data-field="domain">Name</th> <th data-field="type">Type</th> <th data-field="assigned_domains">Assigned Domains</th> </tr> </thead> </table> {% endblock %} <!-- TODO: this seems to be copied directly from endpointgroups there will likely be conflicts now or in the future this needs to be updated --> {% block edit_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Update Certificate <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span> </button> </h4> </div> <div class="modal-body"> <form id="editCertificateForm" action="#" method="POST" role="form" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="form-control" type="text" id="domain2" name="domain" placeholder="Domain name for the certificate" autofocus="autofocus"> </div> <div class="form-group"> <div id="certtypeoptions2" class="btn-group" data-toggle="buttons"> {# <label>#} {# <input type="radio" name="certtype" id="certtype_generate2" value="generated" checked>#} {# Generate#} {# </label>#} <label> <input type="radio" name="certtype" id="certtype_upload2" value="uploaded"> Upload </label> </div> </div> {# <div id="generate2">#} {# <div class="alert alert-warning pre-scrollable hide" id="terminalDiv2">#} {# <strong>#} {# Access this server via ssh and execute the command below. Follow the instructions and upload the certificate<br><br>#} {# </strong>#} {# <pre><code class="bash" id="terminalCommand2">#} {##} {# </code></pre>#} {# </div>#} {# </div><!-- end of generate tab -->#} <div id="uploaded2" class="form-group hide"> <input type="file" name="certandkey" multiple> </div> <!-- end of upload tab --> <div class="modal-footer "> <button type="button" id="updateButton" class="btn btn-primary btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update Certificate </button> </div> </form> </div> {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add</h4> </div> <div class="modal-body"> <form id="addCertificateForm" action="#" method="POST" role="form" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="form-control" type="text" id="domain" name="domain" placeholder="Domain name for the certificate" autofocus="autofocus"> </div> <div class="form-check"> <input type="checkbox" class="form-check-input" id="replace_default_cert" name="replace_default_cert" value="true" checked> <label class="form-check-label" for="replace_default_cert">Replace the default certificate</label> </div> <div class="form-group"> <div id="certtypeoptions" class="btn-group" data-toggle="buttons"> {# <label>#} {# <input type="radio" name="certtype" id="certtype_generate" value="generated">#} {# Generate#} {# </label>#} <label> <input type="radio" name="certtype" id="certtype_upload" value="uploaded" checked> Upload </label> </div> </div> {# <div id="generate">#} {# <div class="alert alert-warning pre-scrollable hide" id="terminalDiv">#} {# <strong>#} {# Access this server via ssh and execute the command below. Follow the instructions and upload the certificate<br><br>#} {# </strong>#} {# <pre><code class="bash" id="terminalCommand">#} {#dsiprouter generatecert#} {# </code></pre>#} {# </div>#} {# </div><!-- end of generate tab -->#} <div id="uploaded" class="form-group hide"> <label class="form-check-label" for="certandkey"> Select certificate (.crt or .cert) and private key file (.key or .pem) </label> <input type="file" id="certandkey" name="certandkey" multiple> </div> <!-- end of upload tab --> <div class="modal-footer "> <button type="button" id="addButton" class="btn btn-primary btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add Certifcate </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form action="/pbxdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="gwgroup form-control" type="hidden" name="gwgroup"> <input class="gwid form-control" type="hidden" name="gwid"> <input class="name form-control" type="hidden" name="name"> </div> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Certificate? </div> <div class="modal-footer "> <button id="deleteButton" type="button" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes </button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('jquery.tabledit') }} {{ script_tag('certificates') }} {% endblock %} ================================================ FILE: gui/templates/dashboard.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}Dashboard{% endblock %} {% block custom_css %} {{ link_tag('dashboard') }} {% endblock %} {% block body %} <div class="wrapper-horizontal edge-centered children-align-inherit content-header"> <h2>Dashboard</h2> {% if reloadstatus %} <h3>Kamailio Reload: {{ reloadstatus }}</h3> {% endif %} </div> <div class="content-section"> <!-- begin placeholders --> <div class="col-xs-4 col-sm-4 placeholder"> <div class="dashboard-metric-label">Active SIP Messages</div> <div id="dashboard_current_calls" class="dashboard-metric"></div> </div> <div class="col-xs-4 col-sm-4 placeholder"> <div class="dashboard-metric-label">SIP Messages in Queue</div> <div id="dashboard_calls_inqueue" class="dashboard-metric"></div> </div> <div class="col-xs-4 col-sm-4 placeholder"> <div class="dashboard-metric-label">Total SIP Messages</div> <div id="dashboard_total_calls_processed" class="dashboard-metric"></div> </div> </div> <!-- end placeholders --> <div class="content-section"> <!-- begin dashboard info --> <div class="col-xs-4"> <div class="panel panel-default"> <div class="panel-heading">Documentation</div> <div class="panel-body"> <ul> <li><a href="/docs/user/index.html" target="_blank">User Documentation</a></li> <li><a href="/docs/dev/index.html" target="_blank">Developer Documentation</a></li> <li><a href="/docs/routes/index.html" target="_blank">Routes Documentation</a></li> </ul> </div> </div> </div> <div class="col-xs-4"> <div class=" panel panel-default"> <div class="panel-heading">Training</div> <div class="panel-body"> <ul> <li><a href="https://dopensource.com/product/dsiprouter-admin-course/" target="_blank">dSIPRouter 1 Day Admin Course</a></li> </ul> </div> </div> </div> <div class="col-xs-4"> <div class="panel panel-default"> <div class="panel-heading">Support</div> <div class="panel-body"> <ul> <li>Paid Support <ul> <li><a href="https://dopensource.com/dsiprouter-core/" target="_blank">Annual Subscription</a></li> </ul> </li> <li>Free Support <ul> <li><a href="https://join.slack.com/t/dsiproutercommunity/shared_invite/zt-1dtqvpyck-H9k~DYgJJ2XIFgh_rWqdPA/" target="_blank">Slack</a></li> <li><a href="https://groups.google.com/forum/#!forum/dsiprouter" target="_blank">User Forum</a></li> </ul> </li> <li>Kamailio Consultants Directory <ul> <li><a href="https://www.kamailio.org/w/business-directory/" target="_black">Kamailio Business Directory</a></li> </ul> </li> </ul> </div> </div> </div> </div> <!-- end dashboard info --> {% endblock %} {% block custom_js %} {{ script_tag('dashboard') }} {% endblock %} ================================================ FILE: gui/templates/domains.html ================================================ {% extends 'table_layout.html' %} {% block title %}Domains{% endblock %} {% block custom_css %} {% endblock %} {% block table_headers %} <div> <h3>List of Domain(s)</h3> </div> <div class="tableAddButton"> <button id='open-DomainAdd' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add"> Add </button> </div> {% endblock %} {% block table %} <table data-toggle="table" id="domains" class="table table-striped table-centered"> <thead> <tr class='element-row'> <!--<th><input class="checkall" type="checkbox"/></th>--> <th></th> <th data-field="domain_id">Domain ID</th> <th data-field="domain_name">Domain Name</th> <th data-field="domain_type">Domain Type</th> <th data-field="pbx_name">Domain Created By</th> <th data-field="authtype">Domain Auth</th> <th data-field="pbx_list">Mapped Endpoints</th> <th data-field="notes">Notes</th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> {% for row in rows %} <tr class='element-row'> <td><input type="checkbox" class="checkthis" value="1"/></td> <td class='domain_id'>{{ row.id }}</td> <td class='domain_name'>{{ row.domain }}</td> <td class='domain_type'>{{ row.type|domainTypeFilter() }}</td> {% if row.type|domainTypeFilter() == "Dynamic" %} <td class='pbx_name'><a href="/endpointgroups?id={{ pbxlookup[row.domain]['pbx_list'] }}">{{ pbxlookup[row.domain]['name'] }}</a> </td> {% else %} <td class='pbx_name'>Manually Created</td> {% endif %} <td class='authtype'>{{ pbxlookup[row.domain]['domain_auth'] }}</td> <td class='pbx_list'>{{ pbxlookup[row.domain]['pbx_list'] }}</td> <td class='notes'>{{ pbxlookup[row.domain]['notes'] }}</td> {% if row.type|domainTypeFilter() != "Dynamic" %} <td> <p data-placement="top" data-toggle="tooltip" title="Edit"> <button id="open-Update" class="open-Update btn btn-primary btn-xs" data-title="Edit" data-toggle="modal" data-target="#edit"><span class="glyphicon glyphicon-pencil"></span> </button> </p> </td> <td> {% if pbxlookup[row.domain]['domain_auth'] == "msteams" %} <p data-placement="top" data-toggle="tooltip" title="Test MSTeams Connectivity"> <button id="open-Test" class="open-Test btn btn-warning btn-xs" data-title="Test MSTeams Connectivity" data-toggle="modal" onclick="window.location='domains/msteams/{{ row.id }}'"> <span class="glyphicon glyphicon-plane"></span></button> </p> {% endif %} </td> <td> <p data-placement="top" data-toggle="tooltip" title="Delete"> <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span></button> </p> </td> {% else %} <td></td> <td></td> {% endif %} </tr> {% endfor %} </tbody> </table> {% endblock %} {% block edit_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Your Domain Detail</h4> </div> <div class="modal-body"> <form id="updateDomainForm" action="/domains" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="domain_id" type="hidden" name="domain_id"> <div class="form-group"> <input class="domain_name form-control" type="text" name="domainlist" placeholder="domainA.com,domainB.com"> </div> <div class="form-group"> <div class="domainauthoptions radio"> <select class="authtype form-control" name="authtype"> <option value="">Select Domain Type</option> <option value="passthru">Pass Thru to PBX</option> {% if licenseValid('DSIP_MSTEAMS') == False %}(Subscription Required) <option value="msteams-nosub">Microsoft Teams Direct Routing (Subscription Required)</option> {% else %} <option value="msteams">Microsoft Teams Direct Routing</option> {% endif %} <option value="local">Local Subscriber Table</option> <option value="realtime">Real Time DB</option> </select> <!-- <label><input class="authtype " type="radio" data-toggle="realtime" name="authtype" value="realtime" checked> Realtime DB (aka Asterisk Realtime)</label> <label><input class="authtype " type="radio" data-toggle="local" name="authtype" value="local"> Local Subscriber Table</label> <label><input class="authtype " type="radio" data-toggle="passthru" name="authtype" value="passthru"> Pass Thru to PBX</label>--> </div> </div> <div class="form-group"> <input class="pbx_list form-control" type="text" name="pbx_list" placeholder="List of backend PBX ID's "> </div> <div class="form-group"> <input class="notes form-control" type="text" name="notes" placeholder="Notes"> </div> <div class="modal-footer "> <button type="submit" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div> {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add New Domain</h4> </div> <div class="modal-body"> <form id="addDomainForm" action="/domains" method="POST" role="form"> <input class="domain_id" type="hidden" name="domain_id"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="domain_name form-control" type="text" name="domainlist" autofocus="autofocus" placeholder="domainA.com,domainB.com"> </div> <div class="form-group"> <div class="domainauthoptions radio"> <select class="authtype form-control" name="authtype"> <option value="">Select Domain Type</option> <option value="passthru">Pass Thru to PBX</option> {% if licenseValid('DSIP_MSTEAMS') == False %} <option value="msteams-nosub">Microsoft Teams Direct Routing (Subscription Required)</option> {% else %} <option value="msteams">Microsoft Teams Direct Routing</option> {% endif %} <option value="local">Local Subscriber Table</option> <option value="realtime">Real Time DB</option> </select> </div> </div> <div class="form-group"> <input class="pbx_list form-control" type="text" name="pbx_list" placeholder="List of backend PBX ID's "> </div> <div class="form-group"> <input class="notes form-control" type="text" name="notes" placeholder="Notes"> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form action="/domainsdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="domain_id form-control" type="hidden" name="domain_id"> <input class="domain_name form-control" type="hidden" name="domain_name"> </div> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Record? </div> <div class="modal-footer "> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes</button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('domains') }} {% endblock %} ================================================ FILE: gui/templates/endpointgroups.html ================================================ {% extends 'table_layout.html' %} {% block title %}Endpoint Groups{% endblock %} {% block custom_css %} {% endblock %} {% block table_headers %} <div> <h3>List of Endpoint Groups</h3> </div> <div class="tableAddButton"> <div class="btn-group mr-2"> <button id='open-EndpointGroupsAdd' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add"> Add </button> </div> <!-- <div class="btn-group"> <div class="dropdown"> <button class="btn btn-secondary dropdown-toggle" type="button" id="dropdownMenuButton" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> Batch Actions<span class="caret"></span> </button> <div class="dropdown-menu" style="width:190px;padding:5px" aria-labelledby="dropdownMenuButton"> <a class="dropdown-item" href="#" onclick='enableMaintenanceMode()'>Enable Maintenance Mode</a> <a class="dropdown-item" href="#" onclick='disableMaintenanceMode()'>Disable Maintenance Mode</a> </div> </div> </div> --> </div> {% endblock %} {% block table %} <table id="endpointgroups" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th data-field="name">Name</th> <th data-field="gwgroupid">ID</th> </tr> </thead> </table> {% endblock %} {% block edit_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Endpoint Group Details <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span> </button> </h4> </div> <div class="modal-body"> <form action="/endpointgroups" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="gwid" type="hidden" name="gwid"> <input class="gwgroupid" type="hidden" name="gwgroupid"> <div class="form-group"> <input class="name form-control" type="text" name="name" placeholder="Friendly Name(Optional)" autofocus="autofocus"> </div> <!-- <div class="form-group"> <input class="ip_addr form-control" type="text" name="ip_addr" placeholder="IP Address"> </div> --> <!-- nav tabs --> <div id="endpoint-nav" class="navbar"> <ul class="nav nav-tabs"> <li role="presentation" class="auth-tab active"> <a href="#auth" name="auth-toggle" data-toggle="tab">Auth</a> </li> <li role="presentation"> <a href="#endpoints" name="endpoints-toggle" data-toggle="tab">Endpoints</a> </li> <li role="presentation"> <a href="#call_settings" name="settings-toggle" data-toggle="tab">Call Settings</a> </li> <li role="presentation"> <a href="#notifications" name="notifications-toggle" data-toggle="tab">Notifications</a> </li> <li role="presentation"> <a href="#cdr" name="cdr-toggle" data-toggle="tab">CDR</a> </li> <li role="presentation"> <a href="#fusionpbx" name="fusion-toggle" data-toggle="tab">FusionPBX</a> </li> </ul> </div> <!-- tab content --> <div class="tab-content"> <div id="auth" class="tab-pane fade in active" name="auth-toggle"> <div class="form-group"> <div id="authoptions2" class="btn-group" data-toggle="buttons"> <label><input class="authtype" type="radio" name="authtype" id="ip2" value="ip" checked> IP Auth</label> <label><input class="authtype" type="radio" name="authtype" id="userpwd2" value="userpwd"> Username/Password Auth</label> </div> </div> <div id="userpwd_enabled2" class="userpwd"> <p>Please enter a username and password for the PBX/Endpoint you want to register to. Specify domain if different than the default domain: <b>{{ DEFAULT_auth_domain }}</b></p> <div class="form-group"> <input class="auth_username form-control" type="text" name="auth_username" placeholder="Auth Username"> </div> <div class="form-group wrapper-fieldicon-right"> <input id="auth_password2" class="form-control" type="password" name="auth_password" placeholder="Auth Password"> <span toggle="#auth_password2" class="field-icon toggle-password glyphicon glyphicon-eye-close"></span> </div> <div class="form-group"> <input class="auth_domain form-control" type="text" name="auth_domain" placeholder="Auth Domain (optional)"> </div> </div> </div> <!-- end of auth tab --> <div id="endpoints" class="tab-pane fade in" name="endpoints-toggle"> <table id="endpoint-table" class="table"> <thead> <tr> <th>ID</th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="hostname or IP address to route to (and/or) authenticate endpoint"> Host </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="port to use when routing to endpoint"> Port </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="signalling transport for calls to endpoint"> Signalling </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="media transport for calls to endpoint"> Media </span> </th> <th>Description</th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="relative weight (load balancing only) out of sum of endpoint weights in this group. value must be 1-100 or 0 to disable load balancing on this endpoint"> Weight </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="when enabled, the gateway is sent keepalive messages (at a default interval of 60 seconds). if disabled, the destination is always attempted when routing"> Keepalive </span> </th> <th> <button class="btn btn-success btn-md" type="button" id="updateEndpointRow">Add Row</button> </th> </tr> </thead> <tbody id="endpoint-tablebody"> </tbody> </table> </div> <!-- enf of endpoints tab --> <div id="call_settings" class="tab-pane fade in" name="settings-toggle"> <div class="form-group"> <input class="strip form-control" type="text" name="strip" placeholder="# of characters to strip from RURI (inbound calls)"> </div> <div class="form-group"> <input class="prefix form-control" type="text" name="prefix" placeholder="The characters to prefix to a RURI (inbound calls)"> </div> <div class="form-group"> <input class="call_limit form-control" type="number" name="call_limit" placeholder="Max Concurrent Calls" autocomplete="off" min="0"> </div> <div class="form-group"> <input class="call_timeout form-control" type="number" name="call_timeout" placeholder="Maximum Call Length (seconds)" autocomplete="off" min="0"> </div> </div> <!-- end of call settings tab --> <div id="notifications" class="tab-pane fade in" name="notifications-toggle"> <div class="form-group"> <input class="email_over_max_calls form-control" type="text" name="email_over_max_calls" placeholder="Email for Over Max Concurrent Calls"> </div> <div class="form-group"> <input class="email_endpoint_failure form-control" type="text" name="email_endpoint_failure" placeholder="Email for Endpont Failure"> </div> </div> <!-- end of notifications tab --> <div id="cdr" class="tab-pane fade in" name="cdr-toggle"> <div class="form-group"> <input class="cdr_email form-control" type="text" name="cdr_email" placeholder="Email to send CDR's"> </div> <!-- CDR report interval chooser, based on crontab design --> <!-- TODO: make this a combobox --> <div class="form-group"> <label>CDR Report Interval (minute hour day month weekday)</label> <div class="wrapper-horizontal centered"> <input class="cdr_send_minute form-control" type="text" name="cdr_send_minute" value="*" title="Minute of the hour (0-59) or (* is any minute)" pattern="\*(/([0-5]?[0-9]))?|([0-5]?[0-9])(,[0-5]?[0-9])*"> <input class="cdr_send_hour form-control" type="text" name="cdr_send_hour" value="*" title="Hour of the day (0-23) or (* is any hour)" pattern="\*(/(2[0-3]|1[0-9]|[0-9]))?|(2[0-3]|1[0-9]|[0-9])(,(2[0-3]|1[0-9]|[0-9]))*"> <input class="cdr_send_day form-control" type="text" name="cdr_send_day" value="1" title="Day of the month (1-31) or (* is any day)" pattern="\*(/(3[0-1]|[1-2][0-9]|[1-9]))?|(3[0-1]|[1-2][0-9]|[1-9])(,(3[0-1]|[1-2][0-9]|[1-9]))*"> <input class="cdr_send_month form-control" type="text" name="cdr_send_month" value="*" title="Month of the year (1-12) or (* is any month) or (jan-dec)" pattern="\*(/(1[0-2]|[1-9]))?|(1[0-2]|[1-9])(,(1[0-2]|[1-9]))*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)"> <input class="cdr_send_weekday form-control" type="text" name="cdr_send_weekday" value="*" title="Day of the week (0-6) or (* is any weekday) or (sun-sat)" pattern="\*(/([0-6]))?|([0-6])(,[0-6])*|(sun|mon|tue|wed|thur|fri|sat)"> </div> </div> </div> <!-- end of cdr tab --> <div id="fusionpbx" class="tab-pane fade in" name="fusion-toggle"> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggleFusionPBXDomain" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-fusionpbx'></span> Enabled" data-off="<span class='icon-fusionpbx'></span> Disabled" data-width="125px"> FusionPBX Domain Support </label> </div> <input class="fusionpbx_db_enabled " type="hidden" name="fusionpbx_db_enabled" value="0"> </div> <div class="FusionPBXDomainOptions form-group hidden"> <div class="alert alert-warning pre-scrollable"> <strong> You need access to the FusionPBX database. Run these commands as root on the FusionPBX server. </strong> <pre><code class="bash">DSIPROUTER_IP={{ dsiprouter_ip }} sed -i -r "s/^#?listen_addresses[ \t]?=[ \t]?.*/listen_addresses = '*'/m" /etc/postgresql/*/main/postgresql.conf iptables -A INPUT -p tcp -s $DSIPROUTER_IP/32 --dport 5432 -j ACCEPT iptables-save &gt;/etc/iptables/rules.v4 # Run this command if your don't want to enter a password for the FusionPBX Database(DB) Password echo -e "host all all $DSIPROUTER_IP/32 trust" &gt;&gt;/etc/postgresql/*/main/pg_hba.conf systemctl restart postgresql # Run this command if your using fail2ban sed -i -r "s|^#?(ignoreip = .*)|\1 $DSIPROUTER_IP/32|" /etc/fail2ban/jail.conf systemctl restart fail2ban</code></pre> </div> <div class="form-group"> <input class="fusionpbx_db_server form-control" type="text" name="fusionpbx_db_server" placeholder="FusionPBX Database IP or Hostname"> </div> <div class="form-group"> <input class="fusionpbx_db_username form-control" type="text" name="fusionpbx_db_username" placeholder="FusionPBX DB Username"> </div> <div class="form-group"> <input class="fusionpbx_db_password form-control" type="password" name="fusionpbx_db_password" placeholder="FusionPBX DB Password(Optional)"> </div> </div> </div> <!-- end of fusionpbx tab --> <div class="FreePBXDomainOptions form-group hidden"> <div class="alert alert-warning pre-scrollable"> <strong> Run these commands as root on the FreePBX server. Replace values within angle brackets '&lt;value&gt;' with your own values. </strong> <pre><code class="bash">DSIPROUTER_IP={{ dsiprouter_ip }} PBX_SIP_PORTS=(5060 5080) for PORT in ${PBX_SIP_PORTS[@]}; do iptables -A INPUT -p udp -s $DSIPROUTER_IP/32 --dport $PORT -j ACCEPT done iptables-save &gt;/etc/iptables/rules.v4 # Run this command if your using fail2ban sed -i -r "s|(ignoreip = .*)|\1 $DSIPROUTER_IP/32|" /etc/fail2ban/jail.conf systemctl restart fail2ban</code></pre> </div> </div> <!-- end of freepbx tab --> </div> <!-- end of tabcontent tab --> <div class="modal-footer"> <button type="submit" id="updateButton" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div> {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add Endpoint Group Details</h4> </div> <div class="modal-body"> <form action="/endpointgroups" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="gwid" type="hidden" name="gwid"> <input class="gwgroup" type="hidden" name="gwgroup"> <div class="form-group"> <input class="name form-control" type="text" name="name" placeholder="Friendly Name(Optional)" autofocus="autofocus"> </div> <!-- <div class="form-group"> <input class="ip_addr form-control" type="text" name="ip_addr" placeholder="IP Address"> </div> --> <!-- nav tabs --> <div id="endpoint-nav" class="navbar"> <ul class="nav nav-tabs"> <li role="presentation" class="active"> <a href="#auth2" name="auth-toggle" data-toggle="tab">Auth</a> </li> <li role="presentation"> <a href="#endpoints2" name="endpoints-toggle" data-toggle="tab">Endpoints</a> </li> <li role="presentation"> <a href="#call_settings2" name="settings-toggle" data-toggle="tab">Config</a> </li> <li role="presentation"> <a href="#notifications2" name="notifications-toggle" data-toggle="tab">Notifications</a> </li> <li role="presentation"> <a href="#cdr2" name="cdr-toggle" data-toggle="tab">CDR</a> </li> <li role="presentation"> <a href="#fusionpbx2" name="fusion-toggle" data-toggle="tab">FusionPBX</a> </li> </ul> </div> <div class="tab-content"> <!-- begin tab content --> <div id="auth2" class="tab-pane fade in active" name="auth-toggle"> <div class="form-group"> <div id="authoptions" class="btn-group" data-toggle="buttons"> <label><input class="authtype" type="radio" name="authtype" id="ip" value="ip" checked> IP Auth</label> <label><input class="authtype" type="radio" name="authtype" id="userpwd" value="userpwd"> Username/Password Auth</label> </div> </div> <div id="userpwd_enabled" class="userpwd"> <p>Please enter a username and password for the PBX/Endpoint you want to register to. Specify domain if different than the default domain: <b>{{ DEFAULT_auth_domain }}</b></p> <div class="form-group"> <input class="auth_username form-control" type="text" name="auth_username" placeholder="Auth Username"> </div> <div class="form-group wrapper-fieldicon-right"> <input id="auth_password" class="form-control" type="password" name="auth_password" placeholder="Auth Password"> <span toggle="#auth_password" class="field-icon toggle-password glyphicon glyphicon-eye-close"></span> </div> <div class="form-group"> <input class="auth_domain form-control" typead="text" name="auth_domain" placeholder="Auth Domain (optional)"> </div> </div> </div> <!-- end of auth tab --> <div id="endpoints2" class="tab-pane fade in" name="endpoints-toggle"> <table id="endpoint-table2" class="table"> <thead> <tr> <th>ID</th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="hostname or IP address to route to (and/or) authenticate endpoint"> Host </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="port to use when routing to endpoint"> Port </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="signalling transport for calls to endpoint"> Signalling </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="media transport for calls to endpoint"> Media </span> </th> <th>Description</th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="relative weight (load balancing only) out of sum of endpoint weights in this group. value must be 1-100 or 0 to disable load balancing on this endpoint"> Weight </span> </th> <th> <span data-toggle="tooltip" data-placement="top" title="" data-original-title="when enabled, the gateway is sent keepalive messages (at a default interval of 60 seconds). if disabled, the destination is always attempted when routing"> Keepalive </span> </th> <th> <button class="btn btn-success btn-md" type="button" id="addEndpointRow">Add Row</button> </th> </tr> </thead> <tbody id="endpoint-tablebody2"> </tbody> </table> </div> <!-- enf of endpoints tab --> <div id="call_settings2" class="tab-pane fade in" name="settings-toggle"> <div class="form-group"> <input class="strip form-control" type="text" name="strip" placeholder="# of characters to strip from RURI (inbound calls)"> </div> <div class="form-group"> <input class="prefix form-control" type="text" name="prefix" placeholder="The characters to prefix to a RURI (inbound calls)"> </div> <div class="form-group"> <input class="call_limit form-control" type="number" name="call_limit" placeholder="Max Concurrent Calls" autocomplete="off" min="0"> </div> <div class="form-group"> <input class="call_timeout form-control" type="number" name="call_timeout" placeholder="Maximum Call Length (seconds)" autocomplete="off" min="0"> </div> </div> <!-- end of call settings tab --> <div id="notifications2" class="tab-pane fade in" name="notifications-toggle"> <div class="form-group"> <input class="email_over_max_calls form-control" type="text" name="email_over_max_calls" placeholder="Email for Over Max Concurrent Calls"> </div> <div class="form-group"> <input class="email_endpoint_failure form-control" type="text" name="email_endpoint_failure" placeholder="Email for Endpont Failure"> </div> </div> <!-- end of notifications tab --> <div id="cdr2" class="tab-pane fade in" name="cdr-toggle"> <div class="form-group"> <input class="cdr_email form-control" type="text" name="cdr_email" placeholder="Email to send CDR's"> </div> <!-- CDR report interval chooser, based on crontab design --> <!-- TODO: make this a combobox --> <div class="form-group"> <label>CDR Report Interval (minute, hour, day, month, weekday)</label> <div class="wrapper-horizontal centered"> <input class="cdr_send_minute form-control" type="text" name="cdr_send_minute" value="*" title="Minute of the hour (0-59) or (* is any minute)" pattern="\*(/([0-5]?[0-9]))?|([0-5]?[0-9])(,[0-5]?[0-9])*"> <input class="cdr_send_hour form-control" type="text" name="cdr_send_hour" value="*" title="Hour of the day (0-23) or (* is any hour)" pattern="\*(/(2[0-3]|1[0-9]|[0-9]))?|(2[0-3]|1[0-9]|[0-9])(,(2[0-3]|1[0-9]|[0-9]))*"> <input class="cdr_send_day form-control" type="text" name="cdr_send_day" value="1" title="Day of the month (1-31) or (* is any day)" pattern="\*(/(3[0-1]|[1-2][0-9]|[1-9]))?|(3[0-1]|[1-2][0-9]|[1-9])(,(3[0-1]|[1-2][0-9]|[1-9]))*"> <input class="cdr_send_month form-control" type="text" name="cdr_send_month" value="*" title="Month of the year (1-12) or (* is any month) or (jan-dec)" pattern="\*(/(1[0-2]|[1-9]))?|(1[0-2]|[1-9])(,(1[0-2]|[1-9]))*|(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)"> <input class="cdr_send_weekday form-control" type="text" name="cdr_send_weekday" value="*" title="Day of the week (0-6) or (* is any weekday) or (sun-sat)" pattern="\*(/([0-6]))?|([0-6])(,[0-6])*|(sun|mon|tue|wed|thur|fri|sat)"> </div> </div> </div> <!-- end of cdr tab --> <div id="fusionpbx2" class="tab-pane fade in" name="fusion-toggle"> <div class="form-group"> <div class="checkbox-inline"> <label class="label-toggle"> <input class="toggleFusionPBXDomain" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-fusionpbx'></span> Enabled" data-off="<span class='icon-fusionpbx'></span> Disabled" data-width="125px"> FusionPBX Domain Support </label> </div> <div class="checkbox-inline"> <label class="checkbox-inline"> <input type="checkbox" class='fusionpbx_clustersupport' name="fusionpbx_clustersupport" value="0">Cluster Support </label> </div> </div> <input class="fusionpbx_db_enabled " type="hidden" name="fusionpbx_db_enabled" value="0"> <div class="FusionPBXDomainOptions form-group hidden"> <div class="alert alert-warning pre-scrollable"> <strong> You need access to the FusionPBX database. Run these commands as root on the FusionPBX server. </strong> <pre><code class="bash">DSIPROUTER_IP={{ dsiprouter_ip }} sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/*/main/postgresql.conf iptables -A INPUT -p tcp -s $DSIPROUTER_IP/32 --dport 5432 -j ACCEPT iptables-save &gt;/etc/iptables/rules.v4 # Run this command if your don't want to enter a password for the FusionPBX Database(DB) Password echo -e "host all all $DSIPROUTER_IP/32 trust" &gt;&gt;/etc/postgresql/*/main/pg_hba.conf systemctl restart postgresql # Run this command if your using fail2ban sed -i -r "s|^#?(ignoreip = .*)|\1 $DSIPROUTER_IP/32|" /etc/fail2ban/jail.conf systemctl restart fail2ban</code></pre> </div> <div class="form-group"> <input class="fusionpbx_db_server form-control" type="text" name="fusionpbx_db_server" placeholder="FusionPBX Database IP or Hostname"> </div> <div class="form-group"> <input class="fusionpbx_db_username form-control" type="text" name="fusionpbx_db_username" placeholder="FusionPBX DB Username"> </div> <div class="form-group"> <input class="fusionpbx_db_password form-control" type="password" name="fusionpbx_db_password" placeholder="FusionPBX DB Password(Optional)"> </div> </div> </div> <!-- end of fusionpbx tab --> <div class="FreePBXDomainOptions form-group hidden"> <div class="alert alert-warning pre-scrollable"> <strong> Run these commands as root on the FreePBX server. Replace PBX_SIP_PORTS with your own values if they are not set to the defaults. </strong> <pre><code class="bash">DSIPROUTER_IP={{ dsiprouter_ip }} PBX_SIP_PORTS=(5060 5080) for PORT in ${PBX_SIP_PORTS[@]}; do iptables -A INPUT -p udp -s $DSIPROUTER_IP/32 --dport $PORT -j ACCEPT done iptables-save &gt;/etc/iptables/rules.v4 # Run this command if your using fail2ban sed -i -r "s|(ignoreip = .*)|\1 $DSIPROUTER_IP/32|" /etc/fail2ban/jail.conf systemctl restart fail2ban</code></pre> </div> </div> <!-- end of freepbx tab --> </div> <!-- end of tabcontent tab --> <div class="modal-footer "> <button type="submit" id="addButton" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form action="/pbxdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group"> <input class="gwgroup form-control" type="hidden" name="gwgroup"> <input class="gwid form-control" type="hidden" name="gwid"> <input class="name form-control" type="hidden" name="name"> </div> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Endpoint Group? </div> <div class="modal-footer "> <button id="deleteButton" type="button" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign" autofocus="autofocus"></span> Yes </button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('jquery.tabledit') }} {{ script_tag('endpointgroups') }} {% endblock %} ================================================ FILE: gui/templates/error.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}Error Page{% endblock %} {% block custom_css %} {% endblock %} {% block body %} {% set github_url = 'https://github.com/dOpensource/dsiprouter' %} {% set github_issues_url = github_url + '/issues' %} {% set dopensource_url = 'https://dopensource.com' %} {% set dopensource_shop_url = dopensource_url + '/product-category/dsiprouter/' %} <div class="wrapper-vertical centered"> {% if type=="db" %} <div class="alert alert-danger container"> <h2><strong>DB Issue</strong></h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>Please check your database and try the request again!</p> <p>Check out the <a href="{{ github_url }}">dSIPRouter Github</a> page for more information about the platform. </p> </div> {% elif type=="http" %} <div class="alert alert-danger container"> <h2><strong>HTTP Issue</strong></h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>Please check your connection and try the request again!</p> <p>Check out the <a href="{{ github_url }}">dSIPRouter Github</a> page for more information about the platform. </p> </div> {% elif type=="server" %} <div class="alert alert-danger container"> <h2><strong>Server Issue</strong></h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>Please check your configuration and try the request again!</p> <p>Check out the <a href="{{ github_url }}">dSIPRouter Github</a> page for more information about the platform. </p> <p>To submit a bug for review go to the <a href="{{ github_issues_url }}">dSIPRouter Issues</a> page and submit a new issue with a <strong>detailed</strong> explanation and <strong>screenshots</strong> of the issue.</p> </div> {% elif type=="woocommerce" %} <div class="alert alert-danger container"> <h2><strong>Licensing Issue</strong></h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>Double check your license is still valid.</p> <p>To renew your license go to the <a href="{{ dopensource_shop_url }}">dopensource shop</a>.</p> <p>If your license was associated with a previous machine you have to remove it first.</p> <p>If you purchased a license and are continuing to have issues please <a href="https://support.dopensource.com/">open a ticket</a> for support.</p> </div> {% else %} <div class="alert alert-danger container"> <h2><strong>Unknown Issue</strong></h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>Please check your request and try it again!</p> <p>Check out the <a href="{{ github_url }}">dSIPRouter Github</a> page for more information about the platform. </p> <p>To submit a bug for review go to the <a href="{{ github_issues_url }}">dSIPRouter Issues</a> page and submit a new issue with a <strong>detailed</strong> explanation and <strong>screenshots</strong> of the issue.</p> </div> {% endif %} </div> {% endblock %} {% block custom_js %} <script type="application/javascript"> /* fix section padding */ $('.wrap .content .content-inner').css({ 'padding': 0 }); /* fix alert text color */ $('.alert > *').css({ 'color': '#000000' }); </script> {% endblock %} ================================================ FILE: gui/templates/fullwidth_layout.html ================================================ <!DOCTYPE html> {% from 'util.jinja2.html' import link_tag, script_tag, tracked_link, img_tag %} <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>dSIPRouter {% block title %}{% endblock %}</title> <!-- inherited CSS --> {{ link_tag('bootstrap') }} {{ link_tag('bootstrap-theme') }} {{ link_tag('bootstrap-toggle') }} {{ link_tag('datatables.min') }} {{ link_tag('highlight/{}'.format(highlight_theme|default('github'))) }} {{ link_tag('main') }} <!-- custom CSS --> {% block custom_css %} {% endblock %} </head> <body> <div class="container"> <div class="wrap"> <nav class="nav-bar navbar-inverse" role="navigation"> <div id="top-menu" class="container-fluid active"> <a class="navbar-brand" href="http://dopensource.com/dsiprouter"><img src="{{ url_for('static', filename='images/dsiprouter_x50px.png') }}"></a> <ul class="nav navbar-nav navbar-right"> <div class="btn-group" style="margin-right: 0.5em"> {% if state.kam_reload_required == True or state.dsip_reload_required == True %} <button type="button" class="btn btn-warning" id="reload">Reload</button> <button type="button" class="btn btn-warning dropdown-toggle" id="reload-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {% else %} <button type="button" class="btn btn-primary" id="reload">Reload</button> <button type="button" class="btn btn-primary dropdown-toggle" id="reload-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> {% endif %} <span class="caret"></span> <span class="sr-only">Toggle Dropdown</span> </button> <ul class="dropdown-menu" role="menu"> {% if state.kam_reload_required == True %} {% set kam_reload_state="btn-warning" %} {% else %} {% set kam_reload_state="btn-secondary" %} {% endif %} {% if state.dsip_reload_required == True %} {% set dsip_reload_state="btn-warning" %} {% else %} {% set dsip_reload_state="btn-secondary" %} {% endif %} <li><a class="dropdown-item {{ kam_reload_state }}" id="reload_kam">Reload Kamailio</a></li> <li><a class="dropdown-item {{ dsip_reload_state }}" id="reload_dsip">Reload dSIPRouter</a></li> </ul> </div> <!-- <form id="qform" class="navbar-form pull-left" role="search"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="text" class="form-control" placeholder="Search" /> </form> --> <li class="dropdown movable"> <a href="#" class="dropdown-toggle" data-toggle="dropdown"><span class="caret"></span><span class="fa fa-4x fa-child"></span>{{ session.username }}</a> <ul class="dropdown-menu" role="menu"> <!-- <li><a href="#"><span class="fa fa-user"></span>My Profile</a></li> <li><a href="#"><span class="fa fa-gear"></span>Settings</a></li> --> <li class="divider"></li> <li><a href="/logout"><span class="fa fa-power-off"></span>Logout</a></li> </ul> </li> </ul> </div> </nav> <aside id="side-menu" class="aside" role="navigation"> <ul class="nav nav-list accordion"> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-globe"></i> <a class="navlink" href="/">Dashboard</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-users"></i> <a class="navlink" href="/carriergroups">Carrier Groups</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-users"></i> <a class="navlink" href="/endpointgroups">Endpoint Groups</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-cloud"></i> <a class="navlink" href="/domains">Domains</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-map-marker"></i> <a class="navlink" href="/inboundmapping">Inbound Routes</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-file-image-o"></i> <a class="navlink" href="/outboundroutes">Outbound Routes</a> <i class="fa fa-chevron-down"></i> </div> </li> <!-- <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-file-image-o"></i> <a class="navlink" href="/numberenrichment">Number Enrichment</a> <i class="fa fa-chevron-down"></i> </div> </li> --> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-file-image-o"></i> <a class="navlink" href="/cdrs">Call Detail Records</a> <i class="fa fa-chevron-down"></i> </div> </li> <li class="nav-header"> <div class="link"> <i class="fa fa-lg fa-file-image-o"></i> <a class="navlink" href="#">System Settings</a> <i class="glyphicon glyphicon-chevron-down"></i> </div> <ul class="submenu"> <li><a class="navlink" href="/backupandrestore">Backup and Restore</a></li> <li><a href="/certificates">Certificates</a></li> <li><a href="/teleblock">Teleblock</a></li> <li><a href="/transnexus">TransNexus</a></li> <li><a href="/stirshaken">STIR/SHAKEN</a></li> <li><a href="/licensing">License Manager</a></li> <li><a href="/upgrade">Upgrade</a></li> </ul> </li> </ul> </aside> <!--Body content--> <div class="content"> <div class="top-bar" style="display: none;"> <div class="message-bar" style="text-align: center;"></div> <a href="#menu" class="side-menu-link burger"> <span class='burger_inside' id='bgrOne'></span> <span class='burger_inside' id='bgrTwo'></span> <span class='burger_inside' id='bgrThree'></span> </a> </div> <section class="content-inner"> <div id="reloading_overlay" class="hidden"></div> {% block body %} {% endblock %} </section> </div> </div> </div> <!-- inherited JS --> {{ script_tag('jquery') }} <script type="application/javascript"> /* globals set in window properties */ Object.defineProperty(window, "GUI_BASE_URL", { configurable: false, writable: false, value: "{{ settings.DSIP_PROTO }}" + "://" + window.location.hostname + ":" + "{{ settings.DSIP_PORT }}" + "/" }); Object.defineProperty(window, "API_BASE_URL", { configurable: false, writable: false, value: "{{ settings.DSIP_API_PROTO }}" + "://" + window.location.hostname + ":" + "{{ settings.DSIP_API_PORT }}" + "/api/v1/" }); </script> {{ script_tag('util') }} <script type="application/javascript"> {% include 'includes/overrides.js' %} </script> {{ script_tag('bootstrap') }} {{ script_tag('bootstrap-toggle') }} {{ script_tag('validator') }} {{ script_tag('main') }} {{ script_tag('datatables.min') }} {{ script_tag('highlight/highlight.pack') }} <script type="application/javascript"> $(document).ready(function() { /* add code syntax highlighting */ $('pre code').each(function(i, block) { hljs.highlightBlock(block); }); }); </script> <!-- custom JS --> {% block custom_js %} {% endblock %} </body> </html> ================================================ FILE: gui/templates/inboundmapping.html ================================================ {% extends 'table_layout.html' %} {% block title %}Inbound Routes{% endblock %} {% block custom_css %} {{ link_tag('combobox') }} {% endblock %} {% block table_headers %} <div> <h3>List of Inbound Routes</h3> </div> <div class="tableAddButton"> <button id='open-Add' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add">Add </button> <button id='open-DIDImport' class='btn btn-success btn-md' data-title="Import DID's" data-toggle="modal" data-target="#import">Import DID </button> </div> {% endblock %} {% block table %} <table id="inboundmapping" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th></th> <th data-field="ruleid">Rule ID</th> <th data-field="prefix">DID (or DID pattern)</th> <th data-field="gwgroupid" class="hidden"></th> <th data-field="gwgroupname">Endpoint Group</th> <th data-field="rulename">Name</th> <th data-field="gwlist" class="hidden">Gateway List</th> <th data-field="lb_enabled" class="hidden"></th> <th></th> <th></th> <th data-field="hf_ruleid" class="hidden"></th> <th data-field="hf_groupid" class="hidden"></th> <th data-field="hf_gwgroupid" class="hidden"></th> <th data-field="hf_fwddid" class="hidden"></th> <th data-field="ff_ruleid" class="hidden"></th> <th data-field="ff_groupid" class="hidden"></th> <th data-field="ff_gwgroupid" class="hidden"></th> <th data-field="ff_fwddid" class="hidden"></th> </tr> </thead> <tbody> {% for row in rows %} <tr class='element-row'> <td><input type="checkbox" class="checkthis" value="1"/></td> <td class='ruleid'>{{ row.ruleid }}</td> <td class='prefix'>{{ row.prefix }}</td> <td class="gwgroupid hidden">{{ row.gwgroupid }}</td> {% if row.rule_description|attrFilter('lb_enabled') == "1" %} <td class='gwgroupname'>{{ row.gwgroup_description|attrFilter('name') }} <small>(Load Balancing)</small></td> {% else %} {% if row.gwlist.split(',')|length > 1 %} <td class='gwgroupname'>{{ row.gwgroup_description|attrFilter('name') }}<sup> +1</sup></td> {% else %} <td class='gwgroupname'>{{ row.gwgroup_description|attrFilter('name') }}</td> {% endif %} {% endif %} <td class="rulename">{{ row.rule_description|attrFilter('name') }}</td> <td class="gwlist hidden">{{ row.gwlist }}</td> <td class="lb_enabled hidden">{{ row.rule_description|attrFilter('lb_enabled') }}</td> <td> <p data-placement="top" data-toggle="tooltip" title="Edit"> <button id="open-Update" class="open-Update btn btn-primary btn-xs" data-title="Edit" data-toggle="modal" data-target="#edit"><span class="glyphicon glyphicon-pencil"></span> </button> </p> </td> <td> <p data-placement="top" data-toggle="tooltip" title="Delete"> <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span> </button> </p> </td> <td class="hf_ruleid hidden">{{ row.hf_ruleid|noneFilter() }}</td> <td class="hf_groupid hidden">{{ row.hf_groupid|noneFilter() }}</td> <td class="hf_gwgroupid hidden">{{ row.hf_gwgroupid|noneFilter() }}</td> <td class="hf_fwddid hidden">{{ row.hf_fwddid|noneFilter() }}</td> <td class="ff_ruleid hidden">{{ row.ff_ruleid|noneFilter() }}</td> <td class="ff_groupid hidden">{{ row.ff_groupid|noneFilter() }}</td> <td class="ff_gwgroupid hidden">{{ row.ff_gwgroupid|noneFilter() }}</td> <td class="ff_fwddid hidden">{{ row.ff_fwddid|noneFilter() }}</td> </tr> {% endfor %} </tbody> </table> {% endblock %} {% block edit_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Your Inbound Route</h4> </div> <div class="modal-body"> <form action="/inboundmapping" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid" type="hidden" name="ruleid"> <input class="hf_ruleid" type="hidden" name="hf_ruleid"> <input class="ff_ruleid" type="hidden" name="ff_ruleid"> <input class="hf_groupid" type="hidden" name="hf_groupid"> <input class="ff_groupid" type="hidden" name="ff_groupid"> <div class="form-group"> <input class="form-control rulename" type="text" name="rulename" placeholder="Friendly Name (Optional)" autofocus="autofocus"> </div> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input prefix form-control" type="text" name="prefix" placeholder="DID" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="prefix form-control" type="text" name="prefix" placeholder="DID"> {% endif %} </div> <div class="form-group"> <select class="gwgroupid form-control" name="gwgroupid" title="gwgroupid" required="required"> <option class="hidden" value="" selected disabled>Endpoint Group</option> {% for epgroup in epgroups %} <option value="{{ epgroup['id'] }}">{{ epgroup['description']|attrFilter('name') }}</option> {% if epgroup['description']|attrFilter('lb') %} <option value="lb_{{ epgroup['id'] }}_{{ epgroup['description']|attrFilter('lb') }}">{{ epgroup['description']|attrFilter('name') }} LB</option> {% endif %} {% if epgroup['description']|attrFilter('lb_ext') %} <option value="lb_{{ epgroup['id'] }}_{{ epgroup['description']|attrFilter('lb_ext') }}">{{ epgroup['description']|attrFilter('name') }} LB (External)</option> {% endif %} {% endfor %} </select> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-hardfwd" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-call_hardfwd'></span> Enabled" data-off="<span class='icon-call_hardfwd'></span> Disabled" data-width="125px"> Hard Forwarding </label> </div> <input class="hardfwd_enabled" type="hidden" name="hardfwd_enabled" value="0"> </div> <div class="hardfwd-options hidden"> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input hf_fwddid form-control" type="text" name="hf_fwddid" placeholder="Forwarded DID (default is unchanged)" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="hf_fwddid form-control" type="text" name="hf_fwddid" placeholder="Forwarded DID (default is unchanged)"> {% endif %} </div> <div class="form-group"> <select class="hf_gwgroupid form-control" name="hf_gwgroupid" title="gwgroupid"> <option value="" selected="selected">Carrier/Endpoint Group (default is route via DID)</option> {% for gwgroup in gwgroups %} <option value="{{ gwgroup['id'] }}">{{ gwgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-failfwd" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-call_failfwd'></span> Enabled" data-off="<span class='icon-call_failfwd'></span> Disabled" data-width="125px"> Failover Forwarding </label> </div> <input class="failfwd_enabled" type="hidden" name="failfwd_enabled" value="0"> </div> <div class="failfwd-options hidden"> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input ff_fwddid form-control" type="text" name="ff_fwddid" placeholder="Forwarded DID (default is unchanged)" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="ff_fwddid form-control" type="text" name="ff_fwddid" placeholder="Forwarded DID (default is unchanged)"> {% endif %} </div> <div class="form-group"> <select class="ff_gwgroupid form-control" name="ff_gwgroupid" title="gwgroupid"> <option value="" selected="selected">Carrier/Endpoint Group (default is route via DID)</option> {% for gwgroup in gwgroups %} <option value="{{ gwgroup['id'] }}">{{ gwgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> </div> <div class="modal-footer "> <button type="submit" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div> {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add New Inbound Route</h4> </div> <div class="modal-body"> <form action="/inboundmapping" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid" type="hidden" name="ruleid"> <div class="form-group"> <input class="form-control rulename" type="text" name="rulename" placeholder="Friendly Name (Optional)" autofocus="autofocus"> </div> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input prefix form-control" type="text" name="prefix" placeholder="DID" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="prefix form-control" type="text" name="prefix" placeholder="DID"> {% endif %} </div> <div class="form-group"> <select class="gwgroupid form-control" name="gwgroupid" title="gwgroupid" required="required"> <option class="hidden" value="" selected disabled>Endpoint Group</option> {% for epgroup in epgroups %} <option value="{{ epgroup['id'] }}">{{ epgroup['description']|attrFilter('name') }}</option> {% if epgroup['description']|attrFilter('lb') %} <option value="lb_{{ epgroup['id'] }}_{{ epgroup['description']|attrFilter('lb') }}">{{ epgroup['description']|attrFilter('name') }} LB</option> {% endif %} {% if epgroup['description']|attrFilter('lb_ext') %} <option value="lb_{{ epgroup['id'] }}_{{ epgroup['description']|attrFilter('lb_ext') }}">{{ epgroup['description']|attrFilter('name') }} LB (External)</option> {% endif %} {% endfor %} </select> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-hardfwd" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-call_hardfwd'></span> Enabled" data-off="<span class='icon-call_hardfwd'></span> Disabled" data-width="125px"> Hard Forwarding </label> </div> <input class="hardfwd_enabled" type="hidden" name="hardfwd_enabled" value="0"> </div> <div class="hardfwd-options form-group hidden"> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input hf_fwddid form-control" type="text" name="hf_fwddid" placeholder="Forwarded DID (default is unchanged)" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="hf_fwddid form-control" type="text" name="hf_fwddid" placeholder="Forwarded DID (default is unchanged)"> {% endif %} </div> <div class="form-group"> <select class="hf_gwgroupid form-control" name="hf_gwgroupid" title="gwgroupid"> <option value="" selected="selected">Carrier/Endpoint Group (default is route via DID)</option> {% for gwgroup in gwgroups %} <option value="{{ gwgroup['id'] }}">{{ gwgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> </div> <div class="form-group"> <div class="checkbox"> <label class="label-toggle"> <input class="toggle-failfwd" type="checkbox" data-toggle="toggle" value="1" data-on="<span class='icon-call_failfwd'></span> Enabled" data-off="<span class='icon-call_failfwd'></span> Disabled" data-width="125px"> Failover Forwarding </label> </div> <input class="failfwd_enabled" type="hidden" name="failfwd_enabled" value="0"> </div> <div class="failfwd-options form-group hidden"> <div class="form-group"> {% if imported_dids|length > 0 %} <div class="combobox-wrapper"> <div class="did-combobox" role="combobox" aria-expanded="false" aria-owns="did-listbox" aria-haspopup="listbox"> <input class="did-combobox-input ff_fwddid form-control" type="text" name="ff_fwddid" placeholder="Forwarded DID (default is unchanged)" aria-autocomplete="both" aria-controls="did-listbox"> <div class="did-combobox-arrow combobox-dropdown wrapper-vertical" tabindex="-1" role="button" aria-label="Toggle DIDs Shown"> <span class="did-combobox-span icon-circle-down centered"></span> </div> </div> <ul class="did-listbox listbox hidden" role="listbox"></ul> </div> {% else %} <input class="ff_fwddid form-control" type="text" name="ff_fwddid" placeholder="Forwarded DID (default is unchanged)"> {% endif %} </div> <div class="form-group"> <select class="ff_gwgroupid form-control" name="ff_gwgroupid" title="gwgroupid"> <option value="" selected="selected">Carrier/Endpoint Group (default is route via DID)</option> {% for gwgroup in gwgroups %} <option value="{{ gwgroup['id'] }}">{{ gwgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form action="/inboundmappingdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid" type="hidden" name="ruleid"> <input class="hf_ruleid" type="hidden" name="hf_ruleid"> <input class="ff_ruleid" type="hidden" name="ff_ruleid"> <input class="hf_groupid" type="hidden" name="hf_groupid"> <input class="ff_groupid" type="hidden" name="ff_groupid"> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Record? </div> <div class="modal-footer "> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes</button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block import_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Import DID's</h4> </div> <div class="modal-body"> <form action="/inboundmappingimport" method="POST" role="form" enctype="multipart/form-data"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid" type="hidden" name="ruleid"> <input class="hf_ruleid" type="hidden" name="hf_ruleid"> <input class="ff_ruleid" type="hidden" name="ff_ruleid"> <input class="hf_groupid" type="hidden" name="hf_groupid"> <input class="ff_groupid" type="hidden" name="ff_groupid"> <div class="form-group"> <label for="importFile">CSV File with DID's (<a href="/static/template/DID_example.csv" target="_blank">Download Example CSV</a>)</label> <input type="file" name="file" class="form-control-file" id="importFile"> </div> <div class="form-group"> <select class="gwgroupid form-control" name="gwgroupid" title="gwgroupid"> <option class="hidden" value="" selected disabled>Override Endpoint Group (Optional)</option> {% for epgroup in epgroups %} <option value="#{{ epgroup['id'] }}">{{ epgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Import </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('combobox') }} <script type="application/javascript"> var DID_LIST = JSON.parse("{{ imported_dids }}") || []; var gw_mapping = []; {% for gw in gatewayList %} gw_mapping.push({ id: {{ gw['gwid'] }}, group: "{{ gw['description']|attrFilter('gwgroup') }}", option_value: "lb_{{ gw['description']|attrFilter('gwgroup') }}_{{ gw['description']|attrFilter('gwgroup') }}", }); {% endfor %} </script> {{ script_tag('inboundmapping') }} {% endblock %} ================================================ FILE: gui/templates/includes/overrides.js ================================================ ;(function(window, document) { 'use strict'; // note that the ajax/fetch overrides do not effect XMLHttpRequest (a.k.a the XHR API) // external libs using the XHR functions will not be effected (unknown side effects) // in the future we may switch to overriding XMLHttpRequest instead of using ajax methods // ref: https://stackoverflow.com/questions/14527360/can-i-set-a-global-header-for-all-ajax-requests // ref: https://jsfiddle.net/cferdinandi/2mc2wnc7/ // ref: https://jsfiddle.net/0bjfLey9/1 // note that in the ajax implementation the global error handler runs AFTER any locally set error handlers // the inverse is true for the fetch implementation, the global error handler runs BEFORE user catch blocks // throw an error if required functions not defined if (typeof showNotification === "undefined") { throw new Error("showNotification() is required and is not defined"); } // if (typeof reloadKamRequired === "undefined") { // throw new Error("reloadKamRequired() is required and is not defined"); // } // throw an error if required globals not defined if (typeof GUI_BASE_URL === "undefined") { throw new Error("GUI_BASE_URL is required and is not defined"); } // global variables/constants for this script const NOCSRF_REQUEST_METHOD_REGEX = new RegExp(/^(GET|HEAD|OPTIONS|TRACE)$/, 'i'); const OLD_FETCH = window.fetch; function requestErrorHandler(status, error_msg, error_type="http") { if (status < 400) { // not an error, likely a direct call to error handler } else if (status === 400) { // bad input show error in page console.error('requestErrorHandler(): ' + status.toString() + ' ' + error_msg) showNotification(error_msg, true); } else if (status === 401) { // unauthorized goto index for login console.error('requestErrorHandler(): ' + status.toString() + ' ' + error_msg) window.location.href = GUI_BASE_URL; } else if (status === 403) { // forbidden show error in page console.error('requestErrorHandler(): ' + status.toString() + ' ' + error_msg) showNotification(error_msg, true); } else if (status === 404) { // not found show error in page console.error('requestErrorHandler(): ' + status.toString() + ' ' + error_msg) showNotification(error_msg, true); } else { // unhandled error goto error page console.error('requestErrorHandler(): ' + status.toString() + ' ' + error_msg) window.location.href = GUI_BASE_URL + "error?type=" + error_type + "&code=" + status.toString() + "&msg=" + error_msg; } } window.requestErrorHandler = requestErrorHandler; // override ajax defaults $.ajaxSetup({ // set anti-CSRF token beforeSend: function(xhr, settings) { if (!NOCSRF_REQUEST_METHOD_REGEX.test(settings.type) && !this.crossDomain) { xhr.setRequestHeader("X-CSRF-Token", "{{ csrf_token() }}"); } } }); /* TODO: marked for review in v0.74 disabled the error handler / reload button handler too hard to program with this abstraction */ // $(document).ajaxError(function(event, xhr, settings, error_msg) { // // handle HTTP errors, may redirect // try { // // try updating error message and type // var data = JSON.parse(xhr.responseText); // var error_type = data["error"]; // error_msg = data["msg"]; // requestErrorHandler(xhr.status, error_msg, error_type); // } // catch(error) { // // non-JSON response or no error info in response, use defaults // requestErrorHandler(xhr.status, xhr.statusText); // } // }); // $(document).ajaxComplete(function(event, xhr, settings) { // try { // // try updating kam reload button // reloadKamRequired(JSON.parse(xhr.responseText)["kamreload"]); // } // catch(error) { // // non-JSON response or no kamreload in response, continue // } // }); /* TODO: marked for review in v0.74 disabled the error handler / reload button handler too hard to program with this abstraction */ // override fetch defaults window.fetch = function(resource, init) { // set anti-CSRF token // if init is undefined then method is GET and no anti-CSRF token needed if (init !== undefined && init.hasOwnProperty('method') && !NOCSRF_REQUEST_METHOD_REGEX.test(init.method)) { if (!(init.hasOwnProperty('mode') && init.mode.toLowerCase() === 'cors')) { init.headers = init.headers || {}; init.headers["X-CSRF-Token"] = "{{ csrf_token() }}"; } } return OLD_FETCH.call(this, resource, init).then(function(response) { // // handle HTTP errors, may redirect // try { // var data = JSON.parse(response.text()); // // try updating reload buttons // if (data.hasOwnProperty("kamreload")) { // reloadKamRequired(data["kamreload"]); // } // // try updating error message and type // var error_type = data["error"]; // var error_msg = data["msg"]; // requestErrorHandler(response.status, error_msg, error_type); // } // catch(error) { // // non-JSON response or no error info in response, use defaults // requestErrorHandler(response.status, response.statusText); // } // pass on the response return response; }).catch(function(error) { // requestErrorHandler(500, error.message); // if error handler doesn't redirect, reject the promise return Promise.reject(error); }); }; /* * IE/Safari polyfill for reportValidity() compatibility * credit: https://stackoverflow.com/questions/17550317/how-to-manually-show-a-html5-validation-message-from-a-javascript-function */ if (!HTMLFormElement.prototype.reportValidity) { HTMLFormElement.prototype.reportValidity = function() { if (this.checkValidity()) return true; var btn = document.createElement('button'); this.appendChild(btn); btn.click(); this.removeChild(btn); return false; } window.HTMLFormElement.prototype.reportValidity = HTMLFormElement.prototype.reportValidity; } })(window, document); ================================================ FILE: gui/templates/index.html ================================================ {% extends 'login_layout.html' %} {% block title %}Login{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="container"> <div class="left-half"> <img class="logon-image" src="/static/images/login-splash.jpg"> </div> <div class="right-half"> <div id="loginbox" style="margin-top:100px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2"> <div id="loginlogo" class="loginlogo"> <img style="padding:20px 20px;" src="{{ url_for('static', filename='images/dsiprouter_x50px.png') }}"> </div> <div class="panel panel-info"> <div style="padding-top:30px" class="panel-body"> {% with messages = get_flashed_messages() %} {% if messages %} <ul class="alert alert-danger col-sm-12"> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} <div style="display:none" id="login-alert" class="alert alert-danger col-sm-12"></div> <form id="loginform" class="form-horizontal" role="form" action="/login" method="POST"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div style="margin-bottom: 25px" class="input-group"> <span class="input-group-addon"><i class="glyphicon glyphicon-user"></i></span> <input id="login-username" type="text" class="form-control" name="username" value="" placeholder="username or email" autofocus="autofocus"> </div> <div style="margin-bottom: 25px" class="input-group"> <span class="input-group-addon"><i class="glyphicon glyphicon-lock"></i></span> <input id="login-password" type="password" class="form-control" name="password" placeholder="password"> </div> <input id="nextpage" type="hidden" name="nextpage" value="{{ nextpage }}"> <div style="margin-top:10px" class="form-group"> <!-- Button --> <div class="col-sm-12 controls"> <button class="btn btn-success" type="submit">Login</button> </div> </div> <div style="border-top: 1px solid#888; padding-top:15px; font-size:85%"> Built in Detroit | Version {{ version }} </div> </form> </div> </div> </div> </div> <!-- End of right side --> </div> {% endblock %} {% block custom_js %} {% endblock %} ================================================ FILE: gui/templates/license_manager.html ================================================ {% extends 'table_layout.html' %} {% block title %}License Manager{% endblock %} {% block custom_css %} <style> #licensing tr > th { height: 2rem; } #licensing tr > td { height: 4rem; } #licensing > tbody > tr:hover { background-color: #acbad4; } #licensing > tbody > tr > td:has(.dt-resize-height) { padding: 0; } #licensing > tbody > tr > td > .dt-resize-height { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; } #licensing > tbody > tr > td > .dt-resize-height > * { -webkit-box-flex: 1; -ms-flex-positive: 1; flex-grow: 1; } #licensing > tbody > tr > td > .dt-resize-height span.toggle-password { position: absolute; top: 1rem; right: 1rem; left: inherit; bottom: inherit; cursor: pointer; line-height: inherit; } #licensing > tbody > tr > td > .dt-resize-height input { background-color: rgba(0, 0, 0, 0); border: 0; text-align: center; padding: 0; } #licensing > tbody > tr > td > .dt-resize-height input.key { padding: 0 2em 0 0; box-sizing: border-box; } #licensing > tbody > tr > td > .dt-resize-height textarea { background-color: rgba(0, 0, 0, 0); border: 0; text-align: center; padding: 0; resize: none; } #licensing > tbody > tr > td > .dt-resize-height input:focus, #licensing > tbody > tr > td > .dt-resize-height input:focus-visible { outline: none; -webkit-box-shadow: none; box-shadow: none; } #licensing > tbody > tr > td > .dt-resize-height button { margin: 1rem 2rem; } </style> {% endblock %} {% block table_headers %} <div> <h3>List of Licenses for this Node</h3> </div> <div class="tableAddButton"> <div class="btn-group mr-2"> <button id='open-LicenseAdd' class='btn btn-success btn-md' data-title="Activate" data-toggle="modal" data-target="#add"> Add </button> </div> </div> {% endblock %} {% block table %} <table id="licensing" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th data-field="tags">License Type</th> <th data-field="key">License Key</th> <th data-field="active">Active</th> <th data-field="active">Valid</th> <th data-field="expires">Expiration Date</th> <th class="hidden"></th> </tr> </thead> </table> {% endblock %} {% block edit_modal %} {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Activate License</h4> </div> <div class="modal-body"> <form action="/licensing" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="form-group wrapper-fieldicon-right"> <input id="add-key" class="form-control key" type="password" name="key" placeholder="License Key" autofocus="autofocus"> <span class="field-icon toggle-password glyphicon glyphicon-eye-close" data-toggle="#add-key"></span> </div> <div class="modal-footer"> <button type="submit" id="addButton" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Deactivate License</h4> </div> <div class="modal-body"> <form action="/licensing" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="key" type="hidden" name="key"> <div class="alert alert-danger"><span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to deactivate this License? </div> <div class="modal-footer "> <button id="deleteButton" type="button" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign" autofocus="autofocus"></span> Yes </button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('jquery.tabledit') }} {{ script_tag('license_manager') }} {% endblock %} ================================================ FILE: gui/templates/license_required.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}License Required{% endblock %} {% block custom_css %} {% endblock %} {% block body %} {% set dopensource_url = 'https://dopensource.com' %} {% set dsiprouter_licenses_url = dopensource_url + "/product-category/dsiprouter/" %} <div class="wrapper-vertical centered"> <div class="alert alert-danger container"> <h2>Acesss Denied: License Required</h2> </div> {% if msg is not none %} <div class="alert alert-info container" style="padding: 10px 10px"> <p>Detailed Information: <b>{{ msg }}</b></p> </div> {% endif %} <br> <div class="alert container"> <p>A license is required to access this resource.</p> <p>You can purchase licenses at <a href="{{ dsiprouter_licenses_url }}">dopensource.com</a>.</p> <p>Once purchased, activate the license key using the dSIPRouter <a href="/licensing">license manager</a>.</p> </div> </div> {% endblock %} {% block custom_js %} <script type="application/javascript"> /* fix section padding */ $('.wrap .content .content-inner').css({ 'padding': 0 }); /* fix alert text color */ $('.alert > *').css({ 'color': '#000000' }); </script> {% endblock %} ================================================ FILE: gui/templates/login_layout.html ================================================ {% from 'util.jinja2.html' import link_tag, script_tag, tracked_link %} <!DOCTYPE html> <html lang="en"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <head> <meta charset="utf-8"> <title>dSIPRouter {% block title %}{% endblock %}</title> {{ link_tag('bootstrap') }} {{ link_tag('main') }} {% block custom_css %} {% endblock %} </head> <body> <div class="container"> {% block body %} {% endblock %} </div> {{ script_tag('jquery') }} {% block custom_js %} {% endblock %} </body> </html> ================================================ FILE: gui/templates/msteams.html ================================================ {% extends 'table_layout.html' %} {% block title %}MSteams Configuration{% endblock %} {% block custom_css %} {{ link_tag('msteams') }} {% endblock %} {% block table_headers %} <div> <h3>Microsoft Direct Routing Configuration for <span style="color:#1E8DB5;font-weight:bold">{{ domain.domain }}</span></h3> </div> <div class="tableAddButton"> <button id="testConnectivity" class="btn btn-success btn-md" value='{{ domain.domain }}'> Test Connectivity </button> </div> {% endblock %} {% block table %} <div class="container-fluid"> <div class="col col-md-3"> <div class="panel-group" id="accordion"> <div class="panel panel-default"> <div class="panel-heading"> <h4 class="panel-title"> <a data-toggle="collapse" data-parent="#accordion" href="#collapse1"> Connectivity Status</a> </h4> </div> <div id="collapse1" class="panel-collapse collapse in"> <ul class="list-group"> <li class="list-group-item">Valid DNS Hostname <span id="hostname_check" class="glyphicon glyphicon-remove" style="color:gray"></span></li> <li id="tls_check_row" class="list-group-item">TLS Certificates <span id="tls_check" class="glyphicon glyphicon-remove" style="color:gray"></span></li> <li class="list-group-item">Option Messages <span id="option_check" class="glyphicon glyphicon-remove" style="color:gray"></span></li> </ul> </div> </div> </div> </div> <div class="col col-md-9 hidden" id="configurationPanel"> </div> </div> {% endblock %} {% block custom_js %} {{ script_tag('msteams') }} {% endblock %} ================================================ FILE: gui/templates/outboundroutes.html ================================================ {% extends 'table_layout.html' %} {% block title %}Outbound Routes{% endblock %} {% block table_headers %} <div> <h3>List of Outbound Routes</h3> </div> <div class="tableAddButton btn-toolbar"> <button id='open-CarrierAdd' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add">Add </button> </div> {% endblock %} {% block table %} <table id="outboundmapping" class="table table-striped table-centered"> <thead> <tr class='element-row'> <th></th> <th data-field="ruleid">Rule ID</th> <th data-field="groupid" class="hidden">Group ID</th> <th data-field="from_prefix">From Prefix</th> <th data-field="prefix">To Prefix</th> <th data-field="timerec">Recurrence</th> <th data-field="priority">Priority</th> <th data-field="routeid">Custom Route</th> <th data-field="gwgroupid" class="hidden"></th> <th data-field="gwgroupname">Carrier Group</th> <th data-field="description">Name</th> <th></th> <th></th> </tr> </thead> <tbody> {% for row in rows %} <tr class='element-row'> <td><input type="checkbox" class="checkthis" value="1"/></td> <td class='ruleid'>{{ row.ruleid }}</td> <td class='groupid hidden'>{{ row.dr_groupid }}</td> <td class='from_prefix'>{{ row.from_prefix|noneFilter() }}</td> <td class='prefix'>{{ row.prefix }}</td> <td class="timerec">{{ row.timerec }}</td> <td class="priority">{{ row.priority }}</td> <td class="routeid">{{ row.routeid }}</td> <td class="gwgroupid hidden">{{ row.gwgroupid }}</td> <td class='gwgroupname'>{{ row.gwgroup_description|attrFilter('name') }}</td> <td class="description">{{ row.description|attrFilter('name') }}</td> <td> <p data-placement="top" data-toggle="tooltip" title="Edit"> <button id="open-Update" class="open-Update btn btn-primary btn-xs" data-title="Edit" data-toggle="modal" data-target="#edit"><span class="glyphicon glyphicon-pencil"></span> </button> </p> </td> <td> <p data-placement="top" data-toggle="tooltip" title="Delete"> <button id="open-Delete" class="open-Delete btn btn-danger btn-xs" data-title="Delete" data-toggle="modal" data-target="#delete"><span class="glyphicon glyphicon-trash"></span> </button> </p> </td> </tr> {% endfor %} </tbody> </table> {% endblock %} {% block edit_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Edit Your Outbound Route</h4> </div> <div class="modal-body"> <form action="/outboundroutes" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid " type="hidden" name="ruleid"> <input class="groupid " type="hidden" name="groupid"> <div class="form-group"> <input class="name form-control" type="text" name="name" placeholder="Friendly Name (Optional)" autofocus="autofocus"> </div> <div class="form-group"> <input class="from_prefix form-control" type="text" name="from_prefix" placeholder="From Prefix Matching (Optional)"> </div> <div class="form-group"> <input class="prefix form-control" type="text" name="prefix" placeholder="To Prefix Matching (Optional)"> </div> <div class="form-group"> <input class="timerec form-control" type="text" name="timerec" placeholder="Recurring Time (Optional)"> </div> <div class="form-group"> <input class="priority form-control" type="text" name="priority" placeholder="Priority (Optional: higher priorities routed first)"> </div> <div class="form-group"> <select class="routeid form-control" name="routeid" title="routeid"> <option value="" selected="selected">Custom Kamailio Route (default none)</option> {% for routeid in custom_routes %} <option value='{{ routeid }}'>{{ routeid }}</option> {% endfor %} </select> </div> <div class="form-group"> <select class="gwgroupid form-control" name="gwgroupid" title="gwgroupid" required="required"> <option class="hidden" value="" selected="selected" disabled>Carrier Group</option> {% for cgroup in cgroups %} <option value="{{ cgroup['id'] }}">{{ cgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> </div> <div class="modal-footer "> <button type="submit" class="btn btn-warning btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Update </button> </div> </form> </div> {% endblock %} {% block add_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Add an Outbound Route</h4> </div> <div class="modal-body"> <form id="addOutboundRoutes" action="/outboundroutes" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input class="ruleid " type="hidden" name="ruleid"> <div class="form-group"> <input class="name form-control" type="text" name="name" placeholder="Friendly Name (Optional)" autofocus="autofocus"> </div> <div class="form-group"> <input class="from_prefix form-control" type="text" name="from_prefix" placeholder="From Prefix Matching (Optional)" data-custom="tocheck"> </div> <div class="form-group"> <input class="prefix form-control" type="text" name="prefix" placeholder="To Prefix Matching (Optional)"> </div> <div class="form-group"> <input class="timerec form-control" type="text" name="timerec" placeholder="Recurring Time (Optional)"> </div> <div class="form-group"> <input class="priority form-control" type="text" name="priority" placeholder="Priority (Optional: higher priorities routed first)"> </div> <div class="form-group"> <select class="routeid form-control" name="routeid" title="routeid"> <option value="" selected="selected">Custom Kamailio Route (default none)</option> {% for routeid in custom_routes %} <option value='{{ routeid }}'>{{ routeid }}</option> {% endfor %} </select> </div> <div class="form-group"> <select class="gwgroupid form-control" name="gwgroupid" title="gwgroupid" required="required"> <option class="hidden" value="" selected="selected" disabled>Carrier Group</option> {% for cgroup in cgroups %} <option value="{{ cgroup['id'] }}">{{ cgroup['description']|attrFilter('name') }}</option> {% endfor %} </select> <div class="help-block with-errors"></div> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success btn-lg" style="width: 100%;"><span class="glyphicon glyphicon-ok-sign"></span> Add </button> </div> </form> </div> {% endblock %} {% block delete_modal %} <div class="modal-header"> <button type="button" class="close" data-dismiss="modal" aria-hidden="true"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span></button> <h4 class="modal-title custom_align" id="Heading">Delete this entry</h4> </div> <div class="modal-body"> <form action="/outboundroutesdelete" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="modal-body"> <div class="form-group"> <input class="ruleid form-control" type="hidden" name="ruleid"> <input class="groupid form-control" type="hidden" name="groupid"> </div> <div class="alert alert-danger"> <span class="glyphicon glyphicon-warning-sign"></span> Are you sure you want to delete this Record? </div> </div> <div class="modal-footer "> <button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-ok-sign"></span> Yes </button> <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove"></span> No </button> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('outboundroutes') }} {% endblock %} ================================================ FILE: gui/templates/stirshaken.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}STIR/SHAKEN Configuration{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="col-md-12"> <form action="/stirshaken" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="saveTeleblock"> <h4>STIR/SHAKEN Settings</h4> <button name='save' id='save' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add">Save </button> </div> <hr> <div class="form-group"> <label class="label-toggle">STIR/SHAKEN Service </label> <input id="toggleStirShaken" name="stir_shaken_enabled" type="checkbox" title="Toggle STIR/SHAKEN" {{ toggle_checked }} data-toggle="toggle" value="{{ settings.STIR_SHAKEN_ENABLED }}" data-on="<span class='icon-gryphon'></span> Enabled" data-off="<span class='icon-gryphon'></span> Disabled" data-width="120px"> </div> <div id="stirShakenOptions" class="form-group {{ options_hidden }}"> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_prefix_a" name="stir_shaken_prefix_a" placeholder="Caller ID Prefix A Validated Calls" value="{{ settings.STIR_SHAKEN_PREFIX_A }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_prefix_b" name="stir_shaken_prefix_b" placeholder="Caller ID Prefix B Validated Calls" value="{{ settings.STIR_SHAKEN_PREFIX_B }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_prefix_c" name="stir_shaken_prefix_c" placeholder="Caller ID Prefix C Validated Calls" value="{{ settings.STIR_SHAKEN_PREFIX_C }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_prefix_invalid" name="stir_shaken_prefix_invalid" placeholder="Caller ID Prefix Invalid Calls" value="{{ settings.STIR_SHAKEN_PREFIX_INVALID }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_cert_url" name="stir_shaken_cert_url" placeholder="Certificate URL" value="{{ settings.STIR_SHAKEN_CERT_URL }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="stir_shaken_key_path" name="stir_shaken_key_path" placeholder="Key Path" value="{{ settings.STIR_SHAKEN_KEY_PATH }}"> </div> <div class="checkbox"> <label> <input type="checkbox" name="stir_shaken_block_invalid" {{ 'checked' if (settings.STIR_SHAKEN_BLOCK_INVALID == 1) else '' }}> Block Invalidated Calls </label> </div> </div> <!--End of STIR/SHAKEN settings--> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('stirshaken') }} {% endblock %} ================================================ FILE: gui/templates/table_layout.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}Dashboard{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="wrapper-horizontal edge-centered children-align-inherit content-header"> {% block table_headers %} {% endblock %} </div> <div class="table-responsive content-section"> {% block table %} {% endblock %} <img id="loading-spinner" class="hidden" src="{{ url_for('static', filename='images/spinner.svg') }}" alt=""/> </div> <div class="modal fade" id="edit" tabindex="-1" role="dialog" aria-labelledby="Edit" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> {% block edit_modal %} {% endblock %} </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <div class="modal fade" id="add" tabindex="-1" role="dialog" aria-labelledby="Add" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> {% block add_modal %} {% endblock %} </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <div class="modal fade" id="delete" tabindex="-1" role="dialog" aria-labelledby="Delete" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> {% block delete_modal %} {% endblock %} </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> <div class="modal fade" id="import" tabindex="-1" role="dialog" aria-labelledby="Import" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> {% block import_modal %} {% endblock %} </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> {% block custom_modals %} {% endblock %} {% endblock %} {% block custom_js %} {% endblock %} ================================================ FILE: gui/templates/teleblock.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}Teleblock Configuration{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="col-md-12"> <form action="/teleblock" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div class="saveTeleblock"> <h4>Teleblock Settings</h4> <button name='save' id='save' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add">Save </button> </div> <hr> <div class="form-group"> <label class="label-toggle">Global Gryphon Teleblock Support </label> {% if teleblock["gw_enabled"] == 1 %} <input id="toggleTeleblock" name ="gw_enabled" type="checkbox" checked title="Toggle Teleblock" data-toggle="toggle" value="{{ teleblock["gw_enabled"] }}" data-on="<span class='icon-gryphon'></span> Enabled" data-off="<span class='icon-gryphon'></span> Disabled" data-width="120px"> {% else %} <input id="toggleTeleblock" name="gw_enabled" type="checkbox" title="Toggle Teleblock" data-toggle="toggle" value="{{ teleblock["gw_enabled"] }}" data-on="<span class='icon-gryphon'></span> Enabled" data-off="<span class='icon-gryphon'></span> Disabled" data-width="120px"> {% endif %} </div> {% if teleblock["gw_enabled"] == 1 %} <div id="teleblockOptions" class="form-group"> {% else %} <div id="teleblockOptions" class="form-group hidden"> {% endif %} <div class="form-group"> <input class="form-control " type="text" id="gw_ip" name="gw_ip" placeholder="Teleblock Gateway IP" value="{{ teleblock["gw_ip"] }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="gw_port" name="gw_port" placeholder="Teleblock Gateway Port" value="{{ teleblock["gw_port"] }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="media_ip" name="media_ip" placeholder="Media IP for Teleblock Messages" value="{{ teleblock["media_ip"] }}"> </div> <div class="form-group"> <input class="form-control " type="text" id="media_port" name="media_port" placeholder="Media Port for Teleblock Messages" value="{{ teleblock["media_port"] }}"> </div> </div> <!--End of Teleblock settings--> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('teleblock') }} {% endblock %} ================================================ FILE: gui/templates/transnexus.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}TransNexus Configuration{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div class="col-md-12"> <form action="/transnexus" method="POST" role="form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <div> <h4>TransNexus Settings</h4> <button name='save' id='save' class='btn btn-success btn-md' data-title="Add" data-toggle="modal" data-target="#add">Save </button> </div> <hr> <div class="form-group"> <label class="label-toggle">STIR/SHAKEN Authentication (outbound calls)</label> <input id="toggle_auth_settings" name="authservice_enabled" type="checkbox" {% if settings.TRANSNEXUS_AUTHSERVICE_ENABLED == 1 %}checked{% endif %} title="Toggle Auth Service" data-toggle="toggle" value="{{ settings.TRANSNEXUS_AUTHSERVICE_ENABLED }}" data-on="<span class='icon-transnexus'></span> Enabled" data-off="<span class='icon-transnexus'></span> Disabled" data-width="120px"> </div> <div id="authservice_settings" class="form-group {% if settings.TRANSNEXUS_AUTHSERVICE_ENABLED != 1 %}hidden{% endif %}"> <div class="form-group"> <input class="form-control " type="text" id="authservice_host" name="authservice_host" placeholder="Auth Service Host" value="{{ settings.TRANSNEXUS_AUTHSERVICE_HOST }}"> </div> </div> <div class="form-group"> <label class="label-toggle">STIR/SHAKEN Verification (inbound calls)</label> <input id="toggle_verify_settings" name="verifyservice_enabled" type="checkbox" {% if settings.TRANSNEXUS_VERIFYSERVICE_ENABLED == 1 %}checked{% endif %} title="Toggle Verify Service" data-toggle="toggle" value="{{ settings.TRANSNEXUS_VERIFYSERVICE_ENABLED }}" data-on="<span class='icon-transnexus'></span> Enabled" data-off="<span class='icon-transnexus'></span> Disabled" data-width="120px"> </div> <div id="verifyservice_settings" class="form-group {% if settings.TRANSNEXUS_VERIFYSERVICE_ENABLED != 1 %}hidden{% endif %}"> <div class="form-group"> <input class="form-control " type="text" id="verifyservice_host" name="verifyservice_host" placeholder="Verify Service Host" value="{{ settings.TRANSNEXUS_VERIFYSERVICE_HOST }}"> </div> </div> </form> </div> {% endblock %} {% block custom_js %} {{ script_tag('transnexus') }} {% endblock %} ================================================ FILE: gui/templates/upgrade.html ================================================ {% extends 'fullwidth_layout.html' %} {% block title %}dSIPRouter Upgrade{% endblock %} {% block custom_css %} {% endblock %} {% block body %} <div> {% if msg %} <div class="alert alert-danger container"> <h2 style="color: rgb(0, 0, 0);"><strong>{{ msg }}</strong></h2> </div> {% endif %} </div> <div class="col-md-12"> <div class="wrapper-horizontal edge-centered children-align-inherit content-header"> <div> <h3>dSIPRouter Upgrade</h3> </div> <div> <button class="btn btn-med btn-info" id="btnShowLog">Show Previous Log</button> </div> </div> <div class="row"> <div class="col-sm-12"> <p>Current Version: {{ upgrade_settings["current_version"] }}</p> <p>Latest Version: {{ upgrade_settings["latest_version"] }}</p> </div> </div> <form action="/upgrade" method="POST" role="form" id="upgrade_form"> <input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/> <input type="hidden" name="current_version" value="{{ upgrade_settings["current_version"] }}"/> <input type="hidden" name="latest_version" value="{{ upgrade_settings["latest_version"] }}"/> {% if upgrade_settings["upgrade_available"] == 1 %} <div class="row"> <div class="col-sm-12"> {% with messages = get_flashed_messages() %} {% if messages %} <ul class=flashes> {% for message in messages %} <li>{{ message }}</li> {% endfor %} </ul> {% endif %} {% endwith %} <div class="alert alert-warning"> <p>Please note that upgrades for cluster installations are not supported at this time.</p> </div> <button class="btn btn-primary" onclick="return confirm('Are you sure you want to start the upgrade process. This will temporarily take your server offline.')"> Upgrade Now </button> </div> </div> {% else %} <div class="row"> <div class="col-sm-12"> <div class="alert alert-success"> <p>Your system is up to date.</p> </div> </div> </div> {% endif %} </form> <div id="upgrade_output_row" class="row" style="display: none"> <div class="col-sm-12" style="height: auto; border-radius: 1em; border-width: medium; border-style: ridge;"> <h4>Upgrade Log</h4> <div style="padding: 0.5em; border-width: medium 0 0 0; border-style: ridge;"></div> <div id="upgrade_output"></div> <div style="padding: 0.5em;"></div> </div> </div> </div> {% endblock %} {% block custom_js %} {{ script_tag('upgrade') }} {% endblock %} ================================================ FILE: gui/templates/util.jinja2.html ================================================ {%- macro link_tag(location) -%} <link rel="stylesheet" href="/static/css/{{ location }}.css"> {%- endmacro -%} {%- macro script_tag(location) -%} <script src="/static/js/{{ location }}.js"></script> {%- endmacro -%} {%- macro tracked_link(title, url) -%} <a href="{{ url_for("click", url=url) }}">{{ title }}</a> {%- endmacro -%} {%- macro img_tag(location, alt="", style="width:auto; height:auto;") -%} <img src="{{ location|imgFilter() }}" alt="{{ alt }}" style="{{ style }}"> {%- endmacro -%} {%- macro modal(id, label='', hidden="true", content="") -%} <div class="modal fade" id="{{ id }}" tabindex="-1" role="dialog" aria-labelledby="{{ label }}" aria-hidden="{{ hidden }}"> <div class="modal-dialog"> <div class="modal-content"> {{ content }} </div><!-- /.modal-content --> </div><!-- /.modal-dialog --> </div><!-- /.modal --> {%- endmacro %} ================================================ FILE: gui/util/conversions.py ================================================ number_alphabet = '0123456789' lc_letter_alphabet = 'abcdefghijklmnopqrstuvwxyz' uc_letter_alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' # process ASTERISK-like regex pattern for a prefix # we are not expanding '!' or '.' # this is because we don't support length based prefix matching # instead we utilize dr_rules longest-to-shortest prefix matching def expand_prefix(prefix, suffix_check=None): finished = False for i in range(len(prefix)): if prefix[i] == 'X' and suffix_check is None: for j in range(0,9+1): yield from expand_prefix(prefix[:i] + str(j) + prefix[i+1:]) elif prefix[i] == 'Z' and suffix_check is None: for j in range(1,9+1): yield from expand_prefix(prefix[:i] + str(j) + prefix[i+1:]) finished = True elif prefix[i] == 'N' and suffix_check is None: for j in range(2,9+1): yield from expand_prefix(prefix[:i] + str(j) + prefix[i+1:]) finished = True elif prefix[i] == '[': tmp = prefix[i+1:] try: dash_idx = tmp.index('-') try: start,end = number_alphabet.index(tmp[dash_idx-1]),number_alphabet.index(tmp[dash_idx+1]) for j in number_alphabet[start:end+1]: yield from expand_prefix(prefix[:i] + j + prefix[prefix.index(']')+1:]) finished = True except ValueError: pass try: start,end = lc_letter_alphabet.index(tmp[dash_idx-1]),lc_letter_alphabet.index(tmp[dash_idx+1]) for j in lc_letter_alphabet[start:end+1]: yield from expand_prefix(prefix[:i] + j + prefix[prefix.index(']')+1:]) finished = True except ValueError: pass try: start,end = uc_letter_alphabet.index(tmp[dash_idx-1]),uc_letter_alphabet.index(tmp[dash_idx+1]) for j in uc_letter_alphabet[start:end+1]: yield from expand_prefix(prefix[:i] + j + prefix[prefix.index(']')+1:], suffix_check=prefix[prefix.index(']')+1:]) finished = True except ValueError: pass except ValueError: for j in tmp[:tmp.index(']')]: yield from expand_prefix(prefix[:i] + j + prefix[prefix.index(']')+1:]) else: if finished: break if suffix_check is None: if not any(c in set('XZN[]') for c in prefix): yield prefix.replace('.', '').replace('!', '') break else: if not any(c in set('[]') for c in prefix) and not any(c in set('XZN[]') for c in suffix_check): yield prefix.replace('.', '').replace('!', '') break def expand_prefixs(prefixs): for p in prefixs: yield sorted(list(expand_prefix(p))) #### Example Usage: #example_prefixs = ['[0-9]', '[a-z]', '[A-Z]', '[01]N'] #for p in expand_prefixs(example_prefixs): # print(p) ================================================ FILE: gui/util/cron.py ================================================ from crontab import CronTab, CronSlices from cron_descriptor import ExpressionDescriptor # TODO: we should propagate exceptions from here so we can # better handle them at the API level def getTaggedCronjob(tag): """ Get a cronjob from current user's crontab by its tag :param tag: tag specified when creating :type tag: str|int :return: cronjob entry or None :rtype: CronTab|None """ try: if isinstance(tag, int): tag = str(tag) cron = CronTab(user=True) return tuple(cron.find_comment(tag))[0] except: return None def addTaggedCronjob(tag, interval, cmd): """ Adds a tagged cronjob to current user's crontab :param tag: tag for new entry :type tag: str|int :param interval: crontab interval :type interval: str :param cmd: crontab cmd to run :type cmd: str :return: whether it succeeded :rtype: bool """ try: if isinstance(tag, int): tag = str(tag) if not CronSlices.is_valid(interval): return False cron = CronTab(user=True) matching_jobs = tuple(cron.find_comment(tag)) if len(matching_jobs) == 0: job = cron.new(command=cmd, comment=tag) else: job = matching_jobs[0] job.set_command(cmd) job.setall(interval) if not job.is_valid(): return False cron.write() return True except: return False def updateTaggedCronjob(tag, interval='', cmd='', new_tag=''): """ Update a tagged cronjob in the current user's crontab :param tag: tag of existing entry :type tag: str|int :param interval: new crontab interval :type interval: str :param cmd: new crontab cmd to run :type cmd: str :param new_tag: new tag for entry :type new_tag: str|int :return: whether it succeeded :rtype: bool """ try: if isinstance(tag, int): tag = str(tag) if isinstance(new_tag, int): new_tag = str(new_tag) cron = CronTab(user=True) matching_jobs = tuple(cron.find_comment(tag)) if len(matching_jobs) == 0: job = cron.new(comment=tag) else: job = tuple(cron.find_comment(tag))[0] if len(interval) > 0: if not CronSlices.is_valid(interval): return False job.setall(interval) if len(cmd) > 0: job.set_command(cmd) if len(new_tag) > 0: job.set_comment(new_tag) if not job.is_valid(): return False cron.write() return True except: return False def deleteTaggedCronjob(tag): """ Delete a tagged cronjob from the existing user's crontab :param tag: tag of existing entry :type tag: str|int :return: whether it succeeded :rtype: bool """ try: if isinstance(tag, int): tag = str(tag) cron = CronTab(user=True) matching_jobs = tuple(cron.find_comment(tag)) if len(matching_jobs) == 0: return True job = tuple(cron.find_comment(tag))[0] cron.remove(job) cron.write() return True except: return False def cronIntervalToDescription(interval): """ Convert a crontab interval to a human readable format :param interval: crontab interval :type interval: str :return: readable format or None :rtype: str|None """ try: descriptor = ExpressionDescriptor(interval, use_24hour_time_format=True) return descriptor.get_description() except: return None ================================================ FILE: gui/util/file_handling.py ================================================ import os import grp, pwd from werkzeug.utils import secure_filename # TODO: files should be validated by magic bytes header as well as extension VALID_IMAGE_EXTENSIONS = {'jpg', 'jpe', 'jpeg', 'png', 'gif', 'svg', 'bmp'} VALID_VIDEO_EXTENSIONS = {'avi' 'flv' 'wmv' 'mov' 'mp4'} VALID_AUDIO_EXTENSIONS = {'wav' 'mp3' 'aac' 'ogg' 'oga' 'flac'} VALID_DOC_EXTENSIONS = {'txt' 'csv', 'rtf' 'odf' 'ods' 'gnumeric' 'abw' 'doc' 'docx' 'xls' 'xlsx'} VALID_LOG_EXTENSIONS = {'log','pcap','pcapng'} def isValidFile(filename, filetype='any'): """ Verifies file type based on extension and type to verify against Returns true if extension is valid for filetype, false otherwise Throws ValueError when filetype can not be handled Filetypes currently supported are: [image | video | audio | doc | log | any] """ if filetype == 'any': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_IMAGE_EXTENSIONS.union( VALID_VIDEO_EXTENSIONS).union(VALID_AUDIO_EXTENSIONS).union(VALID_DOC_EXTENSIONS).union(VALID_LOG_EXTENSIONS) elif filetype == 'image': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_IMAGE_EXTENSIONS elif filetype == 'video': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_VIDEO_EXTENSIONS elif filetype == 'audio': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_AUDIO_EXTENSIONS elif filetype == 'doc': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_DOC_EXTENSIONS elif filetype == 'log': return '.' in filename and filename.rsplit('.', 1)[1] in VALID_LOG_EXTENSIONS else: raise ValueError('Validation of filetype: ' + filetype + ' is not supported') def saveUpload(file, savedir, filetype): """ Saves file from current request context to provided savedir Flashes message indicating success or fail Returns a Dict {status=[error|warning|success], message=[None|<message>], file=[None|<filename>]} """ result = {'status': 'error', 'message': None, 'file': None} if not file or file.filename == '': result['status'] = 'warning' result['message'] = 'No file has been selected' return result # make dirs in path if they don't exist if not os.path.exists(savedir): os.makedirs(savedir) if not isValidFile(file.filename, filetype): result['status'] = 'error' result['message'] = 'Unable to save file, invalid file type' return result else: # save the file filename = secure_filename(file.filename) filepath = os.path.join(savedir, filename) file.save(filepath) # store the results result['status'] = 'success' result['message'] = 'File successfully uploaded' result['file'] = filename return result def change_permissions_recursive(path, mode): for root, dirs, files in os.walk(path, topdown=False): for dir in [os.path.join(root,d) for d in dirs]: os.chmod(dir, mode) for file in [os.path.join(root, f) for f in files]: os.chmod(file, mode) def change_owner(path,user,group): uid = pwd.getpwnam(user).pw_uid gid = grp.getgrnam(group).gr_gid os.chown(path, uid, gid) ================================================ FILE: gui/util/ipc.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import inspect, os, signal from UltraDict import UltraDict, Exceptions as shmem_exceptions import settings SETTINGS_SHMEM_NAME = 'shmem_settings' STATE_SHMEM_NAME = 'shmem_state' def createSharedMemoryDict(val, name): # globals from the top-level module caller_globals = dict(inspect.getmembers(inspect.stack()[-1][0]))["f_globals"] try: caller_globals[name] = UltraDict(val, name=name, create=True, auto_unlink=False, recurse=True) except shmem_exceptions.AlreadyExists: # we always want fresh memory, even if the memory was not deallocated properly UltraDict(name=name, create=False).unlink() caller_globals[name] = UltraDict(val, name=name, create=True, auto_unlink=False, recurse=True) return caller_globals[name] def getSharedMemoryDict(name): # globals from the top-level module caller_globals = dict(inspect.getmembers(inspect.stack()[-1][0]))["f_globals"] if name in caller_globals: return caller_globals[name] caller_globals[name] = UltraDict(name=name, create=False) return caller_globals[name] def sendSyncSettingsSignal(pid_file=settings.DSIP_PID_FILE, load_shared_settings=False): """ Send signals to GUI process to synchronize settings from disk or shared memory :param pid_file: path to PID file for process to send to :type pid_file: str :param load_shared_settings: whether to load settings from shared memory or not :type load_shared_settings: bool :return: None :rtype: None :except: OSError ProcessLookupError ValueError """ with open(pid_file, 'r') as f: pid = int(f.read()) if not load_shared_settings: os.kill(pid, signal.SIGUSR1) else: os.kill(pid, signal.SIGUSR2) ================================================ FILE: gui/util/kamtls.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import re, socket import settings from util.networking import ipv6Test, hostToIP # Server name matching options KAM_TLS_SNI_DOMAIN = 0 KAM_TLS_SNI_ALL = 1 KAM_TLS_SNI_SUBDOMAINS = 2 # header formats per domain profile DOMAIN_START_HEADER = '#========== {domain}_start ==========#' DOMAIN_END_HEADER = '#========== {domain}_end ==========#' def createCustomTLSConfig(domain, ip, port, server_name_mode): """ Create a Domain TLS Profile string for the Kamailio TLS Config\n Reference: https://kamailio.org/docs/modules/5.5.x/modules/tls.html :param domain: domain profile to create :type domain: str :param ip: ip kamailio will match for profile :type ip: str :param port: port kamailio will match for profile :type port: int|str :param server_name_mode: server name matching mode :type server_name_mode: int|str :return: tls config for a custom domain :rtype: str """ port = str(port) server_name_mode = str(server_name_mode) return ( DOMAIN_START_HEADER + '\n' + ( '[server:{ip}:{port}]\n' 'method = TLSv1.2+\n' 'verify_certificate = yes\n' 'require_certificate = yes\n' 'private_key = {certs_dir}/{domain}/dsiprouter-key.pem\n' 'certificate = {certs_dir}/{domain}/dsiprouter-cert.pem\n' 'ca_list = {certs_dir}/ca-list.pem\n' 'server_name = {domain}\n' 'server_name_mode = {name_mode}\n' '\n' '[client:{ip}:{port}]\n' 'method = TLSv1.2+\n' 'verify_certificate = yes\n' 'require_certificate = yes\n' 'private_key = {certs_dir}/{domain}/dsiprouter-key.pem\n' 'certificate = {certs_dir}/{domain}/dsiprouter-cert.pem\n' 'ca_list = {certs_dir}/ca-list.pem\n' 'server_name = {domain}\n' 'server_name_mode = {name_mode}\n' 'server_id = {domain}\n' ) + DOMAIN_END_HEADER + '\n\n' ).format(ip=ip, port=port, certs_dir=settings.DSIP_CERTS_DIR, domain=domain, name_mode=server_name_mode) def getCustomTLSConfigs(domain_filter=None): """ Get kamailio TLS configs for additional domains\n Server and client domain/ip/port are assumed to be the same\n The optional domain filter returns domains & subdomains that match Returned Data Format: .. code-block:: python { '<domain>': { 'ip': '<str>', 'port': '<int>' 'server': {<server params>}, 'client': {<client params>} }, ... } :param domain_filter: filter on this domain / subdomains :type domain_filter: str :return: TLS configs for custom domain profiles :rtype: dict|None """ custom_configs = {} try: with open(settings.KAM_TLSCFG_PATH, 'rb') as kamtlscfg_file: kamtlscfg_bytes = kamtlscfg_file.read() if domain_filter is None: regex = DOMAIN_START_HEADER.format(domain=r'(?P<domain>.*?)').encode('utf-8') + \ rb'''\n(?:\[server\:(?!default)\[?(?P<server_ip>.*?)\]?\:(?P<server_port>[0-9]+)\])\n(?P<server_params>.*?)\n(?=\[|#)\n*''' + \ rb'''(?:\[client\:(?!default)\[?(?P<client_ip>.*?)\]?\:(?P<client_port>[0-9]+)\])\n(?P<client_params>.*?)(?=\[|#)''' + \ DOMAIN_END_HEADER.format(domain=r'(?P=domain)').encode('utf-8') else: regex = DOMAIN_START_HEADER.format(domain=r'(?P<domain>(?:.*?\.)*{})'.format(domain_filter)).encode('utf-8') + \ rb'''\n(?:\[server\:(?!default)\[?(?P<server_ip>.*?)\]?\:(?P<server_port>[0-9]+)\])\n(?P<server_params>.*?)\n(?=\[|#)\n*''' + \ rb'''(?:\[client\:(?!default)\[?(?P<client_ip>.*?)\]?\:(?P<client_port>[0-9]+)\])\n(?P<client_params>.*?)(?=\[|#)''' + \ DOMAIN_END_HEADER.format(domain=r'(?P=domain)').encode('utf-8') matches = [x.groupdict() for x in re.finditer(regex, kamtlscfg_bytes, flags=re.DOTALL | re.MULTILINE)] for match in matches: domain = match['domain'].decode('utf-8') ip = match['server_ip'].decode('utf-8') server_config_strs = match['server_params'].decode('utf-8').rstrip().split('\n') client_config_strs = match['client_params'].decode('utf-8').rstrip().split('\n') custom_configs[domain] = { 'ip': ip if not ipv6Test(ip) else '[' + ip + ']', 'port': int(match['server_port'].decode('utf-8')), 'server': {x.split(' = ')[0]: x.split(' = ')[1] for x in server_config_strs}, 'client': {x.split(' = ')[0]: x.split(' = ')[1] for x in client_config_strs} } return custom_configs except: return None def addCustomTLSConfig(domain, ip='', port=5061, server_name_mode=KAM_TLS_SNI_DOMAIN): """ Add an additional domain to kamailio TLS configs :param domain: domain profile to create :type domain: str :param ip: ip kamailio will match for profile :type ip: str :param port: port kamailio will match for profile :type port: int|str :param server_name_mode: server name matching mode :type server_name_mode: int|str :return: whether addition succeeded :rtype: bool """ try: # TLS config does not accept hostnames, resolve IP if needed if len(ip) == 0: ip = hostToIP(domain) # IPv6 addresses require square brackets elif ipv6Test(ip): ip = '[' + ip + ']' domain_config_bytes = createCustomTLSConfig(domain, ip, port, server_name_mode).encode('utf-8') with open(settings.KAM_TLSCFG_PATH, 'r+b') as kamtlscfg_file: kamtlscfg_file.seek(0, 2) kamtlscfg_file.write(domain_config_bytes) return True except: return False def updateCustomTLSConfig(domain, ip=None, port=None, server_name_mode=None): """ Add an additional domain to kamailio TLS configs :param domain: domain profile to update :type domain: str :param ip: ip kamailio will match for profile :type ip: str :param port: port kamailio will match for profile :type port: int|str :param server_name_mode: server name matching mode :type server_name_mode: int|str :return: whether update succeeded :rtype: bool """ try: domain_configs = getCustomTLSConfigs(domain) if domain not in domain_configs: return False else: domain_data = domain_configs[domain] ip = ip if ip is not None else domain_data['ip'] port = port if port is not None else domain_data['port'] server_name_mode = server_name_mode if server_name_mode is not None \ else domain_data['server']['server_name_mode'] if not deleteCustomTLSConfig(domain): return False if not addCustomTLSConfig(domain, ip, port, server_name_mode): return False return True except: return False def deleteCustomTLSConfig(domain): """ Delete an additional domain in kamailio TLS configs :param domain: domain profile to delete :type domain: str :return: whether delete succeeded :rtype: bool """ try: with open(settings.KAM_TLSCFG_PATH, 'r+b') as kamtlscfg_file: kamtlscfg_bytes = kamtlscfg_file.read() regex = DOMAIN_START_HEADER.format(domain=domain).encode() + rb'.*?' + \ DOMAIN_END_HEADER.format(domain=domain).encode() + rb'\n*' custom_tls_bytes = re.sub(regex, b'', kamtlscfg_bytes, flags=re.DOTALL | re.MULTILINE) kamtlscfg_file.truncate(0) kamtlscfg_file.seek(0, 0) kamtlscfg_file.write(custom_tls_bytes) return True except: return False ================================================ FILE: gui/util/letsencrypt.py ================================================ """ The workflow consists of: (Account creation) - Create account key - Register account and accept TOS (Certificate actions) - Select HTTP-01 within offered challenges by the CA server - Set up http challenge resource - Set up standalone web server - Create domain private key and CSR - Issue certificate """ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import os, shutil, OpenSSL import josepy as jose from util.file_handling import change_owner from contextlib import contextmanager from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa from acme import challenges, client, crypto_util, messages, standalone import settings # This is the ACME Directory URL for `step-ca` DIRECTORY_URL_STAGING = 'https://acme-staging-v02.api.letsencrypt.org/directory' DIRECTORY_URL_PROD = 'https://acme-v02.api.letsencrypt.org/directory' # User Agent USER_AGENT = "dsiprouter" # Account key size ACC_KEY_BITS = 4096 # Certificate private key size CERT_PKEY_BITS = 4096 # Port to listen on for `http-01` challenge ACME_PORT = 80 def new_csr(domain_name, pkey_pem=None): """Create certificate signing request.""" if pkey_pem is None: pkey = OpenSSL.crypto.PKey() pkey.generate_key(OpenSSL.crypto.TYPE_RSA, CERT_PKEY_BITS) pkey_pem = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, pkey) csr_pem = crypto_util.make_csr(pkey_pem, [domain_name]) return pkey_pem, csr_pem def select_http01_chall(orderr): """Look for and select `http-01` from the challenges offered by the server.""" for authz in orderr.authorizations: for i in authz.body.challenges: if isinstance(i.chall, challenges.HTTP01): return i raise Exception('HTTP-01 challenge was not offered by the CA server.') @contextmanager def challenge_server(http_01_resources): """Manage standalone server set up and shutdown.""" # Setting up a server that binds at PORT and any address. address = ('', ACME_PORT) servers = None try: servers = standalone.HTTP01DualNetworkedServers(address, http_01_resources) servers.serve_forever() yield servers finally: if servers is not None: servers.shutdown_and_server_close() def perform_http01(client_acme, challb, orderr): """Set up standalone webserver and perform HTTP-01 challenge.""" response, validation = challb.response_and_validation(client_acme.net.key) resource = standalone.HTTP01RequestHandler.HTTP01Resource( chall=challb.chall, response=response, validation=validation) with challenge_server({resource}): # Let the CA server know that we are ready for the challenge. client_acme.answer_challenge(challb, response) # Wait for challenge status and then issue a certificate. # It is possible to set a deadline time. finalized_orderr = client_acme.poll_and_finalize(orderr) return finalized_orderr.fullchain_pem def generateCertificate(domain, notification_email, directory_url=DIRECTORY_URL_PROD, cert_dir=None, debug=None, default=None): # Create account key acc_key = jose.JWKRSA( key=rsa.generate_private_key(public_exponent=65537, key_size=ACC_KEY_BITS, backend=default_backend())) # Create client configured to use our ACME server and trust our root cert net = client.ClientNetwork(acc_key, user_agent=USER_AGENT) if debug == True: directory_url = DIRECTORY_URL_STAGING directory = messages.Directory.from_json(net.get(directory_url).json()) client_acme = client.ClientV2(directory, net=net) # Register account and accept TOS regr = client_acme.new_account( messages.NewRegistration.from_data( email=notification_email, terms_of_service_agreed=True)) # Create domain private key and CSR pkey_pem, csr_pem = new_csr(domain) # Issue certificate orderr = client_acme.new_order(csr_pem) # Select HTTP-01 within offered challenges by the CA server challb = select_http01_chall(orderr) # The certificate is ready to be used in the variable "fullchain_pem". fullchain_pem = perform_http01(client_acme, challb, orderr) # Store the certificates if cert_dir is None: cert_dir = settings.DSIP_CERTS_DIR if default == True: cert_domain_dir = settings.DSIP_CERTS_DIR else: cert_domain_dir = "{}/{}".format(cert_dir, domain) if not os.path.exists(cert_domain_dir): os.makedirs(cert_domain_dir) key_file = os.path.join(cert_domain_dir, 'dsiprouter-key.pem') cert_file = os.path.join(cert_domain_dir, 'dsiprouter-cert.pem') with os.fdopen(os.open(key_file, os.O_WRONLY | os.O_CREAT, 0o640), 'w') as keyfile: keyfile.write(pkey_pem.decode('utf-8')) with os.fdopen(os.open(cert_file, os.O_WRONLY | os.O_CREAT, 0o640), 'w') as certfile: certfile.write(fullchain_pem) # Change owner to root:kamailio so that Kamailio can load the configurations change_owner(cert_domain_dir, "dsiprouter", "kamailio") change_owner(key_file, "dsiprouter", "kamailio") change_owner(cert_file, "dsiprouter", "kamailio") return pkey_pem.decode('utf-8'), fullchain_pem def deleteCertificate(domain, directory_url=DIRECTORY_URL_PROD, cert_dir=None): # Location of certificates if cert_dir is None: cert_dir = settings.DSIP_CERTS_DIR # Location of the certificates cert_domain_dir = "{}/{}".format(cert_dir, domain) # Remove directory that contains the cert and key for the domain try: shutil.rmtree(cert_domain_dir) except Exception as ex: pass # TODO: Potential send a unregister command to Let's Encrypt if __name__ == "__main__": directory_url = DIRECTORY_URL_STAGING domain = "mack.dsiprouter.net" email = "mack@dopensource.com" key, cert = generateCertificate(domain, email, directory_url) print("Key:\n{}\nCert:\n{}\n".format(key, cert)) ================================================ FILE: gui/util/networking.py ================================================ import socket, binascii, requests, re import requests.packages.urllib3.util.connection as urllib3_conn from util.pyasync import mtexec # constants RTF_UP = 0x0001 # route usable RTF_GATEWAY = 0x0002 # destination is a gateway RTF_HOST = 0x0004 # host entry (net otherwise) DSIP_DNS_ALIASES = ['local.cluster'] # special purpose dsip hostnames RFC2396_USER_TRANSLATION = { 0: '%00', 1: '%01', 2: '%02', 3: '%03', 4: '%04', 5: '%05', 6: '%06', 7: '%07', 8: '%08', 9: '%09', 10: '%0a', 11: '%0b', 12: '%0c', 13: '%0d', 14: '%0e', 15: '%0f', 16: '%10', 17: '%11', 18: '%12', 19: '%13', 20: '%14', 21: '%15', 22: '%16', 23: '%17', 24: '%18', 25: '%19', 26: '%1a', 27: '%1b', 28: '%1c', 29: '%1d', 30: '%1e', 31: '%1f', 32: '%20', 34: '%22', 35: '%23', 37: '%25', 47: '%2f', 60: '%3c', 62: '%3e', 63: '%3f', 64: '%40', 91: '%5b', 92: '%5c', 93: '%5d', 94: '%5e', 96: '%60', 123: '%7b', 124: '%7c', 125: '%7d', 127: '%7f', 128: '%80', 129: '%81', 130: '%82', 131: '%83', 132: '%84', 133: '%85', 134: '%86', 135: '%87', 136: '%88', 137: '%89', 138: '%8a', 139: '%8b', 140: '%8c', 141: '%8d', 142: '%8e', 143: '%8f', 144: '%90', 145: '%91', 146: '%92', 147: '%93', 148: '%94', 149: '%95', 150: '%96', 151: '%97', 152: '%98', 153: '%99', 154: '%9a', 155: '%9b', 156: '%9c', 157: '%9d', 158: '%9e', 159: '%9f', 160: '%a0', 161: '%a1', 162: '%a2', 163: '%a3', 164: '%a4', 165: '%a5', 166: '%a6', 167: '%a7', 168: '%a8', 169: '%a9', 170: '%aa', 171: '%ab', 172: '%ac', 173: '%ad', 174: '%ae', 175: '%af', 176: '%b0', 177: '%b1', 178: '%b2', 179: '%b3', 180: '%b4', 181: '%b5', 182: '%b6', 183: '%b7', 184: '%b8', 185: '%b9', 186: '%ba', 187: '%bb', 188: '%bc', 189: '%bd', 190: '%be', 191: '%bf', 192: '%c0', 193: '%c1', 194: '%c2', 195: '%c3', 196: '%c4', 197: '%c5', 198: '%c6', 199: '%c7', 200: '%c8', 201: '%c9', 202: '%ca', 203: '%cb', 204: '%cc', 205: '%cd', 206: '%ce', 207: '%cf', 208: '%d0', 209: '%d1', 210: '%d2', 211: '%d3', 212: '%d4', 213: '%d5', 214: '%d6', 215: '%d7', 216: '%d8', 217: '%d9', 218: '%da', 219: '%db', 220: '%dc', 221: '%dd', 222: '%de', 223: '%df', 224: '%e0', 225: '%e1', 226: '%e2', 227: '%e3', 228: '%e4', 229: '%e5', 230: '%e6', 231: '%e7', 232: '%e8', 233: '%e9', 234: '%ea', 235: '%eb', 236: '%ec', 237: '%ed', 238: '%ee', 239: '%ef', 240: '%f0', 241: '%f1', 242: '%f2', 243: '%f3', 244: '%f4', 245: '%f5', 246: '%f6', 247: '%f7', 248: '%f8', 249: '%f9', 250: '%fa', 251: '%fb', 252: '%fc', 253: '%fd', 254: '%fe', 255: '%ff' } RFC2396_USER_ESCAPE_REGEX = re.compile(r'%[a-fA-F0-9][a-fA-F0-9]') def ipv4Test(address): try: socket.inet_pton(socket.AF_INET, address) except AttributeError: # no inet_pton here, sorry try: socket.inet_aton(address) except socket.error: return False return address.count('.') == 3 except socket.error: # not a valid address return False return True def ipv6Test(address): try: socket.inet_pton(socket.AF_INET6, address) except socket.error: # not a valid address return False return True def isValidIP(address, ip_ver=''): """ Determine if ip address is valid """ if ip_ver == '4': return ipv4Test(address) elif ip_ver == '6': return ipv6Test(address) else: if not ipv4Test(address) and not ipv6Test(address): return False return True def getInternalIP(ip_ver=''): """ Get the internally routable ip address of the system :param ip_ver: IP version to fetch '4'=>ipv4,'6'=>ipv6,''=>try both :type ip_ver: str :return: Internal IP address :rtype: str|None """ if ip_ver == '4': sock_types = (socket.AF_INET,) elif ip_ver == '6': sock_types = (socket.AF_INET6,) else: sock_types = (socket.AF_INET, socket.AF_INET6) for sock_type in sock_types: try: with socket.socket(sock_type, socket.SOCK_DGRAM) as s: s.connect(('www.google.com', 0)) ip = s.getsockname()[0] if isValidIP(ip): return ip except: pass return None def getExternalIP(ip_ver=''): """ Get the externally routable ip address of the system :param ip_ver: IP version to fetch '4'=>ipv4,'6'=>ipv6,''=>try both :type ip_ver: str :return: External IP address :rtype: str|None """ task_args = [] _allowed_gai_family = urllib3_conn.allowed_gai_family # redundancy in case a service provider goes down ipv4_resolvers = ( 'https://icanhazip.com', 'https://ipecho.net/plain', 'https://myexternalip.com/raw', 'https://api.ipify.org', 'https://bot.whatismyipaddress.com', ) ipv6_resolvers = ( 'https://icanhazip.com', 'https://bot.whatismyipaddress.com', 'https://ifconfig.co', 'https://ident.me', 'https://api6.ipify.org' ) # task performed by each thread def getip(sock_type, url): urllib3_conn.allowed_gai_family = lambda: sock_type iptest = ipv6Test if sock_type == socket.AF_INET6 else ipv4Test try: ip = requests.get(url, timeout=2.0).text.strip() if iptest(ip): return ip except: pass return None # DNS exceptions can take a while to resolve, so instead we run all requests # in parallel and filter out the first good external IP (ipv4 given priority) if ip_ver == '4' or len(ip_ver) == 0: task_args.extend((socket.AF_INET, resolver) for resolver in ipv4_resolvers) if ip_ver == '6' or len(ip_ver) == 0: task_args.extend((socket.AF_INET6, resolver) for resolver in ipv6_resolvers) results = mtexec(getip, task_args) # undo urllib monkey patch urllib3_conn.allowed_gai_family = _allowed_gai_family return next((x for x in results if x is not None), None) def hostToIP(host, ip_ver=''): """ Convert host to IP Address\n Supports conversion to IPv4 and IPv6\n IPv4 takes priority and is tried first :param host: hostname/fqdn to convert to IP :type host: str :param ip_ver: IP version to use :type ip_ver: str :return: IP address of host :rtype: str|None """ if ip_ver == '4' or len(ip_ver) == 0: try: return socket.getaddrinfo(host, 0, socket.AF_INET)[0][4][0] except: raise Exception("Endpoint hostname/address is malformed or not working:{0}".format(host)) if ip_ver == '6' or len(ip_ver) == 0: try: return socket.getaddrinfo(host, 0, socket.AF_INET6)[0][4][0] except: raise Exception("Endpoint hostname/address is malformed or not working:{0}".format(host)) return None def ipToHost(ip, exclude_dsip_aliases=True): """ Converts IP Address to hostname\n Supports conversion from IPv4 and IPv6 :param ip: IP address to convert :type ip: str :return: hostname for IP :rtype: str|None """ try: host = socket.gethostbyaddr(ip)[0] if exclude_dsip_aliases and host in DSIP_DNS_ALIASES: return None return host except: return None def encodeSipUser(user): return user.translate(RFC2396_USER_TRANSLATION) def decodeSipUser(user): return RFC2396_USER_ESCAPE_REGEX.sub(lambda m: chr(int(m.group()[1:], base=16)), user) def parseSipUri(uri): """ Parse SIP URI into its components components dict format: .. code-block:: python res = { 'proto': <str|None>, 'user': <str|None>, 'ipv6': <str|None>, 'ipv4': <str|None>, 'host': <str|None>, 'port': <int|None>, 'params': <dict|None> } :param uri: SIP URI to parse :type uri: str :return: components of SIP URI :rtype: dict|None """ match = re.search( (r'^' r'(?:(?P<proto>[a-zA-Z]+):)?' r'(?:(?P<user>[a-zA-Z0-9\-_.]+)@)?' r'(?:' r'(?:\[?(?P<ipv6>(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]?)|' r'(?P<ipv4>(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|' r'(?P<host>(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]*[a-zA-Z0-9])\.)*(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))' r')' r'(?::(?P<port>[1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5]))?' r'(?:;(?P<params>[^\s]*))?' r'$'), uri) if match: res = match.groupdict() if res['port'] is not None: res['port'] = int(res['port']) if res['params'] is not None: res['params'] = {x[0]: (x[1] if len(x) == 2 else True) for x in [y.split('=', 1) for y in res['params'].split(';')]} return res return None def parseGenericUri(uri): """ Parse Generic URI into its components (as generic as possible)\n Port is the only part type casted (parse delimited fields accordingly) components dict format: .. code-block:: python res = { 'proto': <str|None>, 'userpw': <str|None>, 'ipv6': <str|None>, 'ipv4': <str|None>, 'host': <str|None>, 'port': <int|None>, 'path': <str|None>, 'params': <str|None> } :param uri: URI to parse :type uri: str :return: components of URI :rtype: dict|None """ match = re.search( (r'^' r'(?:(?P<proto>[a-zA-Z]+):/?/?)?' r'(?:(?P<userpw>[a-zA-Z0-9\-_.]+(?::.+)?)(?:@))?' r'(?:' r'(?:\[?(?P<ipv6>(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\]?)|' r'(?P<ipv4>(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\.(?:[0-9]{1,2}|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))|' r'(?P<host>(?:(?:[a-zA-Z0-9]|[a-zA-Z0-9_][a-zA-Z0-9\-_]*[a-zA-Z0-9])\.)*(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))' r')' r'(?::(?P<port>[1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5]))?' r'(?P<path>/[^\s?;]*)?' r'(?:(?:[?;])(?P<params>.*))?' r'$'), uri) if match: res = match.groupdict() if res['port'] is not None: res['port'] = int(res['port']) return res return None def safeUriToHost(uri, default_port=None): """ Strips non-host parts and converges on the host\n Supports IPv4, IPv6, and FQDN addresses as the host\n Optionally formats with port if default port is set :param uri: uri to extract host from :type uri: str :param default_port: default port if not found :type default_port: int :return: host or host:port if port set :rtype: str|None """ ipv6_port_format = False parts = parseGenericUri(uri) if parts is None: return None if parts['ipv4'] is not None: res = parts['ipv4'] elif parts['ipv6'] is not None: res = parts['ipv6'] ipv6_port_format = True elif parts['host'] is not None: res = parts['host'] else: res = None if res is not None and default_port is not None: if parts['port'] is not None: port = str(parts['port']) else: port = str(default_port) if ipv6_port_format: res = '[{}]'.format(res) res = '{}:{}'.format(res, port) return res def safeStripPort(address): """ Strip port from address\n Properly strips on IPv6, IPv4, and FQDN addresses :param address: address to strip port from :type address: str :return: address with port stripped :rtype: str """ match = re.search( (r'^' r'(?P<host>.*?)' r'(?::(?P<port>[1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5]))?' r'$'), address) if match: res = match.groupdict()['host'] if res[0] == '[' and res[-1] == ']': res = res[1:-1] else: res = address return res def safeFormatSipUri(uri, default_proto='sip', default_user='', default_port=5060, default_params={}): """ Given a SIP URI of questionable validity, parse out the good stuff, and fill in the rest :param uri: URI to format / validate :type uri: str :param default_proto: default protocol if missing :type default_proto: str :param default_user: default user if missing :type default_user: str :param default_port: default port if missing :type default_port: int :param default_params: default SIP parameters :type default_params: dict :return: SIP URI safely formatted :rtype: str|None """ parts = parseSipUri(uri) if parts is None: return None if parts['ipv4'] is not None: host = parts['ipv4'] elif parts['ipv6'] is not None: host = '[{}]'.format(parts['ipv6']) elif parts['host'] is not None: host = parts['host'] else: return None proto = parts['proto'] if parts['proto'] is not None else default_proto user = parts['user'] if parts['user'] is not None else default_user port = str(parts['port']) if parts['port'] is not None else str(default_port) tmp = parts['params'] if parts['params'] is not None else default_params params = ';'.join([('='.join([x[0], str(x[1])]) if not isinstance(x[1], bool) else x[0]) for x in tmp.items()]) return proto + ':' + \ (encodeSipUser(user) + '@' if len(user) > 0 else '') + \ host + ':' + \ port + (';' + params if len(params) > 0 else '') def getRoutingTableIPv4(): """ Get IPv4 routing table entries :return: routing table entries :rtype: list """ rt_entries = [] try: with open('/proc/net/route', 'r') as fp: _ = next(fp) for line in fp: fields = line.strip().split() rt_entries.append({ 'iface': fields[0], # interface name 'dst_addr': int(fields[1], 16), # destination network/host address 'gw_addr': int(fields[2], 16), # gateway address 'flags': int(fields[3], 16), # routing flags 'use': int(fields[5]), # number of lookups for this route 'metric': int(fields[6]), # hops to target address 'mask': int(fields[7], 16), # network mask for destination 'mtu': int(fields[8]) # max packet size for this route }) except: pass return rt_entries def getRoutingTableIPv6(): """ Get IPv6 routing table entries :return: routing table entries :rtype: list """ rt_entries = [] try: with open('/proc/net/ipv6_route', 'r') as fp: _ = next(fp) for line in fp: fields = line.strip().split() rt_entries.append({ 'iface': fields[9], # interface name 'dst_addr': int(fields[0], 16), # destination network/host address 'dst_plen': int(fields[1], 16), # destination address prefix length 'src_addr': int(fields[2], 16), # source network/host address 'src_plen': int(fields[3], 16), # source address prefix length 'next_hop': int(fields[4], 16), # gateway / next hop address 'metric': int(fields[5], 16), # hops to target address 'use': int(fields[7], 16), # number of lookups for this route 'flags': int(fields[8], 16), # routing flags }) except: pass return rt_entries # Inspired By: `Python CookBook <https://www.safaribooksonline.com/library/view/python-cookbook/0596001673/ch10s06.html>`_ def ipToInt(ip_str): """ Convert IP string to integer :param ip_str: ipv4 or ipv6 address :type ip_str: str :return: integer value for ip :rtype: int :raises ValueError: on invalid IP conversion """ for sock_type in (socket.AF_INET, socket.AF_INET6): try: return int(binascii.hexlify(socket.inet_pton(sock_type, ip_str)), 16) except: pass raise ValueError("invalid IP address") def ipToStr(ip_int): """ Convert integer IP to string :param ip_int: integer value for ip :type ip_int: int :return: ipv4 or ipv6 address :rtype: str :raises ValueError: on invalid IP conversion """ for sock_type in (socket.AF_INET, socket.AF_INET6): try: return socket.inet_ntop(sock_type, binascii.unhexlify("{0:x}".format(ip_int))) except: pass raise ValueError("invalid IP address") def netMaskToPrefixLen(mask): """ Convert a network address mask to a CIDR prefix length\n For example: 255.255.255.0 -> 24\n Supports IPv4 and IPv6 (ipv6 generally doesn't use net masks though)\n Supplying network addresses other than a mask will give unintended results :param mask: network address mask :type mask: str|int :return: CIDR prefix length :rtype: str """ if isinstance(mask, str): mask = ipToInt(mask) return str(len(bin(mask)[2:])) def prefixLenToNetMask(prefixlen): """ Convert a CIDR prefix length to a network address mask\n For example: 32 -> 255.255.255.255\n Supports IPv4 and IPv6 (prefix length range is from 0-128) :param prefixlen: CIDR prefix length :type prefixlen: str|int :return: network address mask :rtype: str :raises ValueError: on invalid IP conversion """ if isinstance(prefixlen, str): prefixlen = int(prefixlen) mask = int('1' * 128, 2) & int('1' * prefixlen, 2) return ipToStr(mask) def getInternalCIDR(ip_ver=''): """ Get internal IP and network prefix length as CIDR address :param ip_ver: IP version to use :type ip_ver: str :return: CIDR address :rtype: str|None """ if ip_ver == '4' or len(ip_ver) == 0: try: # find default ipv4 route and mask, then create CIDR address rt_entries = getRoutingTableIPv4() if len(rt_entries) == 0: raise Exception() def_iface = next(x for x in rt_entries \ if x['dst_addr'] == 0 and x['flags'] & (RTF_UP | RTF_GATEWAY))['iface'] net_mask = next(x for x in rt_entries if x['gw_addr'] == 0 and x['iface'] == def_iface)['mask'] prefix_len = netMaskToPrefixLen(net_mask) ip = getInternalIP(ip_ver='4') return '{}/{}'.format(ip, prefix_len) except: pass if ip_ver == '6' or len(ip_ver) == 0: try: # find default ipv6 route and mask, then create CIDR address rt_entries = getRoutingTableIPv6() if len(rt_entries) == 0: raise Exception() def_iface = next(x for x in rt_entries \ if x['dst_addr'] == 0 and x['flags'] & (RTF_UP | RTF_GATEWAY))['iface'] prefix_len = str(next(x for x in rt_entries \ if x['dst_addr'] != 0 and x['next_hop'] != 0 \ and x['iface'] == def_iface)['dst_plen']) ip = getInternalIP(ip_ver='6') return '{}/{}'.format(ip, prefix_len) except: pass return None ================================================ FILE: gui/util/notifications.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import os, smtplib from email import encoders from email.mime.base import MIMEBase from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart from util.pyasync import thread from util.security import AES_CTR from shared import debugException import settings @thread def sendEmail(recipients, text_body, html_body=None, subject=settings.MAIL_DEFAULT_SUBJECT, sender=settings.MAIL_DEFAULT_SENDER, data=None, attachments=[]): """ Send an Email asynchronously to recipients :param recipients: email addresses we are sending to :type recipients: list|tuple :param text_body: email plain text message to send :type text_body: str :param html_body: email html message to send :type html_body: str :param subject: subject of the email :type subject: str :param sender: email address we are sending from :type sender: str :param data: key, value pairs to add to message :type data: dict :param attachments: list|tuple :type attachments: files to attach to email :return: no return value :rtype: None """ try: if data is not None: text_body += "\r\n\n" for key, value in data.items(): text_body += "{}: {}\n".format(str(key),str(value)) text_body += "\n" # print("Creating email") msg_root = MIMEMultipart('alternative') msg_root['From'] = sender msg_root['To'] = ", ".join(recipients) msg_root['Subject'] = subject msg_root.preamble = "|-------------------MULTIPART_BOUNDARY-------------------|\n" # print("Adding text body to email") msg_root.attach(MIMEText(text_body, 'plain')) if html_body is not None and html_body != "": # print("Adding html body to email") msg_root.attach(MIMEText(html_body, 'html')) if len(attachments) > 0: # print("Adding attachments to email") for file in attachments: with open(file, 'rb') as fp: msg_attachments = MIMEBase('application', "octet-stream") msg_attachments.set_payload(fp.read()) encoders.encode_base64(msg_attachments) msg_attachments.add_header('Content-Disposition', 'attachment', filename=os.path.basename(file)) msg_root.attach(msg_attachments) # check environ vars if in debug mode if settings.DEBUG: settings.MAIL_USERNAME = os.getenv('MAIL_USERNAME', settings.MAIL_USERNAME) settings.MAIL_PASSWORD = os.getenv('MAIL_PASSWORD', settings.MAIL_PASSWORD) # need to decrypt password if isinstance(settings.MAIL_PASSWORD, bytes): mailpass = AES_CTR.decrypt(settings.MAIL_PASSWORD) else: mailpass = settings.MAIL_PASSWORD # print("sending email") with smtplib.SMTP(settings.MAIL_SERVER, settings.MAIL_PORT) as server: server.connect(settings.MAIL_SERVER, settings.MAIL_PORT) server.ehlo() if settings.MAIL_USE_TLS: server.starttls() server.ehlo() server.login(settings.MAIL_USERNAME, mailpass) msg_root_str = msg_root.as_string() server.sendmail(sender, recipients, msg_root_str) server.quit() except Exception as ex: debugException(ex) ================================================ FILE: gui/util/parse_json.py ================================================ import inspect, uuid, dataclasses from datetime import datetime, date, time from decimal import Decimal from types import MethodType from flask.json import JSONEncoder from sqlalchemy.ext.declarative import DeclarativeMeta # TODO: finish custom json decoder def CreateEncoder(revisit_self=False, fields_to_expand=(), fields_to_remove=()): """ Wrapper method for creating Custom JSON Encoders \n Allows dynamic class definition for use in json.dumps(cls=<encoder>) \n :param revisit_self: True | False :type revisit_self: bool :param fields_to_expand: ORM Model Fields to expand :type fields_to_expand: list|tuple :param fields_to_remove: ORM Model Fields to remove :type fields_to_remove: list|tuple :return: A Voodoo Alchemy Encoder class :rtype: VoodooAlchemyEncoder """ visited_values = [] class VoodooAlchemyEncoder(JSONEncoder): """ JSON serializer for objects not serializable by default json code \n Also adds nested serialization and dynamic field customization of database Models \n ----- The following data types are supported: - :class:`types.FunctionType` - :class:`types.MethodType` - :class:`dataclasses` - :class:`decimal.Decimal` - :class:`datetime.datetime` - :class:`datetime.date` - :class:`datetime.time` - :class:`uuid.UUID` - :class:`flask.Markup` - :class:`sqlalchemy.ext.declarative.DeclarativeMeta` - :class:`collections.Iterable` - :class:`types.GeneratorType` - and all classes supported by :class:`flask.JSONEncoder` """ def default(self, value): """ Override JSONEcoder default class """ if self.is_valid_callable(value): value = value() elif dataclasses and dataclasses.is_dataclass(value): value = dataclasses.asdict(value) if isinstance(value, Decimal): value.to_eng_string() elif isinstance(value, datetime): return value.strftime('%Y-%m-%d %H:%M:%S') elif isinstance(value, date): return value.strftime('%Y-%m-%d') elif isinstance(value, time): return value.strftime('%H:%M:%S') elif isinstance(value, uuid.UUID): return str(value) elif hasattr(value, "__html__"): return str(value.__html__()) elif isinstance(value.__class__, DeclarativeMeta): return self.serialize_model(value) elif isinstance(value, dict): return self.serialize_dict(value) elif isinstance(value, list): return self.serialize_list(value) elif hasattr(value, '__iter__') and hasattr(value, '__next__'): return self.serialize_iter(value) else: return JSONEncoder.default(self, value) @staticmethod def is_valid_callable(func): if callable(func): i = inspect.getfullargspec(func) if i.args == ['self'] and isinstance(func, MethodType) and not any([i.varargs, i.varkw]): return True return not any([i.args, i.varargs, i.varkw]) return False def serialize_model(self, value): # don't re-visit self if revisit_self: if value in visited_values: return None visited_values.append(value) # go through each field in this SQLalchemy class fields = {} for field in [x for x in dir(value) if not x.startswith('_') and x != 'metadata' and not x in fields_to_remove]: val = value.__getattribute__(field) # is this field another SQLalchemy valueect, or a list of SQLalchemy valueects? if isinstance(val.__class__, DeclarativeMeta) or ( isinstance(val, list) and len(val) > 0 and isinstance(val[0].__class__, DeclarativeMeta)): # unless we're expanding this field, stop here if field not in fields_to_expand: # not expanding this field: set it to None and continue fields[field] = None continue # serialize each field if necessary val = self.default(val) fields[field] = val # a json-encodable dict return fields def serialize_iter(self, value): return [self.default(v) for v in value] def serialize_list(self, value): return [self.default(v) for v in value] def serialize_dict(self, value): return {self.default(k):self.default(v) for k,v in value.items()} return VoodooAlchemyEncoder # class CustomJSONDecoder(JSONDecoder): # def __init__(self, *args, **kwargs): # self.orig_obj_hook = kwargs.pop("object_hook", None) # super(CustomJSONDecoder, self).__init__(*args, # object_hook=self.custom_obj_hook, **kwargs) # # def custom_obj_hook(self, dct): # # Calling custom decode function: # dct = HelperFunctions.jsonDecodeHandler(dct) # if (self.orig_obj_hook): # Do we have another hook to call? # return self.orig_obj_hook(dct) # Yes: then do it # return dct # No: just return the decoded dict ================================================ FILE: gui/util/persistence.py ================================================ import json, os # TODO: not thread/process safe, move to shared memory manager def getPersistentState(): if not os.path.exists('/run/dsiprouter/state.json'): return {} with open('/run/dsiprouter/state.json', 'r') as f: return json.load(f) def setPersistentState(state): with open('/run/dsiprouter/state.json', 'w') as f: json.dump(state, f) def updatePersistentState(updates): if not os.path.exists('/run/dsiprouter/state.json'): with open('/run/dsiprouter/state.json', 'w') as f: json.dump(updates, f) return updates with open('/run/dsiprouter/state.json', 'r+') as f: state = json.load(f) state.update(updates) f.seek(0) f.truncate(0) json.dump(state, f) return state ================================================ FILE: gui/util/pyasync.py ================================================ import subprocess from functools import wraps from threading import Thread, Lock from multiprocessing import Process from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor class ThreadingIter(): """ Takes an iterator/generator and makes it thread-safe\n Implements locking for given iterator / generator """ def __init__(self, iter): self.iter = iter self.lock = Lock() def __iter__(self): return self def next(self): with self.lock: return self.iter.next() def thread(func): """ Wrap function to execute within a single thread """ @wraps(func) def wrapper(*args, **kwargs): thr = Thread(target=func, args=args, kwargs=kwargs, daemon=True) thr.start() return wrapper def process(func): """ Wrap function to execute within a single process """ @wraps(func) def wrapper(*args, **kwargs): proc = Process(target=func, args=args, kwargs=kwargs, daemon=True) proc.start() return wrapper @process def daemonize(cmd, timeout=300): """ Run command in detached daemon process :param cmd: commands to run :type cmd: list :param timeout: timeout before daemonized process quits :type timeout: int :return: no return value :rtype: None """ # decouple from parent environment #os.chdir('/') #os.setsid() #os.umask(0) # run command with new sid # TODO: systemd tracks processes by cgroup and will kill these daemons when parent dies # os.unshare should accomplish this but is too new and only supported in python 3.12 # back-porting would require compiling CPython modules or supporting multiple python versions? # alternatively we could ship a small ansi-C program that handles this # for now we workaround this in dsiprouter.sh but we should be more platform independent subprocess.run( cmd, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=timeout, #start_new_session=True, restore_signals=False, #preexec_fn=lambda: os.unshare(os.CLONE_NEWCGROUP) ) def mpexec(func, args=None, kwargs=None, workers=None, callback=None): """ Execute task within pool of processes :param func: callable function to execute :param args: list of arg lists for each function execution :param kwargs: list of kwarg dicts for each function execution :param workers: number of worker processes to use (or rounds to execute when args not provided) :param callback: callable function to execute after each completion :return: list of results returned by executing tasks """ with ProcessPoolExecutor(max_workers=workers) as executor: # no args or kwargs, number of tasks = num workers if args is None and kwargs is None: workers = workers if workers is not None else 1 if callback: tasks = [executor.submit(func).add_done_callback(callback) for _ in range(workers)] else: tasks = [executor.submit(func) for _ in range(workers)] # args without kwargs elif kwargs is None: if callback: tasks = [executor.submit(func, *func_args).add_done_callback(callback) for func_args in args] else: tasks = [executor.submit(func, *func_args) for func_args in args] # args and kwargs else: if callback: tasks = [executor.submit(func, *func_args, **func_kwargs).add_done_callback(callback) for func_args, func_kwargs in zip(args, kwargs)] else: tasks = [executor.submit(func, *func_args, **func_kwargs) for func_args, func_kwargs in zip(args, kwargs)] return [task.result() for task in tasks] def mtexec(func, args=None, kwargs=None, workers=None, callback=None): """ Execute task within pool of threads :param func: callable function to execute :param args: list of arg lists for each function execution :param kwargs: list of kwarg dicts for each function execution :param workers: number of worker processes to use (or rounds to execute when args not provided) :param callback: callable function to execute after each completion :return: list of results returned by executing tasks """ with ThreadPoolExecutor(max_workers=workers) as executor: # no args or kwargs, number of tasks = num workers if args is None and kwargs is None: workers = workers if workers is not None else 1 if callback: tasks = [executor.submit(func).add_done_callback(callback) for _ in range(workers)] else: tasks = [executor.submit(func) for _ in range(workers)] # args without kwargs elif kwargs is None: if callback: tasks = [executor.submit(func, *func_args).add_done_callback(callback) for func_args in args] else: tasks = [executor.submit(func, *func_args) for func_args in args] # args and kwargs else: if callback: tasks = [executor.submit(func, *func_args, **func_kwargs).add_done_callback(callback) for func_args, func_kwargs in zip(args, kwargs)] else: tasks = [executor.submit(func, *func_args, **func_kwargs) for func_args, func_kwargs in zip(args, kwargs)] return [task.result() for task in tasks] ================================================ FILE: gui/util/security.py ================================================ # make sure the generated source files are imported instead of the template ones import sys if sys.path[0] != '/etc/dsiprouter/gui': sys.path.insert(0, '/etc/dsiprouter/gui') import os, hashlib, binascii, string, ssl, OpenSSL, secrets, re from Crypto.Cipher import AES from Crypto.Random import get_random_bytes from shared import updateConfig, StatusCodes import settings # # Notes on storing credentials: # # best practice is to store hash and salt of the credentials # when plaintext access is mandatory we can use symmetric encryption # when sending encrypted data over the net asymmetric encryption is preferred # if credentials need to be stored/accessed in a python module, they need encoded/decoded # def urandomChars(length=32): """ Return printable characters from urandom :param length: number of bytes to return :type length: int :return: random characters :rtype: str """ chars = string.ascii_lowercase + string.ascii_uppercase + string.digits return ''.join([chars[ord(os.urandom(1)) % len(chars)] for _ in range(length)]) class Credentials(): """ Wrapper class for credential management functions """ SALT_LEN = 16 CREDS_MAX_LEN = 64 DK_LEN_DEFAULT = 48 HASH_ITERATIONS = 10000 # literals to make parsing from bash easier HASHED_CREDS_ENCODED_MAX_LEN = 128 assert HASHED_CREDS_ENCODED_MAX_LEN == CREDS_MAX_LEN * 2 @staticmethod def hashCreds(creds, salt=None, dklen=None): """ Hash credentials using pbkdf2_hmac with sha512 algorithm :param creds: byte string to hash :type creds: bytes|str :param salt: salt to hash creds with (hex encoded byte string or string) :type salt: bytes|str :param dklen: the derived key length to return as hash :type dklen: int :return: hash+salt as hex encoded byte string :rtype: bytes """ if isinstance(creds, bytes): pass elif isinstance(creds, str): creds = creds.encode('utf-8') else: raise ValueError('credentials must be a string or hex encoded byte string') if len(creds) > Credentials.CREDS_MAX_LEN: raise ValueError('credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) if salt is None: salt = get_random_bytes(Credentials.SALT_LEN) elif isinstance(salt, str): salt = salt.encode('utf-8') elif isinstance(salt, bytes): salt = binascii.unhexlify(salt) else: raise ValueError('salt must be a string or hex encoded byte string') if len(salt) != Credentials.SALT_LEN: raise ValueError('salt must be {} bytes long'.format(str(Credentials.SALT_LEN))) if dklen is None: dklen = Credentials.DK_LEN_DEFAULT elif not isinstance(dklen, int): raise ValueError('dklen must be an integer') hash = hashlib.pbkdf2_hmac('sha512', creds, salt, iterations=Credentials.HASH_ITERATIONS, dklen=dklen) return binascii.hexlify(hash + salt) @staticmethod def setCreds(dsip_creds=b'', api_creds=b'', kam_creds=b'', mail_creds=b'', ipc_creds=b'', rootdb_creds=b'', sesh_creds=b''): """ Set secure credentials, either by hashing or encrypting\n Values must be within size limit and empty values are ignored\n :param dsip_creds: dsiprouter admin password as byte string :type dsip_creds: bytes|str :param api_creds: dsiprouter api token as byte string :type api_creds: bytes|str :param kam_creds: kamailio db password as byte string :type kam_creds: bytes|str :param mail_creds: dsiprouter mail password as byte string :type mail_creds: bytes|str :param ipc_creds: dsiprouter ipc connection password as byte string :type ipc_creds: bytes|str :param rootdb_creds: root db user's password as byte string :type rootdb_creds: bytes|str :param sesh_creds: flask session manager key as byte string :type sesh_creds: bytes|str :return: None :rtype: None """ fields = {} local_fields = {} if len(dsip_creds) > 0: if len(dsip_creds) > Credentials.CREDS_MAX_LEN: raise ValueError('dsiprouter credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) fields['DSIP_PASSWORD'] = Credentials.hashCreds(dsip_creds) if len(api_creds) > 0: if len(api_creds) > Credentials.CREDS_MAX_LEN: raise ValueError('api credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) fields['DSIP_API_TOKEN'] = AES_CTR.encrypt(api_creds) if len(kam_creds) > 0: if len(kam_creds) > Credentials.CREDS_MAX_LEN: raise ValueError('kamailio credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) fields['KAM_DB_PASS'] = AES_CTR.encrypt(kam_creds) if len(mail_creds) > 0: if len(mail_creds) > Credentials.CREDS_MAX_LEN: raise ValueError('mail credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) fields['MAIL_PASSWORD'] = AES_CTR.encrypt(mail_creds) if len(ipc_creds) > 0: if len(ipc_creds) > Credentials.CREDS_MAX_LEN: raise ValueError('ipc credentials must be {} bytes or less'.format(str(Credentials.CREDS_MAX_LEN))) fields['DSIP_IPC_PASS'] = AES_CTR.encrypt(ipc_creds) # some fields are not synced with DB (also not constrained by max length limitations) if len(rootdb_creds) > 0: local_fields['ROOT_DB_PASS'] = AES_CTR.encrypt(rootdb_creds) if len(sesh_creds) > 0: local_fields['DSIP_SESSION_KEY'] = AES_CTR.encrypt(sesh_creds) # update settings based on where they are loaded from if len(fields) > 0 or len(local_fields) > 0: # update db settings from database import updateDsipSettingsTable # WARNING: if called after updating settings.py the session loader may import # incorrect connection credentials and fail to connect to the DB updateDsipSettingsTable(fields) # update file settings including the local fields updateConfig(settings, dict(fields, **local_fields)) class AES_CTR(): """ Wrapper class for pycrypto's AES256 functions in AES_CTR mode """ BLOCK_SIZE = 16 KEY_SIZE = 32 # literal to make parsing from bash easier NONCE_SIZE = 8 assert NONCE_SIZE == BLOCK_SIZE // 2 AESCTR_CREDS_ENCODED_MAX_LEN = 144 assert AESCTR_CREDS_ENCODED_MAX_LEN == (Credentials.CREDS_MAX_LEN * 2) + (NONCE_SIZE * 2) @staticmethod def genKey(keyfile=settings.DSIP_PRIV_KEY): with open(keyfile, 'wb') as f: key = get_random_bytes(AES_CTR.KEY_SIZE) f.write(key) @staticmethod def encrypt(byte_string, key_file=settings.DSIP_PRIV_KEY): if isinstance(byte_string, str): byte_string = byte_string.encode('utf-8') with open(key_file, 'rb') as f: key = f.read(AES_CTR.KEY_SIZE) nonce = get_random_bytes(AES_CTR.NONCE_SIZE) aes = AES.new(key, AES.MODE_CTR, nonce=nonce) ct_bytes = aes.encrypt(byte_string) return binascii.hexlify(nonce + ct_bytes) @staticmethod def decrypt(byte_string, key_file=settings.DSIP_PRIV_KEY, decode=True): if isinstance(byte_string, str): byte_string = byte_string.encode('utf-8') byte_string = binascii.unhexlify(byte_string) with open(key_file, 'rb') as f: key = f.read(AES_CTR.KEY_SIZE) nonce = byte_string[:AES_CTR.NONCE_SIZE] aes = AES.new(key, AES.MODE_CTR, nonce=nonce) pt_bytes = aes.decrypt(byte_string[AES_CTR.NONCE_SIZE:]) if decode: return pt_bytes.decode('utf-8') return pt_bytes class APIToken: token = None def __init__(self, request): if 'Authorization' in request.headers: auth_header = request.headers.get('Authorization', None) if auth_header is not None: header_values = auth_header.split(' ') if len(header_values) == 2: self.token = header_values[1] def isValid(self): try: if self.token: # Get Environment Variables if in debug mode # This is the only case we allow plain text token comparison if settings.DEBUG: settings.DSIP_API_TOKEN = os.getenv('DSIP_API_TOKEN', settings.DSIP_API_TOKEN) # need to decrypt token if isinstance(settings.DSIP_API_TOKEN, bytes): tokencheck = AES_CTR.decrypt(settings.DSIP_API_TOKEN) else: tokencheck = settings.DSIP_API_TOKEN return secrets.compare_digest(tokencheck, self.token) return False except: return False class CryptoLibInfo(): """ Wrapper class to standardize and simplify gathering info about the system crypto libraries """ @staticmethod def getOpenSSLVer(): """ Get major, minor, patch release numbers as one int :return: openssl release version :rtype: int """ return int(''.join([str(x) for x in ssl.OPENSSL_VERSION_INFO[0:3]])) @staticmethod def getSupportedSSLProtocols(): """ Get the support SSL/TLS protocols we serve :return: all support protocols :rtype: list """ ssl_ver = CryptoLibInfo.getOpenSSLVer() if ssl_ver < 101: return [OpenSSL.SSL.TLSv1_METHOD] elif ssl_ver < 111: return [OpenSSL.SSL.TLSv1_1_METHOD, OpenSSL.SSL.TLSv1_2_METHOD] else: # TODO: pyOpenSSL does not expose TLSv1.3 support yet # ref: https://github.com/pyca/pyopenssl/issues/860 return [OpenSSL.SSL.TLSv1_2_METHOD] # TODO: move to the standard library implementation "cryptography" module class KeyCertPair(): """ Represents a private key and cert(s) pair """ X509_DER_FILESIG = b'0\x82' X509_PEM_FILESIG = b'-----BEGIN CERTIFICATE-----' X509_PEM_CERT_BEGIN = b'-----BEGIN CERTIFICATE-----' X509_PEM_CERT_END = b'-----END CERTIFICATE-----' def __init__(self, files): """ Files to extract key/cert pair from :param files: A list of filepaths or objects implementing the read method :type files: list[str|IO]|tuple[str|IO] """ if not isinstance(files, (list, tuple)): raise ValueError('files must be provided via a list or tuple') # we can accept key/cert pairs in one file or two separate files num_files = len(files) if num_files == 1: buff = KeyCertPair.readFile(files[0]) self.pkey = KeyCertPair.convertKeyBuffToPkey(buff) self.certs = KeyCertPair.convertCertBuffToX509List(buff) elif num_files == 2: for file in files: buff = KeyCertPair.readFile(file) # we don't know which file is which so try each type one at a time try: self.pkey = KeyCertPair.convertKeyBuffToPkey(buff) continue except ValueError: pass try: self.certs = KeyCertPair.convertCertBuffToX509List(buff) continue except ValueError: pass else: raise ValueError("key/cert pairs must be in a single file or two files") @staticmethod def readFile(file): """ Read a file via filepath or using the object's read method """ if isinstance(file, str): with open(file, 'rb') as fp: return fp.read() elif hasattr(file, 'read'): return file.read() else: raise ValueError('files must contain filepaths or file pointers') @staticmethod def convertPKCS7CertToX509List(pkcs7_cert): """ Modified version of: https://github.com/pyca/pyopenssl/pull/367/files#r67300900 \n Returns all certificates for the PKCS7 structure, if present :param pkcs7_cert: the PKCS cert object :type pkcs7_cert: OpenSSL.crypto.PKCS7 :return: The certificates in PEM format :rtype: list[OpenSSL.crypto.X509] """ if not isinstance(pkcs7_cert, OpenSSL.crypto.PKCS7): raise ValueError('Invalid PKCS7 formatted certificate') certs = OpenSSL.crypto._ffi.NULL if pkcs7_cert.type_is_signed(): certs = pkcs7_cert._pkcs7.d.sign.cert elif pkcs7_cert.type_is_signedAndEnveloped(): certs = pkcs7_cert._pkcs7.d.signed_and_enveloped.cert pycerts = [] for i in range(OpenSSL.crypto._lib.sk_X509_num(certs)): pycert = OpenSSL.crypto.X509.__new__(OpenSSL.crypto.X509) pycert._x509 = OpenSSL.crypto._lib.X509_dup(OpenSSL.crypto._lib.sk_X509_value(certs, i)) pycerts.append(pycert) if len(pycerts) == 0: raise ValueError('Invalid PKCS7 formatted certificate') return pycerts @staticmethod def convertKeyBuffToPkey(buff): """ Convert a single private key of any format to PKey :param buff: private key buffer of unknown format :type buff: bytes :return: private key in OpenSSL usable format, type of :rtype: OpenSSL.crypto.PKey :raises: ValueError if the data can not be converted """ if not isinstance(buff, bytes): raise ValueError('buffer must be bytes') # try loading each type of file until we find the format that works # PEM encoded private key try: return OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, buff) except: pass # ASN1/DER encoded private key try: return OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_ASN1, buff) except: pass # PKCS12 formatted file try: return OpenSSL.crypto.load_pkcs12(buff).get_privatekey() except: pass raise ValueError('could not convert private key to PKey') @staticmethod def convertCertBuffToX509List(buff): """ Convert a one or more certificates of any format to X509 :param buff: certificate(s) buffer of unknown format :type buff: bytes :return: certificate(s) in OpenSSL usable format :rtype: list[OpenSSL.crypto.X509] :raises: ValueError if the data can not be converted """ if not isinstance(buff, bytes): raise ValueError('buffer must be bytes') # try loading each type of file until we find the format that works try: # PEM encoded certificate(s) if buff[0:len(KeyCertPair.X509_PEM_FILESIG)] == KeyCertPair.X509_PEM_FILESIG: cert_regex = KeyCertPair.X509_PEM_CERT_BEGIN + rb'.*?' + KeyCertPair.X509_PEM_CERT_END certs = [] for cert_bytes in re.findall(cert_regex, buff, flags=re.DOTALL): certs.append(OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_bytes)) return certs # ASN1/DER encoded certificate(s) if buff[0:len(KeyCertPair.X509_DER_FILESIG)] == KeyCertPair.X509_DER_FILESIG: # return [OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_ASN1, buff)] raise NotImplementedError('parsing DER encoded certificates is not supported, convert to PEM encoding and try again') except OpenSSL.crypto.Error: raise ValueError('could not convert certificate(s) to X509 list') try: pkcs12_obj = OpenSSL.crypto.load_pkcs12(buff) certs = [pkcs12_obj.get_certificate()] certs.extend(pkcs12_obj.get_ca_certificates()) return certs except: pass try: pkcs7_cert = OpenSSL.crypto.load_pkcs7_data(OpenSSL.crypto.FILETYPE_PEM, buff) return KeyCertPair.convertPKCS7CertToX509List(pkcs7_cert) except: pass try: pkcs7_cert = OpenSSL.crypto.load_pkcs7_data(OpenSSL.crypto.FILETYPE_ASN1, buff) return KeyCertPair.convertPKCS7CertToX509List(pkcs7_cert) except: pass raise ValueError('could not convert certificate(s) to X509 list') @staticmethod def getCertSubjectPrintable(cert): return str(cert.get_subject())[18:-2] def validateKeyCertPair(self): """ Check if the key/cert pair is valid :raises: ValueError when invalid """ for cert in self.certs: if cert.has_expired(): raise ValueError('certificate with subject "{}" is expired'.format(KeyCertPair.getCertSubjectPrintable(cert))) try: ctx = OpenSSL.SSL.Context(CryptoLibInfo.getSupportedSSLProtocols()[0]) ctx.use_privatekey(self.pkey) ctx.use_certificate(self.certs[0]) for cert in self.certs[1:]: ctx.add_extra_chain_cert(cert) ctx.check_privatekey() except: raise ValueError('Private key does not match x509 certificates supplied') def dumpPkey(self, encoding=OpenSSL.crypto.FILETYPE_PEM): return OpenSSL.crypto.dump_privatekey(encoding, self.pkey) def dumpCerts(self, encoding=OpenSSL.crypto.FILETYPE_PEM): return b'\n'.join([OpenSSL.crypto.dump_certificate(encoding, cert) for cert in self.certs]) ================================================ FILE: gui/util/time_funcs.py ================================================ ''' @summary: Provides methods for working with datetimes, timestamps, etc.. @author: devopsec ''' import pytz from datetime import datetime, timezone def convert_ts(ts, millis=False, is_utc=False): ''' convert timestamp to human readable format optionally keep up to microsecond precision if timestamp is utc format pass param is_utc=True ''' # if ts is not in string format if not type(ts).__name__ == 'str': ts = str(ts) # if contains ts contains millis and millis is false, strip them # formatted_ts = None # create var above routine # TODO: add try catch error handling if len(ts) > 10: if millis == False: replace = len(ts) - 10 ts = ts[:-replace] if is_utc == True: formatted_ts = datetime.fromtimestamp(int(ts), tz=pytz.utc).strftime('%Y-%m-%d %H:%M:%S') else: formatted_ts = datetime.fromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S') else: # convert w/ millis and return if not '.' in ts: # needs . to distinguish millis ts = ts[0:10] + '.' + ts[10:] if is_utc == True: formatted_ts = datetime.fromtimestamp(float(ts), tz=pytz.utc).strftime('%Y-%m-%d %H:%M:%S.%f') else: formatted_ts = datetime.fromtimestamp(float(ts)).strftime('%Y-%m-%d %H:%M:%S.%f') else: # convert w/o millis and return if is_utc == True: formatted_ts = datetime.fromtimestamp(int(ts), tz=pytz.utc).strftime('%Y-%m-%d %H:%M:%S') else: formatted_ts = datetime.fromtimestamp(int(ts)).strftime('%Y-%m-%d %H:%M:%S') return formatted_ts # DEBUG # print(convert_ts("1486782196652")) # print(convert_ts("1486782196")) # print(convert_ts(1486782196.652655, millis=True)) # print(convert_ts("1486782196652655", millis=True)) # print(convert_ts("1486782196652655", is_utc=True)) # TODO: add try catch error handling def utcnow(format="ts"): ''' returns time-aware utc datetime object or timestamp of current time/date @Param: format - defaults to timestamp, provide "dt" for datetime obj ''' dt = datetime.now(tz=pytz.utc) if format == "dt": return dt else: # timestamp includes millis down to 6th decimal place ts = str(dt.timestamp()) return ts.replace('.', '') # DEBUG # print(utcnow()) # print(utcnow("dt")) # TODO add methods for converting to client local timezone dynamically ================================================ FILE: kamailio/almalinux/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf config-manager --enable powertools && dnf install -y epel-release && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'core' && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'base' && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'Development Tools' && dnf install -y psmisc curl wget sed gawk vim perl firewalld logrotate rsyslog \ uuid openssl-devel libatomic libuuid-devel libjwt-devel bzip2-devel libffi-devel libcurl-devel \ python3.11 python3.11-pip policycoreutils-python-utils if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi # we need a newer version of certbot than the distro repos offer dnf remove -y *certbot* python3 -m venv --upgrade-deps /opt/certbot/ /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # create kamailio user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo dnf clean -y metadata dnf makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket \ kamailio-postgresql kamailio-debuginfo kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls \ kamailio-presence kamailio-outbound kamailio-gzcompress kamailio-http_async_client kamailio-dmq_userloc \ kamailio-sctp # workaround for kamailio rpm transaction failures if (( $? != 0 )); then rpm --import $(grep 'gpgkey' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2) REPOS='kamailio kamailio-ldap kamailio-mysql kamailio-postgresql kamailio-debuginfo kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress' for REPO in $REPOS; do yum install -y $(grep 'baseurl' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2)$(uname -m)/$(repoquery -i ${REPO} | head -4 | tail -n 3 | tr -d '[:blank:]' | cut -d ':' -f 2 | perl -pe 'chomp if eof' | tr '\n' '-').$(uname -m).rpm done fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes # STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl start firewalld systemctl enable firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Make sure MariaDB and Local DNS start before Kamailio if ! grep -q v 'mariadb.service dnsmasq.service' /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e 's/(After=.*)/\1 mariadb.service dnsmasq.service/' /lib/systemd/system/kamailio.service fi if ! grep -q v "${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig" /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e "0,\|^ExecStart.*|{s||ExecStartPre=-${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig\n&|}" /lib/systemd/system/kamailio.service fi systemctl daemon-reload # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules yum remove -y kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/almalinux/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf install -y epel-release && { # TODO: fix upstream kamailio.repo file #dnf config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #dnf config-manager --disable 'kamailio*' && #dnf config-manager --enable "kamailio-$KAM_VERSION_DOTTED" && # Add the Kamailio repos to yum (cat <<EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) >/etc/yum.repos.d/kamailio.repo && dnf makecache -y } && dnf groupinstall -y 'core' && dnf groupinstall -y 'base' && dnf groupinstall -y 'Development Tools' && dnf install -y git curl perl firewalld logrotate rsyslog certbot cmake libuuid-devel \ libcurl-devel libjwt-devel libatomic openssl-devel policycoreutils-python-utils \ libks-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules dnf remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/amzn/2.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_VERSION_DOTTED RHEL_BASE_VER NPROC # Install Dependencies amazon-linux-extras install -y epel >/dev/null && yum install -y yum-utils && yum groupinstall --setopt=group_package_types=mandatory,default -y 'Development Tools' && yum install -y psmisc curl wget sed gawk vim perl firewalld logrotate rsyslog cmake3 gcc10 \ uuid-devel libtool jansson-devel libuuid-devel libcurl-devel libjwt-devel libatomic \ bzip2-devel libffi-devel policycoreutils-python if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi # hardcoded to the latest release available for centos (patch updates broken) KAM_VERSION_DOTTED='5.7.4' RHEL_BASE_VER=$(rpm -E %{rhel}) NPROC=$(nproc) # link latest version of cmake ln -sf $(which cmake3) /usr/local/bin/cmake ## compile and install openssl v1.1.1 (workaround for amazon linux repo conflicts) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile openssl' return 1 } fi # python 3.8 or higher is required # if not installed already, install it now if [[ "$(python3 -V 2>/dev/null | cut -d ' ' -f 2)" != "3.9.18" ]]; then # installation / compilation never completed, start it now if [[ ! -d "${SRC_DIR}/Python-3.9.18" ]]; then ( cd ${SRC_DIR} && curl -s -o Python-3.9.18.tgz https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz && tar -xf Python-3.9.18.tgz && rm -f Python-3.9.18.tgz ) fi ( cd ${SRC_DIR} && cd Python-3.9.18/ && ./configure --enable-optimizations CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install required python version' return 1 } python3 -m pip install -U pip setuptools || { printerr 'Failed to update pip and setuptools' return 1 } fi # we need a newer version of certbot than the distro repos offer yum remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio # TODO: amzn2 should be based off rhel not centos but the kamailio rhel repos are out of date yum-config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && echo $RHEL_BASE_VER >/etc/yum/vars/rhelver && perl -i -pe 's%\$releasever%\$rhelver%g' /etc/yum.repos.d/kamailio.repo yum-config-manager --disable 'kamailio*' >/dev/null && yum-config-manager --enable "kamailio-$KAM_VERSION_DOTTED" >/dev/null && yum install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' exit 1 fi # enable sctp echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp # get info about the kamailio install for later use in script KAM_VERSION_FULL=$(kamailio -v 2>/dev/null | awk '/^version:/ {print $3}') KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld if (( $? != 0 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v1.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt #if [[ ! -d ${SRC_DIR}/libjwt ]]; then # git clone --depth 1 -c advice.detachedHead=false https://github.com/benmcollins/libjwt.git ${SRC_DIR}/libjwt #fi #( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr --libdir=/usr/lib64 && make && make install; exit $?; ) || #{ printerr 'Failed to compile and install libjwt'; return 1; } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install && ln -sft /usr/lib64/ /usr/lib/libks.so* && ln -sft /usr/lib64/pkgconfig/ /usr/lib/pkgconfig/libks.pc ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 CC=/bin/gcc10-gcc CXX=/usr/bin/gcc10-g++ \ CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl \ PKG_CONFIG_PATH=${SRC_DIR}/openssl && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION_FULL}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION_FULL} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION_FULL} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch htable module to support coldelim/colnull on kamailio v5.7.x ( cd ${SRC_DIR}/kamailio/src/modules/htable && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/htable-kam57.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/htable/htable.so ${KAM_MODULES_DIR}/ ) if (( $? != 0 )); then printerr 'Failed to patch htable module' return 1 fi # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) if (( $? != 0 )); then printerr 'Failed to patch uac module' return 1 fi return 0 } function uninstall() { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && { rm -rf ${SRC_DIR}/libks; rm -f /usr/lib64/{,pkgconfig/}libks*; } ( cd ${SRC_DIR}/libstirshaken; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/openssl rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules yum remove -y kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/centos/7.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies yum groupinstall -y 'Development Tools' && yum install -y epel-release && yum install -y centos-release-scl && yum install -y git curl perl gawk sed vim firewalld logrotate rsyslog cmake3 \ policycoreutils-python devtoolset-11 libcurl-devel libjwt-devel libatomic \ uuid-devel jansson-devel libuuid-devel bzip2-devel libffi-devel libtool if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # enable the newer development toolchain source scl_source enable devtoolset-11 # symlink cmake to cmake3 ln -sf $(which cmake3) /usr/local/bin/cmake # sctp support echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp ## compile and install openssl v1.1.1 (repo versions too old) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) if (( $? != 0 )); then printerr 'Failed to compile openssl' return 1 fi fi # python 3.8 or higher is required # if not installed already, install it now if [[ "$(python3 -V 2>/dev/null | cut -d ' ' -f 2)" != "3.9.18" ]]; then # installation / compilation never completed, start it now if [[ ! -d "${SRC_DIR}/Python-3.9.18" ]]; then ( cd ${SRC_DIR} && curl -s -o Python-3.9.18.tgz https://www.python.org/ftp/python/3.9.18/Python-3.9.18.tgz && tar -xf Python-3.9.18.tgz && rm -f Python-3.9.18.tgz ) fi ( cd ${SRC_DIR} && cd Python-3.9.18/ && ./configure --enable-optimizations CFLAGS=-I${SRC_DIR}/openssl/include LDFLAGS=-L${SRC_DIR}/openssl && make -j $NPROC && make -j $NPROC install ) if (( $? != 0 )); then printerr 'Failed to compile and install required python version' return 1 fi python3 -m pip install -U pip setuptools || { printerr 'Failed to update pip and setuptools' return 1 } fi # we need a newer version of certbot than the distro repos offer yum remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio # TODO: fix upstream kamailio.repo file #yum install -y yum-utils && #yum-config-manager --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #yum-config-manager --disable 'kamailio*' >/dev/null && #yum-config-manager --enable "kamailio-$KAM_VERSION_DOTTED" >/dev/null && # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo yum makecache -y yum install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v1.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) if (( $? != 0 )); then printerr 'Failed to compile and install libks' return 1 fi ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install && ldconfig ) if (( $? != 0 )); then printerr 'Failed to compile and install libstirshaken' return 1 fi ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ ) if (( $? != 0 )); then printerr 'Failed to compile and install STIR/SHAKEN module' return 1 fi # patch htable module to support coldelim/colnull on kamailio v5.7.x ( cd ${SRC_DIR}/kamailio/src/modules/htable && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/htable-kam57.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/htable/htable.so ${KAM_MODULES_DIR}/ ) if (( $? != 0 )); then printerr 'Failed to patch htable module' return 1 fi # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) if (( $? != 0 )); then printerr 'Failed to patch uac module' return 1 fi return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules yum remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/centos/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf groupinstall -y 'Development Tools' && dnf install -y epel-release dnf-plugins-core && dnf install -y git curl perl firewalld logrotate rsyslog certbot cmake libuuid-devel \ libcurl-devel libjwt-devel libatomic openssl-devel policycoreutils-python-utils if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio # TODO: fix upstream kamailio.repo file #dnf config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #dnf config-manager --disable 'kamailio*' && #dnf config-manager --enable "kamailio-$KAM_VERSION_DOTTED" && # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo yum makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules dnf remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/centos/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf groupinstall -y 'core' && dnf groupinstall -y 'base' && dnf groupinstall -y 'Development Tools' && dnf install -y epel-release dnf-plugins-core && dnf install -y git curl perl firewalld logrotate rsyslog certbot cmake libuuid-devel \ libcurl-devel libjwt-devel libatomic openssl-devel policycoreutils-python-utils if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio # TODO: fix upstream kamailio.repo file #dnf config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #dnf config-manager --disable 'kamailio*' && #dnf config-manager --enable "kamailio-$KAM_VERSION_DOTTED" && # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo dnf clean -y metadata dnf makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules dnf remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/configs/kamailio.cfg ================================================ #!KAMAILIO #====================================================================== # Defined Features Enabled #====================================================================== #!define WITH_MYSQL #!define WITH_AUTH #!define WITH_IPAUTH #!define WITH_UAC #!define WITH_USRLOCDB #!define WITH_ACCDB #!define WITH_CDRS #!define WITH_DROUTE ##!define WITH_DEBUG #!define WITH_NAT #!define WITH_DISPATCHER #!define WITH_CALL_SETTINGS ##!define WITH_SIGNAL_SERVERNAT ##!define WITH_SIGNAL_SERVERNAT6 ##!define WITH_MEDIA_SERVERNAT #!define WITH_MULTIDOMAIN #!define WITH_TELEBLOCK #!define WITH_ANTIFLOOD ##!define WITH_DBCLUSTER #!define WITH_LCR #!define WITH_TLS #!define WITH_SCTP #!define WITH_WEBSOCKETS ##!define WITH_DMQ #!define WITH_MSTEAMS ##!define WITH_DNID_LNP_ENRICHMENT #!define WITH_RTPENGINE #!define WITH_TRANSNEXUS #!define WITH_STIRSHAKEN ##!define WITH_IPV6 ##!define WITH_HOMER ##!define WITH_DMZ ##!define WITH_PUSH #====================================================================== # Define String Replacements Within Config #====================================================================== #!subst "!DMQ_REPLICATE_ENABLED!0!g" #====================================================================== # Defined Constants with String Replacement #====================================================================== #!substdef "!DSIP_ID!!g" #!substdef "!DSIP_CLUSTER_ID!!g" #!substdef "!DSIP_VERSION!!g" #!substdef "!HOMER_ID!!g" #!substdef "!INTERNAL_IP_ADDR!!g" #!substdef "!INTERNAL_IP_NET!!g" #!substdef "!INTERNAL_IP6_ADDR!!g" #!substdef "!INTERNAL_IP6_NET!!g" #!substdef "!INTERNAL_FQDN!!g" #!substdef "!EXTERNAL_IP_ADDR!!g" #!substdef "!EXTERNAL_IP6_ADDR!!g" #!substdef "!EXTERNAL_FQDN!!g" #!substdef "!UAC_REG_ADDR!!g" #!substdef "!INBOUND_NLB_FQDN!!g" #!substdef "!OUTBOUND_NLB_FQDN!!g" #!substdef "!HOMER_HOST!!g" #!substdef "!SIP_PORT!5060!g" #!substdef "!SIPS_PORT!5061!g" #!substdef "!DMQ_PORT!5090!g" #!substdef "!WSS_PORT!4443!g" #!substdef "!HEP_PORT!9060!g" #!substdef "!RTPENGINE_URI!udp:127.0.0.1:7722!g" #====================================================================== # Defined Constants #====================================================================== # - database URL - used to connect to database server by modules #!ifdef WITH_MYSQL #!ifdef WITH_DBCLUSTER #!define DBURL "cluster://dbcluster" #!define SQLCONN_KAM "kam=>mysql://kamailio:kamailiorw@localhost:3306/kamailio" #!define SQLCONN_AST "asterisk=>mysql://kamailio:kamailiorw@localhost:3306/kamailio" #!else #!define DBURL "mysql://kamailio:kamailiorw@localhost:3306/kamailio" #!define SQLCONN_KAM "kam=>mysql://kamailio:kamailiorw@localhost:3306/kamailio" #!define SQLCONN_AST "asterisk=>mysql://kamailio:kamailiorw@localhost:3306/kamailio" #!endif #!endif ### constants used by dispatcher routes #!define DSTALG_ROUND_ROBIN 4 #!define DSTALG_PRIORITY_BASED 8 #!define DSTALG_WEIGHT_BASED 9 #!define DSTALG_LOAD_DISTRIBUTION 10 #!define DSTALG_RELATIVE_WEIGHT 11 #!define DSTALG_PARALLEL_FORKING 12 #!ifdef WITH_MULTIDOMAIN # - the value for 'use_domain' parameters #!define MULTIDOMAIN 1 #!else #!define MULTIDOMAIN 0 #!endif ### flag definitions and usage: # NOTE: flags are stored in each bit of an integer, there range is from 0-31 (32 bits) # NOTE: it seems each set/type of flags has their own variable / namespace # FLT_ per transaction flags # usage: setflag(), resetflag(), isflagset() # FLB_ per branch flags # usage: setbflag(), resetbflag(), isbflagset() # FLS_ per script flags # usage: setsflag(), resetsflag(), issflagset() # FLD_ per dialog flags # usage: dlg_setflag(), dlg_resetflag(), dlg_isflagset() #!define FLT_ACC 0 #!define FLT_ACCMISSED 1 #!define FLT_ACCFAILED 2 #!define FLT_FAILOVER 3 #!define FLT_DLG_ALLOWED 4 #!define FLT_DOMAINROUTING 6 #!define FLT_PBX_AUTH 7 #!define FLT_CARRIER 8 #!define FLT_PBX 9 #!define FLT_CARRIER_AUTH 10 #!define FLT_EXTERNAL_AUTH 11 #!define FLT_PASSTHRU_AUTH 12 #!define FLT_SRC_SIP 13 #!define FLT_SRC_WS 14 #!define FLT_SRC_ALLOWED 15 #!define FLT_DST_INTERNAL_IP 16 #!define FLT_MSTEAMS 17 #!define FLT_SRC_INTERNAL_IP 18 #!define FLT_DST_IPV4 19 #!define FLT_DST_IPV6 20 #!define FLT_SRC_IPV4 21 #!define FLT_SRC_IPV6 22 #!define FLT_HAS_TOTAG 23 #!define FLB_NATB 0 #!define FLB_NATSIPPING 1 #!define FLB_WS_DEVICE 2 #!define FLB_SRC_PBX 3 #!define FLB_DST_PBX 4 #!define FLB_SRC_CARRIER 5 #!define FLB_DST_CARRIER 6 #!define FLB_SRC_MSTEAMS 7 #!define FLB_DST_MSTEAMS 8 #!define FLB_SRC_MSTEAMS_ONHOLD 9 #!define FLB_SRC_SELF 10 #!define FLD_USE_RTPE 1 # NOTE: not actual flags #!define FLT_OUTBOUND 8000 #!define FLT_INBOUND 9000 #!define NOTIFICATION_OVERLIMIT "0" #!define NOTIFICATION_GWFAILURE "1" #====================================================================== # Global Parameters #====================================================================== ### LOG Levels: # L_ALERT -5 # L_BUG -4 # L_CRIT2 -3 # L_CRIT -2 # L_ERR -1 # L_WARN 0 # L_NOTICE 1 # L_INFO 2 # L_DBG 3 #!ifdef WITH_DEBUG debug = 3 log_stderror = false #!else debug = 2 log_stderror = false #!endif # specifies on which log level the memory debugger/statistics will be logged memdbg = 5 memlog = 5 # syslog log facility messages will be logged to log_facility = LOG_LOCAL0 # configure the prefix for all log messages log_prefix_mode = 1 log_prefix = "[$cfg(name):$cfg(line):$cfg(route)] [$ci:$rm:$rs] " # multiprocess settings on startup fork = true children = 1 # increase loop limit to allow 10000 DID checks for inbound calls max_while_loops = 10000 # uncomment the next line to disable TCP (default on) #disable_tcp = true # uncomment the next line to disable the auto discovery of local aliases # based on reverse DNS on IPs (default on) #auto_aliases = false # add local domain aliases # NOTE: port is required in alias for loose routing to function properly # REF: https://www.kamailio.org/wiki/cookbooks/5.5.x/core#alias alias = "EXTERNAL_FQDN:SIP_PORT" alias = "EXTERNAL_FQDN:SIPS_PORT" alias = "EXTERNAL_FQDN:DMQ_PORT" alias = "EXTERNAL_FQDN:WSS_PORT" alias = "INBOUND_NLB_FQDN:SIP_PORT" alias = "INBOUND_NLB_FQDN:SIPS_PORT" alias = "INBOUND_NLB_FQDN:DMQ_PORT" alias = "INBOUND_NLB_FQDN:WSS_PORT" alias = "OUTBOUND_NLB_FQDN:SIP_PORT" alias = "OUTBOUND_NLB_FQDN:SIPS_PORT" alias = "OUTBOUND_NLB_FQDN:DMQ_PORT" alias = "OUTBOUND_NLB_FQDN:WSS_PORT" # configure interface/port/proto kamailio will bind on listen = udp:127.0.0.1:SIP_PORT listen = tcp:127.0.0.1:SIP_PORT #!ifdef WITH_TLS listen = tls:127.0.0.1:SIPS_PORT #!ifdef WITH_WEBSOCKETS listen = tls:127.0.0.1:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:127.0.0.1:SIP_PORT #!endif #!ifdef WITH_DMZ listen = udp:EXTERNAL_IP_ADDR:SIP_PORT listen = tcp:EXTERNAL_IP_ADDR:SIP_PORT #!endif #!ifdef WITH_SIGNAL_SERVERNAT listen = udp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT listen = tcp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:INTERNAL_IP_ADDR:WSS_PORT advertise "EXTERNAL_FQDN":WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:INTERNAL_IP_ADDR:SIP_PORT advertise EXTERNAL_IP_ADDR:SIP_PORT #!endif #!else listen = udp:INTERNAL_IP_ADDR:SIP_PORT listen = tcp:INTERNAL_IP_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:INTERNAL_IP_ADDR:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:INTERNAL_IP_ADDR:SIP_PORT #!endif #!endif #!ifdef WITH_IPV6 listen = udp:[::1]:SIP_PORT listen = tcp:[::1]:SIP_PORT #!ifdef WITH_TLS listen = tls:[::1]:SIPS_PORT #!ifdef WITH_WEBSOCKETS listen = tls:[::1]:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[::1]:SIP_PORT #!endif #!ifdef WITH_DMZ listen = udp:[EXTERNAL_IP6_ADDR]:SIP_PORT listen = tcp:[EXTERNAL_IP6_ADDR]:SIP_PORT #!endif #!ifdef WITH_SIGNAL_SERVERNAT6 listen = udp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT listen = tcp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:[INTERNAL_IP6_ADDR]:WSS_PORT advertise "EXTERNAL_FQDN":WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[INTERNAL_IP6_ADDR]:SIP_PORT advertise EXTERNAL_IP6_ADDR:SIP_PORT #!endif #!else listen = udp:[INTERNAL_IP6_ADDR]:SIP_PORT listen = tcp:[INTERNAL_IP6_ADDR]:SIP_PORT #!endif #!ifdef WITH_TLS #!ifdef WITH_WEBSOCKETS listen = tls:[INTERNAL_IP6_ADDR]:WSS_PORT #!endif #!endif #!ifdef WITH_SCTP listen = sctp:[INTERNAL_IP6_ADDR]:SIP_PORT #!endif #!endif #!ifdef WITH_DMQ listen = udp:INTERNAL_IP_ADDR:DMQ_PORT #!endif #!ifdef WITH_TLS enable_tls = true tcp_accept_no_cl = true tcp_rd_buf_size = 16384 listen = tls:INTERNAL_IP_ADDR:SIPS_PORT advertise "EXTERNAL_FQDN":SIPS_PORT #!ifdef WITH_IPV6 listen = tls:[INTERNAL_IP6_ADDR]:SIPS_PORT advertise "EXTERNAL_FQDN":SIPS_PORT #!endif #!endif # life time of TCP connection when there is no traffic # - a bit higher than registration expires to cope with UA behind NAT tcp_connection_lifetime = 3605 # Whether the “Server” header for locally generated messages is set server_signature = true # What the generated "Server" header will be set to server_header = "Server: dSIPRouter/DSIP_VERSION" # disable Stream Control Tranmission Protocol (SCTP) #!ifdef WITH_SCTP enable_sctp = 1 #!else enable_sctp = 0 #!endif # enable/disable DMZ support #!ifdef WITH_DMZ mhomed=1 #!else mhomed=0 #!endif ####### Custom Parameters ######### # These parameters can be modified runtime via RPC interface # - see the documentation of 'cfg_rpc' module. # # Format: group.id = value 'desc' description # Access: $sel(cfg_get.group.id) or @cfg_get.group.id # #!ifdef WITH_VOICEMAIL # VoiceMail Routing on offline, busy or no answer # # - by default Voicemail server IP is empty to avoid misrouting voicemail.srv_ip = "" desc "VoiceMail IP Address" voicemail.srv_port = "5060" desc "VoiceMail Port" #!endif #!ifdef WITH_TELEBLOCK teleblock.gw_enabled = 0 desc "Enable Teleblock support" teleblock.gw_ip = "62.34.24.22" desc "Teleblock IP" teleblock.gw_port = "5066" desc "Teleblock Port" teleblock.media_ip = "" desc "Teleblock media ip" teleblock.media_port = "" desc "Teleblock media port" #!endif # Define the role of the server # "" - default behavior # outbound - outbound only (no domain routing) # inout - inbound and outbound only (no domain routing) server.role = "" desc "Role of the server in the topology" # Local calling maximum digits for the initiating PBX - PBX sending the INVITE server.pbx_max_local_digits = 5 desc "Maximum digits for local pbx extensions" # PBX INVITE Timeout (msecs) to support having a Primary and Secondary PBX server.pbx_invite_timeout = 5000 desc "The default PBX INVITE timeout" # PBX INVITE Timeout (msecs) if a SIP 100/180/181/183 message is received server.pbx_invite_timeout_aftertry = 16000 desc "PBX INVITE timeout value if a SIP 100 message is received" # DSIPRouter API Server Settings server.api_server = "https://127.0.0.1:5000" desc "URL to the DSIPRouter API Server" server.api_token = "admin" desc "API Token for DSIPRouter API Server" # Emergency Numbers server.emergency_numbers = "^([2-9]11|112|999|000|988|933)$" desc "Emergency Numbers" # Transnexus External STIR/SHAKEN Support transnexus.authservice_lrn_enabled = 0 desc "Enable LRN for authservice" transnexus.authservice_enabled = 0 desc "Enable auth service for all Trunks" transnexus.authservice_host = "outbound.sip.clearip.com:5060" desc "Host to connect to for auth service" transnexus.verifyservice_enabled = 0 desc "Enable verify service for all Trunks" transnexus.verifyservice_host = "inbound.sip.clearip.com:5060" desc "Host to connect to for verify service" # Native Kamailio STIR/SHAKEN Support stir_shaken.stir_shaken_enabled = 0 desc "Whether native STIR/SHAKEN support is enabled" stir_shaken.stir_shaken_prefix_a = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_b = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_c = "" desc "Default Empty" stir_shaken.stir_shaken_prefix_invalid = "" desc "Default Empty" stir_shaken.stir_shaken_block_invalid = 0 desc "Default Disabled" stir_shaken.stir_shaken_key_path = "/etc/dsiprouter/certs/stirshaken/stirshaken-key.pem" desc "Local path to RSA key" stir_shaken.stir_shaken_cert_url = " https://cr.example.com/xK/order/xk" desc "URL to download X509 certificate from" # MSTeams Settings server.msteams_disable_refer = 1 # set location to load modules from (source or installation folders) #!ifdef WITH_SRCPATH mpath = "/usr/lib/x86_64-linux-gnu/kamailio/modules/" #!else mpath = "/usr/lib/x86_64-linux-gnu/kamailio/modules/" #!endif #====================================================================== # Module Loading #====================================================================== #!ifdef WITH_TLS loadmodule "tls.so" #!endif #!ifdef WITH_SCTP loadmodule "sctp.so" #!endif #!ifdef WITH_MYSQL loadmodule "db_mysql.so" #!endif loadmodule "kex.so" loadmodule "corex.so" loadmodule "tm.so" loadmodule "tmx.so" loadmodule "sl.so" loadmodule "rr.so" loadmodule "path.so" loadmodule "pv.so" loadmodule "maxfwd.so" loadmodule "usrloc.so" loadmodule "registrar.so" loadmodule "textops.so" loadmodule "textopsx.so" loadmodule "siputils.so" loadmodule "xlog.so" loadmodule "sanity.so" loadmodule "ctl.so" loadmodule "cfg_rpc.so" loadmodule "acc.so" loadmodule "xhttp.so" loadmodule "json.so" loadmodule "jansson.so" loadmodule "jsonrpcs.so" loadmodule "http_async_client.so" loadmodule "uuid.so" loadmodule "ipops.so" loadmodule "sdpops.so" loadmodule "rtimer.so" loadmodule "sqlops.so" #!ifdef WITH_DEBUG loadmodule "sipdump.so" #!endif #!ifdef WITH_DMQ loadmodule "dmq.so" loadmodule "dmq_usrloc.so" #!endif # must be loaded after dmq loadmodule "htable.so" loadmodule "dialog.so" #!ifdef WITH_AUTH loadmodule "auth.so" loadmodule "auth_db.so" #!ifdef WITH_IPAUTH loadmodule "permissions.so" #!endif #!ifdef WITH_UAC loadmodule "uac.so" loadmodule "uac_redirect.so" #!endif #!endif #!ifdef WITH_ALIASDB loadmodule "alias_db.so" #!endif #!ifdef WITH_SPEEDDIAL loadmodule "speeddial.so" #!endif #!ifdef WITH_MULTIDOMAIN loadmodule "domain.so" #!endif #!ifdef WITH_PRESENCE loadmodule "presence.so" loadmodule "presence_xml.so" #!endif #!ifdef WITH_NAT loadmodule "nathelper.so" #!endif #!ifdef WITH_RTPENGINE loadmodule "rtpengine.so" #!endif #!ifdef WITH_ANTIFLOOD loadmodule "pike.so" #!endif #!ifdef WITH_XMLRPC loadmodule "xmlrpc.so" #!endif #!ifdef WITH_DEBUG loadmodule "debugger.so" #!endif #!ifdef WITH_DROUTE loadmodule "drouting.so" #!endif #!ifdef WITH_DBCLUSTER loadmodule "db_cluster" #!endif #!ifdef WITH_DISPATCHER loadmodule "keepalive.so" loadmodule "dispatcher.so" #!endif #!ifdef WITH_WEBSOCKETS loadmodule "websocket.so" #!endif #!ifdef WITH_STIRSHAKEN loadmodule "stirshaken.so" #!endif #!ifdef WITH_HOMER loadmodule "siptrace.so" #!endif #!ifdef WITH_PUSH loadmodule "tsilo.so" #!endif #====================================================================== # Module-Specific Parameters #====================================================================== # ---- xlog global params ---- modparam("xlog", "buf_size", 8192) modparam("xlog", "prefix", "") # ---- htable global params ---- modparam("htable", "db_url", DBURL) # ---- dispatcher params ---- #!ifdef WITH_DISPATCHER modparam("dispatcher", "flags", 2) modparam("dispatcher", "db_url", DBURL) modparam("dispatcher", "table_name", "dispatcher") # The flags column controls the mode of a destination and keepalives. It is a bitwise value that can be built using the following flags: # 1 (1 <<0): inactive destination # 2 (1 <<1): temporary trying destination (in the way to become inactive if it does not reply to keepalives # 4 (1 <<2): admin disabled destination # 8 (1 <<3): probing destination (sending keep alives) # 16 (1 <<4): skip DNS A/AAAA resolve at startup, useful when the hostname of the destination address is a NAPTR or SRV record only modparam("dispatcher", "flags_col", "flags") # Controls what gateways are tested to see if they are reachable: # 0: Only the gateways with state PROBING (flags & 0x08 == 1) are tested. After a gateway is probed, the PROBING state is cleared in this mode (it will probe only one time at startup or after dispatcher reload). # 1: All gateways are tested. If there is a failure of keepalive to an active gateway, then it is set to TRYING state. # 2: Only gateways in INACTIVE state with PROBING mode set are tested. # 3: Any gateway with state PROBING is continually probed without modifying/removing the PROBING state. This allows selected gateways to be probed continually, regardless of state changes. modparam("dispatcher", "ds_probing_mode", 3) modparam("dispatcher", "ds_ping_latency_stats", 1) # 1 means to provide latency stats modparam("dispatcher", "ds_ping_method", "OPTIONS") # The SIP method to use when pinging destinations modparam("dispatcher", "ds_ping_interval", 60) # How often (seconds) to ping destinations to check status modparam("dispatcher", "ds_probing_threshold", 1) # How many failed ping requests before marking the destination as inactive modparam("dispatcher", "ds_inactive_threshold", 1) # How many successful ping requests before marking the destination as active modparam("dispatcher", "xavp_dst", "dispatcher_dst") # Will contain selected destination info modparam("dispatcher", "xavp_dst_mode", 0) # What attributes to set in the xavp modparam("dispatcher", "xavp_ctx", "dispatcher_ctx") # Will contain current dispatcher context info modparam("dispatcher", "xavp_ctx_mode", 0) # What attributes to set in the xavp modparam("dispatcher", "reload_delta", 1) # how quickly (in seconds) a RPC reload is allowed #!endif # ----- db_cluster params ---- # connection: set for each db connection uri # cluster: s == serial, r == roundrobin, p == parallel (write/only) #!ifdef WITH_DBCLUSTER modparam("db_cluster", "connection", "c1=>mysql://kamailio:kamailiorw@192.168.1.2/kamailio") modparam("db_cluster", "connection", "c2=>mysql://kamailio:kamailiorw@192.168.1.3/kamailio") modparam("db_cluster", "connection", "c3=>mysql://kamailio:kamailiorw@192.168.1.4/kamailio") modparam("db_cluster", "connection", "c4=>mysql://kamailio:kamailiorw@192.168.1.5/kamailio") modparam("db_cluster", "cluster", "dbcluster=>c1=9r9r;c2=9r9r;c3=9r9r;c4=9r9r") modparam("db_cluster", "inactive_interval", 180) #!endif # ----- jsonrpcs params ----- modparam("jsonrpcs", "pretty_format", 1) modparam("jsonrpcs", "fifo_name", "/var/run/kamailio/kamailio_rpc.fifo") modparam("jsonrpcs", "transport", 3) #modparam#("jsonrpcs", "dgram_socket", "/var/run/kamailio/kamailio_rpc.sock") # ----- ctl params ----- modparam("ctl", "binrpc", "unix:/var/run/kamailio/kamailio_ctl") # ----- tm params ----- # auto-discard branches from previous serial forking leg modparam("tm", "failure_reply_mode", 3) # default retransmission timeout: 30sec modparam("tm", "fr_timer", 30000) # default invite retransmission timeout after 1xx: 60sec modparam("tm", "fr_inv_timer", 60000) # XAVP used to store contacts used by t_load_contacts()/t_next_contacts() modparam("tm", "contacts_avp", "tm_contacts") # Used to store contacts (if any) that it skipped, because they contained same +sip.instance modparam("tm", "contact_flows_avp", "tm_contact_flows") # consider branch failures for to_on_failure() routing as well modparam("tm", "failure_exec_mode", 1) # ----- rr params ----- # set next param to 1 to add value to ;lr param (helps with some UAs) modparam("rr", "enable_full_lr", 0) # append from tag to the RR (required for is_direction() to work) modparam("rr", "append_fromtag", 1) # ----- registrar params ----- modparam("registrar", "method_filtering", 1) /* uncomment the next line to disable parallel forking via location */ # modparam("registrar", "append_branches", 0) /* uncomment the next line not to allow more than 10 contacts per AOR */ #modparam("registrar", "max_contacts", 10) # max value for expires of registrations modparam("registrar", "max_expires", 0) # set it to 1 to enable GRUU modparam("registrar", "gruu_enabled", 0) # ----- acc params ----- # what special events should be accounted? modparam("acc", "early_media", 0) modparam("acc", "report_ack", 0) modparam("acc", "report_cancels", 0) # by default ww do not adjust the direction of the sequential requests # if you enable this parameter, be sure the enable "append_fromtag" in "rr" module modparam("acc", "detect_direction", 0) modparam("acc", "log_flag", FLT_ACC) modparam("acc", "log_facility", "LOG_LOCAL0") modparam("acc", "log_missed_flag", FLT_ACCMISSED) modparam("acc", "log_extra", "src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;" "calltype=$avp(calltype);src_gwgroupid=$dlg_var(src_gwgroupid);dst_gwgroupid=$dlg_var(dst_gwgroupid)") modparam("acc", "failed_transaction_flag", FLT_ACCFAILED) # enhanced DB accounting #!ifdef WITH_ACCDB modparam("acc", "db_flag", FLT_ACC) modparam("acc", "db_missed_flag", FLT_ACCMISSED) modparam("acc", "db_url", DBURL) modparam("acc", "db_extra", "src_user=$fU;src_domain=$fd;src_ip=$si;dst_ouser=$tU;dst_user=$rU;dst_domain=$rd;" "calltype=$avp(calltype);src_gwgroupid=$dlg_var(src_gwgroupid);dst_gwgroupid=$dlg_var(dst_gwgroupid)") #!endif # ----- usrloc params ----- /* enable DB persistency for location entries */ #!ifdef WITH_USRLOCDB modparam("usrloc", "db_url", DBURL) modparam("usrloc", "db_mode", 3) modparam("usrloc", "use_domain", MULTIDOMAIN) modparam("usrloc", "handle_lost_tcp", 1) #!endif # ----- auth_db params ----- #!ifdef WITH_AUTH modparam("auth_db", "db_url", DBURL) modparam("auth_db", "calculate_ha1", 1) modparam("auth_db", "password_column", "password") # We use the rpid field of the subscriber table to track the assigned gwgroup (type of endpoint or carrier) modparam("auth_db", "load_credentials", "$avp(s:src_gwgroupid)=rpid;") modparam("auth_db", "use_domain", MULTIDOMAIN) # ----- permissions params ----- #!ifdef WITH_IPAUTH modparam("permissions", "db_url", DBURL) modparam("permissions", "db_mode", 1) # how quickly (in seconds) a RPC reload is allowed modparam("permissions", "reload_delta", 1) #!endif #!endif # ----- alias_db params ----- #!ifdef WITH_ALIASDB modparam("alias_db", "db_url", DBURL) modparam("alias_db", "use_domain", MULTIDOMAIN) #!endif # ----- speeddial params ----- #!ifdef WITH_SPEEDDIAL modparam("speeddial", "db_url", DBURL) modparam("speeddial", "use_domain", MULTIDOMAIN) #!endif # ----- domain params ----- #!ifdef WITH_MULTIDOMAIN modparam("domain", "db_url", DBURL) # register callback to match myself condition with domains list modparam("domain", "register_myself", 1) #!endif #!ifdef WITH_PRESENCE # ----- presence params ----- modparam("presence", "db_url", DBURL) # ----- presence_xml params ----- modparam("presence_xml", "db_url", DBURL) modparam("presence_xml", "force_active", 1) #!endif #!ifdef WITH_NAT # ----- nathelper params ----- modparam("nathelper", "natping_interval", 60) modparam("nathelper", "ping_nated_only", 1) modparam("nathelper", "sipping_bflag", FLB_NATSIPPING) modparam("nathelper", "sipping_from", "sip:pinger@UAC_REG_ADDR") # params needed for NAT traversal in other modules modparam("nathelper|registrar", "received_avp", "$avp(RECEIVED)") modparam("usrloc", "nat_bflag", FLB_NATB) #!endif #!ifdef WITH_RTPENGINE # ----- rtpengine params ----- modparam("rtpengine", "rtpengine_sock", "RTPENGINE_URI") # Specify if the RTPEngine instances have to be pinged at startup to detect if they are active. Set it to 0 to disable pinging and to 1 to activate pinging modparam("rtpengine", "ping_mode", 0) # Once an RTP proxy was found unreachable and marked as disabled, the rtpengine module will not attempt to establish communication to that RTP proxy for rtpengine_disable_tout seconds modparam("rtpengine", "rtpengine_disable_tout", 0) # How many times the module should retry to send and receive after timeout was generated modparam("rtpengine", "rtpengine_retr", 5) # Number of seconds after an rtpengine hash table entry is marked for deletion. The entries correspond to active calls using an rtpengine instance # This should match the default rtpengine timeout set in rtpengine.conf modparam("rtpengine", "hash_table_tout", 60) #!endif #!ifdef WITH_TLS # ----- tls params ----- modparam("tls", "config", "//etc/kamailio/tls.cfg") #!endif #!ifdef WITH_SCTP modparam("sctp", "sctp_assoc_tracking", 0) modparam("sctp", "sctp_assoc_reuse", 0) #!endif #!ifdef WITH_ANTIFLOOD # ----- pike params ----- modparam("pike", "sampling_time_unit", 2) modparam("pike", "reqs_density_per_unit", 50) modparam("pike", "remove_latency", 30) # ----- htable params ----- # ip ban htable with autoexpire after 5 minutes modparam("htable", "htable", "ipban=>size=8;autoexpire=300;dmqreplicate=DMQ_REPLICATE_ENABLED;") #!endif #!ifdef WITH_XMLRPC # ----- xmlrpc params ----- modparam("xmlrpc", "route", "XMLRPC"); modparam("xmlrpc", "url_match", "^/RPC") #!endif #!ifdef WITH_DEBUG # ----- debugger params ----- modparam("debugger", "cfgtrace", 0) modparam("debugger", "log_level_name", "debugger") # ----- sipdump params ----- modparam("sipdump", "enable", 1) modparam("sipdump", "wait", 100) modparam("sipdump", "rotate", 3600) modparam("sipdump", "folder", "/tmp") modparam("sipdump", "fprefix", "dsipdump-") #!endif #!ifdef WITH_UAC # ----- uac params ----- modparam("uac", "restore_mode", "none") modparam("uac", "reg_db_url", DBURL) modparam("uac", "reg_db_table", "uacreg") modparam("uac", "reg_timer_interval", 60) modparam("uac", "reg_retry_interval", 120) modparam("uac", "reg_keep_callid", 1) modparam("uac", "reg_gc_interval", 30) modparam("uac", "credential", "username:domain:password") modparam("uac", "auth_realm_avp", "$avp(auth_realm)") modparam("uac", "auth_username_avp", "$avp(auth_user)") modparam("uac", "auth_password_avp", "$avp(auth_pass)") modparam("uac", "reg_contact_addr", "UAC_REG_ADDR:SIP_PORT") # how quickly (in seconds) a RPC reload is allowed modparam("uac", "reload_delta", 1) #!endif #!ifdef WITH_DROUTE # ----- drouting params ----- modparam("drouting", "db_url", DBURL) modparam("drouting", "ruri_avp", "$avp(dr_ruri)") # our convention for passing some data about the selected route: # attrs[0]: gwid, # attrs[1]: gwtype # attrs[2]: msteams domain # attrs[3]: signalling transport # attrs[4]: media transport modparam("drouting", "attrs_avp", "$avp(dr_attrs)") # don't match on domain (per group) only use routing group modparam("drouting", "use_domain", 0) # do not resolve DNS names during load (will blindly try them) modparam("drouting", "force_dns", 0) # how gateways are selected from each rule # 0: destination groups are ignored and all the destinations are tried in the given order # 1: the destinations from each group are randomly arranged (only the two first elements are randomly selected); groups do maintain their order (as given) # 2: from each destination group, only a single destination is randomly selected; groups do maintain their order (as given) modparam("drouting", "sort_order", 0) # htable for maintenance mode modparam("htable", "htable", "maintmode=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_maintmode;cols='ipaddr,gwid';") #modparam("drouting", "enable_keepalive", 1) #!endif #!ifdef WITH_LCR # ----- htable params for from/to prefix lookup ----- modparam("htable", "htable", "tofromprefix=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_lcr;cols='pattern,dr_groupid';") #!endif # ----- rtimer params ----- #!ifdef WITH_CDRS modparam("rtimer", "timer", "name=cdr;interval=300;mode=1;") modparam("rtimer", "exec", "timer=cdr;route=CDRS") #!endif # ----- sqlops params ----- # Kamailio Connection modparam("sqlops", "sqlcon", SQLCONN_KAM) # Asterisk Realtime Connection modparam("sqlops", "sqlcon", SQLCONN_AST) # ----- dialog params ----- modparam("dialog", "db_url", DBURL) modparam("dialog", "db_mode", 0) modparam("dialog", "enable_stats", 1) modparam("dialog", "hash_size", 4096) modparam("dialog", "detect_spirals", 1) modparam("dialog", "track_cseq_updates", 1) modparam("dialog", "default_timeout", 21600) modparam("dialog", "early_timeout", 300) modparam("dialog", "noack_timeout", 180) modparam("dialog", "end_timeout", 60) modparam("dialog", "dlg_match_mode", 1) modparam("dialog", "send_bye", 0) modparam("dialog", "timeout_noreset", 1) modparam("dialog", "timeout_avp", "$dlg_ctx(timeout)") #!ifdef WITH_DMQ # ---- dmq params ---- # TODO: dmq module only supports one server_address so we do not listen on IPV6, possibly change to INTERNAL_FQDN? # this would only be an issue for IPv4 disabled machines, which is very uncommon modparam("dmq", "server_address", "sip:INTERNAL_IP_ADDR:DMQ_PORT") modparam("dmq", "notification_address", "sip:local.cluster:DMQ_PORT") modparam("dmq", "multi_notify", 1) modparam("dmq", "num_workers", 4) modparam("dmq", "ping_interval", 15) modparam("dmq_usrloc", "enable", 1) # ---- dmq-related params ---- modparam("dialog", "enable_dmq", 1) modparam("htable", "enable_dmq", 1) # only valid for kam ver >= 5.2 modparam("htable", "dmq_init_sync", 1) #!endif #!ifdef WITH_CALL_SETTINGS modparam("dialog", "profiles_with_value", "gwgroup") modparam("htable", "htable", "call_settings=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_call_settings_h;cols='gwgroupid,limit,timeout';colnull='';") modparam("htable", "htable", "concurrent_calls=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;initval=0;") #!endif # gw2gwroup is used to lookup gwgroupid modparam("htable", "htable", "gw2gwgroup=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_gw2gwgroup;cols='gwid,gwgroupid';") # gwgroup2lb is used to lookup the dispatcher setid associated with a gwgroupid modparam("htable", "htable", "gwgroup2lb=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_gwgroup2lb;cols='gwgroupid,setid,enabled';") # inbound_hardfwd is used to lookup did and dr_groupid for forwarding calls unconditionally modparam("htable", "htable", "inbound_hardfwd=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_hardfwd;cols='dr_ruleid,did,dr_groupid';") # inbound_failfwd is used to lookup did and dr_groupid for forwarding calls on failover modparam("htable", "htable", "inbound_failfwd=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_failfwd;cols='dr_ruleid,did,dr_groupid';") # prefix_to_route is used to lookup dr_ruleid for a prefix modparam("htable", "htable", "prefix_to_route=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_prefix_mapping;cols='prefix,ruleid,priority';") # to manage Pass Thru Auth for Registration. Used to send Authorization requests back to the same backend media server modparam("htable", "htable", "pass_thru_auth=>size=8;autoexpire=3600;dmqreplicate=DMQ_REPLICATE_ENABLED;") # enrichdnid_lnpmap is used to lookup dnid prefixes to match against #!ifdef WITH_DNID_LNP_ENRICHMENT modparam("htable", "htable", "enrichdnid_lnpmap=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED;dbtable=dsip_dnid_lnp_mapping;cols='dnid,prefix';") #!endif # Pass-Thru Auth IP to Domain mapping lookup. Allows PJSIP Pass-Thru to work correctly #modparam("htable", "htable", "pbxip2domain=>size=8;autoexpire=0;dmqreplicate=DMQ_REPLICATE_ENABLED") # ----- http_async_client params ----- modparam("http_async_client", "workers", 1) modparam("http_async_client", "connection_timeout", 500) modparam("http_async_client", "hash_size", 2048) #!ifdef WITH_DEBUG modparam("http_async_client", "curl_verbose", 1) #!endif #!ifdef WITH_TLS modparam("http_async_client", "tls_client_cert", "/etc/dsiprouter/certs/dsiprouter-cert.pem") modparam("http_async_client", "tls_client_key", "/etc/dsiprouter/certs/dsiprouter-key.pem") modparam("http_async_client", "tls_ca_path", "/etc/dsiprouter/certs/") #!endif # ---- sip trace params ---- #!ifdef WITH_HOMER modparam("siptrace", "duplicate_uri", "sip:HOMER_HOST:HEP_PORT") modparam("siptrace", "hep_version", 3) modparam("siptrace", "hep_mode_on", 1) modparam("siptrace", "hep_capture_id", HOMER_ID) modparam("siptrace", "trace_to_database", 0) modparam("siptrace", "trace_on", 1) modparam("siptrace", "trace_mode", 1) modparam("siptrace", "trace_sl_acks", 1) #!endif #!ifdef WITH_PUSH modparam("htable", "htable", "push=>size=10;autoexpire=120;") #!endif #====================================================================== # Routing Logic #====================================================================== # Main SIP request routing logic # - executed for each SIP request received from the network # - specific request processing logic should be abstracted into sub-routes # - sub-routes are defined using the syntax: route[...] { ... } # - sub-routes can be executed within any routing block request_route { #!ifdef WITH_DEBUG xlog("L_DBG", "processing request from $sas on $Rut\n"); #!endif # handle DMQ messages route(DMQ); # per request initial checks route(REQINIT); #!ifdef WITH_STIRSHAKEN if ((int)$sel(cfg_get.stir_shaken.stir_shaken_enabled)) { route(STIRSHAKEN_INBOUND); } #!endif # NAT detection route(NATDETECT); # CANCEL processing route(HANDLE_CANCEL); # is this request allowed to manage dialogs? route(CHECK_DLG_ALLOWED); # handle requests within SIP dialogs route(WITHINDLG); # handle retransmissions route(HANDLE_RETRANS); # authentication route(AUTH); # store the call source info for later usage route(SET_CALLSRC_INFO); # apply NAT changes after dialog is setup route(NATMANAGE); # handle registrations route(REGISTRAR); # handle presence related requests route(PRESENCE); # dispatch to local endpoints that registered thru the proxy route(LOCATION); # enrich dialed number before routing route(ENRICH_DNID); #!ifdef WITH_TRANSNEXUS # Process call if Transnexus validation service is enabled if ((int)$sel(cfg_get.transnexus.verifyservice_enabled) && !isflagset(FLT_HAS_TOTAG)) { route(TRANSNEXUS_INBOUND); } #!endif # route the call to the next hop route(NEXTHOP); } # Main SIP response handling logic # - executed for each SIP response received from the network # - specific reply processing logic should be abstracted into sub-routes # - onreply routes should not be used here and are only executed when set w/ t_on_reply() # - sub-routes called here are executed by the core (onreply routes are executed by tm module) reply_route { #!ifdef WITH_DEBUG xlog("L_DBG", "processing reply from $sas on $Rut\n"); #!endif return; } # Pre-Send SIP request handling logic # - executed prior to forwarding specific SIP requests from the network # - not executed for replies, retransmissions, or locally generated messages # - a very limited set of core functions are available here, no sub-routes either onsend_route { #!ifdef WITH_DEBUG xlog("L_DBG", "sending message\n"); #!endif return; } route[HANDLE_CANCEL] { if (is_method("CANCEL")) { if (t_check_trans()) { route(RELAY); } route(RTPENGINEDELETE); exit; } } route[HANDLE_RETRANS] { if (t_precheck_trans()) { t_check_trans(); exit; } t_check_trans(); } route[DMQ] { #!ifdef WITH_DMQ if ($rm == "KDMQ" && $rp == DMQ_PORT) { dmq_handle_message(); exit; } #!endif return; } route[REFORMATRURI] { xlog("L_DBG", "original rU <$rU> and original tU <$tU>\n"); # This is to deal with those who are used to dialing 7 digits # assuming that the 7 digit number being dialed is in the same area code as the FROM number if ($(rU{s.len}) == 7) { if ($(fU{s.len}) == 10) { $rU = $(fU{s.substr,0,3}) + $rU; } else { $rU = $(fU{s.substr,0,4}) + $rU; } $tU = $rU; } else if ($(rU{s.len}) > 10) { # Check for +1 and remove it from the RURI and the To header if ($(rU{s.substr,0,2}) == "+1") { $rU = $(rU{s.substr,2,0}); $tU = $rU; } # Check for 1 and remove it from the RURI and the To header else if ($(rU{s.substr,0,1}) == "1") { $rU = $(rU{s.substr,1,0}); $tU = $rU; } } xlog("L_DBG", "modified rU <$rU> and modified tU <$tU>\n"); } route[ENRICH_SIPHEADER] { if (!strempty($xavp(ra=>sipdomain))) { append_hf("X-SIPDOMAIN: $xavp(ra=>sipdomain)\r\n"); } } route[ENRICH_DNID] { route(ENRICH_DNID_LNP); #route(REFORMATRURI); } route[ENRICH_DNID_LNP] { #!ifdef WITH_DNID_LNP_ENRICHMENT # enrich dialed number with country code and area code $var(dnid) = $(rU{s.unescape.user}); $avp(dnid_prefix) = $sht(enrichdnid_lnpmap=>$var(dnid)); if ($avp(dnid_prefix) != $null && !strempty($avp(dnid_prefix))) { $rU = $(avp(dnid_prefix){s.escape.user}) + $rU; $tU = $(avp(dnid_prefix){s.escape.user}) + $tU; } #!endif return; } # Route the call to the next hop, which can be a PBX or Carrier route[NEXTHOP] { ###################################### # Endpoint to PBX via Domain Routing # ###################################### # TODO: pbx_type in domain_attrs is not standardized, standardize this in v0.80 if (isflagset(FLT_DOMAINROUTING) && !isflagset(FLT_EXTERNAL_AUTH)) { #Grab the value of the avp that contains the domain_pbx_ip. #This is where requests for that domain should be routed # Route to the endpoints defined in the Endpoint Gateway list (dr_gw_list) if ($avp(domain_pbx_type) == "2") { xlog("L_INFO", "<$ci> Routing to Endpoint Gateway List\n"); if (!strempty($sht(pass_thru_auth=>$ci))) { $du = $sht(pass_thru_auth=>$ci); xlog("L_INFO", "DOMAINROUTING last du was $du\n"); } # Forward the registration onto one of the servers in the cluster else if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (!strempty($avp(domain_dispatcher_reg_alg))) { # Set the registration algoritm $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); } else { # Set the dispatcher algorthim to round robin by default $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } route(DISPATCHER_SELECT); } } # Otherwise, send to the single PBX defined by PBX_IP else { xlog("L_INFO", "DOMAINROUTING Routing to Single Endpoint Gateway\n"); $du = "sip:" + $avp(domain_pbx_ip); # TODO: are these lookups valid here?? route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SETUP_CALLAUTH_INFO); route(SETUP_DIALOG); } xlog("L_INFO", "DOMAINROUTING should be routed to $rd:$rp\n"); route(RELAY); exit; } #Route to one PBX using an algorithm with External Authentication #(aka We are acting as a Registration and Location Server) else if (isflagset(FLT_DOMAINROUTING) && isflagset(FLT_EXTERNAL_AUTH) && !is_method("REGISTER")) { if (strempty($avp(domain_dispatcher_set_id))) { send_reply("404", "Destination Not Found"); exit; } else { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); xlog("L_INFO", "DOMAINROUTING Routing for $fd via dispatcher set $avp(dispatcher_setid)\n"); } # set the algorithm to load balancing if not set if (strempty($avp(domain_dispatcher_alg))) { $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } else { $avp(dispatcher_alg) = $avp(domain_dispatcher_alg); } #!ifdef WITH_MSTEAMS # check if destination is msteams if ($avp(domain_pbx_type) == "3") { setbflag(FLB_DST_MSTEAMS); $dlg_var(dst_msteams_domain) = $fd; # contact must match the domain of the TLS cert (i.e. the CN) remove_hf("Contact"); append_hf("Contact: <sip:$fd:SIPS_PORT;transport=tls>\r\n"); xlog("L_DBG", "Changed contact to $ct\n"); # prevent MSTeams from sending REFER requests if ((int)$sel(cfg_get.server.msteams_disable_refer)) { route(REMOVE_REFER); } route(DISPATCHER_SELECT); route(RELAY); exit; } #!endif # routing to PBX destination set route(ENRICH_SIPHEADER); route(DISPATCHER_SELECT); route(RELAY); exit; } #!ifdef WITH_DROUTE ###################################### # From Carrier # ###################################### # Check if this is coming from carrier if (isbflagset(FLB_SRC_CARRIER)) { xlog("L_INFO", "The call coming from $si is from a carrier\n"); # Enrich inbound calls from carriers route(ENRICH_CARRIER_INBOUND); # Route to PBX xlog("L_DBG", "DROUTING Logic for routing to PBX\n"); append_hf("P-hint: inbound\r\n"); $avp(calltype) = "inbound"; # don't overwrite fwding info if we already set it if (!isflagset(FLT_FAILOVER)) { route(SET_CALLFWD_INFO); } # don't overwrite dr_groupid if this is n'th time executing this route if ($avp(dr_groupid) == $null) { # first time executing, save the dialed DID for forwarding features $avp(dr_saved_rU) = $rU; # check for hard fwd, then set did and dr_groupid accordingly if ($avp(hardfwdinfo) != $null) { # allow DID to be unchanged if (!strempty($(avp(hardfwdinfo){s.select,0,,}))) { $rU = $(avp(hardfwdinfo){s.select,0,,}); } $avp(dr_groupid) = $(avp(hardfwdinfo){s.select,1,,}{s.int}); } # otherwise we are using inbound mapping rules else { $avp(dr_groupid) = FLT_INBOUND; } } # try routing based on rules in dr_rules table if (!do_routing($avp(dr_groupid))) { xlog("L_WARN", "RURI routing failed, trying to route on the To header\n"); $rU = $tU; # otherwise as a last ditch effort try to route based on To header if (!do_routing($avp(dr_groupid))) { # No rules defined for the phone number xlog("L_WARN", "No rules defined for $rU coming from this carrier endpoint: $si\n"); sl_reply("500", "No rules defined for number"); exit; } } # found a match, set du to match what drouting selected $du = $(ru{uri.duri}); route(MAINTMODE_CHECK); route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SETUP_CALLAUTH_INFO); route(SETUP_DIALOG); # handle clientside NAT for the rest of the dialog # TODO: the other NAT checks are all over the place, we should simplify and aggregate them if (nat_uac_test("64")) { add_contact_alias(); } # check if routing via dispatcher if ($avp(dispatcher_setid) != $null && $avp(lb_enabled)) { # dispatcher algo is always weighted here $avp(dispatcher_alg) = DSTALG_RELATIVE_WEIGHT; route(DISPATCHER_SELECT_LB); } #!ifdef WITH_MSTEAMS # Check if routing to MSTeams if ($avp(dr_attrs) != $null) { if ($(dlg_var(dst_msteams_domain){s.len}) > 0) { setbflag(FLB_DST_MSTEAMS); # TODO: review in v0.80, do we need to change any of these other headers? #if (!subst_hf("Remote-Party-ID", "/^(.*<sips?:[0-9]+@)(.*?)(:?[0-9]{1,5}?>.*)$/\1$dlg_var(dst_msteams_domain)\3/", "f")) { # xlog("L_ERR", "failed updating Remote-Party-ID\n"); #} # #$fd = $dlg_var(dst_msteams_domain); #$td = $dlg_var(dst_msteams_domain); # drouting does not have an easy way to update the transport on a matched address # not wasting the time to go through and integrate it into the dr_attrs column $du = "sip:" + $rd + ":" + $rp + ";transport=tls"; # contact must match the domain of the TLS cert (i.e. the CN) remove_hf("Contact"); append_hf("Contact: <sip:$dlg_var(dst_msteams_domain):SIPS_PORT;transport=tls>\r\n"); xlog("L_DBG", "Changed contact to $ct\n"); if (is_present_hf("ALLOW")) { #Prevent MSTeams from sending REFER requests if ((int)$sel(cfg_get.server.msteams_disable_refer)) { route(REMOVE_REFER); } } else { append_hf("ALLOW: INVITE,ACK,OPTIONS,CANCEL,BYE,NOTIFY\r\n"); } # set RTP to RTP/SAVP because all MSTeams audio needs to use RTP/SAVP $dlg_var(dst_media) = "rtp_savp"; } } #!endif # Set INVITE max lifetime to ensure Primary and Secondary PBX server feature works. t_set_fr((int)$sel(cfg_get.server.pbx_invite_timeout_aftertry), (int)$sel(cfg_get.server.pbx_invite_timeout)); #route(SET_CALLID_INBOUND_ENDPOINT_MAP); route(RELAY); exit; } ###################################### # To Carrier # ###################################### else if (isbflagset(FLB_SRC_PBX) || isflagset(FLT_PBX_AUTH) || isbflagset(FLB_SRC_MSTEAMS)) { xlog("L_INFO", "The call coming from $si will be routed to carrier groups via drouting\n"); append_hf("P-hint: outbound\r\n"); $avp(calltype) = "outbound"; #!ifdef WITH_LCR # LCR Routing # - route based on from prefix and to prefix # - match selection is similar to dRouting module from longest to shortest match # Logic Summary: # 1. iterate through htable matching entries starting with prefixes # 2. find diff between match and lookup (must be absolute value) # 3. if diff is less than previous overwrite match # 4. if a match is present attempt to set carrier group and relay # TODO: # we could store and iterate through all matches if we use dispatcher instead # this would allow failover in LCR Routing to shorter prefixes (if we wanted that) $var(lookup) = $(fU{s.unescape.user}) + "-" + $(tU{s.unescape.user}); $avp(lcr_match_group) = $null; $var(lcr_match_diff) = 1000; $var(diff) = 1000; sht_iterator_start("iter", "tofromprefix"); while(sht_iterator_next("iter")) { $var(regex) = $(shtitkey(iter){s.select,0,-}{re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*-" + $(shtitkey(iter){s.select,-1,-}{re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*"; if ($var(lookup) =~ $var(regex)) { xlog("L_DBG", "LCR match on $shtitkey(iter)\n"); $var(diff) = $(var(lookup){s.len}) - $(shtitkey(iter){s.len}); # bitwise absolute value: ( mask = n>>31; (mask^n) - mask ) # we are assuming 32 bit integers $var(mask) = $var(diff) >> 31; $var(diff) = ($var(mask) ^ $var(diff)) - $var(mask); if ($var(diff) < $var(lcr_match_diff)) { xlog("L_DBG", "LCR prefix closer match diff=$var(diff)\n"); $avp(lcr_match_group) = $shtitval(iter); $var(lcr_match_diff) = $var(diff); } } } sht_iterator_end("iter"); if ($avp(lcr_match_group) > 0) { $avp(carrier_groupid) = $avp(lcr_match_group); } else { $avp(carrier_groupid) = FLT_OUTBOUND; } #!else $avp(carrier_groupid) = FLT_OUTBOUND; #!endif if (do_routing($avp(carrier_groupid))) { # set du to match what drouting selected $du = $(ru{uri.duri}); route(SET_CALLDST_INFO); route(CHECK_CALL_LIMIT); route(SETUP_CALLAUTH_INFO); route(SETUP_DIALOG); # Checking if the carrier is using username/password auth if (!strempty($dlg_var(dst_auth_domain))) { $rd = $dlg_var(dst_auth_domain); subst_hf("Contact", "/(.*)?<(sips?):([0-9]+)@([^:]+)(:[0-9]{1,5})?(;.*)?>(.*)?/<\2:\3@UAC_REG_ADDR:$Rp\6>\7/", "f"); remove_hf("P-Asserted-Identity"); append_hf("P-Asserted-Identity: <sip:$avp(auth_user)@$rd>\r\n"); } else { subst_hf("Contact", "/(.*)?<(sips?):([0-9]+)@([^:]+)(:[0-9]{1,5})?(;.*)?>(.*)?/<\2:\3@EXTERNAL_IP_ADDR:$Rp\6>\7/", "f"); remove_hf("P-Asserted-Identity"); append_hf("P-Asserted-Identity: <sip:$fU@$rd>\r\n"); } # drouting only removes prefix from ru, typically we want it removed from the To as well uac_replace_from("\"$fU\"", "$rz:$fU@$rd:$rp"); uac_replace_to("\"$tU\"", "$rz:$tU@$rd:$rp"); msg_apply_changes(); if (!isbflagset(FLB_SRC_MSTEAMS)) { add_contact_alias(); } route(ENRICH_CARRIER_OUTBOUND); # check if routing via dispatcher if ($avp(dispatcher_setid) != $null && $avp(lb_enabled)) { # dispatcher algo is always weighted here $avp(dispatcher_alg) = DSTALG_RELATIVE_WEIGHT; route(DISPATCHER_SELECT_LB); } route(RELAY); exit; } # No rules defined for the phone number else { xlog("L_WARN", "No rules defined for $fu going to $tu\n"); sl_reply("500", "No rules defined for number"); } } else { sl_send_reply("407", "Proxy Authentication Required. Add the PBX or Carrier IP using GUI"); } #!endif } route[NEXTHOP_FAILOVER] { # check for failover fwd, then set did and dr_groupid accordingly if (($avp(failfwdinfo) != $null) && !isflagset(FLT_FAILOVER)) { # flag to make sure we don't loop endlessly setflag(FLT_FAILOVER); # reset DID if user did not explicitly ask to overwrite it if (strempty($(avp(failfwdinfo){s.select,0,,}))) { $rU = $avp(dr_saved_rU); } else { $rU = $(avp(failfwdinfo){s.select,0,,}); } $avp(dr_groupid) = $(avp(failfwdinfo){s.select,1,,}{s.int}); # go back through NEXTHOP to allow our standard routing checks to run for this dr_group route(NEXTHOP); # we successfully routed via NEXTHOP, return true if we did not exit return 1; } # return false if we did not route the call return -1; } # MaintMode Check - recursive function for checking if a number is in maintmode route[MAINTMODE_CHECK] { xlog("L_DBG", "The request domain $rd before maintmode check\n"); if ($sht(maintmode=>$rd) != $null) { xlog("L_DBG", "request $rd is in maintenance mode\n"); # The selected endpoint is in maintenance mode, try next endpoint # If there is only one endpoint then immediately return Service not Available # Otherwise, select the next gateway and see if it is in maintenance mode if (!use_next_gw()) { xlog("L_DBG", "request $rd has no other gateways available\n"); sl_send_reply("503", "Service not available"); exit; } # set du to match what drouting selected $du = $(ru{uri.duri}); route(MAINTMODE_CHECK); } } #!ifdef WITH_TRANSNEXUS import_file "transnexus.cfg" #!endif #!ifdef WITH_STIRSHAKEN import_file "stir-shaken.cfg" #!endif # TeleBlock routing route[TELEBLOCK] { if (!isbflagset(FLB_SRC_PBX)) { xlog("L_DBG", "source is not a pbx, skipping teleblock blacklist check\n"); return; } # TODO: This should be dynamic # Only route to teleblock if User-to-User header is present # if (!is_present_hf("User-to-User")) { # xlog("L_DBG", "User-to-User header not found\n"); # return; # } # save the requested route selections before overwriting for teleblock $avp(tb_saved_fu) = $fu; $avp(tb_saved_ru) = $ru; $avp(tb_saved_du) = $du; # Change source address to this proxy server $fu = "sip:" + $fU + "@" + $Ri + ":" + $Rp; # Send Invite to TeleBlock with header fields: # Number == To Username # CPN == From Username # BTN == Billing Number (optional) # Zipcode == US Postal Code (optional) # refkey == Record ID (optional) $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.gw_ip) + ":" + $sel(cfg_get.teleblock.gw_port); $du = $(ru{uri.duri}); xlog("L_DBG", "Forwarding to teleblock: fu=$fu ru=$ru du=$du\n"); # set failure route if (is_method("INVITE")) { t_on_failure("TELEBLOCK_FAILURE"); } } failure_route[TELEBLOCK_FAILURE] { if (t_is_canceled()) { exit; } xlog("L_DBG", "Processing reply for: $rU\n"); # Check if a media server is setup for teleblock if (strempty($sel(cfg_get.teleblock.media_ip)) || strempty($sel(cfg_get.teleblock.media_port))) { $avp(s:teleblock_media_enabled) = "0"; } else { $avp(s:teleblock_media_enabled) = "1"; } # interpret teleblock response if (t_check_status("499")) { $fu = $avp(tb_saved_fu); $ru = $avp(tb_saved_ru); $du = $avp(tb_saved_du); t_relay(); exit; } xlog("L_DBG", "Relaying to: $sel(cfg_get.teleblock.media_ip):$sel(cfg_get.teleblock.media_port)\n"); if (t_check_status("403|433")) { if ($avp(s:teleblock_media_enabled) == "1") { # make sure media server can route back to kamailio route(SET_RECORD_ROUTE); $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.media_ip) + ":" + $sel(cfg_get.teleblock.media_port); if (!t_relay()) { t_reply("403", "Do-Not-Contact"); } } else { if (!t_relay()) { t_reply("403", "Do-Not-Contact"); } } } else { if ($avp(s:teleblock_media_enabled) == "1") { # make sure media server can route back to kamailio route(SET_RECORD_ROUTE); $ru = "sip:" + $rU + "@" + $sel(cfg_get.teleblock.media_ip) + ":" + $sel(cfg_get.teleblock.media_port); if (!t_relay()) { t_reply("500", "Connection Failure"); } } } exit; } # Wrapper for relaying requests route[RELAY] { if (!has_totag()) { # update the signalling / media settings per the destination endpoint route(SET_DST_SIGNALLING); route(SET_DST_MEDIA); route(SET_CALLROUTE_INFO); } # From WebSocket else if (has_totag() && isflagset(FLT_SRC_WS)) { # update the signalling / media settings per the destination endpoint $dlg_var(dst_media) = "rtp_avp"; # If $rU is empty if ($rU == $null) { $rU = $tU; } route(SET_DST_SIGNALLING); route(SET_DST_MEDIA); } # Carrier to MSTeams REINVITE #else if (has_totag() && allow_address(FLT_MSTEAMS, "$(du{uri.host})", "$(du{uri.port})")) { else if (has_totag() && isbflagset(FLB_DST_MSTEAMS)) { xlog("L_INFO", "+++ Carrier to MSTeams ru=$ru si=$si, du=$du"); # update the signalling / media settings per the destination endpoint $dlg_var(dst_media) = "rtp_savp"; route(SET_DST_SIGNALLING); route(SET_DST_MEDIA); route(SET_CALLROUTE_INFO); # Set the $ru to the contact from the original INVITE #if (is_method("INVITE|ACK") && !allow_address(FLT_MSTEAMS, "$(du{uri.host})", "$(du{uri.port})")) { # $ru = $dlg_var(dst_cturi); #} #append_hf("Contact: <sip:$dlg_var(dst_msteams_domain):SIPS_PORT;transport=tls>\r\n"); } # MSTeams to Carrier REINVITE or REFER else if (has_totag() && isbflagset(FLB_SRC_MSTEAMS)) { # update the signalling / media settings per the destination endpoint $dlg_var(dst_media) = "rtp_avp"; route(SET_DST_SIGNALLING); route(SET_DST_MEDIA); xlog("L_INFO", "+++ MSTeams to Carrier ru=$ru si=$si du=$du"); if (is_method("INVITE|ACK") && is_myself($rd)) { $ru = $dlg_var(dst_cturi); } if (is_method("REFER")) { route(SET_RECORD_ROUTE); } } # check for serverside NAT route(SERVERNATDETECT); # only set Record-Route and destination info for dialog creating/updating requests if (is_method("INVITE|SUBSCRIBE|REFER") && !has_totag()) { route(SET_RECORD_ROUTE); } if (is_method("NOTIFY|BYE") && is_rfc1918("$rd")) { if (!lookup("location","sip:$tU@$td")) { xlog("L_INFO", "Notify Failed - NAT'd address for $tU@$td not found"); } } # handle STIR/SHAKEN #!ifdef WITH_TRANSNEXUS if ((int)$sel(cfg_get.transnexus.authservice_enabled)) { route(TRANSNEXUS_OUTBOUND); } #!endif #!ifdef WITH_STIRSHAKEN if ((int)$sel(cfg_get.stir_shaken.stir_shaken_enabled)) { route(STIRSHAKEN_OUTBOUND); } #!endif # Check TeleBlock Blacklist #!ifdef WITH_TELEBLOCK if ((int)$sel(cfg_get.teleblock.gw_enabled)) { route(TELEBLOCK); } #!endif # enable additional event routes for forwarded requests # - serial forking, RTP relaying handling, a.s.o. if (is_method("INVITE|BYE|SUBSCRIBE|UPDATE")) { if (!t_is_set("branch_route")) t_on_branch("MANAGE_BRANCH"); } if (is_method("INVITE|SUBSCRIBE|UPDATE")) { if (!t_is_set("onreply_route")) t_on_reply("MANAGE_REPLY"); } if (is_method("INVITE")) { if (!t_is_set("failure_route")) t_on_failure("MANAGE_FAILURE"); } # do accounting only for INVITE's if (is_method("INVITE")) { setflag(FLT_ACC); } xlog("L_INFO", "Attempting to route call. ru=$ru du=$du fu=$fu tu=$tu\n"); if (!t_relay()) { sl_reply_error(); route(RTPENGINEDELETE); } exit; } route[CHECK_CALL_LIMIT] { #!ifdef WITH_CALL_SETTINGS # Manage call limits for gwgroups xlog("L_DBG", "dst_gwtype: $dlg_var(dst_gwtype), dst_gwid: $dlg_var(dst_gwid), dst_gwgroupid: $dlg_var(dst_gwgroupid) src_gwtype: $dlg_var(src_gwtype), src_gwid: $avp(src_gwid), src_gwgroupid: $dlg_var(src_gwgroupid)\n"); # if src and dst gwgroup is the same there is no need to check both if ($dlg_var(src_gwgroupid) != $dlg_var(dst_gwgroupid)) { if (!strempty($xavu(src_call_settings=>limit))) { $var(num_calls) = $sht(concurrent_calls=>$dlg_var(src_gwgroupid)); if ($var(num_calls) >= $xavu(src_call_settings=>limit)) { sl_reply("480", "Call Limit Exceeded"); $avp(notification_type) = NOTIFICATION_OVERLIMIT; $avp(notification_gwid) = $dlg_var(src_gwid); $avp(notification_gwgroupid) = $dlg_var(src_gwgroupid); route(SEND_NOTIFICATION); exit; } } } if (!strempty($xavu(dst_call_settings=>limit))) { $var(num_calls) = $sht(concurrent_calls=>$dlg_var(dst_gwgroupid)); if ($var(num_calls) >= $xavu(dst_call_settings=>limit)) { sl_reply("480", "Call Limit Exceeded"); $avp(notification_type) = NOTIFICATION_OVERLIMIT; $avp(notification_gwid) = $dlg_var(dst_gwid); $avp(notification_gwgroupid) = $dlg_var(dst_gwgroupid); route(SEND_NOTIFICATION); exit; } } #!endif return; } # Per SIP request initial checks route[REQINIT] { # reusable flag denoting the source is an allowed address # checking if the flag is already set is a simple optimization for follow-on messages in the transaction if (!isflagset(FLT_SRC_ALLOWED)) { if (is_myself("$si")) { setbflag(FLB_SRC_SELF); setflag(FLT_SRC_ALLOWED); } else if (allow_source_address_group()) { setflag(FLT_SRC_ALLOWED); } } #!ifdef WITH_ANTIFLOOD # if not from self then do flood detection on the source IP if (!isbflagset(FLB_SRC_SELF)) { if ($sht(ipban=>$si) != $null) { # refreshing ip ban pike_check_req(); xlog("L_INFO", "pike blocking request with source address $si:$sp\n"); exit; } if (!pike_check_req()) { # new ip ban xlog("L_ALERT", "pike banning requests from source address $si:$sp\n"); $sht(ipban=>$si) = 1; exit; } } #!endif if (!mf_process_maxfwd_header("10")) { sl_send_reply("483", "Too Many Hops"); exit; } # Only reply to option messages if the endpoint or the carrier is defined if (is_method("OPTIONS") && isflagset(FLT_SRC_ALLOWED)) { sl_send_reply("200", "Keepalive"); exit; } if (!sanity_check("1511", "7")) { xlog("L_WARN", "Malformed SIP message from source address $si:$sp\n"); exit; } # request with no Username in RURI, default to the To username if ($rU == $null && is_method("INVITE")) { $rU = $tU; } # set a flag denoting the source address type # checking if one of the flags is already set is a simple optimization for follow-on messages in the branch if (!isbflagset(FLB_SRC_SELF) && !isbflagset(FLB_SRC_PBX) && !isbflagset(FLB_SRC_CARRIER) && !isbflagset(FLB_SRC_MSTEAMS)) { if (allow_source_address(FLT_PBX)) { setbflag(FLB_SRC_PBX); } else if (allow_source_address(FLT_CARRIER)) { setbflag(FLB_SRC_CARRIER); } else if (allow_source_address(FLT_MSTEAMS)) { setbflag(FLB_SRC_MSTEAMS); } else if (is_myself("$si")) { setbflag(FLB_SRC_SELF); } } # set a flag denoting the type of UAC if ($pr == "ws" || $pr == "wss") { setflag(FLT_SRC_WS); } else { setflag(FLT_SRC_SIP); } } # Handle requests within SIP dialogs route[WITHINDLG] { # whether we were in a dialog when starting to process this transaction if (!has_totag()) { resetflag(FLT_HAS_TOTAG); return; } else { setflag(FLT_HAS_TOTAG); } # Logic to handle BLF when using domain routing if ( is_method("SUBSCRIBE") ) { # Get destination signaling from dialog and replace the transport with that signalling # However, accessing dialog variables seems not to be available when the SIP message # is a subscribe # #Tested with Polycom VVX phones subst("/^Contact:(.*);transport=tcp(.*)/Contact:\1\2/i"); } route(NATMANAGE); route(MANAGE_ONHOLD); # Handling REINVITES from Carriers to MSTeams if (!isbflagset(FLB_SRC_MSTEAMS) && $rd =~ "pstnhub.microsoft.com") { setbflag(FLB_DST_MSTEAMS); } # Handling onhold, but could be used for more if (is_method("INVITE") && $hdr(User-Agent) =~ "Microsoft.PSTNHub" && $avp(sdp_media_direction) == "inactive") { setbflag(FLB_SRC_MSTEAMS_ONHOLD); } # sequential request withing a dialog should # take the path determined by record-routing if (loose_route_mode("1")) { # SBC rewrite takes priority #route(SBC_TRANSLATE_LR); route(DLGURI); if (is_method("BYE") && $rd =~ ".invalid") { if (lookup("location","sip:$tU@$td")) { xlog("L_DBG", "Looking up the domain and getting domain: $fd\n"); } } if ($(ru{uri.param,transport}) == "ws" || $(ru{uri.param,transport}) == "wss") { if (is_method("ACK|BYE|UPDATE|CANCEL")) { if (!strempty($(ru{uri.param,domain}))) { $var(domain) = $(ru{uri.param,domain}); $var(exten) = $(ru{uri.param,exten}); } else if (!strempty($(tu{uri.param,domain}))) { $var(domain) = $(tu{uri.param,domain}); $var(exten) = $(tu{uri.param,exten}); } else { # Use the To domain if domain request param is empty $var(domain) = $td; $var(exten) = $tU; } xlog("L_INFO", "Looking up the exten and domain and getting: $var(exten)@$var(domain)\n"); if (lookup("location","sip:$var(exten)@$var(domain)")) { xlog("L_DBG", "Looking up the domain and getting domain: $fd\n"); } } } # Fix BYE Messages coming from MSTeams to carriers if (isbflagset(FLB_SRC_MSTEAMS) && allow_address(FLT_CARRIER, "$(dlg_var(src_uri){uri.host})", "$(dlg_var(src_uri){uri.port})") && is_method("BYE")) { $ru = $dlg_var(dst_cturi); } # Fix BYE Messages coming from carriers and going to MSTEAMS Domain if (is_method("BYE") && isbflagset(FLB_SRC_CARRIER) && !strempty($dlg_var(src_msteams_domain))) { $ru = $dlg_var(dst_cturi); $td = "sip.pstnhub.microsoft.com"; $fd = $dlg_var(src_msteams_domain); remove_hf("Contact"); append_hf("Contact: <sip:$dlg_var(src_msteams_domain):SIPS_PORT;transport=tls>\r\n"); } if (is_method("BYE")) { # do accounting even if the transaction fails setflag(FLT_ACC); setflag(FLT_ACCFAILED); route(RTPENGINEDELETE); } route(RELAY); exit; } #!ifdef WITH_MSTEAMS # when handling double record route from msteams->dsip->pbx rewrite the destination based on the initial INVITE if (isbflagset(FLB_SRC_SELF) && allow_address(FLT_MSTEAMS, "$(dlg_var(dst_uri){uri.host})", "$(dlg_var(dst_uri){uri.port})")) { xlog("L_INFO", "handling msteams double record route, rewriting ru from $ru to $dlg_var(src_uri)\n"); $ru = $dlg_var(src_uri); } #!endif if (is_method("SUBSCRIBE") && uri == myself) { # in-dialog subscribe requests route(PRESENCE); exit; } if (is_method("ACK|UPDATE|INVITE|BYE|PRACK")) { # SBC rewrite takes priority #route(SBC_TRANSLATE_LR); route(DLGURI); # Set Accounting flags for strict-routing transactions if (is_method("BYE")) { setflag(FLT_ACC); setflag(FLT_ACCFAILED); route(RTPENGINEDELETE); } # if the message has a transaction try strict routing if (t_check_trans()) { route(RELAY); exit; } sl_send_reply("481", "Call/Transaction Does Not Exist"); exit; } sl_send_reply("604", "Does Not Exist Anywhere"); exit; } # Handle on hold route[MANAGE_ONHOLD] { if (!is_method("INVITE")) { return; } # handle sdp media direction for SBC's/proxies that require on reply # rtpengine by default will use a=sendrecv if valid sdp if (has_body("application/sdp")) { $avp(sdp_media_direction) = $null; if (search_body("^a=inactive.*")) { $avp(sdp_media_direction) = "inactive"; } else if (search_body("^a=recvonly.*")) { $avp(sdp_media_direction) = "recvonly"; } else if (search_body("^a=sendonly.*")) { $avp(sdp_media_direction) = "sendonly"; } } } # Handle SIP registrations route[REGISTRAR] { if (!is_method("REGISTER")) { return; } # Set the device type if a WS device # TODO: the type of UAC won't change in the middle of a transaction, marked for review/removal if (isflagset(FLT_SRC_WS)) { setbflag(FLB_WS_DEVICE); } # TODO: why are we setting clientside NAT here if serverside NAT is enabled? #!ifdef WITH_SIGNAL_SERVERNAT setbflag(FLB_NATB); #!endif #!ifdef WITH_SIGNAL_SERVERNAT6 setbflag(FLB_NATB); #!endif #!ifdef WITH_NAT # do SIP NAT pinging via OPTIONS messages setbflag(FLB_NATSIPPING); #!endif if (isflagset(FLT_PBX_AUTH)) { # Handle Register Event - We are now acting as a REGISTRAR. if (!save("location")) { sl_reply_error(); } # TODO: can we move these to in memory changes # Update dr_gateways and dr_gw_lists accordingly if ($sel(contact.expires) == "0") { xlog("L_DBG", "received an unregister request\n"); if (!strempty($dlg_var(src_gwid))) { xlog("L_DBG", "removing registration address $var(received_addr) from gateways for gwgroup $dlg_var(src_gwgroupid)\n"); sql_query("kam", "DELETE FROM dr_gateways WHERE gwid='$dlg_var(src_gwid)'"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(REGEXP_REPLACE(gwlist, '([,;])?$dlg_var(src_gwid)', ''), '^([,;])', '') WHERE id=$dlg_var(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } else { xlog("L_DBG", "received an register request\n"); if (strempty($dlg_var(src_gwid))) { xlog("L_DBG", "adding registration address $var(received_addr) to gateways for gwgroup $dlg_var(src_gwgroupid)\n"); sql_query("kam", "INSERT INTO dr_gateways(type,address,attrs,description) VALUES ($dlg_var(src_gwtype),'$var(received_addr)',',,,proxy,proxy','name:autoregister,type:$dlg_var(src_gwtype),gwgroup:$dlg_var(src_gwgroupid)');"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(CONCAT(gwlist,',',(SELECT MAX(gwid) FROM dr_gateways)), '^,', '') WHERE id=$dlg_var(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } exit; } if (isflagset(FLT_DOMAINROUTING) && !isflagset(FLT_EXTERNAL_AUTH)) { # Save the location, but DON'T send a 200 reply back. # Let the upstream PBX authenticate the UAC (aka endpoint) if (!save("location", "0x02")) { sl_reply_error(); } # Keep the origin request domain $var(rd_orig) = $rd; # Route to the endpoints defined in the Endpoint group (using dispatcher) if ($avp(domain_pbx_type) == "2") { #Forward the registration onto one of the servers in the cluster if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (!strempty($avp(domain_dispatcher_reg_alg))) { # Set the registration algoritm $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); } else { # Set the dispatcher algorthim to round robin by default $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; } if (!strempty($sht(pass_thru_auth=>$ci))) { $du = $sht(pass_thru_auth=>$ci); xlog("L_INFO", "DOMAINROUTING last du was $du\n"); } else { route(DISPATCHER_SELECT); xlog("L_INFO", "DOMAINROUTING Routing to Endpoint Gateway List $avp(dispatcher_setid)\n"); } } } else { # Grab the value of the avp that contains the domain_pbx_ip. # This is where requests for that domain should be routed $var(rd) = $(avp(domain_pbx_ip){s.select,0,:}); $var(portandtransport) = $(avp(domain_pbx_ip){s.select,1,:}); if (strempty($var(portandtransport))) { $var(rp) = "5060"; } else { $var(rp) = $(var(portandtransport){s.select,0,;}); $var(transport) = $(var(portandtransport){s.select,1,;}); } if (strempty($var(transport))) { $ru = $rz + ":" + $var(rd) + ":" + $var(rp); } else { $ru = $rz + ":" + $var(rd) + ":" + $var(rp) + ";" + $var(transport); } } # Rewrite Contact based on the domain being routed to $var(ct_domain) = $fd; route(REPLACE_CONTACT_DOMAIN); if (isbflagset(FLB_WS_DEVICE)) { add_path(); } else { #Add the Path header for SIP UAS know how to route back if (!strempty($var(transport))) { add_path($fU, $var(transport)); } else { add_path($fU); } } # Store the pbx ip to domain mapping so that SIP messages from the PBX can be rewritten if (!is_ip($(avp(domain_pbx_ip){s.select,0,:}))) { if (dns_query($(avp(domain_pbx_ip){s.select,0,:}), "xyz")) { $var(i) = 0; while ($var(i) < $dns(xyz=>count)) { $sht(pass_thru_auth=>$dns(xyz=>addr[$var(i)])) = $var(rd_orig); $var(i) = $var(i) + 1; } } } else { $sht(pass_thru_auth=>$(avp(domain_pbx_ip){s.select,0,:})) = $var(rd_orig); } #We are going to pass this request on to the backend server route(RELAY); exit; } else if (isflagset(FLT_DOMAINROUTING) && isflagset(FLT_EXTERNAL_AUTH)) { if (!save("location")) { sl_reply_error(); } exit; } #!ifdef WITH_PUSH if (($hdr(Expires) != "0") || !($hdr(Contact) =~ "expires=0") && ($sht(push=>join::$tU@td) != $null)) { xlog("L_INFO", "[REGISTER] [PUSH] about to un-suspend transaction rm=$rm ru=$ru tU=$tU td=$td \n"); route(JOIN); } #!endif # #Forward the registration onto one of the servers in the cluster # if (!strempty($avp(domain_dispatcher_set_id))) { # $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); # if (!strempty($avp(domain_dispatcher_reg_alg))) { # #Set the registration algoritm # $avp(dispatcher_alg) = $avp(domain_dispatcher_reg_alg); # } # else { # #Set the dispatcher algorthim to round robin by default # $avp(dispatcher_alg) = DSTALG_ROUND_ROBIN; # } # # route(DISPATCHER_SELECT); # route(RELAY); # } # exit; #} } # Dispatcher request load balancing (we don't route here) route[DISPATCHER_SELECT] { # round robin dispatching on dispatcher gateways set if (!ds_select_dst($avp(dispatcher_setid), $avp(dispatcher_alg))) { xlog("L_ERR", "no destination selected for domain: $fd\n"); send_reply("404", "Destination Not Found"); exit; } if (strempty($xavp(dispatcher_dst=>uri)) && $avp(dispatcher_alg) == DSTALG_PARALLEL_FORKING) { xlog("L_DBG", "sending to multiple servers in parallel\n"); } else if (!strempty($xavp(dispatcher_dst=>uri))) { xlog("L_DBG", "dispatcher selected $xavp(dispatcher_dst=>uri)\n"); } t_on_failure("DISPATCHER_NEXT"); return; } failure_route[DISPATCHER_NEXT] { # try next destionations in failure route if (t_is_canceled()) { route(RTPENGINEDELETE); exit; } if (t_check_status("401|407")) { xlog("L_INFO", "DOMAINROUTING 401 or 407 and the du was $du)\n"); $sht(pass_thru_auth=>$ci) = $du; return; } # next DST - only for 500 or local timeout if (t_check_status("4[0-9][2-6,8-9]|5[0-9][0-9]") or (t_branch_timeout() and !t_branch_replied())) { if (ds_next_dst()) { xlog("L_DBG", "dispatcher selected $xavp(dispatcher_dst=>uri)\n"); t_on_failure("DISPATCHER_NEXT"); route(RELAY); return; } else { # Drop the replies if this is a REGISTER if (is_method('REGISTER')) { t_drop_replies(); t_reply("401", "Unauthorized"); } } } } route[DISPATCHER_SELECT_LB] { # dispatch using the selected algorithm for this endpoint group if (!ds_select_dst($avp(dispatcher_setid), $avp(dispatcher_alg))) { xlog("L_WARN", "no destination selected for dispatcher set $avp(dispatcher_setid)\n"); # check failover forwarding, otherwise reply with an error if (!route(NEXTHOP_FAILOVER)) { send_reply("600", "No Destination Found"); exit; } # if for some reason the failover forwarding was successful but did not exit then we return to calling routine return; } # rewrite domain / port in request URIs $ru = $rz + ":" + $rU + "@" + $dd + ":" + $dp; $tu = $rz + ":" + $rU + "@" + $dd + ":" + $dp; # we have to flush the buffer in case other changes are/were made msg_apply_changes(); t_on_failure("DISPATCHER_NEXT_LB"); return; } failure_route[DISPATCHER_NEXT_LB] { # try next destinations in failure route if (t_is_canceled()) { route(RTPENGINEDELETE); exit; } # next DST if (t_check_status("4[0-9][1-6,7-9]|5[0-9][0-9]") || (t_branch_timeout() && !t_branch_replied())) { if (ds_next_dst()) { xlog("L_DBG", "trying next destination\n"); # rewrite domain / port in request URIs $ru = $rz + ":" + $rU + "@" + $dd + ":" + $dp; $tu = $rz + ":" + $rU + "@" + $dd + ":" + $dp; t_on_failure("DISPATCHER_NEXT_LB"); route(RELAY); } else { xlog("L_INFO", "all destinations unavailable for dispatcher set $avp(dispatcher_setid)\n"); # check failover forwarding, otherwise reply with an error if (!route(NEXTHOP_FAILOVER)) { send_reply("600", "No Destination Available"); route(RTPENGINEDELETE); exit; } # if for some reason the failover forwarding was successful but did not exit then we return to calling routine return; } } exit; } # User location service route[LOCATION] { # Return immediately if the source address is not a PBX. Only PBX's should be trying to route to endpoints if (!isbflagset(FLB_SRC_PBX)) { return; } # Emergency / N11 services should return immediately so that it can be routed to a carrier # ITU officially recognizes 911 (NA) and 112 (EU) as the international emergency numbers # However 999 (UK) and 000 (AU) are still commonly used # Emergency Numbers Overview: https://en.wikipedia.org/wiki/Emergency_telephone_number # N11 Ref: https://nationalnanpa.com/number_resource_info/n11_codes.html # On 2020-Jul-16 the FCC also adopted 988 as an N11 number # 988 Adoption Refs: https://www.fcc.gov/document/fcc-designates-988-national-suicide-prevention-lifeline if ($rU =~ $sel(cfg_get.server.emergency_numbers)) { return; } # Set the extension and request domain for WebSocket request because some # WebSocket clients don't send the extension and domain during registeration if (!strempty($(ru{uri.param,domain}))) { $rU = $(ru{uri.param,exten}); $rd = $(ru{uri.param,domain}); } # Return if the rU is more then local calling maximum digits for the initiating PBX if ($(rU{s.len}) > $sel(cfg_get.server.pbx_max_local_digits)) { return; } # If request is coming from a FreePBX or Asterisk server use the Pass-Thru htable if ($hdr(User-Agent) =~ "FPBX.*|Asterisk.*") { if ($sht(pass_thru_auth=>$si) != "") { $rd = $sht(pass_thru_auth=>$si); } } # Logic to to deal with a broken PATH implmentation in Asterisk PJSIP if (!strempty($(ru{uri.param,x-ast-orig-host}))) { $var(asterisk_domain) = $(ru{uri.param,x-ast-orig-host}); $var(asterisk_domain) = $(var(asterisk_domain){re.subst,/^(.*):(.*)/\1/}); if ($var(asterisk_domain) != "") { $rd = $var(asterisk_domain); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } xlog("L_INFO", "routing message for domain $var(asterisk_domain) to $rU@$rd\n"); } xlog("L_INFO", "routing message for domain $var(asterisk_domain) to $rU@$rd\n"); } $avp(oexten) = $rU; # Lookup the location of the endpoint by username@request_domain if (!lookup("location","sip:$rU@$rd")) { #!ifdef WITH_PUSH xlog("L_INFO", " In the route[LOCATION] [PUSH] logic."); send_reply("100", "Suspending"); route(SENDPUSH); route(SUSPEND); #!endif # Lookup the location of the endpoint by username@from_domain if (!lookup("location","sip:$rU@$fd")) { # Check if coming from a Zoiper Push Server # If so, the username for the extension is part of the Route header, grab it $var(Route) = @hf_value.route.uri; $var(user) = $(var(Route){uri.user}); xlog("L_DBG", "$var(Route) / sip:$var(user)@$fd\n"); if (!lookup("location", "sip:$var(user)@$fd")) { $var(rc) = $rc; route(TOVOICEMAIL); t_newtran(); switch ($var(rc)) { case -1: case -3: send_reply("404", "Not Found"); exit; case -2: send_reply("405", "Method Not Allowed"); exit; } } xlog("L_INFO", "ru: $ru, nh(u): $nh(u), WS:$var(WS_DEVICE)\n"); } } $var(transport) = $(ru{uri.param,transport}); xlog("L_INFO", "The transport is $var(transport)"); # Set the Signalling and Media if ($(ru{uri.param,transport}) != $null) { $var(transport) = $(ru{uri.param,transport}); xlog("L_INFO", "The transport is $var(transport)"); switch ($(var(transport){s.tolower})) { case "wss": $dlg_var(dst_signalling) = "sips_wss"; $dlg_var(dst_media) = "rtp_savp"; setflag(FLB_WS_DEVICE); break; case "ws": $dlg_var(dst_signalling) = "sip_ws"; $dlg_var(dst_media) = "rtp_savp"; setflag(FLB_WS_DEVICE); break; case "udp": $dlg_var(dst_media) = "rtp_avp"; $dlg_var(dst_signalling) = "udp"; break; default: $dlg_var(dst_media) = "proxy"; $dlg_var(dst_signalling) = "proxy"; break; } } # when routing via usrloc, log the missed calls also if (is_method("INVITE")) { setflag(FLT_ACCMISSED); } #Set the INVITE timeout for sending calls to invites t_set_fr(120000,10000); route(SET_CALLDST_INFO); route(RELAY); exit; } #!ifdef WITH_PUSH # Suspend Transaction route[SUSPEND] { xlog("L_INFO", "suspending transaction\n"); t_set_fr(30000); if (!t_suspend()) { xlog("L_ERR", "failed suspending trasaction [$T(id_index):$T(id_label)]\n"); send_reply("501", "Unknown destination"); exit; } xlog("L_INFO", "suspended transaction [$T(id_index):$T(id_label)] $fU => $rU@$rd\n"); $sht(push=>join::$rU@$rd) = "" + $T(id_index) + ":" + $T(id_label); xlog("L_INFO", "suspended htable key value [$sht(push=>join::$rU@$rd)]\n"); exit; } # Logic to invoke push route[SENDPUSH] { xlog("L_INFO", "sending the push notification\n"); #rabbitmq_publish("kamailio", "routing_key", "application/json", "$avp(json_request)"); #$var(luaret) = 0; #if(lua_runstring("do_push([[$hdr(X-VxTo)]], [[$tU]], [[$hdr(X-VxFrom)]], [[$fU]], [[$ci]])")<0){ # send_reply("501", "No link to destination"); # exit; #} return; } # Suspend route[JOIN] { xlog("L_INFO", " In the [PUSH] route[JOIN] logic."); $var(index)=(int) $(sht(push=>join::$tU@$td){s.select,0,:}); $var(label)=(int) $(sht(push=>join::$tU@$td){s.select,1,:}); xlog("L_INFO", "[JOIN] [PUSH] suspend $var(index) $var(label)"); t_set_fr(30000); t_continue("$var(index)", "$var(label)", "RESUME"); } # Resume route[RESUME] { xlog("L_INFO", "resuming transaction"); xlog("L_INFO", "values before lookup: rm=$rm ru=$rU rd=$rd du=$du \n"); if (!lookup("location","sip:$rU@$rd")) { switch ($retcode) { case 1: xlog("L_INFO", "values after lookup rm=$rm ru=$rU rd=$rd du=$du \n"); case -1: case -3: sl_send_reply("404", "Not Found"); exit; break; case -2: sl_send_reply("405", "Not Found"); exit; break; } } xlog("L_INFO","[RESUME] [PUSH] suspend rm=$rm ru=$rU rd=$rd du=$du \n"); record_route(); t_relay(); exit; } #!endif # Presence server processing route[PRESENCE] { if (!is_method("PUBLISH|SUBSCRIBE")) { return; } if (is_method("SUBSCRIBE") && $hdr(Event)=="message-summary") { route(TOVOICEMAIL); # returns here if no voicemail server is configured sl_send_reply("404", "No voicemail service"); exit; } #!ifdef WITH_PRESENCE if (isflagset(FLT_DOMAINROUTING) && !isflagset(FLT_EXTERNAL_AUTH)) { # Rewrite Contact based on the domain being routed to $var(ct_domain) = $fd; route(REPLACE_CONTACT_DOMAIN); } if (!t_newtran()) { sl_reply_error(); exit; } if (is_method("PUBLISH")) { handle_publish(); t_release(); } else if (is_method("SUBSCRIBE")) { handle_subscribe(); t_release(); } exit; #!endif # if presence enabled, this part will not be executed if (is_method("PUBLISH") || $rU == $null) { sl_send_reply("404", "Not here"); exit; } return; } # IP authorization and user authentication route[AUTH] { #!ifdef WITH_AUTH if (is_myself("$si")) { return; } # AUTH route logic summary: # 1) attempt domain auth # 2) Check if request is coming from a carrier that's using username/password auth (remote or local) # 3) attempt IP auth # 4) attempt username/password auth against local subscriber database #================= # Domain AUTH #================= # Check if this is any type of SIP request from a known domain only if the role of the server is not "inout". # The role of "inout" means that the role of this Kamailio instance is to just route calls inbound and outbound # using only IP auth or username/password auth if (lookup_domain("$fd", "domain_") && ($sel(cfg_get.server.role) != 'inout')) { # Turn on domain routing by setting the FLT_DOMAINROUTING flag setflag(FLT_DOMAINROUTING); # If the domain is mapped to single PBX then route to the PBX IP for authentication if ($avp(domain_domain_auth) == "passthru") { setflag(FLT_PASSTHRU_AUTH); xlog("L_INFO", "DOMAIN_AUTH $tU@$fd will be routed to $avp(domain_pbx_ip)\n"); return; } #Check if the domain is configured to route to a cluster of PBX's by checking if the dispatcher set_id is set #If so, we need to auth the user against an external database or local subscriber database #This will allow INVITE requests to be sent to any backend PBX's because we have validated the user #Hence, the backend PBX's should be setup only to trust SIP connections from dSIPRouter instances else if (!strempty($avp(domain_dispatcher_set_id))) { $avp(dispatcher_setid) = $avp(domain_dispatcher_set_id); if (is_method("REGISTER|INVITE") || from_uri==myself) { # Each domain has a auth type thats external to the backend destination server # 1 = Kamailo Subscriber table # 2 = Asterisk DB setflag(FLT_EXTERNAL_AUTH); xlog("L_INFO", "Generic Domain Routing for $tU@$fd - the defined auth type for $fd is $avp(domain_domain_auth)\n"); if ($avp(domain_domain_auth) == "realtime") { xlog("L_INFO", "DOMAIN_AUTH Asterisk Realtime auth is being used\n"); # Load data needed for custom SIP headers if ($avp(domain_enrich_headers) == 1) { $var(query) = "select sippasswd,sipdomain from sipusers where name=$fU"; } else { $var(query) = "select sippasswd from sipusers where name=$fU"; } #Let's auth against the database defined by the domain attributes sql_xquery("asterisk","$var(query)","ra"); $var(sippasswd) = $xavp(ra=>sippasswd); sql_result_free("ra"); xlog("L_DBG", "DOMAIN_AUTH The password for user $fU@$fd is $var(sippasswd)\n"); if (!pv_auth_check("$fd", "$xavp(ra=>sippasswd)", "2","0")) { auth_challenge("$fd", "0"); exit; } } else if ($avp(domain_domain_auth) == "local") { xlog("L_INFO", "DOMAIN_AUTH Local auth is being used\n"); if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } } # TODO: return error if domain_dispatcher_set_id is set and domain_domain_auth not realtime or local? } # user authenticated - remove auth header if (!is_method("REGISTER|PUBLISH")) { xlog("L_INFO", "DOMAIN_AUTH $tU@$fd was authenticated\n"); consume_credentials(); } return; } } #================= # Digest AUTH #================= if (is_subscriber("$fu", "subscriber", "3")) { # authenticate requests if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } # user authenticated - remove Authorization header consume_credentials(); # set flags denoting what auth/src we have setflag(FLT_PBX_AUTH); setbflag(FLB_SRC_PBX); return; } #================= # IP AUTH #================= #!ifdef WITH_IPAUTH # If domain not known, then check IP AUTH to see if the user if allowed to connect # Changed from allow_source_address to allow_source_addess_group because it will allow any addresses within any address group. # This means that both carriers and pbx's will be allowed to access the proxy with one function call # TODO: we already check source address in REQINIT, why are we checking again here? if (isflagset(FLT_SRC_ALLOWED)) { # source IP allowed return; } #!endif if (is_method("REGISTER|INVITE") || from_uri==myself) { # authenticate requests if (!auth_check("$fd", "subscriber", "3")) { auth_challenge("$fd", "0"); exit; } # user authenticated - remove auth header if (!is_method("REGISTER|PUBLISH")) { consume_credentials(); } # Set a flag denoting that a PBX has authenticated with username/password setflag(FLT_PBX_AUTH); } #!endif return; } route[SET_CALLSRC_INFO] { # source request info for manipulating what we send to UAC later in the transaction/dialog $dlg_var(src_oruri) = $ru; $dlg_var(src_ofuri) = $fu; $dlg_var(src_oturi) = $tu; $dlg_var(src_duri) = $sut; $var(received_addr) = $(su{re.subst,/^(sip:|sips:)?(.*)$/\2/}); if (isbflagset(FLB_SRC_MSTEAMS)) { $dlg_var(src_msteams_domain) = $td; } # Set call info for tracking call limits for username/pass auth if (isflagset(FLT_PBX_AUTH)) { if (sql_xquery("kam", "select rpid as gwgroupid from subscriber where username='$au'", "rows") == 1) { $dlg_var(src_gwtype) = (str)FLT_PBX; $dlg_var(src_gwgroupid) = $xavp(rows=>gwgroupid); if (sql_xquery("kam", "select gwid from dr_gateways where address='$var(received_addr)' AND description REGEXP 'gwgroup:$dlg_var(src_gwgroupid)(,|$$)'", "rows") == 1) { $dlg_var(src_gwid) = $xavp(rows=>gwid); } } } # TODO: these assumptions here are broken throughout the codebase, marked for review in v0.80 else if (isflagset(FLT_DOMAINROUTING)) { $dlg_var(src_gwtype) = $avp(domain_pbx_type); $dlg_var(src_gwgroupid) = $avp(domain_pbx_list); } # Set call info for tracking call limits for ip auth else { if (sql_xquery("kam","select type, gwid from dr_gateways where address like '$si%'", "rows") == 1) { $dlg_var(src_gwtype) = $xavp(rows=>type); $dlg_var(src_gwid) = $xavp(rows=>gwid); $dlg_var(src_gwgroupid) = $sht(gw2gwgroup=>$dlg_var(src_gwid)); } } #!ifdef WITH_CALL_SETTINGS $vn(call_settings) = $sht(call_settings=>$dlg_var(src_gwgroupid)); if ($vn(call_settings) != $null) { $xavu(src_call_settings=>limit) = $(vn(call_settings){s.select,0,,}); $xavu(src_call_settings=>timeout) = $(vn(call_settings){s.select,1,,}); } else { $xavu(src_call_settings=>limit) = ""; $xavu(src_call_settings=>timeout) = ""; } #!endif # set call forwarding info to null by default $avp(hardfwdinfo) = $null; $avp(failfwdinfo) = $null; return; } # once a destination is selected route here to get more detailed info about the gwgroup route[SET_CALLDST_INFO] { if ($avp(dr_attrs) != $null) { $var(dst_gwid) = $(avp(dr_attrs){s.select,0,,}); $var(dst_gwtype) = $(avp(dr_attrs){s.select,1,,}); $var(dst_msteams_domain) = $(avp(dr_attrs){s.select,2,,}); $var(dst_signalling) = $(avp(dr_attrs){s.select,3,,}); $var(dst_media) = $(avp(dr_attrs){s.select,4,,}); $var(dst_gwgroupid) = $sht(gw2gwgroup=>$var(dst_gwid)); # by convention the setid is the same as the gwgroupid if (!strempty($var(dst_gwgroupid))) { $avp(dispatcher_setid) = $var(dst_gwgroupid); } else { $avp(dispatcher_setid) = $null; } # special use case: Set the media to rtp_avp if coming from MSTeams and the dst media is set to proxy if (!strempty($var(src_msteams_domain)) && $var(dst_media) == "proxy") { $var(dst_media) = "rtp_avp"; } } else if (isflagset(FLT_PASSTHRU_AUTH)) { # passthru auth only maps to a single pbx $var(dst_gwid) = $(avp(domain_pbx_list){s.select,0,,}); sql_pvquery( "kam", "SELECT SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 2), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 3), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 4), ',', -1), SUBSTRING_INDEX(SUBSTRING_INDEX(attrs, ',', 5), ',', -1), REGEXP_REPLACE(description, '.*gwgroup:([0-9]+).*', '\\1') FROM dr_gateways WHERE gwid='$var(dst_gwid)'", "$var(dst_gwtype), $var(dst_msteams_domain), $var(dst_signalling), $var(dst_media), $var(dst_gwgroupid)" ); # Check if Source is WebSocket and $var(dst_media) = proxy. If so, set $var(dst_media) = rtp_avp as the default if (isflagset(FLT_SRC_WS) && $var(dst_media) == "proxy") { $var(dst_media) = "rtp_avp"; } } else { xlog("L_ERR", "can not lookup destination info\n"); return; } $vn(gwgroup2lb) = $sht(gwgroup2lb=>$var(dst_gwgroupid)); if ($vn(gwgroup2lb) != $null) { $avp(lb_enabled) = (int)$(vn(gwgroup2lb){s.select,1,,}); } else { $avp(lb_enabled) = 0; } #!ifdef WITH_CALL_SETTINGS $vn(call_settings) = $sht(call_settings=>$var(dst_gwgroupid)); if ($vn(call_settings) != $null) { $xavu(dst_call_settings=>limit) = $(vn(call_settings){s.select,0,,}); $xavu(dst_call_settings=>timeout) = $(vn(call_settings){s.select,1,,}); } else { $xavu(dst_call_settings=>limit) = ""; $xavu(dst_call_settings=>timeout) = ""; } #!endif # dialog lookups are computationally expensive, therefore we set them last after checks above $dlg_var(dst_gwid) = $var(dst_gwid); $dlg_var(dst_gwtype) = $var(dst_gwtype); $dlg_var(dst_msteams_domain) = $var(dst_msteams_domain); $dlg_var(dst_signalling) = $var(dst_signalling); $dlg_var(dst_media) = $var(dst_media); $dlg_var(dst_gwgroupid) = $var(dst_gwgroupid); } # store the selected du/ru and reformatted contact route[SET_CALLROUTE_INFO] { $dlg_var(dst_ruri) = $ru; $dlg_var(dst_duri) = $du; if (!has_totag() && is_method("INVITE")) { if ($ct =~ "<.*>") { $dlg_var(dst_cturi) = $(ct{re.subst,/^<(.*)>/\1/}); } else { $dlg_var(dst_cturi) = $ct; } } } # TODO: we should avoid recreating the drouting algo here and instead check hardfwd and failfwd dr_group by default # drouting would be called 3 times by default in this case, satisfying the following logic: # 1. check hardfwd dr_group for prefix matches # 2. check default dr_group for prefix matches (pbx or carrier) # 3. check failfwd dr_group for prefix matches # this would avoid prefix match prediction as we do below and support time criteria by default route[SET_CALLFWD_INFO] { # we need to know what prefix drouting will match before it runs # this only checks against inbound rules, this shouldn't be used for outbound # TODO: this algorithm ignores time criteria of the dr_rule # which could lead to false positives if using this setting in drouting $avp(dr_ruleid) = $null; # dr_ruleid matched $var(prefix_match_diff) = 1000; # last match prefix diff $var(diff) = 1000; # current entry prefix diff $var(prefix_match_priority) = 0; # last match priority $var(priority) = 0; # current entry priority $var(lookup) = $(rU{s.unescape.user}); # dnid to match against sht_iterator_start("iter", "prefix_to_route"); while(sht_iterator_next("iter")) { # TODO: for now we use a literal prefix but we should change to supporting patterns # this would require updating drouting module to support pattern matching $var(regex) = $(shtitkey(iter){re.subst,/(\+|\*|\#)/\\\1/g}) + "([0-9])*"; if ($var(lookup) =~ $var(regex)) { xlog("L_DBG", "prefix match on $shtitkey(iter)\n"); # if priority is less than last match we don't update match $var(priority) = $(shtitval(iter){s.select,1,,}{s.int}); if ($var(priority) >= $var(prefix_match_priority)) { # get the difference between prefix and match $var(diff) = $(var(lookup){s.len}) - $(shtitkey(iter){s.len}); # bitwise absolute value: ( mask = n>>31; (mask^n) - mask ) # we are assuming 32 bit integers $var(mask) = $var(diff) >> 31; $var(diff) = ($var(mask) ^ $var(diff)) - $var(mask); # if priority is greater than last match or priority is equal and # diff is less than last match (closer match) we update match if ($var(priority) > $var(prefix_match_priority) || $var(diff) < $var(prefix_match_diff)) { xlog("L_DBG", "prefix closer match priority=$var(priority) diff=$var(diff)\n"); $avp(dr_ruleid) = $(shtitval(iter){s.select,0,,}); $var(prefix_match_priority) = $var(priority); $var(prefix_match_diff) = $var(diff); } } } } sht_iterator_end("iter"); if ($avp(dr_ruleid) != $null) { $avp(hardfwdinfo) = $sht(inbound_hardfwd=>$avp(dr_ruleid)); $avp(failfwdinfo) = $sht(inbound_failfwd=>$avp(dr_ruleid)); } return; } route[CHECK_DLG_ALLOWED] { # only INVITE/SUBSCRIBE/REFER are allowed to create dialogs # ref: RFC 3261 / RFC 3515 if (!is_method("INVITE|SUBSCRIBE|REFER")) { return; } setflag(FLT_DLG_ALLOWED); } # Sets the following variables: # $avp(auth_user) # $avp(auth_pass) # $avp(auth_realm) # $dlg_var(src_auth_domain) # $dlg_var(src_auth_user) # $dlg_var(src_auth_pass) # $dlg_var(src_auth_realm) # $dlg_var(dst_auth_domain) # $dlg_var(dst_auth_user) # $dlg_var(dst_auth_pass) # $dlg_var(dst_auth_realm) route[SETUP_CALLAUTH_INFO] { $vn(saved_ru) = $ru; $vn(saved_du) = $du; if (uac_reg_request_to($dlg_var(src_gwgroupid), 0)) { $dlg_var(src_auth_domain) = $rd; $dlg_var(src_auth_user) = $avp(auth_user); $dlg_var(src_auth_pass) = $avp(auth_pass); $dlg_var(src_auth_realm) = $avp(auth_realm); $avp(auth_user) = $null; $avp(auth_pass) = $null; $avp(auth_realm) = $null; } if (uac_reg_request_to($dlg_var(dst_gwgroupid), 0)) { $dlg_var(dst_auth_domain) = $rd; $dlg_var(dst_auth_user) = $avp(auth_user); $dlg_var(dst_auth_pass) = $avp(auth_pass); $dlg_var(dst_auth_realm) = $avp(auth_realm); } $ru = $vn(saved_ru); $du = $vn(saved_du); } # Sets the following variables: # $dlg_ctx(timeout) # $dlg_ctx(timeout_route) route[SETUP_DIALOG] { if (!isflagset(FLT_DLG_ALLOWED)) { return; } #!ifdef WITH_CALL_SETTINGS # set the dialog timeout to the smallest timeout between the src and dst gwgroups if (!strempty($xavu(dst_call_settings=>timeout))) { $vn(call_timeout) = (int)$xavu(dst_call_settings=>timeout); if (!strempty($xavu(src_call_settings=>timeout)) && $vn(call_timeout) > $xavu(src_call_settings=>timeout)) { $vn(call_timeout) = (int)$xavu(src_call_settings=>timeout); } } else if (!strempty($xavu(src_call_settings=>timeout))) { $vn(call_timeout) = (int)$xavu(src_call_settings=>timeout); } if ($vn(call_timeout) != $null) { $dlg_ctx(timeout) = $vn(call_timeout); $dlg_ctx(timeout_route) = "DIALOG_TIMEOUT"; } #!endif # Create dialog if one doesn't already exists if (!is_known_dlg()) { dlg_manage(); } } # TODO: ipv6 support route[SET_RECORD_ROUTE] { # remove any previous headers set remove_record_route(); # TODO: someone give good explanation of the NLB record routing here if (("OUTBOUND_NLB_FQDN" != "") && ("INBOUND_NLB_FQDN" != "")) { record_route_preset("OUTBOUND_NLB_FQDN","INBOUND_NLB_FQDN"); $dlg_var(sbc_translate) = "0"; } else if (("OUTBOUND_NLB_FQDN" == "") && ("INBOUND_NLB_FQDN" != "")) { record_route_advertised_address("INBOUND_NLB_FQDN"); $dlg_var(sbc_translate) = "0"; } #!ifdef WITH_MSTEAMS # TODO: needs check everytime this is called, can't store/check flag # MS Teams special use case - add hop from SIPS port to SIP port else if (isbflagset(FLB_DST_MSTEAMS)) { # TODO: if end-to-end encryption is used this may not work # TODO: we should be dynamically checking the transport for each side of the record route # in this case we are missing TLS <-> TCP connections and TLS <-> TLS connections # we should instead generalize the handling of different ports/protocols and whether r2=on should be added # TODO: not adding in check for IPv4 vs IPv6 vs Domain here since we are sending to ourself - is this assumption correct? record_route_preset("$dlg_var(dst_msteams_domain):SIPS_PORT;transport=tls;r2=on", "INTERNAL_IP_ADDR:SIP_PORT;transport=udp;r2=on"); force_send_socket(tls:INTERNAL_IP_ADDR:SIPS_PORT); $dlg_var(sbc_translate) = "0"; } # If coming from MSTEAMS, change Record Route to match the domain it's coming from else if (isbflagset(FLB_SRC_MSTEAMS)) { record_route_preset("$dlg_var(src_msteams_domain):SIP_PORT;transport=udp;r2=on", "$dlg_var(src_msteams_domain):SIPS_PORT;transport=tls;r2=on"); $dlg_var(sbc_translate) = 1; } #!endif # the source of the request will be following the Route / Record-Route headers # so we only need send them back to our internal ip if from within our subnet else if (isflagset(FLT_SRC_INTERNAL_IP)) { if (isflagset(FLT_DST_INTERNAL_IP)) { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[INTERNAL_IP6_ADDR]:$Rp"); } else { record_route_preset("INTERNAL_IP_ADDR:$Rp"); } } else { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[EXTERNAL_IP6_ADDR]:$Rp;r2=on", "[INTERNAL_IP6_ADDR]:$Rp;r2=on"); } else { record_route_preset("EXTERNAL_IP_ADDR:$Rp;r2=on", "INTERNAL_IP_ADDR:$Rp;r2=on"); } } $dlg_var(sbc_translate) = "0"; } else if (isflagset(FLT_DST_INTERNAL_IP)) { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[INTERNAL_IP6_ADDR]:$Rp;r2=on", "[EXTERNAL_IP6_ADDR]:$Rp;r2=on"); } else { record_route_preset("INTERNAL_IP_ADDR:$Rp;r2=on", "EXTERNAL_IP_ADDR:$Rp;r2=on"); } $dlg_var(sbc_translate) = "0"; } else { if (isflagset(FLT_SRC_IPV6)) { record_route_preset("[EXTERNAL_IP6_ADDR]:$Rp"); } else { record_route_preset("EXTERNAL_IP_ADDR:$Rp"); } $dlg_var(sbc_translate) = "0"; } # store the source and destination for later usage within the dialog if ($du == $null) { $dlg_var(dst_uri) = $(ru{uri.duri}); } else { $dlg_var(dst_uri) = $du; } $dlg_var(src_uri) = $sut; return; } # TODO: on configuration failure continue with next endpoint instead of routing to this endpoint route[SET_DST_SIGNALLING] { switch ($dlg_var(dst_signalling)) { case "proxy": # TODO: in future releases "proxy" will mean record route through kamailio # and another option for bypassing kamailio will be available if ($du == $null) { $du = $(ru{uri.duri}); } break; case "sip_udp": uri_param_rm("transport"); if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=udp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=udp"; } break; case "sip_tcp": uri_param_rm("transport"); if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=tcp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=tcp"; } break; case "sip_sctp": uri_param_rm("transport"); if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=sctp"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=sctp"; } break; case "sip_ws": uri_param_rm("transport"); if ($du == $null) { $du = "sip:" + $sel(ruri.hostport) + ";transport=ws"; } else { $du = "sip:" + $sel(dst_uri.hostport) + ";transport=ws"; } break; case "sips_tls": uri_param_rm("transport"); if ($du == $null) { $du = "sips:" + $sel(ruri.hostport) + ";transport=tls"; } else { $du = "sips:" + $sel(dst_uri.hostport) + ";transport=tls"; } break; case "sips_sctp": uri_param_rm("transport"); if ($du == $null) { $du = "sips:" + $sel(ruri.hostport) + ";transport=sctp"; } else { $du = "sips:" + $sel(dst_uri.hostport) + ";transport=sctp"; } break; case "sips_wss": uri_param_rm("transport"); if ($du == $null) { $du = "sips:" + $sel(ruri.hostport) + ";transport=ws"; } else { $du = "sips:" + $sel(dst_uri.hostport) + ";transport=ws"; } break; default: xlog("L_WARN", "invalid signalling configuration requested for endpoint $dlg_var(dst_gwid)\n"); break; } return; } # TODO: on configuration failure continue with next endpoint instead of routing to this endpoint route[SET_DST_MEDIA] { if (!has_body("application/sdp")) { return; } # TODO: revisit $dlg_var(FLD_USE_RTPE) usage here switch ($dlg_var(dst_media)) { case "proxy": dlg_setflag(FLD_USE_RTPE); $dlg_var(dst_media_tp) = ""; # Setting a dialog variable since dlg_setflag doesn't see to work on Manage Branches $dlg_var(FLD_USE_RTPE) = "1"; sdp_transport("$var(src_media_tp)"); break; case "direct": dlg_resetflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "0"; $dlg_var(dst_media_tp) = ""; $var(src_media_tp) = ""; break; case "rtp_avp": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "RTP/AVP"; sdp_transport("$var(src_media_tp)"); break; case "rtp_savp": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "RTP/SAVP"; sdp_transport("$var(src_media_tp)"); break; case "rtp_avpf": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "RTP/AVPF"; sdp_transport("$var(src_media_tp)"); break; case "rtp_savpf": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "RTP/SAVPF"; sdp_transport("$var(src_media_tp)"); break; case "rtp_avp_any": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "UDP/TLS/RTP/SAVP"; sdp_transport("$var(src_media_tp)"); break; case "rtp_avpf_any": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "UDP/TLS/RTP/SAVPF"; sdp_transport("$var(src_media_tp)"); break; case "udptl": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "T.38=force"; sdp_transport("$var(src_media_tp)"); break; case "osrtp_avp": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "transport-protocol=RTP/AVP OSRTP=offer"; sdp_transport("$var(src_media_tp)"); break; case "osrtp_avpf": dlg_setflag(FLD_USE_RTPE); $dlg_var(FLD_USE_RTPE) = "1"; $dlg_var(dst_media_tp) = "transport-protocol=RTP/AVPF OSRTP=offer"; sdp_transport("$var(src_media_tp)"); break; default: xlog("L_WARN", "invalid media configuration requested for endpoint $dlg_var(dst_gwid). Defaulting to RTPEngine enabled.\n"); dlg_setflag(FLD_USE_RTPE); $dlg_var(dst_media_tp) = ""; $var(src_media_tp) = ""; break; } $dlg_var(src_media_tp) = $var(src_media_tp); return; } # Caller NAT detection route[NATDETECT] { #!ifdef WITH_NAT if (nat_uac_test("19")) { setbflag(FLB_NATB); } #!endif return; } # Server / DMZ NAT detection # Determine NAT requirements for destination and source and set flags for later usage # TODO: source self is known after REQINIT, consider copying over transaction flags such as FLT_SRC_SELF (multiple transactions in flow) route[SERVERNATDETECT] { # default to NULL $vn(dst_ipv4) = $null; $vn(dst_ipv6) = $null; # always reset flags when called resetflag(FLT_SRC_INTERNAL_IP); resetflag(FLT_DST_INTERNAL_IP); resetflag(FLT_SRC_IPV4); resetflag(FLT_DST_IPV4); resetflag(FLT_SRC_IPV6); resetflag(FLT_DST_IPV6); if ($dd == $null) { $var(dst) = $rd; } else { $var(dst) = $dd; } if (is_ipv4($var(dst))) { $vn(dst_ipv4) = $var(dst); } else if (is_ipv6($var(dst))) { $vn(dst_ipv6) = $var(dst); } else { if (dns_query($var(dst), "dst")) { $var(i) = 0; while ($var(i) < $dns(dst=>count)) { if ($vn(dst_ipv4) == $null && $dns(dst=>type[$var(i)]) == 4) { $vn(dst_ipv4) = $dns(dst=>addr[$var(i)]); } else if ($vn(dst_ipv6) == $null && $dns(dst=>type[$var(i)]) == 6) { $vn(dst_ipv6) = $dns(dst=>addr[$var(i)]); } $var(i) = $var(i) + 1; } } else { xlog("L_ERR", "dns query failed for $var(dst)\n"); } } #!ifdef WITH_SIGNAL_SERVERNAT # source does not change throughout if (is_in_subnet($si, "INTERNAL_IP_NET") || is_myself("$si")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV4); } if (is_in_subnet($vn(dst_ipv4), "INTERNAL_IP_NET") || is_myself("$var(dst)")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV4); } #!endif #!ifdef WITH_SIGNAL_SERVERNAT6 # source does not change throughout if (!isflagset(FLT_SRC_INTERNAL_IP) && is_in_subnet($si, "INTERNAL_IP6_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV6); } if (!isflagset(FLT_DST_INTERNAL_IP) && is_in_subnet($vn(dst_ipv6), "INTERNAL_IP6_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV6); } #!endif #!ifdef WITH_DMZ # source does not change throughout if (!isflagset(FLT_SRC_INTERNAL_IP)) { if (is_in_subnet($si, "INTERNAL_IP_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV4); } #!ifdef WITH_IPV6 else if (is_in_subnet($si, "INTERNAL_IP6_NET")) { setflag(FLT_SRC_INTERNAL_IP); setflag(FLT_SRC_IPV6); } #!endif } if (!isflagset(FLT_DST_INTERNAL_IP)) { if (is_in_subnet($vn(dst_ipv4), "INTERNAL_IP_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV4); } #!ifdef WITH_IPV6 else if (is_in_subnet($vn(dst_ipv4), "INTERNAL_IP6_NET")) { setflag(FLT_DST_INTERNAL_IP); setflag(FLT_DST_IPV6); } #!endif } #!endif return; } # RTPProxy control and signaling updates for NAT traversal route[NATMANAGE] { #!ifdef WITH_NAT if (is_request() && isflagset(FLT_HAS_TOTAG) && check_route_param("nat=yes")) { setbflag(FLB_NATB); } if (!isbflagset(FLB_NATB)) { return; } # handle clientside NAT traversal (requests) if (is_request()) { # initial requests we can change some headers for the rest of the transaction if (!isflagset(FLT_HAS_TOTAG)) { # Contact and Via updates if (isflagset(FLT_SRC_SIP) && !isbflagset(FLB_SRC_MSTEAMS)) { fix_nated_contact(); msg_apply_changes(); force_rport(); } # registration Contact update if (is_method("REGISTER")) { fix_nated_register(); } # Route updates if (t_is_branch_route()) { add_rr_param(";nat=yes"); } } # for dialog NAT traversal # only forward requests if there is an existing connection to the destination else { set_forward_no_connect(); } } # handle clientside NAT traversal (replies) else { # only set contact alias on replies if B-Leg of call is NATed as well if (is_first_hop() && nat_uac_test("1")) { set_contact_alias(); } # Do NAT traversal stuff for replies to a WebSocket connection # - even if it is not behind a NAT! # This won't be needed in the future if Kamailio and the # WebSocket client support Outbound and Path. #!ifdef WITH_WEBSOCKETS if (isflagset(FLT_SRC_WS) && nat_uac_test("64")) { add_contact_alias(); } #!endif } #!endif return; } # should only be called within request routes route[RTPENGINEOFFER] { #!ifdef WITH_RTPENGINE if (!dlg_isflagset(FLD_USE_RTPE)) { xlog("L_INFO", "RTPEngine is disabled\n"); return; } xlog("L_INFO", "RTPEngine is enabled\n"); # - Web to web if (isflagset(FLT_SRC_WS) && isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection SDES-off ICE=force " + $dlg_var(dst_media_tp); } # - Web to SIP else if (isflagset(FLT_SRC_WS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $dlg_var(dst_media_tp); } # - SIP to web else if (isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=force transcode-PCMU transcode-G722 SDES-off " + $dlg_var(dst_media_tp); } # - MSTEAMS to SIP using RTP/AVP else if (isbflagset(FLB_SRC_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer port-latching ICE=remove " + $dlg_var(dst_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=remove RTP/AVP original-sendrecv"; } # - MSTEAMS to SIP ONHOLD using RTP/AVP else if (isbflagset(FLB_SRC_MSTEAMS_ONHOLD)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-accept ICE=remove " + $dlg_var(dst_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-accept ICE=remove RTP/AVP original-sendrecv"; } # - SIP to MSTEAMS else if (isbflagset(FLB_DST_MSTEAMS)) { if (has_totag()) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-accept ICE=force transcode-PCMU transcode-G722 " + $dlg_var(dst_media_tp); } else { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=force transcode-PCMU transcode-G722 " + $dlg_var(dst_media_tp); } } # - SIP to SIP else { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $dlg_var(dst_media_tp); } #!ifdef WITH_MEDIA_SERVERNAT # for serverside NAT we may need to use one of the internal IPs as the media address if (isflagset(FLT_DST_INTERNAL_IP)) { if (isflagset(FLT_DST_IPV6)) { $var(reflags) = $var(reflags) + " media-address=INTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP_ADDR"; } } else { if (isflagset(FLT_DST_IPV6)) { $var(reflags) = $var(reflags) + " media-address=EXTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP_ADDR"; } } #!ifdef WITH_DMZ if (isflagset(FLT_SRC_INTERNAL_IP) && !isflagset(FLT_DST_INTERNAL_IP)) { $var(reflags)= $var(reflags) + " direction=private direction=public"; } else if (!isflagset(FLT_SRC_INTERNAL_IP) && isflagset(FLT_DST_INTERNAL_IP)) { $var(reflags)= $var(reflags) + " direction=public direction=private"; } #!else #!ifdef WITH_IPV6 # select interface within rtpengine based on IP versions (by default will use the IPv4 interface) # only needed when not using the default interface (1st listen interface for rtpengine) if (isflagset(FLT_SRC_IPV4) && isflagset(FLT_DST_IPV6)) { $var(reflags)= $var(reflags) + " direction=ipv4 direction=ipv6"; } else if (isflagset(FLT_SRC_IPV6) && isflagset(FLT_DST_IPV4)) { $var(reflags)= $var(reflags) + " direction=ipv6 direction=ipv4"; } else if (isflagset(FLT_SRC_IPV6) && isflagset(FLT_DST_IPV6)) { $var(reflags)= $var(reflags) + " direction=ipv6 direction=ipv6"; } else { $var(reflags)= $var(reflags) + " direction=ipv4 direction=ipv4"; } #!endif #!endif #!endif xlog("L_INFO", "reflags: $var(reflags)\n"); if (!rtpengine_offer("$var(reflags)")) { send_reply("503", "Service not available"); exit; } #!endif return; } # URI update for dialog requests route[DLGURI] { #!ifdef WITH_NAT if(!isdsturiset()) { handle_ruri_alias(); } #!endif if (check_route_param("rwdst")) { if (is_direction("downstream")) { $du = $dlg_var(dst_uri); } else { $du = $dlg_var(src_uri); } } } route[RTPENGINEANSWER] { #!ifdef WITH_RTPENGINE if ($dlg_var(FLD_USE_RTPE) == "0") { xlog("L_INFO", "RTPEngine is disabled\n"); return; } xlog("L_INFO", "RTPEngine is enabled\n"); # - Web to web if (isflagset(FLT_SRC_WS) && isbflagset(FLB_WS_DEVICE)) { $var(reflags) = "trust-address replace-origin replace-session-connection SDES-off ICE=force " + $dlg_var(src_media_tp); } # - Web to SIP else if (isflagset(FLT_SRC_WS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force " + $dlg_var(src_media_tp); } # - MSTEAMS to SIP using RTP/AVP else if (isbflagset(FLB_DST_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-offer ICE=remove " + $dlg_var(src_media_tp); } # - SIP to MSTEAMS else if (isbflagset(FLB_SRC_MSTEAMS)) { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force transcode-PCMU transcode-G722 SDES-off " + $dlg_var(src_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force codec-transcode=PCMU codec-transcode=PCMA codec-transcode=G722 codec-transcode=G729 SDES-off RTP/SAVP original-sendrecv"; } # - SIP to MSTEAMS ONHOLD else if (isbflagset(FLB_SRC_MSTEAMS_ONHOLD)) { xlog("L_DBG", "ONHOLD - ANSWER\n"); $var(reflags) = "trust-address replace-origin replace-session-connection ICE=remove transcode-PCMU transcode-G722 SDES-off " + $dlg_var(src_media_tp); # $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-require ICE=force codec-transcode=PCMU codec-transcode=PCMA codec-transcode=G722 codec-transcode=G729 SDES-off RTP/SAVP original-sendrecv"; } # - SIP to SIP else { $var(reflags) = "trust-address replace-origin replace-session-connection rtcp-mux-demux ICE=remove " + $dlg_var(src_media_tp); } #!ifdef WITH_MEDIA_SERVERNAT # NOTE: no need to set direction= here, direction will be determined from the offer # for serverside NAT we may need to use one of the internal IPs as the media address if (isflagset(FLT_SRC_INTERNAL_IP)) { if (isflagset(FLT_SRC_IPV6)) { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=INTERNAL_IP_ADDR"; } } else { if (isflagset(FLT_SRC_IPV6)) { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP6_ADDR"; } else { $var(reflags)= $var(reflags) + " media-address=EXTERNAL_IP_ADDR"; } } #!endif xlog("L_INFO", "reflags: $var(reflags)\n"); if (!rtpengine_answer("$var(reflags)")) { send_reply("503", "Service not available"); route(RTPENGINEDELETE); exit; } #!endif return; } route[RTPENGINEDELETE] { #!ifdef WITH_RTPENGINE if (!dlg_isflagset(FLD_USE_RTPE)) { xlog("L_INFO", "RTPEngine is disabled\n"); return; } xlog("L_INFO", "RTPEngine is enabled\n"); rtpengine_delete(); #!endif return; } # XMLRPC routing #!ifdef WITH_XMLRPC route[XMLRPC] { # allow XMLRPC from localhost if ((method=="POST" || method=="GET") && (src_ip==127.0.0.1)) { # close connection only for xmlrpclib user agents (there is a bug in # xmlrpclib: it waits for EOF before interpreting the response). if ($hdr(User-Agent) =~ "xmlrpclib") set_reply_close(); set_reply_no_connect(); dispatch_rpc(); exit; } send_reply("403", "Forbidden"); exit; } #!endif # Routing to voicemail server route[TOVOICEMAIL] { #!ifdef WITH_VOICEMAIL if (!is_method("INVITE|SUBSCRIBE")) { return; } # check if VoiceMail server IP is defined if (strempty($sel(cfg_get.voicemail.srv_ip))) { xlog("L_ERR", "VoiceMail routing enabled but IP not defined\n"); return; } if (is_method("INVITE")) { if ($avp(oexten) == $null) { return; } $ru = "sip:" + $avp(oexten) + "@" + $sel(cfg_get.voicemail.srv_ip) + ":" + $sel(cfg_get.voicemail.srv_port); } else { if ($rU == $null) { return; } $ru = "sip:" + $rU + "@" + $sel(cfg_get.voicemail.srv_ip) + ":" + $sel(cfg_get.voicemail.srv_port); } route(RELAY); exit; #!endif return; } # Populate CDRs Table #!ifdef WITH_CDRS route[CDRS] { sql_query("kam","call kamailio_cdrs()","rb"); # we are not using billing features #sql_query("kam","call kamailio_rating('default')","rb"); } #!endif # send async http request for notifications # required: $avp(notification_type) # required: $avp(notification_gwgroupid) # optional: $avp(notification_gwid) route[SEND_NOTIFICATION] { if ($avp(notification_type) != $null && $avp(notification_gwgroupid) != $null) { $http_req(method) = "POST"; $http_req(hdr) = "User-Agent: http_async_client"; $http_req(hdr) = "Authorization: Bearer " + $sel(cfg_get.server.api_token); $http_req(body) = '{"gwgroupid":' + $avp(notification_gwgroupid) + ', "type":' + $avp(notification_type) + ', "gwid":' + $avp(notification_gwid) + ', "text_body":"Gateway Group [' + $avp(notification_gwgroupid) + '] triggered the following notification for Gateway [' + $avp(notification_gwid) + ']"}'; $http_req(suspend) = 0; xlog("L_INFO", "Sending request to $sel(cfg_get.server.api_server)/api/v1/notification/gwgroup for type $avp(notification_type)\n"); http_async_query("$sel(cfg_get.server.api_server)/api/v1/notification/gwgroup", "HTTP_REPLY"); } else { xlog("L_ERR", "avp 'notification_type' and 'notification_gwgroupid' are required for notification sending\n"); } $avp(notification_type) = $null; $avp(notification_gwgroupid) = $null; $avp(notification_gwid) = $null; } route[HTTP_REPLY] { if ($http_ok) { xlog("L_INFO", "status: $http_rs\n"); xlog("L_DBG", "body: $http_rb\n"); } else { xlog("L_ERR", "error: $http_err)\n"); } } route[DIALOG_TIMEOUT] { # TODO: handle edge cases # when we rewrite the reply this may cause issues # dialog module is using tm underneath to send these requests # it seems to be using the contact from either side to teardown the call #if ($dlg_var(sbc_translate) == "1") { # #} #else { # dlg_bye("all"); #} dlg_bye("all"); # we still need to cleanup rtpengine sessions route(RTPENGINEDELETE); } # executed when 200 OK reply for INVITE is processed event_route[dialog:start] { #!ifdef WITH_CALL_SETTINGS # increment the concurrent calls htable if ($dlg_var(src_gwgroupid) != $dlg_var(dst_gwgroupid)) { sht_lock("concurrent_calls=>$dlg_var(src_gwgroupid)"); $sht(concurrent_calls=>$dlg_var(src_gwgroupid)) = $sht(concurrent_calls=>$dlg_var(src_gwgroupid)) + 1; sht_unlock("concurrent_calls=>$dlg_var(src_gwgroupid)"); } sht_lock("concurrent_calls=>$dlg_var(dst_gwgroupid)"); $sht(concurrent_calls=>$dlg_var(dst_gwgroupid)) = $sht(concurrent_calls=>$dlg_var(dst_gwgroupid)) + 1; sht_unlock("concurrent_calls=>$dlg_var(dst_gwgroupid)"); #!endif return; } # executed when dialog is not completed (300 or greater reply code to INVITE) #event_route[dialog:failed] { # return; #} # executed when BYE is processed or dialog timed out event_route[dialog:end] { #!ifdef WITH_CALL_SETTINGS # decrement the concurrent calls htable if ($dlg_var(src_gwgroupid) != $dlg_var(dst_gwgroupid)) { sht_lock("concurrent_calls=>$dlg_var(src_gwgroupid)"); $sht(concurrent_calls=>$dlg_var(src_gwgroupid)) = $sht(concurrent_calls=>$dlg_var(src_gwgroupid)) - 1; sht_unlock("concurrent_calls=>$dlg_var(src_gwgroupid)"); } sht_lock("concurrent_calls=>$dlg_var(dst_gwgroupid)"); $sht(concurrent_calls=>$dlg_var(dst_gwgroupid)) = $sht(concurrent_calls=>$dlg_var(dst_gwgroupid)) - 1; sht_unlock("concurrent_calls=>$dlg_var(dst_gwgroupid)"); #!endif return; } event_route[uac:reply] { xlog("L_DBG", "Request sent to $uac_req(ruri) with event code $uac_req(evcode)\n"); } event_route[xhttp:request] { if ($hu =~ "^/api/kamailio" && dst_ip==127.0.0.1) { jsonrpc_dispatch(); } #!ifdef WITH_WEBSOCKETS else if ($Rp == "WSS_PORT") { if ($hdr(Upgrade) =~ "websocket" && $hdr(Connection) =~ "Upgrade" && $rm =~ "GET") { if (ws_handle_handshake()) { # Optional... cache some information about the # successful connection exit; } } } else { xhttp_reply("403", "Forbidden", "text/html", "<html><body>Will only communicate on the local interface or WebSocket Port WSS_PORT</body></html>"); exit; } #!endif return; } # executed for tm locally created requests event_route[tm:local-request] { #!ifdef WITH_MSTEAMS if (is_method("OPTIONS") && $ru =~ "pstnhub.microsoft.com") { append_hf("Contact: <sip:$fd:SIPS_PORT;transport=tls>\r\n"); xlog("L_DBG", "Changed contact to $ct\n"); } #!endif # TODO: why are we changing the contact here? # Get destination IP $var(destIP)=$(du{s.select,1,:}); # Only change the contact if an Inound NLB is set and the Register is going to a carrier if (is_method("REGISTER") && ("INBOUND_NLB_FQDN" != "") && !allow_address(FLT_PBX, "$var(destIP)", 0)) { if (subst('/^Contact: <sip:([0-9]+)@(.*)$/Contact: <sip:\1@INBOUND_NLB_FQDN>/ig')) { xlog("L_DBG", "Changed REGISTER contact to load balancer address: $mb\n"); } } } # executed for tm locally created responses #event_route[tm:local-response] { # #} # executed for sl received (and ignored) ACK responses #event_route[sl:filtered-ack] { # #} # executed for sl locally created responses #event_route[sl:local-response] { # #} event_route[usrloc:contact-expired] { if (sql_xquery("kam", "SELECT rpid AS gwgroupid FROM subscriber WHERE username='$(ulc(exp=>addr){uri.user})'", "rows") == 1) { $var(src_gwgroupid) = $xavp(rows=>gwgroupid); $var(received_addr) = $(ulc(exp=>received){re.subst,/^(sip:|sips:)?(.*)$/\2/}); if (sql_xquery("kam", "SELECT gwid FROM dr_gateways WHERE address='$var(received_addr)' AND description REGEXP 'gwgroup:$var(src_gwgroupid)(,|$$)'", "rows") == 1) { $var(src_gwid) = $xavp(rows=>gwid); xlog("L_DBG", "found gateway $var(src_gwid) for address $var(received_addr), removing from gwgroup $var(src_gwgroupid)\n"); sql_query("kam", "DELETE FROM dr_gateways WHERE gwid='$var(src_gwid)'"); sql_query("kam", "UPDATE dr_gw_lists SET gwlist=REGEXP_REPLACE(REGEXP_REPLACE(gwlist, '([,;])?$var(src_gwid)', ''), '^([,;])', '') WHERE id=$var(src_gwgroupid)"); jsonrpc_exec('{"jsonrpc": "2.0", "method": "drouting.reload", "id": 1}'); } } } route[REMOVE_REFER] { if (subst_hf("Allow", "/(.+)(REFER,)\s?(.+)/\1\3/", "f")) { xlog("L_INFO", "Removing REFER from Accepted Method to $du\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } } } # CUSTOM: stateful SBC translations route[SBC_TRANSLATE_LR] { #!ifdef WITH_NAT if (!isdsturiset()) { handle_ruri_alias(); } #!endif xlog("L_DBG", "request values before SBC translations: ru=$ru, du=$du dlg_var(src_cturi)=$dlg_var(src_cturi) dlg_var(dst_duri)=$dlg_var(dst_duri) dlg_var(dst_cturi)=$dlg_var(dst_cturi)\n"); if (is_direction("downstream")) { $ru = $dlg_var(src_cturi); $du = $dlg_var(dst_duri); subst_hf("Contact", "/.*/<$dlg_var(dst_cturi)>/", "f"); if (!strempty($dlg_var(dst_auth_user))) { $avp(auth_user) = $dlg_var(dst_auth_user); $avp(auth_pass) = $dlg_var(dst_auth_pass); $avp(auth_realm) = $dlg_var(dst_auth_realm); } } else if (is_direction("upstream")) { $ru = $dlg_var(dst_cturi); $du = $dlg_var(src_duri); subst_hf("Contact", "/.*/<$dlg_var(src_cturi)>/", "f"); if (!strempty($dlg_var(src_auth_user))) { $avp(auth_user) = $dlg_var(src_auth_user); $avp(auth_pass) = $dlg_var(src_auth_pass); $avp(auth_realm) = $dlg_var(src_auth_realm); } } else { xlog("L_WARN", "could not determine call direction\n"); } xlog("L_DBG", "request values after SBC translations: ru=$ru, du=$du\n"); } route[SBC_TRANSLATE_SR] { #!ifdef WITH_NAT if (!isdsturiset()) { handle_ruri_alias(); } #!endif xlog("L_DBG", "request values before SBC translations: ru=$ru, du=$du dlg_var(src_cturi)=$dlg_var(src_cturi) dlg_var(dst_duri)=$dlg_var(dst_duri) dlg_var(dst_cturi)=$dlg_var(dst_cturi)\n"); if ($ft == $dlg(from_tag)) { $ru = $dlg_var(src_cturi); $du = $dlg_var(dst_duri); subst_hf("Contact", "/.*/<$dlg_var(dst_cturi)>/", "f"); if (!strempty($dlg_var(dst_auth_user))) { $avp(auth_user) = $dlg_var(dst_auth_user); $avp(auth_pass) = $dlg_var(dst_auth_pass); $avp(auth_realm) = $dlg_var(dst_auth_realm); } } else if ($ft == $dlg(to_tag)) { $ru = $dlg_var(dst_cturi); $du = $dlg_var(src_duri); subst_hf("Contact", "/.*/<$dlg_var(src_cturi)>/", "f"); if (!strempty($dlg_var(src_auth_user))) { $avp(auth_user) = $dlg_var(src_auth_user); $avp(auth_pass) = $dlg_var(src_auth_pass); $avp(auth_realm) = $dlg_var(src_auth_realm); } } else { xlog("L_WARN", "could not determine call direction\n"); } xlog("L_DBG", "request values after SBC translations: ru=$ru, du=$du\n"); } # Rewrite the Contact domain with given one # required: $var(ct_domain) # optional: $var(transport) route[REPLACE_CONTACT_DOMAIN] { if (!strempty($sel(contact.uri.type))) { $var(ct_uri) = $sel(contact.uri.type) + ":"; } else { $var(ct_uri) = "sip:"; } if (!strempty($sel(contact.uri.user))) { $var(ct_uri) = $var(ct_uri) + $sel(contact.uri.user) + "@" + $var(ct_domain); } else { $var(ct_uri) = $var(ct_uri) + "@" + $var(ct_domain); } if (!strempty($sel(contact.uri.port))) { $var(ct_uri) = $var(ct_uri) + ":" + $sel(contact.uri.port); } if (!strempty($var(transport))) { $var(ct_uri) = $var(ct_uri) + ";" + $var(transport); } if (!strempty($sel(contact.uri.params))) { $var(ct_uri) = $var(ct_uri) + ";" + $sel(contact.uri.params); if (isbflagset(FLB_WS_DEVICE)) { $var(ct_uri) = $var(ct_uri) + ";" + "domain=" + $var(ct_domain) + ";" + "exten=" + $fU; } } else { if (isbflagset(FLB_WS_DEVICE)) { $var(ct_uri) = $var(ct_uri) + ";" + "domain=" + $var(ct_domain) + ";" + "exten=" + $fU; } } if (subst_hf("Contact", "/<([^>]+)>(.*)/<$var(ct_uri)>\2/", "f")) { xlog("L_INFO", "changed contact to match From domain\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes to message\n"); } } } # Manage outgoing branches branch_route[MANAGE_BRANCH] { xlog("L_DBG", "new branch [$T_branch_idx] created to $ru via $du\n"); if (isbflagset(FLB_WS_DEVICE)) { $var(FLB_WS_DEVICE) = 1; } else { $var(FLB_WS_DEVICE) = 0; } if (has_body("application/sdp") || isbflagset(FLB_SRC_MSTEAMS)) { route(RTPENGINEOFFER); # if you do not want the extra RTPEngine param then uncomment this line #sdp_remove_line_by_prefix("a=rtpengine"); } } # Manage incoming replies onreply_route[MANAGE_REPLY] { xlog("L_DBG", "incoming reply from source address $si:$sp\n"); # handle non-error replies if (t_check_status("[12][0-9][0-9]")) { if ($dlg_var(sbc_translate) == "1") { # let UAC think we are communicating with original request values # we will handle translations on our end $fu = $dlg_var(src_ofuri); $tu = $dlg_var(src_oturi); subst_hf("Contact", "/.*/<$dlg_var(src_oruri)>/", "f"); } # handle clientside NAT route(NATMANAGE); # handle SDP munging with rtpengine if (has_body("application/sdp")) { route(RTPENGINEANSWER); if ($avp(sdp_media_direction) != $null) { if (!msg_apply_changes()) { xlog("L_ERR", "could not update sdp"); } if (!subst("/^a=(sendrecv|recvonly|sendonly|inactive).*/a=$avp(sdp_media_direction)/")) { #search_append_body("^a=.+", "a=$avp(sdp_media_direction)"); xlog("L_ERR", "could not update sdp\n"); } if (!msg_apply_changes()) { xlog("L_ERR", "could not update sdp\n"); } } } } # TODO: why are we dropping 183 replies to MSTEAMS? - marked for review/validation if (t_check_status("183") && isbflagset(FLB_DST_MSTEAMS)) { drop(); } if (t_check_status("200") && !strempty($dlg_var(src_msteams_domain))) { if (is_present_hf("ALLOW")) { #Prevent MSTeams from sending REFER requests if ((int)$sel(cfg_get.server.msteams_disable_refer)) { route(REMOVE_REFER); } } else { append_hf("ALLOW: INVITE,ACK,OPTIONS,CANCEL,BYE,NOTIFY\r\n"); } xlog("L_INFO", "+++ Remove REFER\n"); } # set the final ru for subsequent requests to the contact from the 200 if (t_check_status("200")) { if ($ct =~ "<.*>") { $dlg_var(src_cturi) = $(ct{s.select,0,>}{s.select,1,<}); } else { $dlg_var(src_cturi) = $ct; } } # if (t_check_status("100|180|181|183") && $avp(calltype) == "inbound") { # # Increase the lifetime of the current INVITE to pbx_invite_timeout_aftertry if endpoint returns 100/180/181/183. # # This means that the endpoint is at least trying to establish the call. So, we will extend the timeout. # # $var(pbx_invite_timeout) = (int)$sel(cfg_get.server.pbx_invite_timeout_aftertry); # t_set_max_lifetime($var(pbx_invite_timeout), 0); # xlog("L_DBG", "Increasing the Invite Timeout for <$ci> to <$var(pbx_invite_timeout)>\n"); # } } # Manage failure routing cases failure_route[MANAGE_FAILURE] { # Capture the Failure in the CDR setflag(FLT_ACCFAILED); route(NATMANAGE); if (t_is_canceled()) { route(RTPENGINEDELETE); exit; } #!ifdef WITH_BLOCK3XX # block call redirect based on 3xx replies. if (t_check_status("3[0-9][0-9]")) { t_reply("403", "Redirect Forbidden"); route(RTPENGINEDELETE); exit; } #!endif # use uac credentials if set by in SET_CALLAUTH_INFO if (t_check_status("401|407") && !strempty($avp(auth_user))) { t_drop_replies(); #!ifdef WITH_UAC if (!(uac_auth() && t_relay())) { xlog("L_INFO", "UAC Authentication failed\n"); t_reply("503","Service not available"); route(RTPENGINEDELETE); } exit; #!else xlog("L_WARN", "destination requested UAC auth when it is not enabled\n"); t_reply("503", "Service not available"); route(RTPENGINEDELETE); exit; #!endif } # if using pass thru auth relay the reply if (t_check_status("401|407") && isflagset(FLT_PASSTHRU_AUTH)) { t_relay(); exit; } #!ifdef WITH_MSTEAMS if (t_check_status("4[0-9][0-9]") && isbflagset(FLB_DST_MSTEAMS)) { t_relay(); exit; } #!endif #!ifdef WITH_DROUTE if (t_check_status("[0-6][0-9][0-9]") || !t_any_replied()) { if (use_next_gw()) { # set du to match what drouting selected $du = $(ru{uri.duri}); # Set INVITE max lifetime to ensure Primary and Secondary PBX server feature works. if (isbflagset(FLB_SRC_CARRIER)) { t_set_fr((int)$sel(cfg_get.server.pbx_invite_timeout_aftertry), (int)$sel(cfg_get.server.pbx_invite_timeout)); } $du = $ru; route(RELAY); exit; } else { # Only intervene on a request from the Carrier if (isbflagset(FLB_SRC_CARRIER)) { # this route will check for failover and return false if it does not route the call # if we are not failover routing, then none of the routes were successful and we send back an error if (!route(NEXTHOP_FAILOVER)) { t_reply("503","Service not available"); # we can only send notification if mapped to endpoint group if ($dlg_var(src_gwtype) == FLT_PBX) { $avp(notification_type) = NOTIFICATION_GWFAILURE; $avp(notification_gwgroupid) = $dlg_var(src_gwgroupid); $avp(notification_gwid) = $dlg_var(src_gwid); route(SEND_NOTIFICATION); } else if ($dlg_var(dst_gwtype) == FLT_PBX) { $avp(notification_type) = NOTIFICATION_GWFAILURE; $avp(notification_gwgroupid) = $dlg_var(dst_gwgroupid); $avp(notification_gwid) = $dlg_var(dst_gwid); route(SEND_NOTIFICATION); } } } # Don't teardown the RTPEngine if coming or going to MSTeams if (!isbflagset(FLB_SRC_MSTEAMS)) { xlog("L_DBG", "Shutting Down RTPEngine due to receiving reply code: $rs from $si"); route(RTPENGINEDELETE); } exit; } } #!endif if (t_branch_timeout()) { route(RTPENGINEDELETE); exit; } #!ifdef WITH_VOICEMAIL # serial forking # - route to voicemail on busy or no answer (timeout) if (t_check_status("486|408")) { $du = $null; route(TOVOICEMAIL); route(RTPENGINEDELETE); exit; } #!endif } # TODO: FLB_SRC_PBX is not set within in-dialog requests route[PBX_TO_ENDPOINT_LOOKUP] { # Lookup the actual location of endpoint if # coming from a PBX and the request domain is a local ip address if (isbflagset(FLB_SRC_PBX) && is_ip_rfc1918($rd)) { if (lookup("location","sip:$rU@$fd")) { xlog("L_DBG", "Looking up the domain and getting domain: $fd\n"); } } } # TODO: dynamically get user_tn / pilot_tn from user configs # Carrier Enrichment for CenturyLink # validated SIP carriers: # voip.centurylink.com # 65.149.22.7, 65.149.23.7, 65.149.24.7, 65.149.25.7 # 216.206.64.7, 216.206.64.71, 216.206.64.91 # 216.206.66.7, 216.206.66.71, 216.206.66.91 route[ENRICH_CARRIER_CENTURYLINK_OUTBOUND] { $var(domain) = "voip.centurylink.com"; $var(user_tn) = "6467687570"; $var(pilot_tn) = "6467687572"; if ($rd =~ "$var(domain).*|65.149.22.7.*|65.149.23.7.*|65.149.24.7.*|65.149.25.7.*|216.206.64.7.*|216.206.64.71.*|216.206.64.91.*|216.206.66.7.*|216.206.66.71.*|216.206.66.91.*") { xlog("L_INFO", "centurylink carrier match\n"); $du = $ru; $rd = $var(domain); # Remove the port $rp = ""; # Change the from and to domain to match the carrier domain name $td = $var(domain); $fd = $var(domain); # Uncomment this line if you need to change the from user to the user_tn #$fU = $var(user_tn); # Uncomment this line if you need to change the domain of the contact - this is not recommended #subst('/^Contact: <sip:([0-9]+)@(.*)$/Contact: <sip:\1@107.21.184.251>/ig') # Add P-Asserted-Identity per the carriers requirement append_hf("P-Asserted-Identity: <sip:$var(pilot_tn)@$var(domain)>\r\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } } } route[ENRICH_CARRIER_SIGNALWIRE_INBOUND] { $var(domain_lookup) = ".+sip.signalwire.com"; xlog("L_DBG", "before transform:\n$mb\n"); if ($td =~ $var(domain_lookup)) { xlog("L_INFO", "signalwire carrier match\n"); # Change the "request username" to "to username" $rU = $tU; if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } xlog("L_DBG", "after transform:\n$mb\n"); } } route[ENRICH_CARRIER_SIGNALWIRE_OUTBOUND] { $var(domain) = "sip.signalwire.com"; $var(domain_lookup) = ".+sip.signalwire.com"; $var(user) = $fU; xlog("L_DBG", "before transform:\n$mb\n"); if ($rd =~ $var(domain_lookup)) { xlog("L_INFO", "signalwire carrier match\n"); # Change the from domain to the request domain $fd = $rd; # Change the from user to the authenticated user $fU = $avp(auth_user); # Add the callerid append_hf("P-Asserted-Identity: <sip:$var(user)@$var(domain)>\r\n"); if (!msg_apply_changes()) { xlog("L_ERR", "failed applying changes\n"); } xlog("L_DBG", "after transform:\n$mb\n"); } } route[ENRICH_CARRIER_INBOUND] { route(ENRICH_CARRIER_SIGNALWIRE_INBOUND); } # Carrier Enrichment route[ENRICH_CARRIER_OUTBOUND] { route(ENRICH_CARRIER_CENTURYLINK_OUTBOUND); route(ENRICH_CARRIER_SIGNALWIRE_OUTBOUND); } ####### CUSTOM_ROUTING_START ######### # add custom routes here ####### CUSTOM_ROUTING_END ######### ================================================ FILE: kamailio/configs/stir-shaken.cfg ================================================ route[STIRSHAKEN_INBOUND] { # Only route if request is coming from an Endpoint if (!isbflagset(FLB_SRC_CARRIER)) { xlog("L_INFO", "[STIRSHAKEN_INBOUND] <$ci> $si not allowed to talk with this server \n"); return; } xlog("L_INFO", "STIR/SHAKEN Inbound Logic"); if ($hdrc(Identity) == 0 ){ xlog("L_INFO", "[STIRSHAKEN_INBOUND] Missing the Identity Header Skipping this logic \n"); return; } #Verify Call Identity xlog("L_INFO", "Identity Header: $hdr(Identity),$hdr(Reason)"); xlog("L_INFO", "Encoded Header 1: $(hdr(Identity){s.select,0,.})"); xlog("L_INFO", "Encoded Header 2: $(hdr(Identity){s.select,1,.})"); xlog("L_INFO", "Decoded Header 1: $(hdr(Identity){s.select,0,.}{s.decode.base64}) "); xlog("L_INFO", "Decoded Header 2: $(hdr(Identity){s.select,1,.}{s.decode.base64}) "); $var(json_payload) = $(hdr(Identity){s.select,1,.}{s.decode.base64}); jansson_get_field($var(json_payload), "attest", "$var(attest_value)"); xlog("L_INFO", "Attest Value: $(var(attest_value))"); if (stirshaken_check_identity() == 1) { xlog("L_INFO", "Shaken Identity is OK\n"); if ($var(attest_value) == "A") { uac_replace_from("$sel(cfg_get.stir_shaken.stir_shaken_prefix_a) $fU",""); } else if ($var(attest_value) == "B") { uac_replace_from("$sel(cfg_get.stir_shaken.stir_shaken_prefix_b) $fU",""); } else if ($var(attest_value) == "C") { uac_replace_from("$sel(cfg_get.stir_shaken.stir_shaken_prefix_c) $fU",""); } else { uac_replace_from("VERIFIED $fU",""); } } else { if ($sel(cfg_get.stir_shaken.stir_shaken_block_invalid) == 1) { sl_reply("488", "Blocked due to invalid or missing STIR/SHAKEN Identity Header"); exit; } xlog("L_INFO", "Shaken Identity is invalid\n"); uac_replace_from("$sel(cfg_get.stir_shaken.stir_shaken_prefix_invalid) $fU",""); } } route[STIRSHAKEN_OUTBOUND] { # Only route if request is coming from an Endpoint # and is an INVITE if (isbflagset(FLB_SRC_CARRIER)) { return; } if (!is_method("INVITE")) { return; } xlog("L_INFO", "STIRSHAKEN Outbound Logic - method: $rm"); # get the outbound caller id number $var(caller_from_number) = $fU; if (1 == stirshaken_add_identity_with_key($sel(cfg_get.stir_shaken.stir_shaken_cert_url ), "A", $fU, $rU, "" , $sel(cfg_get.stir_shaken.stir_shaken_key_path) )) { xlog("L_INFO", "Shaken authentication added (SIP Identity Header created)\n"); } else { xlog("L_INFO", "Failed to add identity\n"); } } ================================================ FILE: kamailio/configs/tls.cfg ================================================ #======================================================= # This is the default server domain profile. # Settings in this domain will be used for all incoming # connections that do not match any other server # domain in this configuration file. # # We do not enable anything else than TLSv1.2+ # over the public internet. Clients do not have # to present client certificates by default. #======================================================= [server:default] method = TLSv1.2+ verify_certificate = yes require_certificate = yes private_key = /etc/dsiprouter/certs/dsiprouter-key.pem certificate = /etc/dsiprouter/certs/dsiprouter-cert.pem ca_list = /etc/dsiprouter/certs/ca-list.pem ca_path = /etc/dsiprouter/certs/ca #crl = /etc/dsiprouter/certs/crl.pem #========== webrtc_ipv4_start ==========# #[server:127.0.0.1:4443] #method = TLSv1.2+ #verify_certificate = no #require_certificate = no #private_key = /etc/dsiprouter/certs/dsiprouter-key.pem #certificate = /etc/dsiprouter/certs/dsiprouter-cert.pem #ca_list = /etc/dsiprouter/certs/ca-list.pem #crl = /etc/dsiprouter/certs/crl.pem #========== webrtc_ipv4_stop ==========# #========== webrtc_ipv6_start ==========# #[server:[::1]:4443] #method = TLSv1.2+ #verify_certificate = no #require_certificate = no #private_key = /etc/dsiprouter/certs/dsiprouter-key.pem #certificate = /etc/dsiprouter/certs/dsiprouter-cert.pem #ca_list = /etc/dsiprouter/certs/ca-list.pem #crl = /etc/dsiprouter/certs/crl.pem #========== webrtc_ipv6_stop ==========# #======================================================= # This is the default client domain profile. # Settings in this domain will be used for all outgoing # TLS connections that do not match any other # client domain in this configuration file. # We require that servers present valid certificate. #======================================================= [client:default] method = TLSv1.2+ verify_certificate = yes require_certificate = yes private_key = /etc/dsiprouter/certs/dsiprouter-key.pem certificate = /etc/dsiprouter/certs/dsiprouter-cert.pem ca_list = /etc/dsiprouter/certs/ca-list.pem ca_path = /etc/dsiprouter/certs/ca #crl = /etc/dsiprouter/certs/crl.pem #======================================================= # Other domain profiles may be added here #======================================================= ================================================ FILE: kamailio/configs/transnexus.cfg ================================================ route[TRANSNEXUS_OUTBOUND] { xlog("L_INFO", "Transnexus Outbound Logic - original: $var(orig_rU)"); # Return if emergency route if ($var(orig_rU) =~ $sel(cfg_get.server.emergency_numbers)) { return; } # Only route if request is coming from an Endpoint # and is an INVITE if (isbflagset(FLB_SRC_CARRIER)) { return; } if (!is_method("INVITE")) { return; } xlog("L_INFO", "Transnexus Outbound Logic - method: $rm"); #Store the $ru that was selected $avp(dr_orig_ruri) = $ru; $avp(dr_orig_du) = $du; #Send call to clearip server $ru = "sip:" + $rU + "@" + $sel(cfg_get.transnexus.authservice_host) + ";transport=tcp"; $du = $ru; #Set a failure route to get the 302 if (is_method("INVITE")) { t_on_reply("TRANSNEXUS_OUTBOUND_REPLY"); t_on_failure("TRANSNEXUS_OUTBOUND_CARRIER"); } } failure_route[TRANSNEXUS_OUTBOUND_CARRIER] { if (t_check_status("302|403|503")) { xlog("L_INFO", "Transnexus Outbound Failure Logic"); #Append Identity Header if ($avp(Identity) != "") { append_hf("Identity: $avp(Identity)\r\n"); } #Check if contact contains if ((int)$sel(cfg_get.transnexus.authservice_lrn_enabled)) { #Remove the main branch $ru = $null; #Get Redirects from Transnexus get_redirects("*"); #Try one contact at a time if (!t_load_contacts()) { t_reply("500", "Server Internal Error - Cannot load contacts from TransNexus. Check TransNexus config!"); exit; } else { t_next_contacts(); t_on_failure("TRANSNEXUS_OUTBOUND_CARRIER_NEXT"); t_relay(); } } else { #Send call outbound using dSIPRouter Routes $ru = $avp(dr_orig_ruri); $du = $avp(dr_orig_du); t_relay(); exit; } } } failure_route[TRANSNEXUS_OUTBOUND_CARRIER_NEXT] { if (!t_next_contacts()) { t_reply("408", "Request Timeout"); } else { t_on_failure("TRANSNEXUS_OUTBOUND_CARRIER_NEXT"); t_relay(); } } onreply_route[TRANSNEXUS_OUTBOUND_REPLY] { if (t_check_status("302|403|503")) { #Get Identity header and append it to header. Look for Identity or X-Identity if ($hdr(Identity) != "") { $avp(Identity) = $hdr(Identity); } else if ($hdr(X-Identity) != "") { $avp(Identity) = $hdr(X-Identity); } if ($avp(Identity) != "") { xlog("L_INFO", "Identity Header Found: $avp(Identity),$hdr(Reason)"); } else { xlog("L_INFO", "Identity Header Not Found"); } } if (is_method("BYE")) { xlog("L_INFO", "*******TRANSNEXUS_OUTBOUND_REPLY BYE*******"); } } onreply_route[TRANSNEXUS_CARRIER_REPLY] { xlog("L_INFO", "*******TRANSNEXUS_CARRIER_REPLY*******"); } route[TRANSNEXUS_INBOUND] { # Only route if request is coming from an Endpoint if (!isbflagset(FLB_SRC_CARRIER)) { xlog("L_INFO", "[TRANSNEXUS_INBOUND] <$ci> $si not allowed to talk with Transnexus \n"); return; } xlog("L_INFO", "[TRANSNEXUS_INBOUND] Transnexus Inbound Logic"); #Store the $ru that was selected $avp(dr_orig_ruri) = $ru; #Send call to clearip server for validation $ru = "sip:" + $rU + "@" + $sel(cfg_get.transnexus.verifyservice_host) + ";transport=tcp"; xlog("L_INFO", "[TRANSNEXUS_INBOUND] ru = $ru"); #Set a failure route to get the 302 if (is_method("INVITE")) { t_on_reply("TRANSNEXUS_INBOUND_REPLY"); t_on_failure("TRANSNEXUS_INBOUND_FAILURE"); } t_relay(); exit; } onreply_route[TRANSNEXUS_INBOUND_REPLY] { if (t_check_status("302")) { #Get Identity header and append it to header xlog("L_INFO", "Identity Header: $hdr(Identity),$hdr(Reason)"); $avp(X-P-Asserted-Identity) = $hdr(P-Asserted-Identity); } } failure_route[TRANSNEXUS_INBOUND_FAILURE] { if (t_check_status("302")) { xlog("L_INFO", "Transnexus Inbound Failure Logic"); t_drop_replies(); send_reply("100", "Trying"); append_hf("X-P-Asserted-Identity: $avp(X-P-Asserted-Identity)\r\n"); # Replace with the original URI $ru = $avp(dr_orig_ruri); # Import custom logic to route the inbound call import_file "transnexus_inbound_custom.cfg" route(NEXTHOP); return; } } ================================================ FILE: kamailio/debian/10.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # nf_tables is the default fw on debian but it has too many bugs at this time # instead we will use legacy iptables until these issues are ironed out update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake build-essential pkg-config \ zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libsqlite3-dev libreadline-dev \ libffi-dev libbz2-dev libpq-dev libev-dev uuid-runtime tar dpkg && apt-get install -t bullseye -y firewalld python3 python3-venv if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # we need a newer version of certbot than the distro repos offer apt-get remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G kamailio root # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} buster main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} buster main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo wget -O- https://deb-archive.kamailio.org/kamailiodebkey.gpg | apt-key add - # Update repo sources cache apt-get update -y # Install Kamailio packages ## the kamailio deb must be updated so we don't overwrite the compiled python version ( TMP_DIR=$(mktemp -d) && cd /tmp && apt-get download kamailio && KAM_DEB=$(ls kamailio_*.deb) && dpkg-deb -R $KAM_DEB $TMP_DIR && rm -f $KAM_DEB && perl -i -pe 's%(Depends: .*)python3, (.*)%\1\2%g' $TMP_DIR/DEBIAN/control && dpkg-deb -b $TMP_DIR kamailio.deb && apt-get install -y ./kamailio.deb && rm -f kamailio.deb ) || { printerr 'Failed installing kamailio from packages' return 1 } apt-get install -y --allow-downgrades kamailio-mysql-modules kamailio-extra-modules kamailio-tls-modules \ kamailio-websocket-modules kamailio-presence-modules kamailio-json-modules \ kamailio-sctp-modules if (( $? != 0 )); then printerr "failed installing kamailio modules from packages" return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create || { printerr 'Failed creating kamailio database' return 1 } # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( # TODO: commit updates to upstream to fix EVP_PKEY_cmp being deprecated cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr CFLAGS='-Wno-deprecated-declarations' LDFLAGS='-L/usr/lib' && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/debian/11.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # nf_tables is the default fw on debian but it has too many bugs at this time # instead we will use legacy iptables until these issues are ironed out update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake firewalld python3 build-essential python3-venv if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # we need a newer version of certbot than the distro repos offer apt-get remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G kamailio root # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} bullseye main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} bullseye main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo wget -O- https://deb-archive.kamailio.org/kamailiodebkey.gpg | apt-key add - # Update repo sources cache apt-get update -y # Install Kamailio packages apt-get install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules \ kamailio-tls-modules kamailio-websocket-modules kamailio-presence-modules \ kamailio-json-modules kamailio-sctp-modules # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create || { printerr 'Failed creating kamailio database' return 1 } # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( # TODO: commit updates to upstream to fix EVP_PKEY_cmp being deprecated cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr CFLAGS='-Wno-deprecated-declarations' LDFLAGS='-L/usr/lib' && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall() { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/debian/12.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # Install Dependencies and remove any conflicting packages { dpkg -l ufw &>/dev/null && apt-get remove -y ufw || :; } && apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake firewalld build-essential certbot \ pkg-config dh-autoreconf if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # Configure OpenSSL to a default provider sed -i -e 's/# providers =/providers =/' -e 's/# \[provider_sect/\[provider_sect/' -e 's/# default =/default =/' -e 's/# \[default_sect/\[default_sect/' -e 's/# activate/activate/' /etc/ssl/openssl.cnf # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G kamailio root # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} bookworm main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} bookworm main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo curl -s https://deb.kamailio.org/kamailiodebkey.gpg | gpg --dearmor >/etc/apt/trusted.gpg.d/kamailiodebkey.gpg # Update repo sources cache apt-get update -y # Install Kamailio packages apt-get install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules \ kamailio-tls-modules kamailio-websocket-modules kamailio-presence-modules \ kamailio-json-modules kamailio-sctp-modules # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTHOST="${ROOT_DB_HOST}" DBROOTPORT="${ROOT_DB_PORT}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute drouting userblocklist htable purple uac pipelimit mtree sca mohqueue rtpproxy rtpengine secfilter" INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # in mariadb ver >= 10.6.1 --port= now defaults to transport=tcp # we want socket connections for root as default so apply our patch to kamdbctl # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd /usr/lib/x86_64-linux-gnu/kamailio/kamctl && patch -p3 -N <${DSIP_PROJECT_DIR}/kamailio/kamdbctl.patch ) if (( $? > 1 )); then printerr 'Failed patching kamdbctl' return 1 fi # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create || { printerr 'Failed creating kamailio database' return 1 } # Enable and start firewalld if not already running systemctl enable firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --zone=public --add-port=22/tcp --permanent firewall-cmd --reload systemctl start firewalld # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( # TODO: commit updates to upstream to fix EVP_PKEY_cmp being deprecated cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr CFLAGS='-Wno-deprecated-declarations' LDFLAGS='-L/usr/lib' && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall() { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/debian/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" # Install Dependencies and remove any conflicting packages apt-get remove -y ufw && apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake firewalld python3 python3-venv && apt-get install -y -t buster build-essential if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi # we need a newer version of certbot than the distro repos offer apt-get remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G kamailio root # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} stretch main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} stretch main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo wget -O- https://deb-archive.kamailio.org/kamailiodebkey.gpg | apt-key add - # Update repo sources cache apt-get update -y # Install Kamailio packages apt-get install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules kamailio-tls-modules \ kamailio-websocket-modules kamailio-presence-modules kamailio-json-modules # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create || { printerr 'Failed creating kamailio database' return 1 } # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v1.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup dSIPRouter module for kamailio ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi # setup STIR/SHAKEN module for kamailio ## compile and install libjwt if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr . && make install; exit $?; ) || { printerr 'Failed to compile and install libks'; return 1; } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr && make && make install && ldconfig; exit $?; ) || { printerr 'Failed to compile and install libstirshaken'; return 1; } ## compile and install STIR/SHAKEN module ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make; exit $?; ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module'; return 1; } return 0 } function uninstall { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/defaults/address.csv ================================================ 1;FLT_MSTEAMS;52.114.132.46;32;0;name:msteams-sbc,gwgroup:0 2;FLT_MSTEAMS;52.114.14.70;32;0;name:msteams-sbc,gwgroup:0 3;FLT_MSTEAMS;52.114.148.0;32;0;name:msteams-sbc,gwgroup:0 4;FLT_MSTEAMS;52.114.16.74;32;0;name:msteams-sbc,gwgroup:0 5;FLT_MSTEAMS;52.114.20.29;32;0;name:msteams-sbc,gwgroup:0 6;FLT_MSTEAMS;52.114.7.24;32;0;name:msteams-sbc,gwgroup:0 7;FLT_MSTEAMS;52.114.75.24;32;0;name:msteams-sbc,gwgroup:0 8;FLT_MSTEAMS;52.114.76.76;32;0;name:msteams-sbc,gwgroup:0 9;FLT_MSTEAMS;52.127.64.33;32;0;name:msteams-sbc,gwgroup:0 10;FLT_MSTEAMS;52.127.64.34;32;0;name:msteams-sbc,gwgroup:0 11;FLT_MSTEAMS;52.127.88.59;32;0;name:msteams-sbc,gwgroup:0 12;FLT_MSTEAMS;52.127.92.64;32;0;name:msteams-sbc,gwgroup:0 13;FLT_MSTEAMS;sip.pstnhub.microsoft.com;32;0;name:msteams-sbc,gwgroup:0 14;FLT_MSTEAMS;sip2.pstnhub.microsoft.com;32;0;name:msteams-sbc,gwgroup:0 15;FLT_MSTEAMS;sip3.pstnhub.microsoft.com;32;0;name:msteams-sbc,gwgroup:0 16;FLT_MSTEAMS;sip.pstnhub.dod.teams.microsoft.us;32;0;name:msteams-sbc,gwgroup:0 17;FLT_MSTEAMS;sip.pstnhub.gov.teams.microsoft.us;32;0;name:msteams-sbc,gwgroup:0 18;FLT_CARRIER;54.172.60.0;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 19;FLT_CARRIER;54.172.60.1;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 20;FLT_CARRIER;54.172.60.2;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 21;FLT_CARRIER;54.172.60.3;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 22;FLT_CARRIER;54.244.51.0;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 23;FLT_CARRIER;54.244.51.1;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 24;FLT_CARRIER;54.244.51.2;32;0;name:Twilio NA Inbound,gwgroup:1 25;FLT_CARRIER;54.244.51.3;32;0;name:Twilio NA Inbound Carrier,gwgroup:1 26;FLT_CARRIER;54.244.60.0;23;0;name:Twilio NA Inbound Carrier,gwgroup:1 27;FLT_CARRIER;34.203.250.0;23;0;name:Twilio NA Inbound Carrier,gwgroup:1 28;FLT_CARRIER;54.244.51.0;24;0;name:Twilio NA Inbound Carrier,gwgroup:1 29;FLT_CARRIER;52.41.52.34;32;0;name:Skyetel North West Inbound,gwgroup:2 30;FLT_CARRIER;52.8.201.128;32;0;name:Skyetel South West Inbound,gwgroup:2 31;FLT_CARRIER;52.60.138.31;32;0;name:Skyetel North East Inbound,gwgroup:2 32;FLT_CARRIER;50.17.48.216;32;0;name:Skyetel South East Inbound,gwgroup:2 33;FLT_CARRIER;35.156.192.164;32;0;name:Skyetel Europe Inbound,gwgroup:2 34;FLT_CARRIER;term.skyetel.com;32;0;name:Skyetel 1st Priority Outbound Call,gwgroup:2 35;FLT_CARRIER;52.41.52.34;32;0;name:Skyetel 2nd Priority Outbound Call,gwgroup:2 36;FLT_CARRIER;52.8.201.128;32;0;name:Skyetel 3rd Priority Outbound Call,gwgroup:2 37;FLT_CARRIER;50.17.48.216;32;0;name:Skyetel 4rd Priority Outbound Call,gwgroup:2 38;FLT_CARRIER;52.32.223.28;32;0;name:Skyetel North West High Cost Outbound Traffic,gwgroup:2 39;FLT_CARRIER;52.4.178.107;32;0;name:Skyetel South East High Cost Outbound Traffic,gwgroup:2 41;FLT_CARRIER;34.210.91.112;28;0;name:Flowroute US-West-OR,gwgroup:3 43;FLT_CARRIER;34.226.36.32;28;0;name:Flowroute US-East-VA,gwgroup:3 44;FLT_CARRIER;81.201.82.45;32;0;name:Voxbone Belgium,gwgroup:4 45;FLT_CARRIER;81.201.84.195;32;0;name:Voxbone LA,gwgroup:4 46;FLT_CARRIER;81.201.85.45;32;0;name:Voxbone NYC,gwgroup:4 47;FLT_CARRIER;81.201.83.45;32;0;name:Voxbone Germany,gwgroup:4 48;FLT_CARRIER;81.201.86.45;32;0;name:Voxbone Hong Kong,gwgroup:4 49;FLT_CARRIER;81.201.84.195;32;0;name:Voxbone Australia,gwgroup:4 50;FLT_CARRIER;64.136.174.30;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 51;FLT_CARRIER;64.136.173.22;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 52;FLT_CARRIER;209.166.128.200;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 53;FLT_CARRIER;192.240.151.100;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 54;FLT_CARRIER;64.136.173.31;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 55;FLT_CARRIER;64.136.174.30;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 56;FLT_CARRIER;64.136.174.20;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 57;FLT_CARRIER;209.166.154.70;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 58;FLT_CARRIER;64.136.174.65;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 59;FLT_CARRIER;64.136.173.23;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 60;FLT_CARRIER;209.166.128.201;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 61;FLT_CARRIER;192.240.151.101;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 62;FLT_CARRIER;64.136.173.65;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 63;FLT_CARRIER;64.136.174.65;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 64;FLT_CARRIER;64.136.174.21;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 65;FLT_CARRIER;209.166.154.71;32;0;name:VoIP Innovations Inbound Carrier,gwgroup:5 66;FLT_CARRIER;64.136.174.30;32;0;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6 67;FLT_CARRIER;64.136.173.22;32;0;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6 68;FLT_CARRIER;209.166.128.200;32;0;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6 69;FLT_CARRIER;192.240.151.100;32;0;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6 70;FLT_CARRIER;72.15.219.140;32;0;name:Thinq Carrier,gwgroup:7 71;FLT_CARRIER;216.147.191.157;32;0;name:Voxtelesys Carrier,gwgroup:8 72;FLT_CARRIER;64.34.181.47;32;0;name:Les.net Carrier,gwgroup:9 73;FLT_CARRIER;206.80.250.100;32;0;name:ThinkTel,gwgroup:10 74;FLT_CARRIER;208.68.17.52;32;0;name:ThinkTel,gwgroup:10 75;FLT_CARRIER;209.197.130.80;32;0;name:ThinkTel,gwgroup:10 ================================================ FILE: kamailio/defaults/address.sql ================================================ -- update address schema ALTER TABLE address MODIFY COLUMN `ip_addr` VARCHAR(253) NOT NULL, MODIFY COLUMN `tag` VARCHAR(255) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/defaults/dispatcher.csv ================================================ 1,1,sip:54.172.60.0:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Virginia Carrier;gwid=1 2,1,sip:54.172.60.1:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Virginia Carrier;gwid=2 3,1,sip:54.172.60.2:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Virginia Carrier;gwid=3 4,1,sip:54.172.60.3:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Virginia Carrier;gwid=4 5,1,sip:54.244.51.0:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Oregon Carrier;gwid=5 6,1,sip:54.244.51.1:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Oregon Carrier;gwid=6 7,1,sip:54.244.51.2:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Oregon Carrier;gwid=7 8,1,sip:54.244.51.3:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Twilio NA-Oregon Carrier;gwid=8 9,2,sip:52.41.52.34:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel North West Inbound;gwid=9 10,2,sip:52.8.201.128:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel South West Inbound;gwid=10 11,2,sip:52.60.138.31:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel North East Inbound;gwid=11 12,2,sip:50.17.48.216:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel South East Inbound;gwid=12 13,2,sip:35.156.192.164:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel Europe Inbound;gwid=13 14,2,sip:term.skyetel.com:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel 1st Priority Outbound Call;gwid=14 15,2,sip:52.41.52.34:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel 2nd Priority Outbound Call;gwid=15 16,2,sip:52.8.201.128:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel 3rd Priority Outbound Call;gwid=16 17,2,sip:50.17.48.216:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel 4rd Priority Outbound Call;gwid=17 18,2,sip:52.32.223.28:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel North West High Cost Outbound Traffic;gwid=18 19,2,sip:52.4.178.107:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Skyetel South East High Cost Outbound Traffic;gwid=19 20,3,sip:147.75.60.160:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Flowroute US-West-WA;gwid=20 21,3,sip:34.210.91.112:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Flowroute US-West-OR;gwid=21 22,3,sip:147.75.65.192:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Flowroute US-East-NJ;gwid=22 23,3,sip:34.226.36.32:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Flowroute US-East-VA;gwid=23 24,4,sip:81.201.82.45:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone Belgium;gwid=24 25,4,sip:81.201.84.195:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone LA;gwid=25 26,4,sip:81.201.85.45:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone NYC;gwid=26 27,4,sip:81.201.83.45:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone Germany;gwid=27 28,4,sip:81.201.86.45:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone Hong Kong;gwid=28 29,4,sip:81.201.84.195:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxbone Australia;gwid=29 30,5,sip:64.136.174.30:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=30 31,5,sip:64.136.173.22:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=31 32,5,sip:209.166.128.200:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=32 33,5,sip:192.240.151.100:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=33 34,5,sip:64.136.173.31:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=34 35,5,sip:64.136.174.30:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=35 36,5,sip:64.136.174.20:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=36 37,5,sip:209.166.154.70:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=37 38,5,sip:64.136.174.65:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=38 39,5,sip:64.136.173.23:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=39 40,5,sip:209.166.128.201:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=40 41,5,sip:192.240.151.101:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=41 42,5,sip:64.136.173.65:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=42 43,5,sip:64.136.174.65:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=43 44,5,sip:64.136.174.21:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=44 45,5,sip:209.166.154.71:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Inbound Carrier;gwid=45 46,6,sip:64.136.174.30:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Outbound Conversational Carrier;gwid=46 47,6,sip:64.136.173.22:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Outbound Conversational Carrier;gwid=47 48,6,sip:209.166.128.200:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Outbound Conversational Carrier;gwid=48 49,6,sip:192.240.151.100:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=VoIP Innovations Outbound Conversational Carrier;gwid=49 50,7,sip:72.15.219.140:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Thinq Carrier;gwid=50 51,8,sip:216.147.191.157:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Voxtelesys Carrier;gwid=51 52,9,sip:64.34.181.47:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=Les.net Carrier;gwid=52 53,10,sip:206.80.250.100:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=ThinkTel;gwid=53 54,10,sip:208.68.17.52:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=ThinkTel;gwid=54 55,10,sip:209.197.130.80:5060,0,0,signalling=proxy;media=proxy;rweight=1,name=ThinkTel;gwid=55 ================================================ FILE: kamailio/defaults/dispatcher.sql ================================================ -- update dispatcher schema to fit our storage requirements ALTER TABLE dispatcher MODIFY description varchar(255) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/defaults/dr_gateways.csv ================================================ 1;FLT_CARRIER;54.172.60.0;0;;1,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Virginia Carrier,gwgroup:1,addr_id:18 2;FLT_CARRIER;54.172.60.1;0;;2,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Virginia Carrier,gwgroup:1,addr_id:19 3;FLT_CARRIER;54.172.60.2;0;;3,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Virginia Carrier,gwgroup:1,addr_id:20 4;FLT_CARRIER;54.172.60.3;0;;4,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Virginia Carrier,gwgroup:1,addr_id:21 5;FLT_CARRIER;54.244.51.0;0;;5,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Oregon Carrier,gwgroup:1,addr_id:22 6;FLT_CARRIER;54.244.51.1;0;;6,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Oregon Carrier,gwgroup:1,addr_id:23 7;FLT_CARRIER;54.244.51.2;0;;7,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Oregon Carrier,gwgroup:1,addr_id:24 8;FLT_CARRIER;54.244.51.3;0;;8,FLT_CARRIER,,proxy,proxy;name:Twilio NA-Oregon Carrier,gwgroup:1,addr_id:25 9;FLT_CARRIER;52.41.52.34;0;;9,FLT_CARRIER,,proxy,proxy;name:Skyetel North West Inbound,gwgroup:2,addr_id:29 10;FLT_CARRIER;52.8.201.128;0;;10,FLT_CARRIER,,proxy,proxy;name:Skyetel South West Inbound,gwgroup:2,addr_id:30 11;FLT_CARRIER;52.60.138.31;0;;11,FLT_CARRIER,,proxy,proxy;name:Skyetel North East Inbound,gwgroup:2,addr_id:31 12;FLT_CARRIER;50.17.48.216;0;;12,FLT_CARRIER,,proxy,proxy;name:Skyetel South East Inbound,gwgroup:2,addr_id:32 13;FLT_CARRIER;35.156.192.164;0;;13,FLT_CARRIER,,proxy,proxy;name:Skyetel Europe Inbound,gwgroup:2,addr_id:33 14;FLT_CARRIER;term.skyetel.com;0;;14,FLT_CARRIER,,proxy,proxy;name:Skyetel 1st Priority Outbound Call,gwgroup:2,addr_id:34 15;FLT_CARRIER;52.41.52.34;0;;15,FLT_CARRIER,,proxy,proxy;name:Skyetel 2nd Priority Outbound Call,gwgroup:2,addr_id:35 16;FLT_CARRIER;52.8.201.128;0;;16,FLT_CARRIER,,proxy,proxy;name:Skyetel 3rd Priority Outbound Call,gwgroup:2,addr_id:36 17;FLT_CARRIER;50.17.48.216;0;;17,FLT_CARRIER,,proxy,proxy;name:Skyetel 4rd Priority Outbound Call,gwgroup:2,addr_id:37 18;FLT_CARRIER;52.32.223.28;0;;18,FLT_CARRIER,,proxy,proxy;name:Skyetel North West High Cost Outbound Traffic,gwgroup:2,addr_id:38 19;FLT_CARRIER;52.4.178.107;0;;19,FLT_CARRIER,,proxy,proxy;name:Skyetel South East High Cost Outbound Traffic,gwgroup:2,addr_id:39 21;FLT_CARRIER;34.210.91.112;0;;21,FLT_CARRIER,,proxy,proxy;name:Flowroute US-West-OR,gwgroup:3,addr_id:41 23;FLT_CARRIER;34.226.36.32;0;;23,FLT_CARRIER,,proxy,proxy;name:Flowroute US-East-VA,gwgroup:3,addr_id:43 24;FLT_CARRIER;81.201.82.45;0;;24,FLT_CARRIER,,proxy,proxy;name:Voxbone Belgium,gwgroup:4,addr_id:44 25;FLT_CARRIER;81.201.84.195;0;;25,FLT_CARRIER,,proxy,proxy;name:Voxbone LA,gwgroup:4,addr_id:45 26;FLT_CARRIER;81.201.85.45;0;;26,FLT_CARRIER,,proxy,proxy;name:Voxbone NYC,gwgroup:4,addr_id:46 27;FLT_CARRIER;81.201.83.45;0;;27,FLT_CARRIER,,proxy,proxy;name:Voxbone Germany,gwgroup:4,addr_id:47 28;FLT_CARRIER;81.201.86.45;0;;28,FLT_CARRIER,,proxy,proxy;name:Voxbone Hong Kong,gwgroup:4,addr_id:48 29;FLT_CARRIER;81.201.84.195;0;;29,FLT_CARRIER,,proxy,proxy;name:Voxbone Australia,gwgroup:4,addr_id:49 30;FLT_CARRIER;64.136.174.30;0;;30,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:50 31;FLT_CARRIER;64.136.173.22;0;;31,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:51 32;FLT_CARRIER;209.166.128.200;0;;32,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:52 33;FLT_CARRIER;192.240.151.100;0;;33,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:53 34;FLT_CARRIER;64.136.173.31;0;;34,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:54 35;FLT_CARRIER;64.136.174.30;0;;35,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:55 36;FLT_CARRIER;64.136.174.20;0;;36,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:56 37;FLT_CARRIER;209.166.154.70;0;;37,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:57 38;FLT_CARRIER;64.136.174.65;0;;38,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:58 39;FLT_CARRIER;64.136.173.23;0;;39,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:59 40;FLT_CARRIER;209.166.128.201;0;;40,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:60 41;FLT_CARRIER;192.240.151.101;0;;41,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:61 42;FLT_CARRIER;64.136.173.65;0;;42,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:62 43;FLT_CARRIER;64.136.174.65;0;;43,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:63 44;FLT_CARRIER;64.136.174.21;0;;44,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:64 45;FLT_CARRIER;209.166.154.71;0;;45,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Inbound Carrier,gwgroup:5,addr_id:65 46;FLT_CARRIER;64.136.174.30;0;;46,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6,addr_id:66 47;FLT_CARRIER;64.136.173.22;0;;47,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6,addr_id:67 48;FLT_CARRIER;209.166.128.200;0;;48,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6,addr_id:68 49;FLT_CARRIER;192.240.151.100;0;;49,FLT_CARRIER,,proxy,proxy;name:VoIP Innovations Outbound Conversational Carrier,gwgroup:6,addr_1d:69 50;FLT_CARRIER;72.15.219.140;0;;50,FLT_CARRIER,,proxy,proxy;name:Thinq Carrier,gwgroup:7,addr_id:70 51;FLT_CARRIER;216.147.191.157;0;;51,FLT_CARRIER,,proxy,proxy;name:Voxtelesys Carrier,gwgroup:8,addr_id:71 52;FLT_CARRIER;64.34.181.47;0;;52,FLT_CARRIER,,proxy,proxy;name:Les.net Carrier,gwgroup:9,addr_id:72 53;FLT_CARRIER;206.80.250.100;0;;53,FLT_CARRIER,,proxy,proxy;name:ThinkTel,gwgroup:10,addr_id:73 54;FLT_CARRIER;208.68.17.52;0;;54,FLT_CARRIER,,proxy,proxy;name:ThinkTel,gwgroup:10,addr_id:74 55;FLT_CARRIER;209.197.130.80;0;;55,FLT_CARRIER,,proxy,proxy;name:ThinkTel,gwgroup:10,addr_id:75 ================================================ FILE: kamailio/defaults/dr_gateways.sql ================================================ -- update dr_gateways schema ALTER TABLE dr_gateways MODIFY COLUMN `address` VARCHAR(253) NOT NULL, MODIFY COLUMN `pri_prefix` VARCHAR(64) NOT NULL DEFAULT '', MODIFY COLUMN `attrs` VARCHAR(255) NOT NULL DEFAULT '', MODIFY COLUMN `description` VARCHAR(255) NOT NULL DEFAULT ''; -- update dr_gateways attrs column when entry created DROP TRIGGER IF EXISTS insert_dr_gateways; DELIMITER // CREATE TRIGGER insert_dr_gateways BEFORE INSERT ON dr_gateways FOR EACH ROW BEGIN -- set explicit defaults IF (NEW.gwid = 0) THEN SET NEW.gwid = NULL; END IF; IF (NEW.attrs IS NULL) THEN SET NEW.attrs = ''; END IF; SET @new_gwid := COALESCE(NEW.gwid, @new_gwid, ( SELECT auto_increment FROM information_schema.tables WHERE table_name = 'dr_gateways' AND table_schema = DATABASE())); -- only rewrite gwid,type part of attrs SET NEW.attrs = CONCAT(CAST(@new_gwid AS char), ',', CAST(NEW.type AS char), SUBSTRING(NEW.attrs, LENGTH(SUBSTRING_INDEX(NEW.attrs, ',', 2)) + 1)); SET @new_gwid = @new_gwid + 1; END;// DELIMITER ; -- update dr_gateways attrs column when entry updated DROP TRIGGER IF EXISTS update_dr_gateways; DELIMITER // CREATE TRIGGER update_dr_gateways BEFORE UPDATE ON dr_gateways FOR EACH ROW BEGIN -- only rewrite gwid,type part of attrs SET NEW.attrs = CONCAT(CAST(NEW.gwid AS char), ',', CAST(NEW.type AS char), SUBSTRING(NEW.attrs, LENGTH(SUBSTRING_INDEX(NEW.attrs, ',', 2)) + 1)); END;// DELIMITER ; ================================================ FILE: kamailio/defaults/dr_gw_lists.csv ================================================ 1;1,2,3,4,5,6,7,8;name:Twilio NA Inbound CarrierGroup,type:8,lb:1 2;9,10,11,12,13,14,15,16,17,18,19;name:Skyetel CarrierGroup,type:8,lb:2 3;21,23;name:Flowroute CarrierGroup,type:8,lb:3 4;24,25,26,27,28,29;name:Voxbone CarrierGroup,type:8,lb:4 5;30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45;name:VoIP Innovations Inbound CarrierGroup,type:8,lb:5 6;46,47,48,49;name:VoIP Innovations Outbound Conversational CarrierGroup,type:8,lb:6 7;50;name:Thinq CarrierGroup,type:8,lb:7 8;51;name:Voxtelesys CarrierGroup,type:8,lb:8 9;52;name:Les.net CarrierGroup,type:8,lb:9 10;53,54,55;name:ThinkTel CarrierGroup,type:8,lb:10 ================================================ FILE: kamailio/defaults/dr_gw_lists.sql ================================================ -- update dr_gw_lists schema to fit our storage requirements ALTER TABLE dr_gw_lists MODIFY description varchar(255) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/defaults/dr_rules.csv ================================================ 1;FLT_OUTBOUND;;;;;#2;name:Default Outbound Route ================================================ FILE: kamailio/defaults/dr_rules.sql ================================================ -- update dr_rules schema to fit our storage requirements ALTER TABLE dr_rules MODIFY description varchar(255) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/defaults/dsip_call_settings.sql ================================================ DROP TABLE IF EXISTS `dsip_call_settings`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_call_settings` ( `gwgroupid` INT UNSIGNED NOT NULL, `limit` INT UNSIGNED DEFAULT NULL, `timeout` INT UNSIGNED DEFAULT NULL, PRIMARY KEY (`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS `dsip_call_settings_h`; DROP VIEW IF EXISTS `dsip_call_settings_h`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE VIEW `dsip_call_settings_h` AS SELECT CAST(gwgroupid AS char) AS gwgroupid, CAST(`limit` AS char) AS `limit`, CAST(timeout AS char) AS timeout FROM dsip_call_settings; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_cdrinfo.sql ================================================ DROP TABLE IF EXISTS `dsip_cdrinfo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_cdrinfo` ( `gwgroupid` int(11) NOT NULL, `email` varchar(255) NOT NULL DEFAULT '', `send_interval` varchar(255) NOT NULL DEFAULT '* * 1 * *', `last_sent` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`gwgroupid`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_forwarding.sql ================================================ -- dr_ruleid refers to owning inbound rule DROP TABLE IF EXISTS dsip_hardfwd; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE dsip_hardfwd ( dr_ruleid varchar(64) NOT NULL, did varchar(64) NOT NULL, dr_groupid varchar(64) NOT NULL, key_type varchar(64) NOT NULL DEFAULT '0', value_type varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (dr_ruleid) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- dr_ruleid refers to owning inbound rule DROP TABLE IF EXISTS dsip_failfwd; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE dsip_failfwd ( dr_ruleid varchar(64) NOT NULL, did varchar(64) NOT NULL, dr_groupid varchar(64) NOT NULL, key_type varchar(64) NOT NULL DEFAULT '0', value_type varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (dr_ruleid) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; DROP TABLE IF EXISTS dsip_prefix_mapping; DROP VIEW IF EXISTS dsip_prefix_mapping; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE VIEW dsip_prefix_mapping AS SELECT prefix, CAST(ruleid AS char) AS ruleid, CAST(priority AS char) AS priority, '0' AS key_type, '0' AS value_type FROM dr_rules WHERE groupid='FLT_INBOUND_REPLACE'; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_gw2gwgroup.sql ================================================ DROP TABLE IF EXISTS dsip_gw2gwgroup; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE dsip_gw2gwgroup ( gwid varchar(64) NOT NULL, gwgroupid varchar(64) NOT NULL, key_type varchar(64) NOT NULL DEFAULT '0', value_type varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (gwid, gwgroupid) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- create gw2gwgroup entries when dr_gw_lists entry created DROP TRIGGER IF EXISTS insert_gw2gwgroup; DELIMITER // CREATE TRIGGER insert_gw2gwgroup AFTER INSERT ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); -- loop through gwlist (1-based index) WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.gwlist, ',', gw_index), ',', -1), cast(NEW.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; END;// DELIMITER ; -- update gw2gwgroup entries when dr_gw_lists entry updated DROP TRIGGER IF EXISTS update_gw2gwgroup; DELIMITER // CREATE TRIGGER update_gw2gwgroup AFTER UPDATE ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; -- best approach is to delete OLD rows and create NEW ones IF NOT (NEW.gwlist <=> OLD.gwlist) THEN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(OLD.id AS char(64)); IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); -- loop through gwlist (1-based index) WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.gwlist, ',', gw_index), ',', -1), cast(NEW.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; -- only need to update gwid here ELSEIF NOT (NEW.id <=> OLD.id) THEN UPDATE dsip_gw2gwgroup SET gwgroupid = cast(NEW.id AS char(64)) WHERE gwgroupid = cast(OLD.id AS char(64)); END IF; END;// DELIMITER ; -- delete gw2gwgroup entries when dr_gw_lists entry deleted DROP TRIGGER IF EXISTS delete_gw2gwgroup; DELIMITER // CREATE TRIGGER delete_gw2gwgroup AFTER DELETE ON dr_gw_lists FOR EACH ROW BEGIN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(OLD.id AS char(64)); END;// DELIMITER ; ================================================ FILE: kamailio/defaults/dsip_gwgroup2lb.sql ================================================ DROP TABLE IF EXISTS dsip_gwgroup2lb; CREATE TABLE dsip_gwgroup2lb ( gwgroupid varchar(64) NOT NULL, setid varchar(64) NOT NULL, enabled char(1) NOT NULL DEFAULT '0', key_type varchar(64) NOT NULL DEFAULT '0', value_type varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (gwgroupid) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4; -- create dsip_gwgroup2lb entry when dr_gw_lists entry created with lb or lb_ext fields in description DROP TRIGGER IF EXISTS insert_gwgroup2lb; DELIMITER // CREATE TRIGGER insert_gwgroup2lb AFTER INSERT ON dr_gw_lists FOR EACH ROW BEGIN DECLARE v_setid varchar(64); SET @new_gwgroupid := COALESCE(NEW.id, @new_gwgroupid, ( SELECT auto_increment FROM information_schema.tables WHERE table_name = 'dr_gw_lists' AND table_schema = DATABASE())); IF NEW.description REGEXP '(?:lb:|lb_ext:)([0-9]+)' THEN SET v_setid = REGEXP_REPLACE(NEW.description, '.*(?:lb:|lb_ext:)([0-9]+).*', '\\1'); REPLACE INTO dsip_gwgroup2lb VALUES (CAST(@new_gwgroupid AS char), CAST(v_setid AS char), DEFAULT, DEFAULT, DEFAULT); END IF; SET @new_gwgroupid = @new_gwgroupid + 1; END; // DELIMITER ; -- update dsip_gwgroup2lb entry when dr_gw_lists entry description is updated DROP TRIGGER IF EXISTS update_gwgroup2lb; DELIMITER // CREATE TRIGGER update_gwgroup2lb AFTER UPDATE ON dr_gw_lists FOR EACH ROW BEGIN DECLARE v_gwgroupid varchar(64) DEFAULT NULL; DECLARE v_setid varchar(64) DEFAULT NULL; -- always update description changed IF NOT (NEW.description <=> OLD.description) THEN -- in case the gwgroupid changed SET v_gwgroupid = CAST(COALESCE(NEW.id, OLD.id) AS char); -- make sure we have a setid IF NEW.description REGEXP '(?:lb:|lb_ext:)([0-9]+)' THEN SET v_setid = REGEXP_REPLACE(NEW.description, '.*(?:lb:|lb_ext:)([0-9]+).*', '\\1'); INSERT INTO dsip_gwgroup2lb VALUES(v_gwgroupid, v_setid, DEFAULT, DEFAULT, DEFAULT) ON DUPLICATE KEY UPDATE setid=v_setid; END IF; END IF; END; // DELIMITER ; -- delete dsip_gwgroup2lb entry when dr_gw_lists entry deleted DROP TRIGGER IF EXISTS delete_gwgroup2lb; DELIMITER // CREATE TRIGGER delete_gwgroup2lb AFTER DELETE ON dr_gw_lists FOR EACH ROW BEGIN DELETE FROM dsip_gwgroup2lb WHERE gwgroupid = cast(OLD.id AS char); END; // DELIMITER ; -- update dsip_gwgroup2lb when dr_rules are created DROP TRIGGER IF EXISTS insert_rule_gwgroup2lb; DELIMITER // CREATE TRIGGER insert_rule_gwgroup2lb AFTER INSERT ON dr_rules FOR EACH ROW BEGIN -- only inbound routes can have load balancing associated with it IF (NEW.groupid = 9000) THEN IF (NEW.description REGEXP 'lb_enabled:1(,|$)') THEN UPDATE dsip_gwgroup2lb SET enabled = '1' WHERE gwgroupid = REPLACE(NEW.gwlist, '#', ''); ELSE UPDATE dsip_gwgroup2lb SET enabled = '0' WHERE gwgroupid = REPLACE(NEW.gwlist, '#', ''); END IF; END IF; END; // DELIMITER ; -- update dsip_gwgroup2lb when dr_rules are updated DROP TRIGGER IF EXISTS update_rule_gwgroup2lb; DELIMITER // CREATE TRIGGER update_rule_gwgroup2lb AFTER UPDATE ON dr_rules FOR EACH ROW BEGIN DECLARE v_gwgroupid varchar(64) DEFAULT NULL; DECLARE v_description varchar(255) DEFAULT ''; DECLARE v_groupid varchar(255) DEFAULT ''; SET v_gwgroupid = REPLACE(COALESCE(NEW.gwlist, OLD.gwlist), '#', ''); SET v_description = COALESCE(NEW.description, OLD.description); SET v_groupid = CAST(COALESCE(NEW.groupid, OLD.groupid) AS int); -- only inbound routes can have load balancing associated with it IF (v_groupid = 9000) THEN IF (v_description REGEXP 'lb_enabled:1(,|$)') THEN UPDATE dsip_gwgroup2lb SET enabled = '1' WHERE gwgroupid = v_gwgroupid; ELSE UPDATE dsip_gwgroup2lb SET enabled = '0' WHERE gwgroupid = v_gwgroupid; END IF; END IF; END; // DELIMITER ; -- update dsip_gwgroup2lb when dr_rules are deleted DROP TRIGGER IF EXISTS delete_rule_gwgroup2lb; DELIMITER // CREATE TRIGGER delete_rule_gwgroup2lb AFTER DELETE ON dr_rules FOR EACH ROW BEGIN -- only inbound routes can have load balancing associated with it IF (OLD.groupid = 9000) THEN -- if it is the last rule for the gwgroup then delete load balancing entry IF ((SELECT COUNT(ruleid) FROM dr_rules WHERE gwlist=OLD.gwlist AND groupid=OLD.groupid AND ruleid!=OLD.ruleid) = 0) THEN DELETE FROM dsip_gwgroup2lb WHERE gwgroupid=REPLACE(OLD.gwlist, '#', ''); END IF; END IF; END; // DELIMITER ; ================================================ FILE: kamailio/defaults/dsip_lcr.sql ================================================ DROP TABLE IF EXISTS `dsip_lcr`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_lcr` ( `pattern` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `dr_groupid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `cost` decimal(3,2) NOT NULL DEFAULT '0.0', `from_prefix` varchar(64) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`pattern`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_maintmode.sql ================================================ DROP TABLE IF EXISTS `dsip_maintmode`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_maintmode` ( `ipaddr` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `gwid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `status` TINYINT NOT NULL DEFAULT '1', PRIMARY KEY (`ipaddr`) ) ENGINE = InnoDB DEFAULT CHARSET = utf8; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_notification.sql ================================================ DROP TABLE IF EXISTS `dsip_notification`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_notification` ( `gwgroupid` int(11) NOT NULL, `type` int(11) NOT NULL, `method` int(11) DEFAULT NULL, `value` varchar(255) DEFAULT NULL, `createdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`gwgroupid`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; ================================================ FILE: kamailio/defaults/dsip_settings.sql ================================================ -- constant values must be added when pre-processing the file (by calling script) -- using user-defined variables allows us to keep syntax highlighting in our IDEs, but note they won't work without replacement -- the following strings are expected to be replaced by the pre-processing script (INCLUDE THE PRECEDING @ SYMBOL!): -- HASHED_CREDS_ENCODED_MAX_LEN -- AESCTR_CREDS_ENCODED_MAX_LEN -- DB representation of settings.py with non-db backed settings left out DROP TABLE IF EXISTS dsip_settings; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE dsip_settings ( DSIP_ID VARBINARY(@HASHED_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, DSIP_CLUSTER_ID INT UNSIGNED NOT NULL DEFAULT 1, DSIP_CLUSTER_SYNC TINYINT(1) NOT NULL DEFAULT 1, DSIP_PROTO VARCHAR(16) NOT NULL DEFAULT 'http', DSIP_PORT INT NOT NULL DEFAULT 5000, DSIP_USERNAME VARCHAR(255) NOT NULL DEFAULT 'admin', DSIP_PASSWORD VARBINARY(@HASHED_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, DSIP_IPC_PASS VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, DSIP_API_PROTO VARCHAR(16) NOT NULL DEFAULT 'http', DSIP_API_PORT INT NOT NULL DEFAULT 5000, DSIP_PRIV_KEY VARCHAR(255) NOT NULL DEFAULT '/etc/dsiprouter/privkey', DSIP_PID_FILE VARCHAR(255) NOT NULL DEFAULT '/run/dsiprouter/dsiprouter.pid', DSIP_UNIX_SOCK VARCHAR(255) NOT NULL DEFAULT '/run/dsiprouter/dsiprouter.sock', DSIP_IPC_SOCK VARCHAR(255) NOT NULL DEFAULT '/run/dsiprouter/ipc.sock', DSIP_API_TOKEN VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, DSIP_LOG_LEVEL INT NOT NULL DEFAULT 3, DSIP_LOG_FACILITY INT NOT NULL DEFAULT 18, DSIP_SSL_KEY VARCHAR(255) NOT NULL DEFAULT '', DSIP_SSL_CERT VARCHAR(255) NOT NULL DEFAULT '', DSIP_SSL_CA VARCHAR(255) NOT NULL DEFAULT '/etc/dsiprouter/certs/ca-list.pem', DSIP_SSL_EMAIL VARCHAR(255) NOT NULL DEFAULT '', DSIP_CERTS_DIR VARCHAR(255) NOT NULL DEFAULT '/etc/dsiprouter/certs', VERSION VARCHAR(32) NOT NULL, DEBUG TINYINT(1) NOT NULL DEFAULT 0, `ROLE` VARCHAR(32) NOT NULL DEFAULT '', GUI_INACTIVE_TIMEOUT INT UNSIGNED NOT NULL DEFAULT 20, KAM_DB_HOST VARCHAR(255) NOT NULL DEFAULT 'localhost', KAM_DB_DRIVER VARCHAR(255) NOT NULL DEFAULT '', KAM_DB_TYPE VARCHAR(255) NOT NULL DEFAULT 'mysql', KAM_DB_PORT VARCHAR(255) NOT NULL DEFAULT '3306', KAM_DB_NAME VARCHAR(255) NOT NULL DEFAULT 'kamailio', KAM_DB_USER VARCHAR(255) NOT NULL DEFAULT 'kamailio', KAM_DB_PASS VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, KAM_KAMCMD_PATH VARCHAR(255) NOT NULL DEFAULT '/usr/sbin/kamcmd', KAM_CFG_PATH VARCHAR(255) NOT NULL DEFAULT '/etc/kamailio/kamailio.cfg', KAM_TLSCFG_PATH VARCHAR(255) NOT NULL DEFAULT '/etc/kamailio/tls.cfg', RTP_CFG_PATH VARCHAR(255) NOT NULL DEFAULT '/etc/kamailio/kamailio.cfg', FLT_CARRIER INT NOT NULL DEFAULT 8, FLT_PBX INT NOT NULL DEFAULT 9, FLT_MSTEAMS INT NOT NULL DEFAULT 17, FLT_OUTBOUND INT NOT NULL DEFAULT 8000, FLT_INBOUND INT NOT NULL DEFAULT 9000, FLT_LCR_MIN INT NOT NULL DEFAULT 10000, FLT_FWD_MIN INT NOT NULL DEFAULT 20000, DEFAULT_AUTH_DOMAIN VARCHAR(255) NOT NULL DEFAULT 'sip.dsiprouter.org', TELEBLOCK_GW_ENABLED TINYINT(1) NOT NULL DEFAULT 0, TELEBLOCK_GW_IP VARCHAR(255) NOT NULL DEFAULT '62.34.24.22', TELEBLOCK_GW_PORT VARCHAR(255) NOT NULL DEFAULT '5066', TELEBLOCK_MEDIA_IP VARCHAR(255) NOT NULL DEFAULT '', TELEBLOCK_MEDIA_PORT VARCHAR(255) NOT NULL DEFAULT '', FLOWROUTE_ACCESS_KEY VARCHAR(255) NOT NULL DEFAULT '', FLOWROUTE_SECRET_KEY VARCHAR(255) NOT NULL DEFAULT '', FLOWROUTE_API_ROOT_URL VARCHAR(255) NOT NULL DEFAULT 'https://api.flowroute.com/v2', HOMER_ID BIGINT NOT NULL, HOMER_HEP_HOST VARCHAR(255) NOT NULL DEFAULT '', HOMER_HEP_PORT INT NOT NULL DEFAULT 9060, NETWORK_MODE INT NOT NULL DEFAULT 0, IPV6_ENABLED TINYINT(1) NOT NULL DEFAULT 0, INTERNAL_IP_ADDR VARCHAR(255) NOT NULL DEFAULT '', INTERNAL_IP_NET VARCHAR(255) NOT NULL DEFAULT '', INTERNAL_IP6_ADDR VARCHAR(255) NOT NULL DEFAULT '', INTERNAL_IP6_NET VARCHAR(255) NOT NULL DEFAULT '', INTERNAL_FQDN VARCHAR(255) NOT NULL DEFAULT '', EXTERNAL_IP_ADDR VARCHAR(255) NOT NULL DEFAULT '', EXTERNAL_IP6_ADDR VARCHAR(255) NOT NULL DEFAULT '', EXTERNAL_FQDN VARCHAR(255) NOT NULL DEFAULT '', PUBLIC_IFACE VARCHAR(255) NOT NULL DEFAULT '', PRIVATE_IFACE VARCHAR(255) NOT NULL DEFAULT '', UPLOAD_FOLDER VARCHAR(255) NOT NULL DEFAULT '/tmp', MAIL_SERVER VARCHAR(255) NOT NULL DEFAULT 'smtp.gmail.com', MAIL_PORT INT NOT NULL DEFAULT 587, MAIL_USE_TLS TINYINT(1) NOT NULL DEFAULT 1, MAIL_USERNAME VARCHAR(255) NOT NULL DEFAULT '', MAIL_PASSWORD VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN) COLLATE 'binary' NOT NULL, MAIL_ASCII_ATTACHMENTS TINYINT(1) NOT NULL DEFAULT 0, MAIL_DEFAULT_SENDER VARCHAR(255) NOT NULL DEFAULT 'DoNotReply@smtp.gmail.com', MAIL_DEFAULT_SUBJECT VARCHAR(255) NOT NULL DEFAULT 'dSIPRouter System Notification', DSIP_LICENSE_STORE BLOB NOT NULL, RTPENGINE_URI VARCHAR(255) NOT NULL DEFAULT 'udp:localhost:7722', CHECK (`ROLE` IN ('', 'outbound', 'inout')), PRIMARY KEY (DSIP_ID) ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 MIN_ROWS = 1; /*!40101 SET character_set_client = @saved_cs_client */; -- Update dsip_settings table for current dsiprouter instance, then -- Sync changes to dsip_settings across same cluster with the following exceptions: -- DSIP_ID is unchanged, it is associated to a single node -- DSIP_CLUSTER_ID is unchanged -- DSIP_CLUSTER_SYNC is unchanged -- HOMER_ID is unchanged, it is associated to a single node -- NETWORK_MODE is unchanged -- IPV6_ENABLED is unchanged -- INTERNAL_IP_ADDR is unchanged -- INTERNAL_IP_NET is unchanged -- INTERNAL_IP6_ADDR is unchanged -- INTERNAL_IP6_NET is unchanged -- INTERNAL_FQDN is unchanged -- EXTERNAL_IP_ADDR is unchanged -- EXTERNAL_IP6_ADDR is unchanged -- EXTERNAL_FQDN is unchanged -- PUBLIC_IFACE is unchanged -- PRIVATE_IFACE is unchanged -- DSIP_LICENSE_STORE is unchanged, it is associated to a single node DROP PROCEDURE IF EXISTS update_dsip_settings; DELIMITER // CREATE PROCEDURE update_dsip_settings( IN NEW_DSIP_ID VARBINARY(@HASHED_CREDS_ENCODED_MAX_LEN), IN NEW_DSIP_CLUSTER_ID INT UNSIGNED, IN NEW_DSIP_CLUSTER_SYNC TINYINT(1), IN NEW_DSIP_PROTO VARCHAR(16), IN NEW_DSIP_PORT INT, IN NEW_DSIP_USERNAME VARCHAR(255), IN NEW_DSIP_PASSWORD VARBINARY(@HASHED_CREDS_ENCODED_MAX_LEN), IN NEW_DSIP_IPC_PASS VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN), IN NEW_DSIP_API_PROTO VARCHAR(16), IN NEW_DSIP_API_PORT INT, IN NEW_DSIP_PRIV_KEY VARCHAR(255), IN NEW_DSIP_PID_FILE VARCHAR(255), IN NEW_DSIP_UNIX_SOCK VARCHAR(255), IN NEW_DSIP_IPC_SOCK VARCHAR(255), IN NEW_DSIP_API_TOKEN VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN), IN NEW_DSIP_LOG_LEVEL INT, IN NEW_DSIP_LOG_FACILITY INT, IN NEW_DSIP_SSL_KEY VARCHAR(255), IN NEW_DSIP_SSL_CERT VARCHAR(255), IN NEW_DSIP_SSL_CA VARCHAR(255), IN NEW_DSIP_SSL_EMAIL VARCHAR(255), IN NEW_DSIP_CERTS_DIR VARCHAR(255), IN NEW_VERSION VARCHAR(32), IN NEW_DEBUG TINYINT(1), IN NEW_ROLE VARCHAR(32), IN NEW_GUI_INACTIVE_TIMEOUT INT UNSIGNED, IN NEW_KAM_DB_HOST VARCHAR(255), IN NEW_KAM_DB_DRIVER VARCHAR(255), IN NEW_KAM_DB_TYPE VARCHAR(255), IN NEW_KAM_DB_PORT VARCHAR(255), IN NEW_KAM_DB_NAME VARCHAR(255), IN NEW_KAM_DB_USER VARCHAR(255), IN NEW_KAM_DB_PASS VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN), IN NEW_KAM_KAMCMD_PATH VARCHAR(255), IN NEW_KAM_CFG_PATH VARCHAR(255), IN NEW_KAM_TLSCFG_PATH VARCHAR(255), IN NEW_RTP_CFG_PATH VARCHAR(255), IN NEW_FLT_CARRIER INT, IN NEW_FLT_PBX INT, IN NEW_FLT_MSTEAMS INT, IN NEW_FLT_OUTBOUND INT, IN NEW_FLT_INBOUND INT, IN NEW_FLT_LCR_MIN INT, IN NEW_FLT_FWD_MIN INT, IN NEW_DEFAULT_AUTH_DOMAIN VARCHAR(255), IN NEW_TELEBLOCK_GW_ENABLED TINYINT(1), IN NEW_TELEBLOCK_GW_IP VARCHAR(255), IN NEW_TELEBLOCK_GW_PORT VARCHAR(255), IN NEW_TELEBLOCK_MEDIA_IP VARCHAR(255), IN NEW_TELEBLOCK_MEDIA_PORT VARCHAR(255), IN NEW_FLOWROUTE_ACCESS_KEY VARCHAR(255), IN NEW_FLOWROUTE_SECRET_KEY VARCHAR(255), IN NEW_FLOWROUTE_API_ROOT_URL VARCHAR(255), IN NEW_HOMER_ID BIGINT, IN NEW_HOMER_HEP_HOST VARCHAR(255), IN NEW_HOMER_HEP_PORT INT, IN NEW_NETWORK_MODE INT, IN NEW_IPV6_ENABLED TINYINT(1), IN NEW_INTERNAL_IP_ADDR VARCHAR(255), IN NEW_INTERNAL_IP_NET VARCHAR(255), IN NEW_INTERNAL_IP6_ADDR VARCHAR(255), IN NEW_INTERNAL_IP6_NET VARCHAR(255), IN NEW_INTERNAL_FQDN VARCHAR(255), IN NEW_EXTERNAL_IP_ADDR VARCHAR(255), IN NEW_EXTERNAL_IP6_ADDR VARCHAR(255), IN NEW_EXTERNAL_FQDN VARCHAR(255), IN NEW_PUBLIC_IFACE VARCHAR(255), IN NEW_PRIVATE_IFACE VARCHAR(255), IN NEW_UPLOAD_FOLDER VARCHAR(255), IN NEW_MAIL_SERVER VARCHAR(255), IN NEW_MAIL_PORT INT, IN NEW_MAIL_USE_TLS TINYINT(1), IN NEW_MAIL_USERNAME VARCHAR(255), IN NEW_MAIL_PASSWORD VARBINARY(@AESCTR_CREDS_ENCODED_MAX_LEN), IN NEW_MAIL_ASCII_ATTACHMENTS TINYINT(1), IN NEW_MAIL_DEFAULT_SENDER VARCHAR(255), IN NEW_MAIL_DEFAULT_SUBJECT VARCHAR(255), IN NEW_DSIP_LICENSE_STORE BLOB, IN NEW_RTPENGINE_URI VARCHAR(255) ) BEGIN START TRANSACTION; REPLACE INTO dsip_settings VALUES (NEW_DSIP_ID, NEW_DSIP_CLUSTER_ID, NEW_DSIP_CLUSTER_SYNC, NEW_DSIP_PROTO, NEW_DSIP_PORT, NEW_DSIP_USERNAME, NEW_DSIP_PASSWORD, NEW_DSIP_IPC_PASS, NEW_DSIP_API_PROTO, NEW_DSIP_API_PORT, NEW_DSIP_PRIV_KEY, NEW_DSIP_PID_FILE, NEW_DSIP_UNIX_SOCK, NEW_DSIP_IPC_SOCK, NEW_DSIP_API_TOKEN, NEW_DSIP_LOG_LEVEL, NEW_DSIP_LOG_FACILITY, NEW_DSIP_SSL_KEY, NEW_DSIP_SSL_CERT, NEW_DSIP_SSL_CA, NEW_DSIP_SSL_EMAIL, NEW_DSIP_CERTS_DIR, NEW_VERSION, NEW_DEBUG, NEW_ROLE, NEW_GUI_INACTIVE_TIMEOUT, NEW_KAM_DB_HOST, NEW_KAM_DB_DRIVER, NEW_KAM_DB_TYPE, NEW_KAM_DB_PORT, NEW_KAM_DB_NAME, NEW_KAM_DB_USER, NEW_KAM_DB_PASS, NEW_KAM_KAMCMD_PATH, NEW_KAM_CFG_PATH, NEW_KAM_TLSCFG_PATH, NEW_RTP_CFG_PATH, NEW_FLT_CARRIER, NEW_FLT_PBX, NEW_FLT_MSTEAMS, NEW_FLT_OUTBOUND, NEW_FLT_INBOUND, NEW_FLT_LCR_MIN, NEW_FLT_FWD_MIN, NEW_DEFAULT_AUTH_DOMAIN, NEW_TELEBLOCK_GW_ENABLED, NEW_TELEBLOCK_GW_IP, NEW_TELEBLOCK_GW_PORT, NEW_TELEBLOCK_MEDIA_IP, NEW_TELEBLOCK_MEDIA_PORT, NEW_FLOWROUTE_ACCESS_KEY, NEW_FLOWROUTE_SECRET_KEY, NEW_FLOWROUTE_API_ROOT_URL, NEW_HOMER_ID, NEW_HOMER_HEP_HOST, NEW_HOMER_HEP_PORT, NEW_NETWORK_MODE, NEW_IPV6_ENABLED, NEW_INTERNAL_IP_ADDR, NEW_INTERNAL_IP_NET, NEW_INTERNAL_IP6_ADDR, NEW_INTERNAL_IP6_NET, NEW_INTERNAL_FQDN, NEW_EXTERNAL_IP_ADDR, NEW_EXTERNAL_IP6_ADDR, NEW_EXTERNAL_FQDN, NEW_PUBLIC_IFACE, NEW_PRIVATE_IFACE, NEW_UPLOAD_FOLDER, NEW_MAIL_SERVER, NEW_MAIL_PORT, NEW_MAIL_USE_TLS, NEW_MAIL_USERNAME, NEW_MAIL_PASSWORD, NEW_MAIL_ASCII_ATTACHMENTS, NEW_MAIL_DEFAULT_SENDER, NEW_MAIL_DEFAULT_SUBJECT, NEW_DSIP_LICENSE_STORE, NEW_RTPENGINE_URI); IF NEW_DSIP_CLUSTER_SYNC = 1 THEN UPDATE dsip_settings SET DSIP_PROTO = NEW_DSIP_PROTO, DSIP_PORT = NEW_DSIP_PORT, DSIP_USERNAME = NEW_DSIP_USERNAME, DSIP_PASSWORD = NEW_DSIP_PASSWORD, DSIP_IPC_PASS = NEW_DSIP_IPC_PASS, DSIP_API_PROTO = NEW_DSIP_API_PROTO, DSIP_API_PORT = NEW_DSIP_API_PORT, DSIP_PRIV_KEY = NEW_DSIP_PRIV_KEY, DSIP_PID_FILE = NEW_DSIP_PID_FILE, DSIP_UNIX_SOCK = NEW_DSIP_UNIX_SOCK, DSIP_IPC_SOCK = NEW_DSIP_IPC_SOCK, DSIP_API_TOKEN = NEW_DSIP_API_TOKEN, DSIP_LOG_LEVEL = NEW_DSIP_LOG_LEVEL, DSIP_LOG_FACILITY = NEW_DSIP_LOG_FACILITY, DSIP_SSL_KEY = NEW_DSIP_SSL_KEY, DSIP_SSL_CERT = NEW_DSIP_SSL_CERT, DSIP_SSL_CA = NEW_DSIP_SSL_CA, DSIP_SSL_EMAIL = NEW_DSIP_SSL_EMAIL, DSIP_CERTS_DIR = NEW_DSIP_CERTS_DIR, VERSION = NEW_VERSION, DEBUG = NEW_DEBUG, `ROLE` = NEW_ROLE, GUI_INACTIVE_TIMEOUT = NEW_GUI_INACTIVE_TIMEOUT, KAM_DB_HOST = NEW_KAM_DB_HOST, KAM_DB_DRIVER = NEW_KAM_DB_DRIVER, KAM_DB_TYPE = NEW_KAM_DB_TYPE, KAM_DB_PORT = NEW_KAM_DB_PORT, KAM_DB_NAME = NEW_KAM_DB_NAME, KAM_DB_USER = NEW_KAM_DB_USER, KAM_DB_PASS = NEW_KAM_DB_PASS, KAM_KAMCMD_PATH = NEW_KAM_KAMCMD_PATH, KAM_CFG_PATH = NEW_KAM_CFG_PATH, KAM_TLSCFG_PATH = NEW_KAM_TLSCFG_PATH, RTP_CFG_PATH = NEW_RTP_CFG_PATH, FLT_CARRIER = NEW_FLT_CARRIER, FLT_PBX = NEW_FLT_PBX, FLT_MSTEAMS = NEW_FLT_MSTEAMS, FLT_OUTBOUND = NEW_FLT_OUTBOUND, FLT_INBOUND = NEW_FLT_INBOUND, FLT_LCR_MIN = NEW_FLT_LCR_MIN, FLT_FWD_MIN = NEW_FLT_FWD_MIN, DEFAULT_AUTH_DOMAIN = NEW_DEFAULT_AUTH_DOMAIN, TELEBLOCK_GW_ENABLED = NEW_TELEBLOCK_GW_ENABLED, TELEBLOCK_GW_IP = NEW_TELEBLOCK_GW_IP, TELEBLOCK_GW_PORT = NEW_TELEBLOCK_GW_PORT, TELEBLOCK_MEDIA_IP = NEW_TELEBLOCK_MEDIA_IP, TELEBLOCK_MEDIA_PORT = NEW_TELEBLOCK_MEDIA_PORT, FLOWROUTE_ACCESS_KEY = NEW_FLOWROUTE_ACCESS_KEY, FLOWROUTE_SECRET_KEY = NEW_FLOWROUTE_SECRET_KEY, FLOWROUTE_API_ROOT_URL = NEW_FLOWROUTE_API_ROOT_URL, HOMER_HEP_HOST = NEW_HOMER_HEP_HOST, HOMER_HEP_PORT = NEW_HOMER_HEP_PORT, UPLOAD_FOLDER = NEW_UPLOAD_FOLDER, MAIL_SERVER = NEW_MAIL_SERVER, MAIL_PORT = NEW_MAIL_PORT, MAIL_USE_TLS = NEW_MAIL_USE_TLS, MAIL_USERNAME = NEW_MAIL_USERNAME, MAIL_PASSWORD = NEW_MAIL_PASSWORD, MAIL_ASCII_ATTACHMENTS = NEW_MAIL_ASCII_ATTACHMENTS, MAIL_DEFAULT_SENDER = NEW_MAIL_DEFAULT_SENDER, MAIL_DEFAULT_SUBJECT = NEW_MAIL_DEFAULT_SUBJECT, RTPENGINE_URI = NEW_RTPENGINE_URI WHERE DSIP_CLUSTER_ID = NEW_DSIP_CLUSTER_ID AND DSIP_CLUSTER_SYNC = 1 AND DSIP_ID != NEW_DSIP_ID; END IF; COMMIT; END // DELIMITER ; ================================================ FILE: kamailio/defaults/subscribers.sql ================================================ -- update schema for subscribers table ALTER TABLE subscriber ADD email_address varchar(128) NOT NULL DEFAULT '', ADD rpid varchar(128) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/defaults/uacreg.sql ================================================ -- update uacreg schema ALTER TABLE uacreg MODIFY COLUMN `l_domain` VARCHAR(253) NOT NULL DEFAULT '', MODIFY COLUMN `r_domain` VARCHAR(253) NOT NULL DEFAULT '', MODIFY COLUMN `realm` varchar(253) NOT NULL DEFAULT '', MODIFY COLUMN `auth_proxy` varchar(16000) NOT NULL DEFAULT ''; ================================================ FILE: kamailio/htable-kam57.patch ================================================ diff --git a/src/modules/htable/ht_api.c b/src/modules/htable/ht_api.c index 913d038748..2391cb5c5d 100644 --- a/src/modules/htable/ht_api.c +++ b/src/modules/htable/ht_api.c @@ -255,7 +255,7 @@ ht_t *ht_get_table(str *name) int ht_add_table(str *name, int autoexp, str *dbtable, str *dbcols, int size, int dbmode, int itype, int_str *ival, int updateexpire, - int dmqreplicate) + int dmqreplicate, char coldelim, char colnull) { unsigned int htid; ht_t *ht; @@ -342,8 +342,8 @@ int ht_add_table(str *name, int autoexp, str *dbtable, str *dbcols, int size, } ht->ncols = c + 1; ht->pack[0] = 'l'; - ht->pack[1] = ','; - ht->pack[2] = '*'; + ht->pack[1] = coldelim; + ht->pack[2] = colnull; } ht->next = _ht_root; @@ -957,6 +958,8 @@ int ht_table_spec(char *spec) unsigned int dbmode = 0; unsigned int updateexpire = 1; unsigned int dmqreplicate = 0; + char coldelim = ','; + char colnull = '*'; str in; str tok; param_t *pit = NULL; @@ -1023,13 +1026,34 @@ int ht_table_spec(char *spec) LM_DBG("htable [%.*s] - dmqreplicate [%u]\n", name.len, name.s, dmqreplicate); + } else if(pit->name.len == 8 + && strncmp(pit->name.s, "coldelim", 8) == 0) { + if(tok.len > 1) + goto error; + + coldelim = tok.s[0]; + LM_DBG("htable [%.*s] - coldelim [%c]\n", name.len, name.s, + coldelim); + } else if(pit->name.len == 7 + && strncmp(pit->name.s, "colnull", 7) == 0) { + if(tok.len > 1) + goto error; + + if(tok.len == 0) { + colnull = '\0'; + } else { + colnull = tok.s[0]; + } + + LM_DBG("htable [%.*s] - colnull [%c]\n", name.len, name.s, + colnull); } else { goto error; } } return ht_add_table(&name, autoexpire, &dbtable, &dbcols, size, dbmode, - itype, &ival, updateexpire, dmqreplicate); + itype, &ival, updateexpire, dmqreplicate, coldelim, colnull); error: LM_ERR("invalid htable parameter [%.*s]\n", in.len, in.s); diff --git a/src/modules/htable/ht_api.h b/src/modules/htable/ht_api.h index d8bdc2aab2..e24a93b1f1 100644 --- a/src/modules/htable/ht_api.h +++ b/src/modules/htable/ht_api.h @@ -88,7 +88,7 @@ typedef struct _ht_pv int ht_add_table(str *name, int autoexp, str *dbtable, str *dbcols, int size, int dbmode, int itype, int_str *ival, int updateexpire, - int dmqreplicate); + int dmqreplicate, char coldelim, char colnull); int ht_init_tables(void); int ht_destroy(void); int ht_set_cell(ht_t *ht, str *name, int type, int_str *val, int mode); diff --git a/src/modules/htable/ht_db.c b/src/modules/htable/ht_db.c index 631788b1a5..7a22ff6c48 100644 --- a/src/modules/htable/ht_db.c +++ b/src/modules/htable/ht_db.c @@ -121,7 +121,9 @@ static int ht_pack_values( len = 0; for(c = 1; c < cols; c++) { if(VAL_NULL(&RES_ROWS(db_res)[row].values[c])) { - len += 1; + if(ht->pack[2] != '\0') { + len += 1; + } } else if(RES_ROWS(db_res)[row].values[c].type == DB1_STRING) { len += strlen(RES_ROWS(db_res)[row].values[c].val.string_val); } else if(RES_ROWS(db_res)[row].values[c].type == DB1_STR) { @@ -143,8 +145,10 @@ static int ht_pack_values( p = vbuf; for(c = 1; c < cols; c++) { if(VAL_NULL(&RES_ROWS(db_res)[row].values[c])) { - *p = ht->pack[2]; - p++; + if(ht->pack[2] != '\0') { + *p = ht->pack[2]; + p++; + } } else if(RES_ROWS(db_res)[row].values[c].type == DB1_STRING) { strcpy(p, RES_ROWS(db_res)[row].values[c].val.string_val); p += strlen(RES_ROWS(db_res)[row].values[c].val.string_val); ================================================ FILE: kamailio/kamdbctl.patch ================================================ diff --git a/utils/kamctl/kamdbctl.base b/utils/kamctl/kamdbctl.base index 093334c024..488ebfa821 100644 --- a/utils/kamctl/kamdbctl.base +++ b/utils/kamctl/kamdbctl.base @@ -20,6 +20,9 @@ DBROUSER=${DBROUSER:-kamailioro} # password for read-only user DBROPW=${DBROPW:-kamailioro} +# address of database server for root coonections +DBROOTHOST=${DBROOTHOST:-$DBHOST} + # user name column USERCOL=${USERCOL:-username} diff --git a/utils/kamctl/kamdbctl.mysql b/utils/kamctl/kamdbctl.mysql index 81a730bbe6..49915dc795 100644 --- a/utils/kamctl/kamdbctl.mysql +++ b/utils/kamctl/kamdbctl.mysql @@ -27,23 +27,22 @@ fi # config vars ################################################################# -# full privileges MySQL user -if [ -z "$DBROOTUSER" ]; then - DBROOTUSER="root" -fi - # Set DBROOTPW in kamctlrc or via next line to set the database # root password if you want to run this script without any user prompt. # This is unsafe, but useful e.g. for automatic testing. #DBROOTPW="" - -if [ -z "$DBPORT" ] ; then - CMD="mysql -h $DBHOST -u$DBROOTUSER " - DUMP_CMD="mysqldump -h $DBHOST -u$DBROOTUSER -c -t " -else - CMD="mysql -h $DBHOST -P $DBPORT -u$DBROOTUSER " - DUMP_CMD="mysqldump -h $DBHOST -P $DBPORT -u$DBROOTUSER -c -t " +# build the client base commands one param at a time +# let the client choose defaults where not specified +CMD="mysql -h $DBROOTHOST" +DUMP_CMD="mysqldump -c -t -h $DBROOTHOST" +if [ -n "$DBROOTPORT" ] ; then + CMD="$CMD -P $DBROOTPORT" + DUMP_CMD="$DUMP_CMD -P $DBROOTPORT" +fi +if [ -n "$DBROOTUSER" ]; then + CMD="$CMD -u $DBROOTUSER" + DUMP_CMD="mysqldump -u $DBROOTUSER" fi ################################################################# ================================================ FILE: kamailio/modules/dsiprouter/Makefile ================================================ # # Usrloc module Makefile # # WARNING: do not run this directly, it should be run by the master Makefile include ../../Makefile.defs auto_gen= NAME=dsiprouter.so LIBS=-lssl -lcrypto SERLIBPATH=../../lib SER_LIBS+=$(SERLIBPATH)/srdb2/srdb2 include ../../Makefile.modules ================================================ FILE: kamailio/modules/dsiprouter/README.md ================================================ ## Instructions ### assumptions - You have dSIPRouter and Kamailio installed ### clone your kamailio version's branch: ``` KAM_VERSIONL=$(kamailio -v 2>/dev/null | grep '^version:' | awk '{print $3}' | sed -e 's/\([0-9]\.[0-9]\)\.[0-9]/\1/') rm -rf /tmp/kamailio 2>/dev/null git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git /tmp/kamailio ``` ### copy to src dir and compile: ``` DSIP_PROJECT_DIR=/opt/dsiprouter cp -rf ${DSIP_PROJECT_DIR}/kamailio/modules/dsiprouter/ /tmp/kamailio/src/modules/ cd /tmp/kamailio/src/modules/dsiprouter make ``` ### copy to deployment location: ``` MPATH=$(grep mpath /etc/kamailio/kamailio.cfg | awk 'NR==2' | awk '{print $3}') cp /tmp/kamailio/src/modules/dsiprouter/*.so $MPATH ``` ### load module in kamailio (/etc/kamailio/kamailio.cfg): ``` loadmodule "dsiprouter.so" ``` ### restart kamailio ``` systemctl restart kamailio ``` ## Notes - Anytime Kamailio is upgraded (even patch releases) you must recompile this module. ================================================ FILE: kamailio/modules/dsiprouter/mod_dsiprouter.c ================================================ /* * mod_dsiprouter.c */ /*! * \file * \brief dSipRouter Module Interface * \ingroup dsiprouter * Module: \ref dsiprouter */ /** * @defgroup dsiprouter dsiprouter :: Kamailio dsiprouter module * @brief Kamailio dsiprouter module */ #include <string.h> #include <stdio.h> #include "../../core/ver_defs.h" #include "../../core/sr_module.h" #include "../../core/mod_fix.h" #include "../../core/rpc.h" #include "../../core/rpc_lookup.h" #include "mod_funcs.h" MODULE_VERSION /* module params */ static char *license_file = "/etc/dsiprouter/license.txt"; static char *signature_file = "/etc/dsiprouter/license-sig.b64"; static char *uuid_file = "/etc/dsiprouter/uuid.txt"; /* Module management function prototypes */ static int mod_init(void); static int mod_child_init(int rank); static void mod_destroy(void); static int fixup_get_params(void **param, int param_no); static int fixup_get_params_free(void **param, int param_no); static int mod_health_check(); static void rpc_health_check(rpc_t *rpc, void *ctx); /* Exported functions */ static cmd_export_t cmds[] = { {"health_check", (cmd_function) mod_health_check, 1, fixup_get_params, fixup_get_params_free, ANY_ROUTE}, {0,0, 0,0,0} }; /* Exported rpc functions */ static const char *rpc_health_check_doc[2] = {"Check health of dsiprouter system.", 0}; static rpc_export_t rpc_cmds[] = { {"dsiprouter.health_check", rpc_health_check, rpc_health_check_doc,0}, {0,0,0,0} }; /* Exported parameters */ static param_export_t params[] = { {"license_file", PARAM_STRING, &license_file}, {"signature_file", PARAM_STRING, &signature_file}, {"uuid_file", PARAM_STRING, &uuid_file}, {0,0,0} }; /* Module interface */ module_exports_t exports = { "dsiprouter", /* module name */ DEFAULT_DLFLAGS, /* dlopen flags */ cmds, /* exported functions */ params, /* exported parameters */ 0, /* exported rpc functions */ 0, /* exported pseudo-variables */ 0, /* exported response function */ mod_init, /* exported initialization function */ mod_child_init, /* exported child initialization function */ mod_destroy, /* exported destroy function */ }; /* Module initialization function - The main initialization function will be called before any other function exported by the module. The function will be called only once, before the main process forks. This function is good for initialization that is common for all the children (processes). The function should return 0 if everything went OK and a negative error code otherwise. Server will abort if the function returns a negative value. */ static int mod_init(void) { LM_DBG("mod_dsiprouter initializing\n"); if (rpc_register_array(rpc_cmds) != 0) { LM_ERR("failed to register RPC commands\n"); return -1; } return 0; } static int mod_child_init(int rank) { return 0; } static void mod_destroy(void) { LM_DBG("mod_dsiprouter unloading\n"); } static int fixup_get_params(void **param, int param_no) { if (param_no == 1) { return fixup_pvar_null(param, 1); } LM_ERR("invalid parameter number <%d>\n", param_no); return -1; } static int fixup_get_params_free(void **param, int param_no) { if (param_no == 1) { return fixup_free_pvar_null(param, 1); } LM_ERR("invalid parameter number <%d>\n", param_no); return -1; } static int mod_health_check() { return validate_license(license_file, signature_file, uuid_file); } static void rpc_health_check(rpc_t *rpc, void *ctx) { if (!validate_license(license_file, signature_file, uuid_file)) { rpc->fault(ctx, 500, "Health Check Failed"); } else { rpc->rpl_printf(ctx, "Health Check Succeeded"); } } ================================================ FILE: kamailio/modules/dsiprouter/mod_funcs.c ================================================ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> #include <time.h> #include <openssl/pem.h> static char licensing_public_key[] = "-----BEGIN PUBLIC KEY-----\n" "MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0zcK4H4q9NWC4MkW7y2G\n" "V/Tm91U5pnL+VkzwlrXSI/Eh45pGeNfosSVN2NGQciEjeDrcbPdP4QbWguHIDDmi\n" "CJ0vFAMyHchNIJa5nt0QAW3V7nQ217PYLr0A3KVkVqGwR5+Z1i/1xEIuXy4ZHUqd\n" "pJlYfkmJIcGgGGpUDoZhEB1zLySutIxArmuMqj6DNt9fYfsMCYTBjmVY2IJfgNha\n" "zrLrQY+SNYjad1A0XuegZy48fKM9hqXR55ZO1yVZ3a7Mea9xwSsXcuAu3ZRL0kWt\n" "p/yNWqAco26fJ00veqVA+rOT0qhW6VmRn9eE4pJoOhkUXYnw2xY5yo0oROAnuQ18\n" "kZUzkfcHIVWjLqfK0+rW4Bmbx0jjYKZRo5kQKwWBghc+ASf9m5LARtj4qx9ihicl\n" "gUdhdEQr4sVSYPoqSj5BTH/oaC04qw2bwx/TKFM2+YZ6O6fee85Su4pYTRznzGL0\n" "7B4xReWpLfylAKkex+lmkVfeJ+O5ZwB/Id77oZhrghpi9ylMn+slopBnOyJlvz2t\n" "2z6DVi1Ryn1p59t1b4VTyMTot3QaMGD3y8KRDvooDfY5jDtANirG0W9ugXlBOCyT\n" "3ML7CaMgTcI24R33lVF/jtNfOMScKj+J9/d0qY6LIYf4U55oda4RUk+a++PW3fCm\n" "eOUVXKmEsIkzo5YsAokiMeUCAwEAAQ==\n" "-----END PUBLIC KEY-----\n"; /** * Read file into an allocated buffer * @param path file path * @return string or NULL */ char *readFile(char *path) { char *source = NULL; FILE *fp = fopen(path, "rb"); if (fp != NULL) { /* Go to the end of the file. */ if (fseek(fp, 0L, SEEK_END) == 0) { /* Get the size of the file. */ size_t bufsize = ftell(fp); if (bufsize == -1) { fprintf(stderr, "Error reading file %s\n", path); return NULL; } /* Allocate our buffer to that size. */ source = malloc(sizeof(char) * (bufsize + 1)); if (source == NULL) { fprintf(stderr, "Error reading file %s\n", path); return NULL; } /* Go back to the start of the file. */ if (fseek(fp, 0L, SEEK_SET) != 0) { fprintf(stderr, "Error reading file %s\n", path); free(source); return NULL; } /* Read the entire file into memory. */ size_t fileLen = fread(source, sizeof(char), bufsize, fp); if (ferror(fp) != 0) { fprintf(stderr, "Error reading file %s\n", path); free(source); return NULL; } else { source[fileLen++] = '\0'; /* Just to be safe. */ } } fclose(fp); } return source; } /** Function for tokenizing strings (extends strtok function) * @param str string to tokenize (in) * @param delims substring delimiters (in) * @param len length of the returned string array (out) * @return a ptr to an array of ptrs to strings * @note consecutive delims will return empty string in array * @note the return array is terminated with a null ptr '\0' */ char **strsplit(char *str, const char delims[], size_t *len) { char *save, *tok; /* holds tok val btwn calls */ char **result = NULL; /* set result to NULL */ char *tmp = strdup(str); /* leaves original str intact */ size_t delims_size = strlen(delims); size_t count = 0; /* number of main strings */ size_t sub_count = 0; /* number of substrings */ int i = 0; /* get number of delims in str */ do { tmp += delims_size; count++; } while ((tmp = strstr(tmp, delims)) != NULL); count++; /* add one for trailing token */ tmp = strdup(str); save = malloc(sizeof(char) * strlen(str)); result = malloc(sizeof(char *) * count); if (result && save) { while ((tok = strstr(tmp, delims)) != NULL) { strncpy(save, tmp, (tok - tmp)); save[(tok - tmp)] = '\0'; // printf("Token extracted: <<%s>>\n", save); result[i++] = strdup(save); tok += delims_size; tmp = tok; } /* grab trailing token */ if (tmp && tmp[0] != '\0') { result[i++] = strdup(tmp); // printf("Token extracted: <<%s>>\n", tmp); } /* set last ptr to NULL */ result[i] = '\0'; *len = count; /* pass num toks */ } else { /* set len to 0 on error */ *len = 0; } if (save) free(save); return result; } /** * Decodes a base64 encoded string * @param b64message * @param out_length * @return */ unsigned char *b64decode(char *b64message, size_t *out_length) { BIO *b64_bio, *mem_bio; size_t b64_len = strlen(b64message); unsigned char *base64_decoded = calloc((b64_len * 3) / 4 + 1, sizeof(char)); b64_bio = BIO_new(BIO_f_base64()); mem_bio = BIO_new(BIO_s_mem()); BIO_write(mem_bio, b64message, b64_len); BIO_push(b64_bio, mem_bio); if (b64message[b64_len-1] != '\n') { BIO_set_flags(b64_bio, BIO_FLAGS_BASE64_NO_NL); } int decoded_byte_index = 0; while (0 < BIO_read(b64_bio, base64_decoded + decoded_byte_index, 1)) { decoded_byte_index++; } *out_length = decoded_byte_index; BIO_free_all(b64_bio); return base64_decoded; } /** * Create an RSA key info struct * @param key * @param public * @return */ RSA *createRSA(unsigned char *key, int public) { RSA *rsa = NULL; BIO *keybio = NULL; keybio = BIO_new_mem_buf(key, -1); if (keybio == NULL) { fprintf(stderr, "Failed to create key BIO\n"); return NULL; } if (public) { rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL); } else { rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL); } if (rsa == NULL) { fprintf(stderr, "Failed to create RSA\n"); free(keybio); return NULL; } if (keybio) { BIO_free(keybio); } return rsa; } /** * Verify a binary signature using RSA * @param msg * @param msglen * @param sig * @param siglen * @param pubkey * @return */ int verifyRSA(const unsigned char *msg, size_t msglen, unsigned char *sig, size_t siglen, unsigned char *pubkey) { unsigned char hash[SHA512_DIGEST_LENGTH]; RSA *rsa = createRSA(pubkey, true); if (rsa == NULL) { goto verifyRSA_failure; } if (!SHA512(msg, msglen, hash)) { goto verifyRSA_failure; } if (!RSA_verify(NID_sha512, hash, sizeof(hash), sig, (unsigned int) siglen, rsa)) { goto verifyRSA_failure; } if (rsa) { RSA_free(rsa); } return true; verifyRSA_failure: if (rsa) { RSA_free(rsa); } return false; } /** * Validate a dsiprouter license * current license format: * dsiprouter_unique_id,license_type,expiration_date * @param license_file path to dsip license * @param signature_file path to license signature * @param uuid_file path to dsip uuid * @return true or false */ int validate_license(char *license_file, char *signature_file, char *uuid_file) { char *license = NULL, *dsip_uuid = NULL, *signature_b64 = NULL; unsigned char* signature = NULL; char **fields = NULL; size_t sig_len, fields_len = 0; int status = false; // get data from files license = readFile(license_file); dsip_uuid = readFile(uuid_file); signature_b64 = readFile(signature_file); if (license == NULL || dsip_uuid == NULL || signature_b64 == NULL) { goto validate_license_ret; } // validate license hasn't been changed using signature signature = b64decode(signature_b64, &sig_len); if (signature == NULL) { goto validate_license_ret; } if (!verifyRSA(license, strlen(license), signature, sig_len, licensing_public_key)) { goto validate_license_ret; } // parse fields from license fields = strsplit(license, ",", &fields_len); if (fields == NULL) { goto validate_license_ret; } // validate this license is valid for this instance if (strcmp(fields[0], dsip_uuid) != 0) { goto validate_license_ret; } // validate license type if (strcmp(fields[1], "enterprise") != 0) { goto validate_license_ret; } // validate expiration date time_t now = time(NULL); time_t expires = (time_t) atoll(fields[2]); if (difftime(expires, now) <= 0) { goto validate_license_ret; } // passed all checks status = true; validate_license_ret: if (license) { free(license); } if (signature_b64) { free(signature_b64); } if (signature) { free(signature); } if (dsip_uuid) { free(dsip_uuid); } if (fields) { free(fields); } return status; } /* paths in real application should be: * license_file: /etc/dsiprouter/license.txt * signature_file: /etc/dsiprouter/license-sig.b64 * uuid_file: /etc/dsiprouter/uuid.txt */ int main() { if (validate_license("resources/license-good.txt", "resources/license-good-sig.b64", "resources/dsip-uuid.txt")) { printf("license-good.txt: valid\n"); } else { printf("license-good.txt: invalid\n"); } if (validate_license("resources/license-bad.txt", "resources/license-bad-sig.b64", "resources/dsip-uuid.txt")) { printf("license-bad.txt: valid\n"); } else { printf("license-bad.txt: invalid\n"); } return EXIT_SUCCESS; } ================================================ FILE: kamailio/modules/dsiprouter/mod_funcs.h ================================================ #ifndef DSIPROUTER_LICENSING_MOD_FUNCS_H #define DSIPROUTER_LICENSING_MOD_FUNCS_H #include <stddef.h> #include <openssl/pem.h> char *readFile(char *path); char **strsplit(char *str, const char delims[], size_t *len); unsigned char *b64decode(char *b64message, size_t *out_length); RSA *createRSA(unsigned char *key, int public); int verifyRSA(const unsigned char *msg, size_t msglen, unsigned char *sig, size_t siglen, unsigned char *pubkey); int validate_license(char *license_file, char *signature_file, char *uuid_file); #endif //DSIPROUTER_LICENSING_MOD_FUNCS_H ================================================ FILE: kamailio/rhel/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) local OS_ARCH=$(uname -m) # Install Dependencies dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'Development Tools' dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${DISTRO_MAJOR_VER}.noarch.rpm dnf install -y psmisc curl wget sed gawk perl firewalld openssl-devel logrotate rsyslog python3 libuuid-devel \ libtool jansson-devel libcurl-devel libatomic python3-virtualenv policycoreutils-python-utils # we need a newer version of certbot than the distro repos offer dnf remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo dnf clean -y metadata dnf makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-sctp # workaround for kamailio rpm transaction failures if (( $? != 0 )); then rpm --import $(grep 'gpgkey' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2) REPOS='kamailio kamailio-ldap kamailio-mysql kamailio-postgresql kamailio-debuginfo kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress' for REPO in $REPOS; do yum install -y $(grep 'baseurl' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2)$(uname -m)/$(repoquery -i ${REPO} | head -4 | tail -n 3 | tr -d '[:blank:]' | cut -d ':' -f 2 | perl -pe 'chomp if eof' | tr '\n' '-').$(uname -m).rpm done fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl start firewalld systemctl enable firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Make sure MariaDB and Local DNS start before Kamailio if ! grep -q v 'mariadb.service dnsmasq.service' /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e 's/(After=.*)/\1 mariadb.service dnsmasq.service/' /lib/systemd/system/kamailio.service fi if ! grep -q v "${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig" /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e "0,\|^ExecStart.*|{s||ExecStartPre=-${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig\n&|}" /lib/systemd/system/kamailio.service fi systemctl daemon-reload # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules yum remove -y kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/rhel/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies { dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-$(uname -m)-rpms || dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-rhui-rpms } && dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-9.noarch.rpm && dnf groupinstall -y 'core' && dnf groupinstall -y 'base' && dnf groupinstall -y 'Development Tools' && dnf install -y git curl perl firewalld logrotate rsyslog certbot cmake libuuid-devel \ libcurl-devel libjwt-devel libatomic openssl-devel policycoreutils-python-utils \ libks-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio # TODO: fix upstream kamailio.repo file #dnf config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #dnf config-manager --disable 'kamailio*' && #dnf config-manager --enable "kamailio-$KAM_VERSION_DOTTED" && # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo dnf clean -y metadata dnf makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules dnf remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/rocky/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf config-manager --enable -y powertools && dnf install -y epel-release && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'core' && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'base' && dnf groupinstall --setopt=group_package_types=mandatory,default,optional -y 'Development Tools' && dnf install -y psmisc curl wget sed gawk vim perl firewalld logrotate rsyslog \ uuid openssl-devel libuuid-devel libjwt-devel libatomic bzip2-devel libffi-devel libcurl-devel \ python3.11 python3.11-pip policycoreutils-python-utils if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi # we need a newer version of certbot than the distro repos offer dnf remove -y *certbot* python3 -m venv --upgrade-deps /opt/certbot/ /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot yum install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' yum install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # create kamailio user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # Add the Kamailio repos to yum (cat << EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) > /etc/yum.repos.d/kamailio.repo dnf clean -y metadata dnf makecache -y dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket \ kamailio-postgresql kamailio-debuginfo kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls \ kamailio-presence kamailio-outbound kamailio-gzcompress kamailio-http_async_client kamailio-dmq_userloc \ kamailio-sctp # workaround for kamailio rpm transaction failures if (( $? != 0 )); then rpm --import $(grep 'gpgkey' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2) REPOS='kamailio kamailio-ldap kamailio-mysql kamailio-postgresql kamailio-debuginfo kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress' for REPO in $REPOS; do yum install -y $(grep 'baseurl' /etc/yum.repos.d/kamailio.repo | cut -d '=' -f 2)$(uname -m)/$(repoquery -i ${REPO} | head -4 | tail -n 3 | tr -d '[:blank:]' | cut -d ':' -f 2 | perl -pe 'chomp if eof' | tr '\n' '-').$(uname -m).rpm done fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes # STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl start firewalld systemctl enable firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Make sure MariaDB and Local DNS start before Kamailio if ! grep -q v 'mariadb.service dnsmasq.service' /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e 's/(After=.*)/\1 mariadb.service dnsmasq.service/' /lib/systemd/system/kamailio.service fi if ! grep -q v "${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig" /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e "0,\|^ExecStart.*|{s||ExecStartPre=-${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig\n&|}" /lib/systemd/system/kamailio.service fi systemctl daemon-reload # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules yum remove -y kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/rocky/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_MINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1.\2%' <<<"$KAM_VERSION") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install Dependencies dnf install -y epel-release && { # TODO: fix upstream kamailio.repo file #dnf config-manager -y --add-repo https://rpm.kamailio.org/centos/kamailio.repo && #dnf config-manager --disable 'kamailio*' && #dnf config-manager --enable "kamailio-$KAM_VERSION_DOTTED" && # Add the Kamailio repos to yum (cat <<EOF [kamailio] name=Kamailio baseurl=https://rpm.kamailio.org/centos/${RHEL_BASE_VER}/${KAM_MINOR_VERSION}/${KAM_VERSION}/\$basearch/ enabled=1 metadata_expire=30d gpgcheck=1 repo_gpgcheck=0 gpgkey=https://rpm.kamailio.org/rpm-pub.key type=rpm EOF ) >/etc/yum.repos.d/kamailio.repo && dnf makecache -y } && dnf groupinstall -y 'core' && dnf groupinstall -y 'base' && dnf groupinstall -y 'Development Tools' && dnf install -y git curl perl firewalld logrotate rsyslog certbot cmake libuuid-devel \ libcurl-devel libjwt-devel libatomic openssl-devel policycoreutils-python-utils \ libks-devel if (( $? != 0 )); then printerr 'Failed installing required packages' return 1 fi dnf install -y kernel-modules-extra-$(uname -r) || { printwarn 'could not install kernel modules for current kernel' echo 'upgrading kernel and installing new modules' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-modules-extra } if (( $? == 0 )); then echo 'sctp' >/etc/modules-load.d/sctp.conf sed -i -re 's%^blacklist sctp%#blacklist sctp%g' /etc/modprobe.d/* modprobe sctp else printwarn 'Could not install kernel modules for SCTP support. Continuing installation...' fi # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio dnf install -y kamailio kamailio-ldap kamailio-mysql kamailio-sipdump kamailio-websocket kamailio-postgresql kamailio-debuginfo \ kamailio-xmpp kamailio-unixodbc kamailio-utils kamailio-tls kamailio-presence kamailio-outbound kamailio-gzcompress \ kamailio-http_async_client kamailio-dmq_userloc kamailio-jansson kamailio-json kamailio-uuid kamailio-sctp if (( $? != 0 )); then printerr 'Failed installing kamailio packages' return 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # make sure run dir exists mkdir -p /var/run/kamailio chown -R kamailio:kamailio /var/run/kamailio # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf touch /etc/tmpfiles.d/kamailio.conf echo "d /run/kamailio 0750 kamailio users" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested cat <<EOF >${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # give kamailio permissions in SELINUX semanage port -a -t sip_port_t -p udp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIP_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIP_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_SIPS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_SIPS_PORT} semanage port -a -t sip_port_t -p tcp ${KAM_WSS_PORT} || semanage port -m -t sip_port_t -p tcp ${KAM_WSS_PORT} semanage port -a -t sip_port_t -p udp ${KAM_DMQ_PORT} || semanage port -m -t sip_port_t -p udp ${KAM_DMQ_PORT} # Start firewalld systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-bundle.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC CFLAGS='-Wno-deprecated-declarations' && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall { # Stop servers systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR} ${SYSTEM_KAMAILIO_CONFIG_DIR}.bak.$(date +%Y%m%d_%H%M%S) # Uninstall Kamailio modules dnf remove -y kamailio\* # remove our selinux changes semanage port -D -t sip_port_t -p udp semanage port -D -t sip_port_t -p tcp semanage port -D -t rabbitmq_port_t -p udp # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/stir_shaken.patch ================================================ diff --git a/include/stir_shaken.h b/include/stir_shaken.h index d408be5..4f5e08d 100644 --- a/include/stir_shaken.h +++ b/include/stir_shaken.h @@ -19,6 +19,7 @@ extern "C" { #include <pthread.h> +#include <openssl/opensslv.h> #include <openssl/crypto.h> #include <openssl/pem.h> #include <openssl/rand.h> diff --git a/src/stir_shaken.c b/src/stir_shaken.c index afe28e2..a755d6b 100644 --- a/src/stir_shaken.c +++ b/src/stir_shaken.c @@ -723,7 +723,13 @@ stir_shaken_status_t stir_shaken_is_key_trusted(stir_shaken_context_t *ss, EVP_P } // Let SSL confirm - if (!EVP_PKEY_cmp(pkey, candidate_pkey)) { + if (! +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_eq(pkey, candidate_pkey) +#else + EVP_PKEY_cmp(pkey, candidate_pkey) +#endif + ) { return STIR_SHAKEN_STATUS_FALSE; } diff --git a/test/stir_shaken_test_12.c b/test/stir_shaken_test_12.c index 43caa1e..265aa1f 100644 --- a/test/stir_shaken_test_12.c +++ b/test/stir_shaken_test_12.c @@ -101,7 +101,13 @@ stir_shaken_status_t stir_shaken_unit_test_x509_cert_path_verification(void) snprintf(ca.tn_auth_list_uri, STIR_SHAKEN_BUFLEN, "http://ca.com/api"); //sp.cert.x = stir_shaken_generate_x509_cert_from_csr(&ss, sp.code, sp.csr.req, ca.keys.private_key, ca.issuer_c, ca.issuer_cn, sp.serial, sp.expiry_days); pkey = X509_REQ_get_pubkey(sp.csr.req); - stir_shaken_assert(1 == EVP_PKEY_cmp(pkey, sp.keys.public_key), "Public key in CSR different than SP's"); + stir_shaken_assert(1 == +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_eq(pkey, sp.keys.public_key), +#else + EVP_PKEY_cmp(pkey, sp.keys.public_key), +#endif + "Public key in CSR different than SP's"); //sp.cert.x = stir_shaken_generate_x509_end_entity_cert(&ss, ca.cert.x, ca.keys.private_key, sp.keys.public_key, ca.issuer_c, ca.issuer_cn, sp.subject_c, sp.subject_cn, ca.serial_sp, ca.expiry_days_sp, ca.number_start_sp, ca.number_end_sp); sp.cert.x = stir_shaken_generate_x509_end_entity_cert_from_csr(&ss, ca.cert.x, ca.keys.private_key, ca.issuer_c, ca.issuer_cn, sp.csr.req, ca.serial, ca.expiry_days, ca.tn_auth_list_uri); PRINT_SHAKEN_ERROR_IF_SET diff --git a/test/stir_shaken_test_17.c b/test/stir_shaken_test_17.c index aa862b3..7fde1be 100644 --- a/test/stir_shaken_test_17.c +++ b/test/stir_shaken_test_17.c @@ -153,7 +153,13 @@ stir_shaken_status_t stir_shaken_unit_test_vs_verify(void) snprintf(ca.tn_auth_list_uri, STIR_SHAKEN_BUFLEN, "http://ca.com/api"); //sp.cert.x = stir_shaken_generate_x509_cert_from_csr(&ss, sp.code, sp.csr.req, ca.keys.private_key, ca.issuer_c, ca.issuer_cn, sp.serial, sp.expiry_days); pkey = X509_REQ_get_pubkey(sp.csr.req); - stir_shaken_assert(1 == EVP_PKEY_cmp(pkey, sp.keys.public_key), "Public key in CSR different than SP's"); + stir_shaken_assert(1 == +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_eq(pkey, sp.keys.public_key), +#else + EVP_PKEY_cmp(pkey, sp.keys.public_key), +#endif + "Public key in CSR different than SP's"); //sp.cert.x = stir_shaken_generate_x509_end_entity_cert(&ss, ca.cert.x, ca.keys.private_key, sp.keys.public_key, ca.issuer_c, ca.issuer_cn, sp.subject_c, sp.subject_cn, ca.serial_sp, ca.expiry_days_sp, ca.number_start_sp, ca.number_end_sp); sp.cert.x = stir_shaken_generate_x509_end_entity_cert_from_csr(&ss, ca.cert.x, ca.keys.private_key, ca.issuer_c, ca.issuer_cn, sp.csr.req, ca.serial, ca.expiry_days, ca.tn_auth_list_uri); PRINT_SHAKEN_ERROR_IF_SET ================================================ FILE: kamailio/systemd/kamailio-v1.service ================================================ [Unit] Description=Kamailio - the Open Source SIP Server Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=rsyslog.service dnsmasq.service mariadb.service rtpengine.service Wants=mariadb.service DefaultDependencies=no [Service] Type=forking User=kamailio Group=kamailio PermissionsStartOnly=true Environment='RUNDIR=/run/kamailio' EnvironmentFile=/etc/default/kamailio.conf # PIDFile requires a full absolute path PIDFile=/run/kamailio/kamailio.pid # Exec* requires a full absolute path ExecStartPre=/usr/bin/dsiprouter chown -certs -kamailio ExecStart=/usr/sbin/kamailio -P $PIDFILE -f $CFGFILE -m $SHM_MEMORY -M $PKG_MEMORY --atexit=no Restart=on-failure # necessary for chown of control files e.g. for jsonrpcs and ctl modules AmbientCapabilities=CAP_CHOWN [Install] WantedBy=multi-user.target ================================================ FILE: kamailio/systemd/kamailio-v2.service ================================================ [Unit] Description=Kamailio - the Open Source SIP Server Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=rsyslog.service dnsmasq.service mariadb.service rtpengine.service Wants=mariadb.service DefaultDependencies=no [Service] Type=forking User=kamailio Group=kamailio Environment='RUNDIR=/run/kamailio' EnvironmentFile=/etc/default/kamailio.conf EnvironmentFile=-/etc/default/kamailio.d/*.conf # PIDFile requires a full absolute path PIDFile=/run/kamailio/kamailio.pid # Exec* requires a full absolute path ExecStartPre=!-/usr/bin/dsiprouter chown -certs -kamailio ExecStart=/usr/sbin/kamailio -P $PIDFILE -f $CFGFILE -m $SHM_MEMORY -M $PKG_MEMORY --atexit=no Restart=on-failure # /run/kamailio in tmpfs RuntimeDirectory=kamailio RuntimeDirectoryMode=0770 # necessary for chown of control files e.g. for jsonrpcs and ctl modules AmbientCapabilities=CAP_CHOWN [Install] WantedBy=multi-user.target ================================================ FILE: kamailio/systemd/kamailio.conf ================================================ RUN_KAMAILIO=yes USER=kamailio GROUP=kamailio SHM_MEMORY=512 PKG_MEMORY=64 PIDFILE=/run/kamailio/kamailio.pid CFGFILE=/etc/kamailio/kamailio.cfg #DUMP_CORE=yes ================================================ FILE: kamailio/uac.patch ================================================ diff --git a/src/modules/uac/uac_reg.c b/src/modules/uac/uac_reg.c --- a/src/modules/uac/uac_reg.c (revision aa335e6138414f764dcf7287db271207f76bc6d8) +++ b/src/modules/uac/uac_reg.c (date 1713982080938) @@ -63,6 +63,7 @@ #define UAC_REG_DB_COLS_NUM 15 int _uac_reg_gc_interval = 150; +int _uac_reg_reload_delta = 30; typedef struct _reg_uac { @@ -393,11 +394,11 @@ tn = time(NULL); lock_get(_reg_htable_gc_lock); - if(_reg_htable_gc->stime > tn - _uac_reg_gc_interval) { + if(_reg_htable_gc->stime > tn - _uac_reg_reload_delta) { lock_release(_reg_htable_gc_lock); LM_ERR("shifting in-memory table is not possible in less than %d " "secs\n", - _uac_reg_gc_interval); + _uac_reg_reload_delta); return -1; } uac_reg_reset_ht_gc(); diff --git a/src/modules/uac/uac.c b/src/modules/uac/uac.c --- a/src/modules/uac/uac.c (revision aa335e6138414f764dcf7287db271207f76bc6d8) +++ b/src/modules/uac/uac.c (date 1713982956895) @@ -115,6 +115,7 @@ extern int reg_timer_interval; extern int _uac_reg_gc_interval; extern int _uac_reg_use_domain; +extern int _uac_reg_reload_delta; static pv_export_t mod_pvs[] = { {{"uac_req", sizeof("uac_req") - 1}, PVT_OTHER, pv_get_uac_req, @@ -187,7 +188,9 @@ {"reg_hash_size", INT_PARAM, &reg_htable_size}, {"reg_use_domain", PARAM_INT, &_uac_reg_use_domain}, {"default_socket", PARAM_STR, &uac_default_socket}, - {"event_callback", PARAM_STR, &uac_event_callback}, {0, 0, 0}}; + {"event_callback", PARAM_STR, &uac_event_callback}, + {"reload_delta", INT_PARAM, &_uac_reg_reload_delta}, + {0, 0, 0}}; struct module_exports exports = {"uac", /* module name */ ================================================ FILE: kamailio/ubuntu/20.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # nf_tables is the default fw on ubuntu but it has too many bugs at this time # instead we will use legacy iptables until these issues are ironed out update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy # Install Dependencies apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog firewalld \ python3 libcurl4-openssl-dev libjansson-dev cmake python3-venv if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi ## compile and install openssl v1.1.1 (workaround for amazon linux repo conflicts) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile openssl' return 1 } fi # we need a newer version of certbot than the distro repos offer apt-get remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} focal main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} focal main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo wget -O- https://deb-archive.kamailio.org/kamailiodebkey.gpg | apt-key add - # Update repo sources cache apt-get update -y # Install Kamailio packages apt-get install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules kamailio-tls-modules kamailio-websocket-modules \ kamailio-presence-modules kamailio-json-modules # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute drouting userblocklist htable purple uac pipelimit mtree sca mohqueue rtpproxy rtpengine secfilter" INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Make sure MariaDB and Local DNS start before Kamailio if ! grep -q -v 'mariadb.service dnsmasq.service' /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e 's/(After=.*)/\1 mariadb.service dnsmasq.service/' /lib/systemd/system/kamailio.service fi if ! grep -q -v "${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig" /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e "0,\|^ExecStart.*|{s||ExecStartPre=-${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig\n&|}" /lib/systemd/system/kamailio.service fi systemctl daemon-reload # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr && make && make install && ldconfig; exit $?; ) || { printerr 'Failed to compile and install libstirshaken'; return 1; } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall() { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd /libjwt; make uninstall; exit $?; ) && rm -rf /libjwt ( cd /libks; make uninstall; exit $?; ) && rm -rf /libks ( cd /libstirshaken; make uninstall;exit $?; ) && rm -rf /libstirshaken rm -rf /kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/ubuntu/22.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # nf_tables is the default fw on ubuntu but it has too many bugs at this time # instead we will use legacy iptables until these issues are ironed out update-alternatives --set iptables /usr/sbin/iptables-legacy update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy # Install Dependencies apt-get install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake firewalld python3 python3-venv if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi ## compile and install openssl v1.1.1 (workaround for amazon linux repo conflicts) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile openssl' return 1 } fi # we need a newer version of certbot than the distro repos offer apt-get remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # add repo sources to apt mkdir -p /etc/apt/sources.list.d (cat << EOF # kamailio repo's deb https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} jammy main #deb-src https://deb-archive.kamailio.org/repos/kamailio-${KAM_VERSION} jammy main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb-archive.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo wget -O- https://deb-archive.kamailio.org/kamailiodebkey.gpg | apt-key add - # Update repo sources cache apt-get update -y # Install Kamailio packages apt-get install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules kamailio-tls-modules \ kamailio-websocket-modules kamailio-presence-modules kamailio-json-modules kamailio-sctp-modules # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute drouting userblocklist htable purple uac pipelimit mtree sca mohqueue rtpproxy rtpengine secfilter" INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Make sure MariaDB and Local DNS start before Kamailio if ! grep -q -v 'mariadb.service dnsmasq.service' /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e 's/(After=.*)/\1 mariadb.service dnsmasq.service/' /lib/systemd/system/kamailio.service fi if ! grep -q -v "${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig" /lib/systemd/system/kamailio.service 2>/dev/null; then sed -i -r -e "0,\|^ExecStart.*|{s||ExecStartPre=-${DSIP_PROJECT_DIR}/dsiprouter.sh updatednsconfig\n&|}" /lib/systemd/system/kamailio.service fi systemctl daemon-reload # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( # TODO: commit updates to upstream to fix EVP_PKEY_cmp being deprecated cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr CFLAGS='-Wno-deprecated-declarations' LDFLAGS='-L/usr/lib' && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall() { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: kamailio/ubuntu/24.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { local KAM_DEBMINOR_VERSION=$(perl -pe 's%^([0-9])\.([0-9]).*$%\1\2%' <<<"$KAM_VERSION") local KAM_SOURCES_LIST="/etc/apt/sources.list.d/kamailio.list" local KAM_PREFS_CONF="/etc/apt/preferences.d/kamailio.pref" local NPROC=$(nproc) # Install Dependencies apt install -y curl wget sed gawk vim perl uuid-dev libssl-dev logrotate rsyslog \ libcurl4-openssl-dev libjansson-dev cmake firewalld python3 python3-venv if (( $? != 0 )); then printerr 'Failed installing required packages' exit 1 fi # we need a newer version of certbot than the distro repos offer apt remove -y *certbot* python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot # create kamailio user and group mkdir -p /var/run/kamailio # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel kamailio &>/dev/null; groupdel kamailio &>/dev/null useradd --system --user-group --shell /bin/false --comment "Kamailio SIP Proxy" kamailio chown -R kamailio:kamailio /var/run/kamailio # add repo sources to apt mkdir -p /etc/apt/sources.list.d # TODO: noble not available in the archive repos (cat << EOF # kamailio repo's deb https://deb.kamailio.org/kamailio${KAM_DEBMINOR_VERSION} noble main #deb-src https://deb-archive.kamailio.org/kamailio${KAM_DEBMINOR_VERSION} noble main EOF ) > ${KAM_SOURCES_LIST} # give higher precedence to packages from kamailio repo mkdir -p /etc/apt/preferences.d (cat << 'EOF' Package: * Pin: origin deb.kamailio.org Pin-Priority: 1000 EOF ) > ${KAM_PREFS_CONF} # Add Key for Kamailio Repo curl -s https://deb.kamailio.org/kamailiodebkey.gpg | gpg --dearmor >/etc/apt/trusted.gpg.d/kamailiodebkey.gpg # Update repo sources cache apt update -y # Install Kamailio packages apt install -y --allow-downgrades kamailio kamailio-mysql-modules kamailio-extra-modules \ kamailio-tls-modules kamailio-websocket-modules kamailio-presence-modules \ kamailio-json-modules kamailio-sctp-modules if (( $? != 0 )); then printerr 'Failed installing kamailio packages' exit 1 fi # get info about the kamailio install for later use in script KAM_MODULES_DIR=$(find /usr/lib{32,64,}/{i386*/*,i386*/kamailio/*,x86_64*/*,x86_64*/kamailio/*,*} -name drouting.so -printf '%h' -quit 2>/dev/null) # create kamailio defaults config cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio.conf /etc/default/kamailio.conf # create kamailio tmp files echo "d /run/kamailio 0750 kamailio kamailio" > /etc/tmpfiles.d/kamailio.conf # Configure Kamailio and Required Database Modules mkdir -p ${SYSTEM_KAMAILIO_CONFIG_DIR} ${BACKUPS_DIR}/kamailio mv -f ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc ${BACKUPS_DIR}/kamailio/kamctlrc.$(date +%Y%m%d_%H%M%S) if [[ -z "${ROOT_DB_PASS-unset}" ]]; then local ROOTPW_SETTING="DBROOTPWSKIP=yes" else local ROOTPW_SETTING="DBROOTPW=\"${ROOT_DB_PASS}\"" fi # TODO: we should set STORE_PLAINTEXT_PW to 0, this is not default but would need tested (cat << EOF DBENGINE=MYSQL DBHOST="${KAM_DB_HOST}" DBPORT="${KAM_DB_PORT}" DBNAME="${KAM_DB_NAME}" DBROUSER="${KAM_DB_USER}" DBROPW="${KAM_DB_PASS}" DBRWUSER="${KAM_DB_USER}" DBRWPW="${KAM_DB_PASS}" DBROOTUSER="${ROOT_DB_USER}" ${ROOTPW_SETTING} CHARSET=utf8 EXTRA_MODULES="imc cpl siptrace domainpolicy carrierroute drouting userblocklist htable purple uac pipelimit mtree sca mohqueue rtpproxy rtpengine secfilter" INSTALL_EXTRA_TABLES=yes INSTALL_PRESENCE_TABLES=yes INSTALL_DBUID_TABLES=yes #STORE_PLAINTEXT_PW=0 EOF ) > ${SYSTEM_KAMAILIO_CONFIG_DIR}/kamctlrc # in mariadb ver >= 10.6.1 --port= now defaults to transport=tcp # we want socket connections for root as default so apply our patch to kamdbctl # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd /usr/lib/x86_64-linux-gnu/kamailio/kamctl && patch -p3 -N <${DSIP_PROJECT_DIR}/kamailio/kamdbctl.patch ) if (( $? > 1 )); then printerr 'Failed patching kamdbctl' return 1 fi # Execute 'kamdbctl create' to create the Kamailio database schema kamdbctl create || { printerr 'Failed creating kamailio database' return 1 } # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup firewall rules firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --add-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --add-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --zone=public --add-port=22/tcp --permanent firewall-cmd --reload # Configure Kamailio systemd service cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/kamailio-v2.service /lib/systemd/system/kamailio.service chmod 644 /lib/systemd/system/kamailio.service systemctl daemon-reload systemctl enable kamailio # Enable Kamailio for system startup systemctl enable kamailio # Configure rsyslog defaults if ! grep -q 'dSIPRouter rsyslog.conf' /etc/rsyslog.conf 2>/dev/null; then cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rsyslog.conf /etc/rsyslog.conf fi # Setup kamailio Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/kamailio.conf /etc/rsyslog.d/kamailio.conf touch /var/log/kamailio.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/kamailio /etc/logrotate.d/kamailio # Setup Kamailio to use the CA cert's that are shipped with the OS mkdir -p ${DSIP_SYSTEM_CONFIG_DIR}/certs/stirshaken ln -s /etc/ssl/certs/ca-certificates.crt ${DSIP_SSL_CA} updateCACertsDir # setup STIR/SHAKEN module for kamailio ## compile and install libjwt (version in repos is too old) if [[ ! -d ${SRC_DIR}/libjwt ]]; then git clone --depth 1 -c advice.detachedHead=false -b v2.1.1 https://github.com/devopsec/libjwt.git ${SRC_DIR}/libjwt fi ( cd ${SRC_DIR}/libjwt && autoreconf -i && ./configure --prefix=/usr && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libjwt' return 1 } ## compile and install libks if [[ ! -d ${SRC_DIR}/libks ]]; then git clone --single-branch -c advice.detachedHead=false https://github.com/signalwire/libks -b v1.8.3 ${SRC_DIR}/libks fi ( cd ${SRC_DIR}/libks && cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release . && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libks' return 1 } ## compile and install libstirshaken if [[ ! -d ${SRC_DIR}/libstirshaken ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/signalwire/libstirshaken ${SRC_DIR}/libstirshaken fi ( # TODO: commit updates to upstream to fix EVP_PKEY_cmp being deprecated cd ${SRC_DIR}/libstirshaken && ./bootstrap.sh && ./configure --prefix=/usr CFLAGS='-Wno-deprecated-declarations' LDFLAGS='-L/usr/lib' && make -j $NPROC && make -j $NPROC install && ldconfig ) || { printerr 'Failed to compile and install libstirshaken' return 1 } ## compile and install STIR/SHAKEN module ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/kamailio ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/kamailio)" != "${KAM_VERSION}" ]]; then rm -rf ${SRC_DIR}/kamailio git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi else git clone --depth 1 -c advice.detachedHead=false -b ${KAM_VERSION} https://github.com/kamailio/kamailio.git ${SRC_DIR}/kamailio fi ( cd ${SRC_DIR}/kamailio/src/modules/stirshaken && make -j $NPROC ) && cp -f ${SRC_DIR}/kamailio/src/modules/stirshaken/stirshaken.so ${KAM_MODULES_DIR}/ || { printerr 'Failed to compile and install STIR/SHAKEN module' return 1 } # patch uac module to support reload_delta # TODO: commit upstream (https://github.com/kamailio/kamailio.git) ( cd ${SRC_DIR}/kamailio/src/modules/uac && patch -p4 -N <${DSIP_PROJECT_DIR}/kamailio/uac.patch (( $? > 1 )) && exit 1 make -j $NPROC && cp -f ${SRC_DIR}/kamailio/src/modules/uac/uac.so ${KAM_MODULES_DIR}/ ) || { printerr 'Failed to patch uac module' return 1 } return 0 } function uninstall() { # Stop and disable services systemctl stop kamailio systemctl disable kamailio # Backup kamailio configuration directory cp -rf ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${BACKUPS_DIR}/kamailio/ rm -rf ${SYSTEM_KAMAILIO_CONFIG_DIR} # Uninstall Stirshaken Required Packages ( cd ${SRC_DIR}/libjwt; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libjwt ( cd ${SRC_DIR}/libks; make uninstall; exit $?; ) && rm -rf ${SRC_DIR}/libks ( cd ${SRC_DIR}/libstirshaken; make uninstall;exit $?; ) && rm -rf ${SRC_DIR}/libstirshaken rm -rf ${SRC_DIR}/kamailio # Uninstall Kamailio modules apt-get -y remove --purge kamailio\* # Remove firewall rules that was created by us: firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/udp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIP_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_SIPS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_WSS_PORT}/tcp --permanent firewall-cmd --zone=public --remove-port=${KAM_DMQ_PORT}/udp --permanent firewall-cmd --reload # Remove kamailio Logging rm -f /etc/rsyslog.d/kamailio.conf # Remove logrotate settings rm -f /etc/logrotate.d/kamailio return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: mysql/almalinux/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages yum install -y mariadb mariadb-server # Setup mysql config locations in a reliable manner # Setup mysql config locations in a reliable manner ln -s /usr/share/mariadb /usr/share/mysql ln -s /var/log/mariadb /var/log/mysql rm -f ~/.my.cnf 2>/dev/null mkdir -p /var/run/mariadb chown -R mysql:mysql /var/run/mariadb /var/lib/mysql /var/log/mysql /usr/share/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # add in the original aliases (from debian repo) to mariadb.service #perl -0777 -i -pe 's|(\[Install\]\s+WantedBy.*?\n+)|\1Alias=mysql.service\nAlias=mysqld.service\n\n|gms' /lib/systemd/system/mariadb.service # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages yum remove -y mysql\* yum remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/almalinux/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server && if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages dnf remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/amzn/2.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages amazon-linux-extras enable mariadb10.5 >/dev/null yum makecache -y yum install -y mariadb mariadb-server # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # setup aliases and if db is remote replace with dummy service file reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop servers systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages yum remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/centos/7.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install dependencies yum install -y curl # setup the mariadb community repos for newer packages ( cd /tmp && curl -sLO https://r.mariadb.com/downloads/mariadb_repo_setup && chmod +x mariadb_repo_setup && ./mariadb_repo_setup --mariadb-server-version="mariadb-10.4" ) || { printerr 'Failed setting up mariadb package repos' return 1 } # install mysql packages yum install -y mariadb mariadb-server if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop servers systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -f /lib/systemd/system/mysql.service /lib/systemd/system/mysqld.service # Uninstall mysql / Mariadb packages yum remove -y mysql\* yum remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/centos/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages dnf remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/centos/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server && if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages dnf remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/debian/10.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y -t bullseye default-mysql-server mariadb-server # if db is remote don't run local service reconfigureMysqlSystemdService # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/debian/11.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y --allow-unauthenticated default-mysql-server || apt-get install -y --allow-unauthenticated mariadb-server # if db is remote don't run local service reconfigureMysqlSystemdService # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # ensure data directory exists mkdir -p /var/lib/mysql chown mysql:mysql /var/lib/mysql # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/debian/12.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y mariadb-server if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # if db is remote don't run local service reconfigureMysqlSystemdService # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # ensure data directory exists mkdir -p /var/lib/mysql chown mysql:mysql /var/lib/mysql # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # Uninstall mariadb packages apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/debian/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # install mysql packages apt-get install -y --allow-unauthenticated default-mysql-server || apt-get install -y --allow-unauthenticated mariadb-server # if db is remote don't run local service reconfigureMysqlSystemdService # Enable mysql on boot systemctl enable mariadb # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/rhel/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /usr/share/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # add in the original aliases (from debian repo) to mariadb.service #perl -0777 -i -pe 's|(\[Install\]\s+WantedBy.*?\n+)|\1Alias=mysql.service\nAlias=mysqld.service\n\n|gms' /lib/systemd/system/mariadb.service # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages yum remove -y mysql\* yum remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/rhel/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server && if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages dnf remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/rocky/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages yum install -y mariadb mariadb-server # Setup mysql config locations in a reliable manner # Setup mysql config locations in a reliable manner ln -s /usr/share/mariadb /usr/share/mysql ln -s /var/log/mariadb /var/log/mysql rm -f ~/.my.cnf 2>/dev/null mkdir -p /var/run/mariadb chown -R mysql:mysql /var/run/mariadb /var/lib/mysql /var/log/mysql /usr/share/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # add in the original aliases (from debian repo) to mariadb.service #perl -0777 -i -pe 's|(\[Install\]\s+WantedBy.*?\n+)|\1Alias=mysql.service\nAlias=mysqld.service\n\n|gms' /lib/systemd/system/mariadb.service # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages yum remove -y mysql\* yum remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/rocky/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages dnf install -y mariadb mariadb-server && if (( $? != 0 )); then printerr 'Failed installing mariadb packages' return 1 fi # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null ln -snf /usr/share/mariadb /usr/share/mysql ln -snf /var/log/mariadb /var/log/mysql mkdir -p /var/run/mariadb /var/lib/mysql chown -R mysql:mysql /var/run/mariadb/ /var/lib/mysql/ /var/log/mysql/ /usr/share/mysql/ /var/lib/mysql # allow symlinks in mariadb service sed -i 's/symbolic-links=0/#symbolic-links=0/' /etc/my.cnf # if db is remote don't run local service reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / Mariadb packages dnf remove -y mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/systemd/dummy.service ================================================ [Unit] Description=MySQL Dummy Service [Service] Type=oneshot ExecStart=/bin/true RemainAfterExit=true TimeoutSec=0 [Install] WantedBy=multi-user.target ================================================ FILE: mysql/systemd/override.conf ================================================ [Install] Alias= Alias=mysql.service Alias=mysqld.service ================================================ FILE: mysql/systemd/override.sh ================================================ #!/usr/bin/env bash DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # alias mariadb.service to mysql.service and mysqld.service using systemd drop-in replacements # allowing us to use same service name (mysql.service) across platforms mkdir -p /etc/systemd/system/mariadb.service.d cp -f ${DSIP_PROJECT_DIR}/mysql/systemd/override.conf /etc/systemd/system/mariadb.service.d/override.conf chmod 644 /etc/systemd/system/mariadb.service.d/override.conf ================================================ FILE: mysql/ubuntu/20.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y mariadb-server mariadb-client libmariadbd-dev # Setup mysql config locations in a reliable manner rm -f ~/.my.cnf 2>/dev/null mkdir -p /var/run/mariadb chown -R mysql:mysql /var/run/mariadb /var/lib/mysql /var/log/mysql /usr/share/mysql #( cd /lib/systemd/system/; ln -s mariadb.service mysqld.service; ln -s mariadb.service mysql.service; ) # setup aliases and if db is remote replace with dummy service file reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/ubuntu/22.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y mariadb-server mariadb-client # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # setup aliases and if db is remote replace with dummy service file reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: mysql/ubuntu/24.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # create mysql user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel mysql &>/dev/null; groupdel mysql &>/dev/null useradd --system --user-group --shell /bin/false --comment "Mysql Database Server" mysql # install mysql packages apt-get install -y mariadb-server mariadb-client # Make sure no extra configs present on fresh install rm -f ~/.my.cnf # setup aliases and if db is remote replace with dummy service file reconfigureMysqlSystemdService # TODO: selinux/apparmor permissions for mysql # firewall rules (cluster install needs remote access) # configure galera replication (cluster install) # configure group replication (cluster install) # TODO: configure mysql to redirect error_log to syslog (as our other services do) # https://mariadb.com/kb/en/systemd/#configuring-mariadb-to-write-the-error-log-to-syslog # TODO: configure logrotate to rotate syslog logs from mysql return 0 } function uninstall { # Stop and disable services systemctl stop mariadb systemctl disable mariadb # Backup mysql / mariadb mv -f /var/lib/mysql /var/lib/mysql.bak.$(date +%Y%m%d_%H%M%S) # remove mysql unit files we created rm -rf /etc/systemd/system/mariadb.service.d/ rm -f /etc/systemd/system/mariadb.service 2>/dev/null systemctl daemon-reload # Uninstall mysql / mariadb packages apt-get -y remove --purge mysql\* apt-get -y remove --purge mariadb\* rm -rf /etc/my.cnf*; rm -f /etc/my.cnf*; rm -f ~/*my.cnf # TODO: remove selinux/apparmor rules # TODO: remove mysql firewall rules # TODO: remove mysql syslog config # TODO: remove mysql logrotate config return 0 } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/almalinux/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf remove -y rs-epel-release* dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${DISTRO_MAJOR_VER}.noarch.rpm dnf install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/almalinux/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf install -y nginx if (( $? != 0 )); then printerr 'failed installing nginx packages' return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload # remove SELINUX permissions semanage port -d -t http_port_t -p tcp ${DSIP_PORT} return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/amzn/2.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install nginx amazon-linux-extras enable -y nginx1 >/dev/null yum clean -y metadata yum install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v1.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v1.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx yum remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/centos/7.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install { # Get the default version of python enabled VER=`python -V 2>&1` VER=`echo $VER | cut -d " " -f 2` # Uninstall 3.6 and install a specific version of 3.6 if already installed if [[ "$VER" =~ 3.6 ]]; then yum remove -y rs-epel-release yum remove -y python36 python36-libs python36-devel python36-pip yum install -y https://centos7.iuscommunity.org/ius-release.rpm yum install -y python36u python36u-libs python36u-devel python36u-pip elif [[ "$VER" =~ 3 ]]; then yum remove -y rs-epel-release yum remove -y python3* python3*-libs python3*-devel python3*-pip yum install -y https://centos7.iuscommunity.org/ius-release.rpm yum install -y python36u python36u-libs python36u-devel python36u-pip else yum install -y https://centos7.iuscommunity.org/ius-release.rpm yum install -y python36u python36u-libs python36u-devel python36u-pip fi # Install dependencies for dSIPRouter yum install -y yum-utils yum --setopt=group_package_types=mandatory,default,optional groupinstall -y "Development Tools" yum install -y firewalld nginx yum install -y python36 python36-libs python36-devel python36-pip MySQL-python yum install -y logrotate rsyslog perl libev-devel util-linux postgresql-devel mariadb-devel # create dsiprouter and nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock useradd --system --user-group --shell /bin/false --comment "dSIPRouter SIP Provider Platform" dsiprouter useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # make sure the nginx user has access to dsiprouter directories usermod -a -G dsiprouter nginx # make dsiprouter user has access to kamailio files usermod -a -G kamailio dsiprouter # setup runtime directorys for dsiprouter and nginx mkdir -p ${DSIP_RUN_DIR} /run/nginx chown -R dsiprouter:dsiprouter ${DSIP_RUN_DIR} chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # reset python cmd in case it was just installed setPythonCmd # Enable and start firewalld systemctl enable firewalld systemctl start firewalld if (( $? != 0 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # Setup Firewall for DSIP_PORT firewall-offline-cmd --zone=public --add-port=${DSIP_PORT}/tcp cat ${DSIP_PROJECT_DIR}/gui/requirements.txt | xargs -n 1 ${PYTHON_CMD} -m pip install if [ $? -eq 1 ]; then printerr "dSIPRouter install failed: Couldn't install required libraries" exit 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v1.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v1.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall { # Uninstall dependencies for dSIPRouter PIP_CMD="pip" cat ${DSIP_PROJECT_DIR}/gui/requirements.txt | xargs -n 1 $PYTHON_CMD -m ${PIP_CMD} uninstall --yes if [ $? -eq 1 ]; then printerr "dSIPRouter uninstall failed or the libraries are already uninstalled" exit 1 else printdbg "DSIPRouter uninstall was successful" exit 0 fi yum remove -y python36u\* yum remove -y ius-release yum remove -y nginx yum groupremove -y "Development Tools" # Remove the repos rm -f /etc/yum.repos.d/ius* rm -f /etc/pki/rpm-gpg/IUS-COMMUNITY-GPG-KEY yum clean all # Remove Firewall for DSIP_PORT firewall-cmd --zone=public --remove-port=${DSIP_PORT}/tcp --permanent firewall-cmd --reload # Remove dSIPRouter Logging rm -f /etc/rsyslog.d/dsiprouter.conf # Remove logrotate settings rm -f /etc/logrotate.d/dsiprouter # Remove dSIProuter as a service systemctl disable dsiprouter.service rm -f /lib/systemd/system/dsiprouter.service systemctl daemon-reload } case "$1" in uninstall|remove) uninstall ;; install) install ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/centos/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload # remove SELINUX permissions semanage port -d -t http_port_t -p tcp ${DSIP_PORT} return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/centos/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf install -y nginx if (( $? != 0 )); then printerr 'failed installing nginx packages' return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload # remove SELINUX permissions semanage port -d -t http_port_t -p tcp ${DSIP_PORT} return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/configs/dsiprouter.conf ================================================ # setup the dsiprouter server group (using unix sockets) # if multiple instances are running they can be configured here upstream dsiprouter { server unix:DSIP_UNIX_SOCK; } # handle the https requests server { # by default we listen on all interfaces listen DSIP_PORT ssl http2 so_keepalive=on; listen [::]:DSIP_PORT ssl http2 so_keepalive=on; server_name _; ssl_certificate DSIP_SSL_CERT; ssl_certificate_key DSIP_SSL_KEY; # reverse proxy for dsiprouter location / { proxy_pass http://dsiprouter; proxy_http_version 1.1; proxy_cache_bypass $http_upgrade; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; } location /stirshaken_certs { alias /etc/dsiprouter/certs/stirshaken; } # redirect http to https error_page 497 https://$host:DSIP_PORT$request_uri; } ================================================ FILE: nginx/configs/nginx.conf ================================================ user nginx; worker_processes auto; error_log /var/log/nginx/error.log warn; pid /run/nginx/nginx.pid; daemon on; master_process on; # Load dynamic modules (ref: /usr/share/doc/nginx/README.dynamic) include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 60; access_log off; sendfile_max_chunk 512k; # TODO: needs optimizing # https://medium.com/@mvuksano/how-to-properly-configure-your-nginx-for-tls-564651438fe0 ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; ssl_protocols TLS_PROTOCOLS; ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5; ssl_prefer_server_ciphers on; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. include /etc/nginx/conf.d/*.conf; # Include sites enabled in /etc/nginx/sites-enabled include /etc/nginx/sites-enabled/*.conf; } ================================================ FILE: nginx/debian/10.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/debian/11.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/debian/12.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/debian/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v1.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v1.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/rhel/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf remove -y rs-epel-release* dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${DISTRO_MAJOR_VER}.noarch.rpm dnf install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v1.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v1.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/rhel/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf install -y nginx if (( $? != 0 )); then printerr 'failed installing nginx packages' return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload # remove SELINUX permissions semanage port -d -t http_port_t -p tcp ${DSIP_PORT} return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/rocky/8.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf remove -y rs-epel-release* dnf install -y https://dl.fedoraproject.org/pub/epel/epel-release-latest-${DISTRO_MAJOR_VER}.noarch.rpm dnf install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/rocky/9.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx dnf install -y nginx if (( $? != 0 )); then printerr 'failed installing nginx packages' return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # give nginx permissions in SELINUX semanage port -a -t http_port_t -p tcp ${DSIP_PORT} || semanage port -m -t http_port_t -p tcp ${DSIP_PORT} # NOTE: /var/run is required here due to the aliasing in the fcontexts #semanage fcontext -a -t httpd_var_run_t '/var/run/dsiprouter/dsiprouter\.sock' # TODO: this is a workaround, this the "wrong" way to do it # we need to figure out why the fcontexts are not applying by default to new files # and possibly (preferably) create our own type with those specific permissions # for example a new type dsiprouter_run_t labeled on '/var/run/dsiprouter/.+' ( if semodule -l | grep -q 'dsiprouter'; then semodule -r dsiprouter fi cd /tmp && checkmodule -M -m -o dsiprouter.mod ${DSIP_PROJECT_DIR}/nginx/selinux/centos.te && semodule_package -o dsiprouter.pp -m dsiprouter.mod && semodule -i dsiprouter.pp ) if (( $? != 0 )); then printerr 'failed updating selinux permissions' return 1 fi # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx dnf remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload # remove SELINUX permissions semanage port -d -t http_port_t -p tcp ${DSIP_PORT} return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: nginx/selinux/centos.te ================================================ module dsiprouter 1.0; require { type unconfined_t; type unconfined_service_t; type var_run_t; type httpd_t; class sock_file write; class unix_stream_socket connectto; } allow httpd_t unconfined_t:unix_stream_socket connectto; allow httpd_t unconfined_service_t:unix_stream_socket connectto; allow httpd_t var_run_t:sock_file write; ================================================ FILE: nginx/systemd/nginx-stop.sh ================================================ #!/usr/bin/env bash TIMEOUT=5 MAINPID="$1" PIDFILE="$2" if [[ -z "$(ps -p $MAINPID -o pid= 2>/dev/null)" ]]; then rm -f ${PIDFILE} 2>/dev/null exit 0 fi kill -s SIGSTOP $MAINPID for (( ROUND=0; ROUND<$TIMEOUT; ROUND++ )); do if [[ -z "$(ps -p $MAINPID -o pid= 2>/dev/null)" ]]; then rm -f ${PIDFILE} 2>/dev/null exit 0 fi sleep 1 done kill -s SIGQUIT $MAINPID for (( ROUND=0; ROUND<$TIMEOUT; ROUND++ )); do if [[ -z "$(ps -p $MAINPID -o pid= 2>/dev/null)" ]]; then rm -f ${PIDFILE} 2>/dev/null exit 0 fi sleep 1 done exit 1 ================================================ FILE: nginx/systemd/nginx-v1.service ================================================ # ExecStop sends SIGQUIT to the main process # If, after 3s nginx is still running, sends SIGTERM to main process # If, after 6s nginx is still running, sends SIGKILL to all processes [Unit] Description=A high performance web server and a reverse proxy server Documentation=man:nginx(8) Requires=basic.target network.target Wants=nginx-watcher.path After=network.target network-online.target basic.target nss-lookup.target remote-fs.target DefaultDependencies=no [Service] Type=forking Environment='RUNDIR=/run/nginx' PIDFile=/run/nginx/nginx.pid ExecStartPre=/usr/bin/dsiprouter chown -certs -nginx ExecStartPre=/usr/sbin/nginx -t ExecStart=/usr/sbin/nginx ExecReload=/usr/sbin/nginx -s reload ExecStop=/bin/kill -s SIGQUIT ${MAINPID} TimeoutStopSec=3 KillMode=mixed Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: nginx/systemd/nginx-v2.service ================================================ # ExecStop sends SIGQUIT to the main process # If, after 3s nginx is still running, sends SIGTERM to main process # If, after 6s nginx is still running, sends SIGKILL to all processes [Unit] Description=A high performance web server and a reverse proxy server Documentation=man:nginx(8) Requires=basic.target network.target Wants=nginx-watcher.path After=network.target network-online.target basic.target nss-lookup.target remote-fs.target DefaultDependencies=no [Service] Type=forking Environment='RUNDIR=/run/nginx' PIDFile=/run/nginx/nginx.pid ExecStartPre=!-/usr/bin/dsiprouter chown -certs -nginx ExecStartPre=/usr/sbin/nginx -t ExecStart=/usr/sbin/nginx ExecReload=/usr/sbin/nginx -s reload ExecStop=/bin/kill -s SIGQUIT ${MAINPID} TimeoutStopSec=3 KillMode=mixed Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: nginx/systemd/nginx-watcher-v1.service ================================================ [Unit] Description=Nginx Service Reloader [Service] Type=oneshot ExecStart=/usr/sbin/nginx -s reload StartLimitInterval=5 StartLimitBurst=3 [Install] WantedBy=multi-user.target ================================================ FILE: nginx/systemd/nginx-watcher-v2.service ================================================ [Unit] Description=Nginx Service Reloader StartLimitIntervalSec=5 StartLimitBurst=3 [Service] Type=oneshot ExecStart=/usr/sbin/nginx -s reload [Install] WantedBy=multi-user.target ================================================ FILE: nginx/systemd/nginx-watcher.path ================================================ [Unit] Description=Nginx Service Reloader PartOf=nginx.service [Path] Unit=nginx-watcher.service PathChanged=/etc/dsiprouter/certs/ [Install] WantedBy=multi-user.target ================================================ FILE: nginx/ubuntu/20.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/ubuntu/22.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: nginx/ubuntu/24.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function install() { # create nginx user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel nginx &>/dev/null; groupdel nginx &>/dev/null useradd --system --user-group --shell /bin/false --comment "nginx HTTP Service Provider" nginx # Install dependencies for dSIPRouter apt-get install -y nginx if (( $? != 0 )); then return 1 fi # setup runtime directorys for nginx mkdir -p /run/nginx chown -R nginx:nginx /run/nginx # Configure nginx # determine available TLS protocols (try using highest available) OPENSSL_VER=$(openssl version 2>/dev/null | awk '{print $2}' | perl -pe 's%([0-9])\.([0-9]).([0-9]).*%\1\2\3%') if (( ${OPENSSL_VER} < 101 )); then TLS_PROTOCOLS="TLSv1" elif (( ${OPENSSL_VER} < 111 )); then TLS_PROTOCOLS="TLSv1.1 TLSv1.2" else TLS_PROTOCOLS="TLSv1.2 TLSv1.3" fi mkdir -p /etc/nginx/sites-enabled /etc/nginx/sites-available /etc/nginx/nginx.conf.d/ # remove the defaults rm -f /etc/nginx/sites-enabled/* /etc/nginx/sites-available/* /etc/nginx/nginx.conf.d/* # setup our own nginx configs perl -e "\$tls_protocols='${TLS_PROTOCOLS}';" \ -pe 's%TLS_PROTOCOLS%${tls_protocols}%g;' \ ${DSIP_PROJECT_DIR}/nginx/configs/nginx.conf >/etc/nginx/nginx.conf # configure nginx systemd service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-stop.sh /usr/sbin/nginx-stop cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-v2.service /lib/systemd/system/nginx.service cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher-v2.service /lib/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/lib/systemd/system/nginx-watcher.path chmod 644 /lib/systemd/system/nginx.service chmod 644 /lib/systemd/system/nginx-watcher.service chmod 644 /lib/systemd/system/nginx-watcher.path systemctl daemon-reload systemctl enable nginx return 0 } function uninstall() { # stop nginx and remove nginx package systemctl stop nginx systemctl disable nginx apt-get remove -y nginx # remove nginx systemd service rm -f /usr/sbin/nginx-stop rm -f /lib/systemd/system/nginx.service rm -f /lib/systemd/system/nginx-watcher.service rm -f /lib/systemd/system/nginx-watcher.path systemctl daemon-reload return 0 } case "$1" in uninstall) uninstall && exit 0 || exit 1 ;; install) install && exit 0 || exit 1 ;; *) printerr "usage $0 [install | uninstall]" ;; esac ================================================ FILE: resources/apt/debian/10/official-releases.list ================================================ #------------------------------------------------------------------- # packages from debian 10 (buster) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ buster main contrib non-free #deb-src https://deb.debian.org/debian/ buster main contrib non-free deb https://deb.debian.org/debian/ buster-updates main contrib non-free #deb-src https://deb.debian.org/debian/ buster-updates main contrib non-free deb https://deb.debian.org/debian/ buster-backports main contrib non-free #deb-src https://deb.debian.org/debian/ buster-backports main contrib non-free deb https://deb.debian.org/debian-security/ buster/updates main #deb-src https://deb.debian.org/debian-security/ buster/updates main #------------------------------------------------------------------- # packages from debian 11 (bullseye) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ bullseye main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye main contrib non-free deb https://deb.debian.org/debian/ bullseye-updates main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye-updates main contrib non-free deb https://deb.debian.org/debian/ bullseye-backports main #deb-src https://deb.debian.org/debian/ bullseye-backports main deb https://deb.debian.org/debian-security/ bullseye-security main #deb-src https://deb.debian.org/debian-security/ bullseye-security main ================================================ FILE: resources/apt/debian/10/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Debian experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Debian backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for debian 10 (buster) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=buster Pin-Priority: 990 Package: * Pin: release o=Debian,n=buster-updates Pin-Priority: 990 Package: * Pin: release o=Debian,n=buster-backports Pin-Priority: 990 #------------------------------------------------------------------- # priority for debian 11 (bullseye) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=bullseye Pin-Priority: 500 Package: * Pin: release o=Debian,n=bullseye-updates Pin-Priority: 500 Package: * Pin: release o=Debian,n=bullseye-security Pin-Priority: 500 Package: * Pin: release o=Debian,n=bullseye-backports Pin-Priority: 500 ================================================ FILE: resources/apt/debian/11/official-releases.list ================================================ #------------------------------------------------------------------- # packages from debian 11 (bullseye) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ bullseye main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye main contrib non-free deb https://deb.debian.org/debian/ bullseye-updates main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye-updates main contrib non-free deb https://deb.debian.org/debian/ bullseye-backports main #deb-src https://deb.debian.org/debian/ bullseye-backports main deb https://deb.debian.org/debian-security/ bullseye-security main #deb-src https://deb.debian.org/debian-security/ bullseye-security main ================================================ FILE: resources/apt/debian/11/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Debian experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Debian backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for debian 11 (bullseye) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=bullseye Pin-Priority: 990 Package: * Pin: release o=Debian,n=bullseye-updates Pin-Priority: 990 Package: * Pin: release o=Debian,n=bullseye-security Pin-Priority: 990 Package: * Pin: release o=Debian,n=bullseye-backports Pin-Priority: 990 ================================================ FILE: resources/apt/debian/12/official-releases.list ================================================ #------------------------------------------------------------------- # packages from debian 12 (bookworm) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ bookworm main contrib non-free #deb-src https://deb.debian.org/debian/ bookworm main contrib non-free deb https://deb.debian.org/debian/ bookworm-updates main contrib non-free #deb-src https://deb.debian.org/debian/ bookworm-updates main contrib non-free deb https://deb.debian.org/debian/ bookworm-backports main #deb-src https://deb.debian.org/debian/ bookworm-backports main deb https://deb.debian.org/debian-security/ bookworm-security main #deb-src https://deb.debian.org/debian-security/ bookworm-security main ================================================ FILE: resources/apt/debian/12/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Debian experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Debian backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for debian 12 (bookworm) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=bookworm Pin-Priority: 990 Package: * Pin: release o=Debian,n=bookworm-updates Pin-Priority: 990 Package: * Pin: release o=Debian,n=bookworm-security Pin-Priority: 990 Package: * Pin: release o=Debian,n=bookworm-backports Pin-Priority: 990 ================================================ FILE: resources/apt/debian/9/official-releases.list ================================================ #------------------------------------------------------------------- # packages from debian 9 (stretch) release #------------------------------------------------------------------- deb http://deb.debian.org/debian/ stretch main contrib non-free #deb-src http://deb.debian.org/debian/ stretch main contrib non-free deb http://deb.debian.org/debian/ stretch-updates main contrib non-free #deb-src http://deb.debian.org/debian/ stretch-updates main contrib non-free deb http://deb.debian.org/debian-security stretch/updates main #deb-src http://deb.debian.org/debian-security stretch/updates main deb http://deb.debian.org/debian stretch-backports main #deb-src http://deb.debian.org/debian stretch-backports main #------------------------------------------------------------------- # packages from debian 10 (buster) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ buster main contrib non-free #deb-src https://deb.debian.org/debian/ buster main contrib non-free deb https://deb.debian.org/debian/ buster-updates main contrib non-free #deb-src https://deb.debian.org/debian/ buster-updates main contrib non-free deb https://deb.debian.org/debian/ buster-backports main contrib non-free #deb-src https://deb.debian.org/debian/ buster-backports main contrib non-free deb https://deb.debian.org/debian-security/ buster/updates main #deb-src https://deb.debian.org/debian-security/ buster/updates main #------------------------------------------------------------------- # packages from debian 11 (bullseye) release #------------------------------------------------------------------- deb https://deb.debian.org/debian/ bullseye main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye main contrib non-free deb https://deb.debian.org/debian/ bullseye-updates main contrib non-free #deb-src https://deb.debian.org/debian/ bullseye-updates main contrib non-free deb https://deb.debian.org/debian/ bullseye-backports main #deb-src https://deb.debian.org/debian/ bullseye-backports main deb https://deb.debian.org/debian-security/ bullseye-security main #deb-src https://deb.debian.org/debian-security/ bullseye-security main ================================================ FILE: resources/apt/debian/9/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Debian experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Debian backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for debian 9 (stretch) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=stretch Pin-Priority: 990 Package: * Pin: release o=Debian,n=stretch-updates Pin-Priority: 990 Package: * Pin: release o=Debian,n=stretch-backports Pin-Priority: 990 #------------------------------------------------------------------- # priority for debian 10 (buster) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=buster Pin-Priority: 500 Package: * Pin: release o=Debian,n=buster-updates Pin-Priority: 500 Package: * Pin: release o=Debian,n=buster-backports Pin-Priority: 500 #------------------------------------------------------------------- # priority for debian 11 (bullseye) release packages #------------------------------------------------------------------- Package: * Pin: release o=Debian,n=bullseye Pin-Priority: 100 Package: * Pin: release o=Debian,n=bullseye-updates Pin-Priority: 100 Package: * Pin: release o=Debian,n=bullseye-security Pin-Priority: 100 Package: * Pin: release o=Debian,n=bullseye-backports Pin-Priority: 100 ================================================ FILE: resources/apt/ubuntu/20.04/official-releases.list ================================================ #------------------------------------------------------------------- # packages from ubuntu 20.04 (focal) release #------------------------------------------------------------------- deb https://nyc.mirrors.clouvider.net/ubuntu/ focal main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ focal main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ focal-security main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ focal-security main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ focal-updates main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ focal-updates main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ focal-backports main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ focal-backports main restricted universe multiverse #deb https://nyc.mirrors.clouvider.net/ubuntu/ focal-proposed main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ focal-proposed main restricted universe multiverse ================================================ FILE: resources/apt/ubuntu/20.04/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Ubuntu experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Ubuntu backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for ubuntu 20.04 (focal) release packages #------------------------------------------------------------------- Package: * Pin: release o=Ubuntu,n=focal Pin-Priority: 990 Package: * Pin: release o=Ubuntu,a=focal Pin-Priority: 990 ================================================ FILE: resources/apt/ubuntu/22.04/official-releases.list ================================================ #------------------------------------------------------------------- # packages from ubuntu 22.04 (jammy) release #------------------------------------------------------------------- deb https://nyc.mirrors.clouvider.net/ubuntu/ jammy main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ jammy main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ jammy-security main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ jammy-security main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ jammy-updates main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ jammy-updates main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ jammy-backports main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ jammy-backports main restricted universe multiverse #deb https://nyc.mirrors.clouvider.net/ubuntu/ jammy-proposed main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ jammy-proposed main restricted universe multiverse ================================================ FILE: resources/apt/ubuntu/22.04/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Ubuntu experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Ubuntu backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for ubuntu 22.04 (jammy) release packages #------------------------------------------------------------------- Package: * Pin: release o=Ubuntu,n=jammy Pin-Priority: 990 Package: * Pin: release o=Ubuntu,a=jammy Pin-Priority: 990 ================================================ FILE: resources/apt/ubuntu/24.04/official-releases.list ================================================ #------------------------------------------------------------------- # packages from ubuntu 24.04 (noble) release #------------------------------------------------------------------- deb https://nyc.mirrors.clouvider.net/ubuntu/ noble main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ noble main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ noble-security main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ noble-security main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ noble-updates main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ noble-updates main restricted universe multiverse deb https://nyc.mirrors.clouvider.net/ubuntu/ noble-backports main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ noble-backports main restricted universe multiverse #deb https://nyc.mirrors.clouvider.net/ubuntu/ noble-proposed main restricted universe multiverse #deb-src https://nyc.mirrors.clouvider.net/ubuntu/ noble-proposed main restricted universe multiverse ================================================ FILE: resources/apt/ubuntu/24.04/official-releases.pref ================================================ ## default priory assignments # priority 1 # versions coming from archives which in their Release files are marked as "NotAutomatic: yes", # but not as "ButAutomaticUpgrades: yes" like the Ubuntu experimental archive. # # priority 100 # a version that is already installed (if any) and to the versions coming from archives which, # in their Release files are marked as "NotAutomatic: yes" and "ButAutomaticUpgrades: yes", # like the Ubuntu backports archive since squeeze-backports. # # priority 500 # versions that do not belong to the target release. # # priority 990 # versions that belong to the target release. # ## interpretation of priority (P) # P >= 1000 # causes a version to be installed even if this constitutes a downgrade of the package # # 990 <= P < 1000 # causes a version to be installed even if it does not come from the target release, unless the installed version is more recent # # 500 <= P < 990 # causes a version to be installed unless there is a version available belonging to the target release or the installed version is more recent # # 100 <= P < 500 # causes a version to be installed unless there is a version available belonging to some other distribution or the installed version is more recent # # 0 < P < 100 # causes a version to be installed only if there is no installed version of the package # # P < 0 # prevents the version from being installed # # P = 0 # has undefined behaviour (do not use it) # #------------------------------------------------------------------- # priority for ubuntu 24.04 (noble) release packages #------------------------------------------------------------------- Package: * Pin: release o=Ubuntu,n=noble Pin-Priority: 990 Package: * Pin: release o=Ubuntu,a=noble Pin-Priority: 990 ================================================ FILE: resources/git/check_syntax.py ================================================ #!/usr/bin/env python3 import os, sys, re, subprocess, shutil # TODO: add support for other basic preprocessor checks (c/kamcfg) # TODO: add support for missing semi-colon / dangling curly brace (c/kamcfg) # TODO: add support for recursing through kamcfg include files (kamcfg) # global config variables project_root = subprocess.Popen(['git', 'rev-parse', '--show-toplevel'], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).communicate()[0].strip() if len(project_root) == 0: project_root = os.path.dirname(os.path.dirname(os.path.abspath(sys.argv[0]))) # find C src files in project matched_csrc_files = subprocess.Popen(['find', project_root, '-type', 'f', '-regextype', 'posix-extended', '-regex', '.*\.(cpp|hpp|c|h)$'], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).communicate()[0].strip().split() # find kamailio .cfg files in project shell_pipe = subprocess.Popen(['find', project_root, '-type', 'f', '-name', '*.cfg', '-print0'], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout matched_kamcfg_files = subprocess.Popen(['xargs', '-0', 'sh', '-c', 'for arg do sed -n "/^\#\!KAMAILIO/q 0;q 1" ${arg} && echo "${arg}"; done', '_'], universal_newlines=True, stdin=shell_pipe, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL ).communicate()[0].strip().split() files_found = len(matched_csrc_files) + len(matched_kamcfg_files) term_width = shutil.get_terminal_size((80, 24))[0] # global constants CSRC_STYLE_IFDEF_REGEX = re.compile(rb'^[ \t]*#(?:ifdef|ifndef).*') CSRC_STYLE_ELSE_REGEX = re.compile(rb'^[ \t]*#else.*') CSRC_STYLE_ENDIF_REGEX = re.compile(rb'^[ \t]*#endif.*') CSRC_CURLYBRACE_OPEN_REGEX = re.compile(rb'^[ \t]*(?!//|/\*).*\{[ \t]*') CSRC_CURLYBRACE_CLOSE_REGEX = re.compile(rb'^[ \t]*(?!//|/\*)\}[ \t]*') KAMCFG_STYLE_IFDEF_REGEX = re.compile(rb'^[ \t]*#\!(?:ifdef|ifndef).*') KAMCFG_STYLE_ELSE_REGEX = re.compile(rb'^[ \t]*#\!else.*') KAMCFG_STYLE_ENDIF_REGEX = re.compile(rb'^[ \t]*#\!endif.*') KAMCFG_CURLYBRACE_OPEN_REGEX = re.compile(rb'^[ \t]*(?!//|#|/\*).*\{[ \t]*') KAMCFG_CURLYBRACE_CLOSE_REGEX = re.compile(rb'^[ \t]*(?!//|#|/\*)\}[ \t]*') # holds state for entire test test_succeeded = True files_checked = 0 # holds state for current file check unmatched_ifdefs = [] unmatched_elses = [] outoforder_elses = [] unmatched_endifs = [] unmatched_lcurly_braces = [] unmatched_rcurly_braces = [] # check for common syntax errors, currently supported checks: # + preprocessor statement closure def haveValidSyntax(test_files, syntax='c-src'): global files_checked global unmatched_ifdefs, unmatched_elses, outoforder_elses, unmatched_endifs global unmatched_lcurly_braces, unmatched_rcurly_braces if syntax == 'c-src': ifdef_regex = CSRC_STYLE_IFDEF_REGEX else_regex = CSRC_STYLE_ELSE_REGEX endif_regex = CSRC_STYLE_ENDIF_REGEX lcurly_regex = CSRC_CURLYBRACE_OPEN_REGEX rcurly_regex = CSRC_CURLYBRACE_CLOSE_REGEX elif syntax == 'kam-cfg': ifdef_regex = KAMCFG_STYLE_IFDEF_REGEX else_regex = KAMCFG_STYLE_ELSE_REGEX endif_regex = KAMCFG_STYLE_ENDIF_REGEX lcurly_regex = KAMCFG_CURLYBRACE_OPEN_REGEX rcurly_regex = KAMCFG_CURLYBRACE_CLOSE_REGEX else: return False for test_file in test_files: with open(test_file, 'rb') as fp: i = 1 for line in fp: if ifdef_regex.match(line): unmatched_ifdefs.append([test_file,i,line]) elif else_regex.match(line): if len(unmatched_ifdefs) == 0: outoforder_elses.append([test_file,i,line]) else: unmatched_elses.append([test_file,i,line]) elif endif_regex.match(line): try: unmatched_elses.pop() except IndexError: pass try: unmatched_ifdefs.pop() except IndexError: unmatched_endifs.append([test_file,i,line]) elif lcurly_regex.match(line): unmatched_lcurly_braces.append([test_file,i,line]) elif rcurly_regex.match(line): unmatched_rcurly_braces.append([test_file,i,line]) i += 1 files_checked += 1 if len(unmatched_ifdefs) + len(outoforder_elses) + len(unmatched_elses) + len(unmatched_endifs) + \ len(unmatched_lcurly_braces) + len(unmatched_rcurly_braces) != 0: return False return True # print summary of test results def printSummary(): print('|', '='*(term_width-2), '|', sep='') if test_succeeded: print('Test Result: PASSED') else: print('Test Result: FAILED') print('Number Of Files Tested: {}'.format(str(files_checked))) print('Number Of Files Matched: {}'.format(str(files_found))) print('|', '='*(term_width-2), '|', sep='') # print error results for a single test def printErrorBlock(header, test_results): header_len = len(header) avail_space = term_width - 4 - header_len header_fill = '=' * (int(avail_space / 2)) header_pad = '=' * (avail_space % 2) print('|', header_fill, ' ' + header + ' ', header_fill, header_pad, '|', sep='') for result in test_results: print('[{}] line {}: {}'.format(result[0], str(result[1]), result[2]), file=sys.stderr) print('|', '=' * (term_width - 2), '|', sep='', file=sys.stderr) # print detailed failure info def printErrorInfo(): if len(unmatched_ifdefs) != 0: printErrorBlock('unmatched preprocessor ifdef statements', unmatched_ifdefs) if len(outoforder_elses) != 0: printErrorBlock('out of order preprocessor else statements', outoforder_elses) if len(unmatched_elses) != 0: printErrorBlock('unmatched preprocessor else statements', unmatched_elses) if len(unmatched_endifs) != 0: printErrorBlock('unmatched preprocessor endif statements', unmatched_endifs) if len(unmatched_lcurly_braces) != 0: printErrorBlock('unmatched left curly braces', unmatched_lcurly_braces) if len(unmatched_rcurly_braces) != 0: printErrorBlock('unmatched right curly braces', unmatched_rcurly_braces) # wrapper for the final cleanup def printResultsAndExit(): printSummary() if not test_succeeded: printErrorInfo() sys.exit(int(test_succeeded == False)) # main testing logic if __name__ == "__main__": if not haveValidSyntax(matched_csrc_files, syntax='c-src'): test_succeeded = False elif not haveValidSyntax(matched_kamcfg_files, syntax='kam-cfg'): test_succeeded = False printResultsAndExit() ================================================ FILE: resources/git/commit-msg ================================================ %s Commit: %h Tree: %t Author: %an Email: %ae Date: %aD %b %N ================================================ FILE: resources/git/gitattributes ================================================ CHANGELOG.md merge=merge-changelog CONTRIBUTORS.md merge=merge-contributors *requirements.txt merge=merge-requirements ================================================ FILE: resources/git/gitconfig ================================================ [merge "merge-changelog"] name = A custom merge driver used to resolve CHANGELOG.md conflicts driver = _merge-changelog %O %A %B %L %P [merge "merge-contributors"] name = A custom merge driver used to resolve CONTRIBUTORS.md conflicts driver = true [merge "merge-requirements"] name = A custom merge driver used to resolve requirements.txt conflicts driver = true ================================================ FILE: resources/git/gitignore ================================================ #============================================================ # General Ignores *~ .#* *. *.slo *.mk *.mem *.gcda *.gcno *.la *.lo *.loT *.o *.a *.ncb *.opt *.plg *swp *.patch *.tgz *.tar.gz *.tar.bz2 *.tar.xz *.tar.gz.asc *.tar.bz2.asc *.tar.xz.asc .FBCIndex .FBCLockFolder .deps .libs diff libtool #============================================================ #============================================================ # JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # All .idea config files .idea* # IntelliJ out/ *.iml *.iws # JIRA plugin atlassian-ide-plugin.xml # Crashlytics plugin (for Android Studio and IntelliJ) com_crashlytics_export_strings.xml crashlytics.properties crashlytics-build.properties fabric.properties # Editorconfig files *.editorconfig #============================================================ #============================================================ # Visual Studio .vs* #============================================================ #============================================================ # CMake cmake-build-*/ CMakeLists.txt.user CMakeCache.txt CMakeFiles CMakeScripts cmake_install.cmake install_manifest.txt compile_commands.json CTestTestfile.cmake _deps #============================================================ # Custom Git Hook config files .commit .fetch .changelog_commited .postcommit_disabled .push_remote # Local repo git configs .git* # Docker configs .dockerignore # C extensions *.so #============================================================ # Python # Byte-compiled / optimized / DLL files **/__pycache__/ *.py[cod] *$py.class *.cpython-35.pyc # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # pyenv .python-version # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # pyenv .python-version # celery beat schedule file celerybeat-schedule # Django stuff: *.log .static_storage/ .media/ local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # IPython profile_default/ ipython_config.py #============================================================ #============================================================ # Terraform / Packer # Local .terraform directories **/.terraform/* # .tfstate files *.tfstate *.tfstate.* # Crash log files crash.log # Ignore override files as they are usually used to override resources locally override.tf override.tf.json *_override.tf *_override.tf.json # Cache objects packer_cache/ # For built boxes *.box #============================================================ ================================================ FILE: resources/git/gitwrapper.sh ================================================ #!/usr/bin/env bash # git wrapper: should be sourced by a login script # extends git command functionality __gitwrapper() { local ARGS=() COMMIT_FLAG=0 REMOTE_NAME="" while (( $# > 0 )); do case "$1" in commit) ARGS+=("$1") COMMIT_FLAG=1 shift ;; --remote=*) if (( ${COMMIT_FLAG} == 1 )); then REMOTE_NAME=$(printf '%s' "$1" | cut -d '=' -f 2-) else ARGS+=("$1") fi shift ;; *) ARGS+=("$1") shift ;; esac done # if default remote used we have to lookup the remote name if [[ "${REMOTE_NAME}" == "." ]]; then REMOTE_NAME=$(git config --get checkout.defaultremote) fi # if using default and not set then use origin REMOTE_NAME=${REMOTE_NAME:-origin} export REMOTE_NAME command git "${ARGS[@]}" unset REMOTE_NAME } # Shadows git cmd git() { __gitwrapper "$@" } ================================================ FILE: resources/git/hooks/commit-msg ================================================ #!/usr/bin/env bash # # Summary: Reformat proposed commit message # Author: DevOpSec <https://github.com/devopsec> # Usage: Copy to your repo in <repo>/.git/hooks/commit-msg # Notes: Adding this hook to your workflow will fix crosslinking issues # caused when referencing issues in one remotee and pushing to another # This hook runs after prepare-commit-msg and before post-commit # TODO: Support resolving gitlab url's for merges/issues/commits # # unshadow git command unset -f git # project root PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" # indicator that we commited the changelog CHANGELOG_INDICATOR_FILE="${PROJECT_ROOT}/.changelog_commited" # indicator that post commit hooks has been disabled DISABLED_INDICATOR_FILE="${PROJECT_ROOT}/.postcommit_disabled" # should be exported by git wrapper script REMOTE_NAME="${REMOTE_NAME:-origin}" # remote info used in script REMOTE_URL=$(git remote get-url ${REMOTE_NAME} 2>/dev/null | perl -pe 's%(?:git\@|https\://)(.+)(?:\:|/)(.+)/(.+)\.git%https://\1/\2/\3%') REMOTE_SITE=$(cut -d '/' -f -3 <<<"${REMOTE_URL}") REMOTE_USER=$(cut -d '/' -f 4 <<<"${REMOTE_URL}") REMOTE_REPO=$(cut -d '/' -f 5- <<<"${REMOTE_URL}") # passed in when hook is called COMMIT_MSG_FILE="$1" # utility functions isRemoteGitlab() { curl -L ${REMOTE_URL} 2>/dev/null | grep -q -ioP '<title>.*?gitlab</title>' return $? } # reformat commit msg to follow our committing rules reformatCommitMsg() { if isRemoteGitlab; then # replace all issues/merges/commits with full path perl -e "\$rs='${REMOTE_SITE}'; \$ru='${REMOTE_USER}'; \$rr='${REMOTE_REPO}';" \ -pe 's%(?:(?!#)([ \t]+)(?:\!|${rr}\!|${ru}/${rr}\!)|^(?:\!|${rr}\!|${ru}/${rr}\!))(\d+)%\1${rs}/${ru}/${rr}/merge_requests/\2%gm; s%(?:(?!#)([ \t]+)(?:#|${rr}#|${ru}/${rr}#)|^(?:#|${rr}#|${ru}/${rr}#))(\d+)%\1${rs}/${ru}/${rr}/issues/\2%gm; s%(?!#)(?:${rr}@|${ru}/${rr}@|${rs}/${ru}/${rr}/commit/)?([0-9a-f]{7,40})(?!\w)%${rs}/${ru}/${rr}/commit/\1%gm' \ ${COMMIT_MSG_FILE} | # try to fix one-liners perl -e '$str=do{ local $/; <STDIN> }; if ( $str =~ m%^\w.*?(?:\n|\r\n)(?:\n|\r\n)+$% ) { @lines=split(/, |\. |- /, $str); foreach(@lines) { print "$_\n"; } } else { print "$str"; }' | # fix missing subject / missing double newline after subject perl -0777 -pe 's%^((:[ \t]+|- ?|[ \t]+|- ?).*?[\r\n]+.*)%Updates Listed Below\n\n\1%s; s%^(\w.*?(?:\n|\r\n)(?!\n|\r\n))(.*)%\1\n\2%s' \ > ${COMMIT_MSG_FILE}.tmp mv -f ${COMMIT_MSG_FILE}.tmp ${COMMIT_MSG_FILE} else # replace all issues/pulls/commits with full path perl -e "\$rn='${REMOTE_NAME}'; \$rs='${REMOTE_SITE}'; \$ru='${REMOTE_USER}'; \$rr='${REMOTE_REPO}';" \ -e 'my $tmp=`git ls-remote $rn "pull/*head" 2>/dev/null`; @pulls=($tmp =~ m|.*/pull/(\d+)/.*|g); $pr_expr=join "|", @pulls;' \ -pe 's%(?:(?!#)([ \t]+)(?:#|GH-|${ru}/${rr}#)|^(?:GH-|${ru}/${rr}#))($pr_expr)(?!\d)%\1${rs}/${ru}/${rr}/pull/\2%gm; s%(?:(?!#)([ \t]+)(?:#|GH-|${ru}/${rr}#)|^(?:GH-|${ru}/${rr}#))(\d+)%\1${rs}/${ru}/${rr}/issues/\2%gm; s%(?!#)(?:${ru}@|${ru}/${rr}@|${rs}/${ru}/${rr}/commit/)?([0-9a-f]{7,40})(?!\w)%${rs}/${ru}/${rr}/commit/\1%gm' \ ${COMMIT_MSG_FILE} | # try to fix one-liners perl -e '$str=do{ local $/; <STDIN> }; if ( $str =~ m%^\w.*?(?:\n|\r\n)(?:\n|\r\n)+$% ) { @lines=split(/, |\. |- /, $str); foreach(@lines) { print "$_\n"; } } else { print "$str"; }' | # fix missing subject / missing double newline after subject perl -0777 -pe 's%^((:[ \t]+|- ?|[ \t]+|- ?).*?[\r\n]+.*)%Updates Listed Below\n\n\1%s; s%^(\w.*?(?:\n|\r\n)(?!\n|\r\n))(.*)%\1\n\2%s' \ > ${COMMIT_MSG_FILE}.tmp mv -f ${COMMIT_MSG_FILE}.tmp ${COMMIT_MSG_FILE} fi } # don't run if hook is disabled # this is primarily for conflict resolution if [[ -e ${DISABLED_INDICATOR_FILE} ]]; then exit 0 # prevent recursion # we only reformat commit msg before changlog is commited elif [[ ! -e ${CHANGELOG_INDICATOR_FILE} ]]; then reformatCommitMsg fi exit 0 ================================================ FILE: resources/git/hooks/post-commit ================================================ #!/usr/bin/env bash # # Summary: Create a changelog on commit # Author: DevOpSec <https://github.com/devopsec> # Notes: Original idea from Martin Seeler <https://github.com/MartinSeeler> # Usage: Copy to your repo in <repo>/.git/hooks/post-commit # Alternatively the functions can be run outside of git (using -exec option) # Notes: Syntax for markdown comment: [//]: # (...) # All sections in the changelog are tagged within markdown comments # TODO: Support resolving gitlab url's for merges/issues/commits # # unshadow git command unset -f git # project root PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" # destination changelog file CHANGELOG_FILE="CHANGELOG.md" # where we will be creating our changes TMP_CHANGELOG_FILE="/tmp/CHANGELOG.md" # title of our changelog CHANGELOG_TITLE="CHANGELOG" # indicator that we commited the changelog CHANGELOG_INDICATOR_FILE="${PROJECT_ROOT}/.changelog_commited" # indicator that post commit hooks have been disabled DISABLED_INDICATOR_FILE="${PROJECT_ROOT}/.postcommit_disabled" # should be exported by git wrapper script REMOTE_NAME="${REMOTE_NAME:-origin}" # file that will hold remote that must be pushed to REMOTE_FILE="${PROJECT_ROOT}/.push_remote" # remote info used in script export REMOTE_URL=$(git remote get-url ${REMOTE_NAME} 2>/dev/null | perl -pe 's%(?:git\@|https\://).+?@(.+)(?:\:|/)(.+)/(.+)\.git%https://\1/\2/\3%') export REMOTE_SITE=$(cut -d '/' -f -3 <<<"${REMOTE_URL}") export REMOTE_USER=$(cut -d '/' -f 4 <<<"${REMOTE_URL}") export REMOTE_REPO=$(cut -d '/' -f 5- <<<"${REMOTE_URL}") # utility functions isRemoteGitlab() { curl -L ${REMOTE_URL} 2>/dev/null | grep -q -ioP '<title>.*?gitlab</title>' return $? } createMDRenderedSection() { local NAME="$1" local CONTENT="$2" printf '%s\n' "[//]: # (START_SECTION ${NAME})" printf '%s' "$CONTENT" | sed -e '$a\' printf '%s\n' "[//]: # (END_SECTION ${NAME})" } export -f createMDRenderedSection createMDCommentSection() { local NAME="$1" local CONTENT="$2" printf '%s\n' "[//]: # (START_SECTION ${NAME}" printf '%s' "$CONTENT" | sed -e '$a\' printf '%s\n' "END_SECTION ${NAME})" } export -f createMDCommentSection removeMDRenderedSection() { local NAME="$1" perl -e "\$name='${NAME}';" -0777 \ -pe 's%\[//\]: # \(START_SECTION ${name}\)\n(.*?)\[//\]: # \(END_SECTION ${name}\)\n%%s' } export -f removeMDRenderedSection removeMDRenderedSections() { local NAMES=$(printf '%s' "$1" | tr '\n' '|') perl -e "\$names='${NAMES}';" -0777 \ -pe 's%\[//\]: # \(START_SECTION (?:${names})\)\n(.*?)\[//\]: # \(END_SECTION (?:${names})\)\n%%sg' } export -f removeMDRenderedSections removeMDCommentSection() { local NAME="$1" perl -e "\$name='${NAME}';" -0777 \ -pe 's%\[//\]: # \(START_SECTION ${name}\n(.*?)END_SECTION ${name}\)\n%%s' } export -f removeMDCommentSection getMDRenderedSection() { local NAME="$1" perl -e "\$name='${NAME}';" -0777 \ -e ' while (<>) { if (s%.*\[//\]: # \(START_SECTION ${name}\)\n(.*?)\[//\]: # \(END_SECTION ${name}\)\n.*%\1%s) { print; } }' } export -f getMDRenderedSection getMDCommentSection() { local NAME="$1" perl -e "\$name='${NAME}';" -0777 \ -e ' while (<>) { if (s%.*\[//\]: # \(START_SECTION ${name}\n(.*?)END_SECTION ${name}\)\n.*%\1%s) { print; } }' } export -f getMDCommentSection # create a formatted commit section createCommitSection() { local HASH="$1" CONTENT="" OIFS=$IFS local PULL_OR_MERGE=${PULL_OR_MERGE:-pull} IFS= read -rd '' CONTENT < <( # format commit header git --no-pager log -n 1 --format='### %s%n%N%n%N> Commit: %H %n%N> Date: %aD %n%N> Author: %aN (%aE) %n%N> Committer: %cN (%cE) %n%N> Signed: %GS %n%N%n%N' ${HASH} 2>/dev/null | perl -e "\$rs='${REMOTE_SITE}'; \$ru='${REMOTE_USER}'; \$rr='${REMOTE_REPO}';" \ -pe 's%([0-9a-f]{40})(?!\w)%[\1](${rs}/${ru}/${rr}/commit/\1)%m' # format commit body git log --format='%b' -n 1 ${HASH} 2>/dev/null | # format issues/pulls/commits -> links # delete empty lines, lines -> bullets perl -e "\$rs='${REMOTE_SITE}'; \$ru='${REMOTE_USER}'; \$rr='${REMOTE_REPO}';" \ -pe 's%(${rs}/${ru}/${rr}/'"$PULL_OR_MERGE"'/)(\d+)%[#\2](\1\2)%gm; s%(${rs}/${ru}/${rr}/issues/)(\d+)%[#\2](\1\2)%gm; s%(${rs}/${ru}/${rr}/commit/)([0-9a-f]{7})([0-9a-f]{0,33})(?!\w)%[\2](\1\2\3)%gm; s%^\s+?$%%; s%^(?:[ \t]+(?:- )?)?([^\s])%- \1%gm;' # spacing between commit messages printf '\n\n%s\n\n' '---' ) createMDRenderedSection "$HASH" "$CONTENT" IFS=$OIFS } export -f createCommitSection # diff of newline delimeted string arrays (A - B) strarrDiff() { local SKIP A="$1" B="$2" for i in $A; do SKIP= for j in $B; do [[ "$i" == "$j" ]] && { SKIP=1; break; } done [[ -n "$SKIP" ]] || printf '%s\n' "$i" done } # create a changelog file createChangelog() { local HEADER NEW_COMMIT_HASHES OLD_COMMIT_HASHES COMMITS_ADDED COMMITS_REMOVED local NEW_CHANGELOG=1 # start at project root cd ${PROJECT_ROOT} # if gitlab we use merges if github we use pulls if isRemoteGitlab; then export PULL_OR_MERGE="merge_requests" else export PULL_OR_MERGE="pull" fi # if exists and in correct format we update the changelog # otherwise we create new changelog from scratch NEW_COMMIT_HASHES=$(git --no-pager log --no-merges --format='%H') if [[ -e "$CHANGELOG_FILE" ]]; then OLD_COMMIT_HASHES=$(getMDCommentSection 'COMMITS') if [[ -n "$OLD_COMMIT_HASHES" ]]; then NEW_CHANGELOG=0 # get commits added / removed from last changelog COMMITS_ADDED=$(strarrDiff "$NEW_COMMIT_HASHES" "$OLD_COMMIT_HASHES") COMMITS_REMOVED=$(strarrDiff "$OLD_COMMIT_HASHES" "$NEW_COMMIT_HASHES") fi fi # create the new header printf -v HEADER '%s\n\n\n\n' "## $CHANGELOG_TITLE" # create changelog from scratch if (( ${NEW_CHANGELOG} == 1 )); then createMDRenderedSection 'HEADER' "$HEADER" > ${CHANGELOG_FILE} createMDCommentSection 'COMMITS' "$NEW_COMMIT_HASHES" >> ${CHANGELOG_FILE} parallel --env -k -j 0 createCommitSection ::: $NEW_COMMIT_HASHES >>${CHANGELOG_FILE} # create temp changelog and merge with old else createMDRenderedSection 'HEADER' "$HEADER" > ${TMP_CHANGELOG_FILE} createMDCommentSection 'COMMITS' "$NEW_COMMIT_HASHES" >> ${TMP_CHANGELOG_FILE} parallel --env -k -j 0 createCommitSection ::: $COMMITS_ADDED >>${TMP_CHANGELOG_FILE} ( cat ${TMP_CHANGELOG_FILE} cat ${CHANGELOG_FILE} | removeMDRenderedSection 'HEADER' | removeMDCommentSection 'COMMITS' | removeMDRenderedSections "$COMMITS_REMOVED" ) > ${CHANGELOG_FILE} rm -f ${TMP_CHANGELOG_FILE} fi } main() { touch ${CHANGELOG_INDICATOR_FILE} createChangelog git add ${CHANGELOG_FILE} && git commit --amend -C HEAD --no-verify && echo "$REMOTE_NAME" > ${REMOTE_FILE} && exit 0 || exit 1 } # allow execution outside of git hook if (( $# > 0 )) && [[ "$1" == "-exec" ]]; then main fi # don't run if hook is disabled # this is primarily for conflict resolution if [[ -e ${DISABLED_INDICATOR_FILE} ]]; then rm -f ${DISABLED_INDICATOR_FILE} exit 0 # prevent recursion # since a 'commit --amend' will trigger the post-commit script again # we have to check if the changelog file has been commited yet # if changelog commited this is recursive call, do nothing elif [[ -e ${CHANGELOG_INDICATOR_FILE} ]]; then rm -f ${CHANGELOG_INDICATOR_FILE} exit 0 else main fi ================================================ FILE: resources/git/hooks/pre-commit ================================================ #!/usr/bin/env bash # # Summary: Create CONTRIBUTORS.md and requirements.txt on commit # Author: DevOpSec <https://github.com/devopsec> # Usage: Copy to your repo in <repo>/.git/hooks/pre-commit # Alternatively the functions can be run outside of git (using -exec option) # Notes: Requires pipreqs -> pip install pipreqs # To add directories to recursive search, change INCLUDE_DIRS (its an array) # TODO: Need to add automated CVE checking for python libs, such as with safety # more info: https://github.com/pyupio/safety # TODO: Need to add python linting / validation checks, here are a couple examples: # https://github.com/pre-commit/pre-commit-hooks # https://econ-project-templates.readthedocs.io/en/stable/pre-commit.html # # unshadow git command unset -f git # project root PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" # destination file CONTRIBUTING_FILE="CONTRIBUTORS.md" # indicator that we commited the changelog CHANGELOG_INDICATOR_FILE="${PROJECT_ROOT}/.changelog_commited" # where are the python projects for this repo? INCLUDE_DIRS=("${PROJECT_ROOT}/gui") # dirs to exclude from requirements EXCLUDE_DIRS=( ) # destination file REQUIREMENTS_FILE="requirements.txt" # pipreqs only catches stdlib libraries INCLUDE_LIBS=( 'Jinja2==3.0.3' 'Werkzeug==2.0.2' 'itsdangerous==2.0.1' 'Flask~=2.2.0' 'docutils<0.17,>=0.12' 'mysqlclient' 'docker' 'sphinx' 'recommonmark' 'sphinxcontrib-httpdomain' 'sphinx-rtd-theme' 'pem' 'twilio' 'SQLAlchemy~=2.0' 'acme' ) # excludes for conflicting libs EXCLUDE_LIBS=( 'Jinja2' 'Werkzeug' 'itsdangerous' 'Flask' 'docker_py' 'pyspark' 'acme.hello' 'MySQL-python' 'SQLAlchemy' 'certbot' ) # utility function joinwith() { local START="$1" IFS="$2" END="$3" ARR=() shift;shift;shift for VAR in "$@"; do ARR+=("${START}${VAR}${END}") done echo "${ARR[*]}" } # creates CONTRIBUTORS.md for project createContributors() { local OUT_FILE="${PROJECT_ROOT}/${CONTRIBUTING_FILE}" printf '%s\n\n%s\n\n' \ "## Thank you to all contributors for your hard work" \ "### Contributors" > ${OUT_FILE} git shortlog -sn HEAD | grep -oP '[\s\d]*\K.*'| awk '{for (i=1; i<=NF; i++) print "- " $0}' | sort -u >> ${OUT_FILE} git add ${OUT_FILE} } # creates requirements.txt for python projects # TODO: reformat so code is more readable createRequirements() { local OUT_FILE="" local EXCLUDE_ARGS="" # sanity check, is this a python project? if (( $(find ${INCLUDE_DIRS[@]} -type f -name "*.py" 2>/dev/null | wc -l) == 0 )); then return fi if (( ${#EXCLUDE_DIRS[@]} > 0 )); then EXCLUDE_ARGS="--ignore $(joinwith '' ',' '' ${EXCLUDE_DIRS[@]})" fi for PYTHON_PROJECT in ${INCLUDE_DIRS[@]}; do OUT_FILE="${PYTHON_PROJECT}/${REQUIREMENTS_FILE}" if (( ${#EXCLUDE_LIBS[@]} != 0 )); then LIBS=( ${INCLUDE_LIBS[@]} $(pipreqs --mode no-pin --print ${EXCLUDE_ARGS} ${PYTHON_PROJECT} 2>/dev/null | sed -r '/^\s*$/d' | grep -E -v $(joinwith '^' '|' '$' ${EXCLUDE_LIBS[@]}); exit ${PIPESTATUS[0]};) ) else LIBS=( ${INCLUDE_LIBS[@]} $(pipreqs --mode no-pin --print ${EXCLUDE_ARGS} ${PYTHON_PROJECT} 2>/dev/null | sed -r '/^\s*$/d'; exit ${PIPESTATUS[0]};) ) fi # make sure pipreqs didn't fail if (( $? != 0 )); then exit 1 fi # only create requirements.txt if we found dependencies if (( ${#LIBS[@]} > 0 )); then printf '%s\n' "${LIBS[@]}" | sort -u > ${OUT_FILE} git add ${OUT_FILE} fi done } # prevent some common syntax errors from getting committed checkSyntaxErrors() { if ! _git_check_syntax >/dev/null; then exit 1 fi } # make sure merge conflicts are all handled checkMergeConflicts() { if [[ "$(git diff-index --cached -G '^<<<<<<< HEAD' HEAD)" != "" ]]; then exit 1 fi } main() { # make sure un-staged and un-tracked code is not checked by this script # in the future this will be more important as we add linting BACKUPS_DIR="/tmp/$(date +%s)" # reset unstaged files before exiting even if we fail early # if resetting files fail then they will still exist in backup dir resetUnstagedFiles() { find ${BACKUPS_DIR} -type f -print 2>/dev/null | perl -e "\$bd='${BACKUPS_DIR}';" -pe 's%^${bd}/(.*)$%\1%gm' | xargs sh -c 'for arg do mkdir -p "'"${PROJECT_ROOT}"'/$(dirname ${arg} 2>/dev/null)"; mv -f "'"${BACKUPS_DIR}"'/${arg}" "'"${PROJECT_ROOT}"'/${arg}"; done' _ } trap resetUnstagedFiles EXIT git ls-files -z -o --exclude-standard | xargs -0 sh -c 'for arg do mkdir -p "'"${BACKUPS_DIR}"'/$(dirname ${arg} 2>/dev/null)"; mv -f "${arg}" "'"${BACKUPS_DIR}"'/${arg}"; done' _ git ls-files -z -m --exclude-standard | xargs -0 sh -c 'for arg do mkdir -p "'"${BACKUPS_DIR}"'/$(dirname ${arg} 2>/dev/null)"; cp -f "${arg}" "'"${BACKUPS_DIR}"'/${arg}"; done' _ git checkout -- ${PROJECT_ROOT} checkMergeConflicts checkSyntaxErrors createContributors createRequirements exit 0 } # allow execution outside of git hook if (( $# > 0 )) && [[ "$1" == "-exec" ]]; then main fi # prevent recursion if [[ -e ${CHANGELOG_INDICATOR_FILE} ]]; then exit 0 else main fi ================================================ FILE: resources/git/hooks/pre-push ================================================ #!/usr/bin/env bash # # Summary: Allow / Disallow push based on checks # Author: DevOpSec <https://github.com/devopsec> # Usage: Copy to your repo in <repo>/.git/hooks/pre-push # Notes: Adding this hook to your workflow works with the git wrapper # script to verify the remote url's were changed correctly # This hook runs after post-commit # # Params: This hook is called with the following parameters: # # $1 -- Name of the remote to which the push is being done # $2 -- URL to which the push is being done # # If pushing without using a named remote those arguments will be equal # # Information about the commits which are being pushed is supplied as lines to # the standard input in the form: # # <local ref> <local sha1> <remote ref> <remote sha1> # # project root PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" # passed in when hook is called PUSH_REMOTE="$1" PUSH_URL="$2" # created by post-commit hook REMOTE_FILE="${PROJECT_ROOT}/.push_remote" # if remote file exists we have commits that are not pushed yet if [[ -e "$REMOTE_FILE" ]]; then REMOTE_NAME="$(cat ${REMOTE_FILE})" # if default remote used we have to lookup the remote name if [[ "${PUSH_REMOTE}" == "." ]]; then PUSH_REMOTE=$(git config --get checkout.defaultremote) fi # if default is not set (likely going to fail) then use origin PUSH_REMOTE=${PUSH_REMOTE:-origin} # if user is pushing to a different remote without rewriting url's fail if [[ "${PUSH_REMOTE}" != "${REMOTE_NAME}" ]]; then echo >&2 "Can not push to a different remote without rewriting commit msg url's" echo >&2 "Recommit with the following cmd: git commit --amend --remote=<new remote>" exit 1 else rm -f ${REMOTE_FILE} fi fi exit 0 ================================================ FILE: resources/git/hooks/prepare-commit-msg ================================================ #!/usr/bin/env bash # # Summary: Allow / Disallow commit hooks dependent on situation # Author: DevOpSec <https://github.com/devopsec> # Usage: Copy to your repo in <repo>/.git/hooks/prepare-commit-msg # Notes: Adding this hook to your workflow can alleviate some merging issues # caused by the automated file creation in the commit hooks # This hook runs after pre-commit and before commit-msg # # unshadow git command unset -f git # project root PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" # passed in when hook is called COMMIT_MSG_FILE="$1" COMMIT_SOURCE="$2" COMMIT_HASH="$3" # indicator that post commit hooks has been disabled DISABLED_INDICATOR_FILE="${PROJECT_ROOT}/.postcommit_disabled" # fast-forward pulls/commits/merges if git reflog -1 | grep -q 'Fast-forward' 2>/dev/null; then RUN_COMMIT_HOOKS=0 # git revert elif [[ -e "${PROJECT_ROOT}/.git/REVERT_HEAD" ]]; then RUN_COMMIT_HOOKS=0 # git cherry-pick elif [[ -e "${PROJECT_ROOT}/.git/CHERRY_PICK_HEAD" ]]; then RUN_COMMIT_HOOKS=0 # squashed commits/merges/rebases elif [[ "$COMMIT_SOURCE" == "squash" || -e "${PROJECT_ROOT}/.git/SQUASH_MSG" ]]; then RUN_COMMIT_HOOKS=1 # git merge (will only run if not an automatic merge) elif [[ "$COMMIT_SOURCE" == "merge" || -e "${PROJECT_ROOT}/.git/MERGE_MSG" ]]; then RUN_COMMIT_HOOKS=1 # git commit elif [[ "$COMMIT_SOURCE" == "commit" ]]; then RUN_COMMIT_HOOKS=1 # default is allow hooks if executed by git else RUN_COMMIT_HOOKS=1 fi if (( $RUN_COMMIT_HOOKS == 0 )); then touch ${DISABLED_INDICATOR_FILE} else rm -f ${DISABLED_INDICATOR_FILE} 2>/dev/null fi exit 0 ================================================ FILE: resources/git/merge-changelog.sh ================================================ # # Summary: Merge driver for CHANGELOG conflicts # Author: DevOpSec <https://github.com/devopsec> # Usage: Copy gitconfig to <repo>/.git/config and gitattributes to <repo>/.gitattributes # # $1 == %O - temporary file name for the merge base (origin) # $2 == %A - temporary file name for our version (ours) # $3 == %B - temporary file name for the other branches version (theirs) # $4 == %L - conflict marker length # $5 == %P - the original path quoted for the shell # # TODO: make more elegant solution; such as diffing & merging then rewriting changelog # possible example (if no other conflicts update changelog): # #PROJECT_ROOT="$(git rev-parse --show-toplevel)" # #CONFLICTS=$(git diff --cached --name-only -S '<<<<<<') #if ! echo "$CONFLICTS" | grep -q -v 'CHANGELOG.md'; then # ${PROJECT_ROOT}/.git/hooks/post-commit #fi # For now just keep our version of CHANGELOG.md and update on next commit exit 0 ================================================ FILE: resources/logrotate/consul ================================================ /var/log/consul*.log { daily missingok rotate 5 compress delaycompress create 640 root root sharedscripts copytruncate postrotate systemctl kill -s HUP --kill-who=main rsyslog 2>/dev/null || true endscript } ================================================ FILE: resources/logrotate/dsiprouter ================================================ /var/log/dsiprouter*.log { daily missingok rotate 5 compress delaycompress create 640 root root sharedscripts copytruncate postrotate systemctl kill -s HUP --kill-who=main rsyslog 2>/dev/null || true endscript } ================================================ FILE: resources/logrotate/kamailio ================================================ /var/log/kamailio*.log { daily missingok rotate 5 compress delaycompress create 640 root root sharedscripts postrotate systemctl kill -s HUP --kill-who=main rsyslog 2>/dev/null || true endscript } ================================================ FILE: resources/logrotate/rtpengine ================================================ /var/log/rtpengine*.log { daily missingok rotate 5 compress delaycompress create 640 root root sharedscripts copytruncate postrotate systemctl kill -s HUP --kill-who=main rsyslog 2>/dev/null || true endscript } ================================================ FILE: resources/man/dsiprouter.1 ================================================ .\" Process this file with .\" groff -man -Tascii dsiprouter.1 .\" .TH DSIPROUTER 1 "SEPTEMBER 2022" Linux "User Manuals" .SH NAME dsiprouter \- command line toolm for configuring a dSIPRouter platform. .SH SYNOPSIS .B dsiprouter {COMMAND} [OPTIONS] .SH DESCRIPTION .B dSIPRouter allows you to quickly turn Kamailio into an easy to use SIP Service Provider platform. .SH COMMAND .IP install Installs dSIPRouter and related components as specified. .IP uninstall Uninstall dSIPRouter. .IP clusterinstall Install dSIPRouter in a cluster of nodes .IP upgrade Upgrade dSIPRouter version to the latest release. .IP start Starts dSIPRouter services. .IP stop Stops dSIPRouter services. .IP restart Restarts dSIPRouter services. .IP configurekam Generate new config files for Kamailio based on the dSIPRouter settings. .IP configuredsip Generate new config files for dSIPRouter based on the environment variables (don't use unless you have read through dsiprouter.sh). .IP configurertp Generate new config files for RTPEngine based on the dSIPRouter settings. .IP renewsslcert Renew configured letsencrypt SSL certificate. .IP configuresslcert Configure a new SSL certificate. .IP installmodules Install / uninstall dSIPRouter modules. .IP resetpassword Generate new unique credentials for dSIPRouter services. .IP setcredentials Set credentials for dSIPRouter services manually. .IP licensemanager Manage the dSIPRouter licenses for this system. .IP backup Backup the dSIPRouter data and settings in a SQL file. .IP restore Restore the dSIPRouter settings and data from a SQL file. .IP version|-v|--version Show the currently installed dSIPRouter version. .IP help|-h|--help Show the dSIPRouter CLI usage. .SH OPTIONS .IP "install options" -debug Debug mode to display install step by step. -all|--all Install dsiprouter, kamailio, and rtpengine. -kam|--kamailio Install kamailio. -dsip|--dsiprouter Install dSIPRouter GUI. -rtp|--rtpengine Install rtpengine. -dns|--dnsmasq Install DNSMasq and the dSIPRouter network stack. -homer <homerhost[:heplifyport]> Set the Homer node for dSIPRouter to integrate with. -db <db conn uri>|--database=<db conn uri> Set the database connection uri. -dsipcid <num>|--dsip-clusterid=<num> Set the dsiprouter cluster id. -dbadmin <[user[:pass]@]dbhost[:port][/dbname]>|--database-admin=<[user[:pass]@]dbhost[:port][/dbname]> Set the connection settings for the root/admin database account. -dsipcsync <num>|--dsip-clustersync=<num> Enable or disable cluster synchronization between dSIPRouter nodes. -dsipkey <32 chars>|--dsip-privkey=<32 chars> Set the private key for dSIPRouter. -with_lcr|--with_lcr=<num> Enable or disable least-cost routing module. -with_dev|--with_dev=<num> Whether to install development tools and testing tools. .IP "uninstall options" -debug Debug mode to display uninstall step by step. -all|--all Uninstall entire dSIPRouter platform. -kam|--kamailio Uninstall kamailio. -dsip|--dsiprouter Uninstall dSIPRouter GUI. -rtp|--rtpengine Uninstall rtpengine. .IP "clusterinstall options" -debug Debug mode to display install step by step. <[user1[:pass1]@]node1[:port1]> <[user2[:pass2]@]node2[:port2> ... -- [<install options>] URIs denoting the connection parameters for the nodes to install dSIPRouter on. -- [INSTALL OPTIONS] After the "--", supply all the arguments to the "install" command as normal to configure the dSIPRouter install. .IP "upgrade options" -debug Debug mode to display upgrade step by step. -dsipcid <num>|--dsip-clusterid=<num> Set the dsiprouter cluster id. -rel|--release <release number> The release number to upgrade to. .IP "start options" -debug Debug mode to display starting step by step. -all|--all Start all services. -kam|--kamailio Start kamailio. -dsip|--dsiprouter Start dsiprouter GUI. -rtp|--rtpengine Start rtpengine. .IP "stop options" -debug Debug mode to display stopping step by step. -all|--all Stop all services. -kam|--kamailio Stop kamailio. -dsip|--dsiprouter Stop dsiprouter GUI. -rtp|--rtpengine Stop rtpengine. .IP "restart options" -debug Debug mode to display restarting step by step. -all|--all Restart all services. -kam|--kamailio Restart kamailio. -dsip|--dsiprouter Restart dsiprouter. -rtp|--rtpengine Restart rtpengine. .IP "configurekam options" -debug Show detailed info while configuring kamailio settings. .IP "renewsslcert options" -debug Debug mode to display renewing ssl certificate step by step. .IP "configuresslcert options" -debug Debug mode to display configuring ssl certificate step by step. -f|--force Remove previous SSL ceritificates and configs and configure new one. .IP "installmodules options" -debug Debug mode to display installing modules step by step. .IP "resetpassword options" -debug Debug mode to display resetting password step by step. -all|--all Used to reset all passwords. -dc|--dsip-creds Used to reset dsiprouter gui password. -ac|--api-creds Used to reset api password. -kc|--kam-creds Used to reset kamailio password. -ic|--ipc-creds Used to reset ipc password. -fid|--force-instance-id Force dSIPRouter to use the cloud instance ID as the GUI password. .IP "setcredentials options" -debug Debug mode to display setting credentials step by step. -dc <pass>|--dsip-creds=<pass> Used to set dSIPRouter GUI username/password manually. -ac <token>|--api-creds=<token> Used to set the dSIPRouter API token manually. -kc <pass>|--kam-creds=<pass> Used to set kamalio username/password/host/port/database name manually. -mc <pass>|--mail-creds=<pass> Used to set email useername/password manually. -ic <pass>|--ipc-creds=<pass> Used to set the dSIPRouter IPC token manually. -dac <[user[:pass]@]dbhost[:port][/dbname]>|--db-admin-creds=<[user[:pass]@]dbhost[:port][/dbname]> Update the root/admin database connection settings. -sc <key>|--session-creds=<key> Used to set the key for the flask session manager manually. .IP "licensemanager options" -debug Show detailed info while running the licensemanager sub-command. -list List all the licenses associated with this machine. -retrieve <license_key or 'tag=<tag>'> Get detailed information about the license(s) either by key or by tag. To filter by tag the argument should be passed as "tag=THE_TAG_TO_FILTER_ON". Similar filtering is done for options that support filtering by tag below. -activate <license_key> Associate and activate a license on this system. -import <file containing keys> Import a file contianing license keys. The file should contain only license keys, one per line. -clear Remove all licenses associated with this machine. Note that this should be run before decommissioning an dSIPRouter instance. If the licenses are not cleared from the machine before de-provisioning then dOpenSource support staff will have to manually fix them. -deactivate <license_key or 'tag=<tag>'> Dissociate and deactivate a license that is currently installed on this system. -check <license_key or 'tag=<tag>' Check whether the license key(s) is valid and active on this machine. .IP "backup options" -debug Show detailed info while running the backup sub-command. -f <sql file> Specify the path to output the backup file. .IP "restore options" -debug Show detailed info while running the restore sub-command. -f <sql file> Specify the path to import the backup file from. .SH BUGS Report to Github Issues: https://github.com/dOpensource/dsiprouter.git .SH AUTHOR dOpenSource/dSIPRouter ================================================ FILE: resources/mysql/asterisk-realtime-config.sql ================================================ ### asterisk realtime db # configure asterisk server for realtime DB connections/updates # explanation and how to configure: #https://www.voip-info.org/asterisk-realtime/ #https://www.voip-info.org/asterisk-realtime-static CREATE TABLE `bit_ast_config` ( `id` int(11) NOT NULL auto_increment, `cat_metric` int(11) NOT NULL default '0', `var_metric` int(11) NOT NULL default '0', `commented` int(11) NOT NULL default '0', `filename` varchar(128) NOT NULL default '', `category` varchar(128) NOT NULL default 'default', `var_name` varchar(128) NOT NULL default '', `var_val` varchar(128) NOT NULL default '', PRIMARY KEY (`id`), KEY `filename_comment` (`filename`,`commented`) ); #https://www.voip-info.org/asterisk-realtime-sip CREATE TABLE `bit_sip_buddies` ( `id` int(11) NOT NULL auto_increment, `name` varchar(80) NOT NULL default '', `host` varchar(31) NOT NULL default '', `nat` varchar(5) NOT NULL default 'no', `type` enum('user','peer','friend') NOT NULL default 'friend', `accountcode` varchar(20) default NULL, `amaflags` varchar(13) default NULL, `call-limit` smallint(5) unsigned default NULL, `callgroup` varchar(10) default NULL, `callerid` varchar(80) default NULL, `cancallforward` char(3) default 'yes', `canreinvite` char(3) default 'yes', `context` varchar(80) default NULL, `defaultip` varchar(15) default NULL, `dtmfmode` varchar(7) default NULL, `fromuser` varchar(80) default NULL, `fromdomain` varchar(80) default NULL, `insecure` varchar(4) default NULL, `language` char(2) default NULL, `mailbox` varchar(50) default NULL, `md5secret` varchar(80) default NULL, `deny` varchar(95) default NULL, `permit` varchar(95) default NULL, `mask` varchar(95) default NULL, `musiconhold` varchar(100) default NULL, `pickupgroup` varchar(10) default NULL, `qualify` char(3) default NULL, `regexten` varchar(80) default NULL, `restrictcid` char(3) default NULL, `rtptimeout` char(3) default NULL, `rtpholdtimeout` char(3) default NULL, `secret` varchar(80) default NULL, `setvar` varchar(100) default NULL, `disallow` varchar(100) default 'all', `allow` varchar(100) default 'g729;ilbc;gsm;ulaw;alaw', `fullcontact` varchar(80) NOT NULL default '', `ipaddr` varchar(15) NOT NULL default '', `port` smallint(5) unsigned NOT NULL default '0', `regserver` varchar(100) default NULL, `regseconds` int(11) NOT NULL default '0', `lastms` int(11) NOT NULL default '0', `username` varchar(80) NOT NULL default '', `defaultuser` varchar(80) NOT NULL default '', `subscribecontext` varchar(80) default NULL, `useragent` varchar(20) default NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `name_2` (`name`) ) ENGINE=InnoDB ROW_FORMAT=DYNAMIC; ## OR ## #https://wiki.asterisk.org/wiki/display/AST/SIP+Realtime%2C+MySQL+table+structure CREATE TABLE IF NOT EXISTS `sipfriends` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(10) NOT NULL, `ipaddr` varchar(15) DEFAULT NULL, `port` int(5) DEFAULT NULL, `regseconds` int(11) DEFAULT NULL, `defaultuser` varchar(10) DEFAULT NULL, `fullcontact` varchar(35) DEFAULT NULL, `regserver` varchar(20) DEFAULT NULL, `useragent` varchar(20) DEFAULT NULL, `lastms` int(11) DEFAULT NULL, `host` varchar(40) DEFAULT NULL, `type` enum('friend','user','peer') DEFAULT NULL, `context` varchar(40) DEFAULT NULL, `permit` varchar(40) DEFAULT NULL, `deny` varchar(40) DEFAULT NULL, `secret` varchar(40) DEFAULT NULL, `md5secret` varchar(40) DEFAULT NULL, `remotesecret` varchar(40) DEFAULT NULL, `transport` enum('udp','tcp','udp,tcp','tcp,udp') DEFAULT NULL, `dtmfmode` enum('rfc2833','info','shortinfo','inband','auto') DEFAULT NULL, `directmedia` enum('yes','no','nonat','update') DEFAULT NULL, `nat` enum('yes','no','never','route') DEFAULT NULL, `callgroup` varchar(40) DEFAULT NULL, `pickupgroup` varchar(40) DEFAULT NULL, `language` varchar(40) DEFAULT NULL, `allow` varchar(40) DEFAULT NULL, `disallow` varchar(40) DEFAULT NULL, `insecure` varchar(40) DEFAULT NULL, `trustrpid` enum('yes','no') DEFAULT NULL, `progressinband` enum('yes','no','never') DEFAULT NULL, `promiscredir` enum('yes','no') DEFAULT NULL, `useclientcode` enum('yes','no') DEFAULT NULL, `accountcode` varchar(40) DEFAULT NULL, `setvar` varchar(40) DEFAULT NULL, `callerid` varchar(40) DEFAULT NULL, `amaflags` varchar(40) DEFAULT NULL, `callcounter` enum('yes','no') DEFAULT NULL, `busylevel` int(11) DEFAULT NULL, `allowoverlap` enum('yes','no') DEFAULT NULL, `allowsubscribe` enum('yes','no') DEFAULT NULL, `videosupport` enum('yes','no') DEFAULT NULL, `maxcallbitrate` int(11) DEFAULT NULL, `rfc2833compensate` enum('yes','no') DEFAULT NULL, `mailbox` varchar(40) DEFAULT NULL, `session-timers` enum('accept','refuse','originate') DEFAULT NULL, `session-expires` int(11) DEFAULT NULL, `session-minse` int(11) DEFAULT NULL, `session-refresher` enum('uac','uas') DEFAULT NULL, `t38pt_usertpsource` varchar(40) DEFAULT NULL, `regexten` varchar(40) DEFAULT NULL, `fromdomain` varchar(40) DEFAULT NULL, `fromuser` varchar(40) DEFAULT NULL, `qualify` varchar(40) DEFAULT NULL, `defaultip` varchar(40) DEFAULT NULL, `rtptimeout` int(11) DEFAULT NULL, `rtpholdtimeout` int(11) DEFAULT NULL, `sendrpid` enum('yes','no') DEFAULT NULL, `outboundproxy` varchar(40) DEFAULT NULL, `callbackextension` varchar(40) DEFAULT NULL, `registertrying` enum('yes','no') DEFAULT NULL, `timert1` int(11) DEFAULT NULL, `timerb` int(11) DEFAULT NULL, `qualifyfreq` int(11) DEFAULT NULL, `constantssrc` enum('yes','no') DEFAULT NULL, `contactpermit` varchar(40) DEFAULT NULL, `contactdeny` varchar(40) DEFAULT NULL, `usereqphone` enum('yes','no') DEFAULT NULL, `textsupport` enum('yes','no') DEFAULT NULL, `faxdetect` enum('yes','no') DEFAULT NULL, `buggymwi` enum('yes','no') DEFAULT NULL, `auth` varchar(40) DEFAULT NULL, `fullname` varchar(40) DEFAULT NULL, `trunkname` varchar(40) DEFAULT NULL, `cid_number` varchar(40) DEFAULT NULL, `callingpres` enum('allowed_not_screened','allowed_passed_screen','allowed_failed_screen','allowed','prohib_not_screened','prohib_passed_screen','prohib_failed_screen','prohib') DEFAULT NULL, `mohinterpret` varchar(40) DEFAULT NULL, `mohsuggest` varchar(40) DEFAULT NULL, `parkinglot` varchar(40) DEFAULT NULL, `hasvoicemail` enum('yes','no') DEFAULT NULL, `subscribemwi` enum('yes','no') DEFAULT NULL, `vmexten` varchar(40) DEFAULT NULL, `autoframing` enum('yes','no') DEFAULT NULL, `rtpkeepalive` int(11) DEFAULT NULL, `call-limit` int(11) DEFAULT NULL, `g726nonstandard` enum('yes','no') DEFAULT NULL, `ignoresdpversion` enum('yes','no') DEFAULT NULL, `allowtransfer` enum('yes','no') DEFAULT NULL, `dynamic` enum('yes','no') DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `ipaddr` (`ipaddr`,`port`), KEY `host` (`host`,`port`) ) ENGINE=MyISAM; #https://www.voip-info.org/asterisk-realtime-iax CREATE TABLE bit_iax_buddies ( name varchar(30) primary key NOT NULL, username varchar(30), type varchar(6) NOT NULL, secret varchar(50), md5secret varchar(32), dbsecret varchar(100), notransfer varchar(10), inkeys varchar(100), outkey varchar(100), auth varchar(100), accountcode varchar(100), amaflags varchar(100), callerid varchar(100), context varchar(100), defaultip varchar(15), host varchar(31) NOT NULL default 'dynamic', language char(5), mailbox varchar(50), deny varchar(95), permit varchar(95), qualify varchar(4), disallow varchar(100), allow varchar(100), ipaddr varchar(15), port integer default 0, regseconds integer default 0 ); CREATE UNIQUE INDEX bit_iax_buddies_username_idx ON bit_iax_buddies(username); #https://www.voip-info.org/asterisk-realtime-h323 DROP TABLE IF EXISTS h323_peer; CREATE TABLE h323_peer( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(128) NOT NULL UNIQUE, host VARCHAR(15) DEFAULT NULL , secret VARCHAR(64) DEFAULT NULL, context VARCHAR(64) NOT NULL, type VARCHAR(6) NOT NULL, port INT DEFAULT NULL, permit VARCHAR(128) DEFAULT NULL, deny VARCHAR(128) DEFAULT NULL, mailbox VARCHAR(128) DEFAULT NULL, e164 VARCHAR(128) DEFAULT NULL, prefix VARCHAR(128) DEFAULT NULL, allow VARCHAR(128) DEFAULT NULL, disallow VARCHAR(128) DEFAULT NULL, dtmfmode VARCHAR(128) DEFAULT NULL, accountcode INT DEFAULT NULL, amaflags varchar(13) DEFAULT NULL, INDEX idx_name(name), INDEX idx_host(host) ); #https://www.voip-info.org/asterisk-realtime-voicemail CREATE TABLE `bit_voicemail` ( `uniqueid` INT(4) NOT NULL AUTO_INCREMENT, `customer_id` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `context` VARCHAR(10) COLLATE utf8_bin NOT NULL, `mailbox` VARCHAR(10) COLLATE utf8_bin NOT NULL, `password` INT(4) NOT NULL, `fullname` VARCHAR(150) COLLATE utf8_bin DEFAULT NULL, `email` VARCHAR(50) COLLATE utf8_bin DEFAULT NULL, `pager` VARCHAR(50) COLLATE utf8_bin DEFAULT NULL, `tz` VARCHAR(10) COLLATE utf8_bin DEFAULT 'central', `attach` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'yes', `saycid` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'yes', `dialout` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `callback` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `review` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `operator` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `envelope` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `sayduration` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `saydurationm` TINYINT(4) NOT NULL DEFAULT '1', `sendvoicemail` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `delete` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `nextaftercmd` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'yes', `forcename` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `forcegreetings` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'no', `hidefromdir` ENUM('yes','no') COLLATE utf8_bin NOT NULL DEFAULT 'yes', `stamp` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `attachfmt` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `searchcontexts` ENUM('yes','no') COLLATE utf8_bin DEFAULT NULL, `cidinternalcontexts` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `exitcontext` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `volgain` VARCHAR(4) COLLATE utf8_bin DEFAULT NULL, `tempgreetwarn` ENUM('yes','no') COLLATE utf8_bin DEFAULT 'yes', `messagewrap` ENUM('yes','no') COLLATE utf8_bin DEFAULT 'no', `minpassword` INT(2) DEFAULT '4', `vm-password` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-newpassword` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-passchanged` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-reenterpassword` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-mismatch` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-invalid-password` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `vm-pls-try-again` VARCHAR(10) COLLATE utf8_bin DEFAULT NULL, `listen-control-forward-key` VARCHAR(2) COLLATE utf8_bin DEFAULT NULL, `listen-control-reverse-key` VARCHAR(1) COLLATE utf8_bin DEFAULT NULL, `listen-control-pause-key` VARCHAR(1) COLLATE utf8_bin DEFAULT NULL, `listen-control-restart-key` VARCHAR(1) COLLATE utf8_bin DEFAULT NULL, `listen-control-stop-key` VARCHAR(13) COLLATE utf8_bin DEFAULT NULL, `backupdeleted` VARCHAR(3) COLLATE utf8_bin DEFAULT '25', PRIMARY KEY (`uniqueid`), KEY `mailbox_context` (`mailbox`,`context`) ) ENGINE=INNODB DEFAULT CHARSET=latin1; #https://www.voip-info.org/asterisk-realtime-queue CREATE TABLE queue_table ( name VARCHAR(128) PRIMARY KEY, musiconhold VARCHAR(128), announce VARCHAR(128), context VARCHAR(128), timeout INT(11), monitor_join BOOL, monitor_format VARCHAR(128), queue_youarenext VARCHAR(128), queue_thereare VARCHAR(128), queue_callswaiting VARCHAR(128), queue_holdtime VARCHAR(128), queue_minutes VARCHAR(128), queue_seconds VARCHAR(128), queue_lessthan VARCHAR(128), queue_thankyou VARCHAR(128), queue_reporthold VARCHAR(128), announce_frequency INT(11), announce_round_seconds INT(11), announce_holdtime VARCHAR(128), retry INT(11), wrapuptime INT(11), maxlen INT(11), servicelevel INT(11), strategy VARCHAR(128), joinempty VARCHAR(128), leavewhenempty VARCHAR(128), eventmemberstatus BOOL, eventwhencalled BOOL, reportholdtime BOOL, memberdelay INT(11), weight INT(11), timeoutrestart BOOL, periodic_announce VARCHAR(50), periodic_announce_frequency INT(11), ringinuse BOOL, setinterfacevar BOOL ); #https://www.voip-info.org/asterisk-realtime-extensions CREATE TABLE `bit_extensions_table` ( `id` int(11) NOT NULL auto_increment, `context` varchar(20) NOT NULL default '', `exten` varchar(20) NOT NULL default '', `priority` tinyint(4) NOT NULL default '0', `app` varchar(20) NOT NULL default '', `appdata` varchar(128) NOT NULL default '', PRIMARY KEY (`context`,`exten`,`priority`), KEY `id` (`id`) ); #https://www.voip-info.org/ldap #https://www.voip-info.org/asterisk-realtime-meetme CREATE TABLE `bit_meetme` ( `confno` varchar(80) DEFAULT '0' NOT NULL, `pin` varchar(20) NULL, `adminpin` varchar(20) NULL, `members` integer DEFAULT 0 NOT NULL, PRIMARY KEY (confno) ); #https://www.voip-info.org/asterisk-realtime-chansccp2 CREATE TABLE `sccpdevices` ( `name` varchar(15) NOT NULL default '', `type` varchar(45) default NULL, `autologin` varchar(45) default NULL, `description` varchar(45) default NULL, `tzoffset` varchar(45) default NULL, `transfer` varchar(45) default NULL, `speeddial` varchar(45) default NULL, `cfwdall` varchar(45) default NULL, `cfwdbusy` varchar(45) default NULL, `dtmfmode` varchar(45) default NULL, `imageversion` varchar(45) default NULL, `deny` varchar(45) default NULL, `permit` varchar(45) default NULL, `dnd` varchar(45) default NULL, PRIMARY KEY (`name`) ); CREATE TABLE `sccplines` ( `name` varchar(45) NOT NULL default '', `id` varchar(45) default NULL, `pin` varchar(45) default NULL, `label` varchar(45) default NULL, `description` varchar(45) default NULL, `context` varchar(45) default NULL, `incominglimit` varchar(45) default NULL, `transfer` varchar(45) default NULL, `mailbox` varchar(45) default NULL, `vmnum` varchar(45) default NULL, `cid_name` varchar(45) default NULL, `cid_num` varchar(45) default NULL, `trnsfvm` varchar(45) default NULL, `secondary_dialtone_digits` varchar(45) default NULL, `secondary_dialtone_tone` varchar(45) default NULL, `musicclass` varchar(45) default NULL, `language` varchar(45) default NULL, `accountcode` varchar(45) default NULL, `rtptos` varchar(45) default NULL, `echocancel` varchar(45) default NULL, `silencesuppression` varchar(45) default NULL, `callgroup` varchar(45) default NULL, `pickupgroup` varchar(45) default NULL, `amaflags` varchar(45) default NULL, PRIMARY KEY (`name`) ); ## another version of realtime db for replication #http://www.ntegratedsolutions.com/wp-content/uploads/2012/07/Asterisk_MySQL_Cluster_Presentation.pdf CREATE database asteriskdb; CREATE TABLE `extensions` ( `id` int(11) NOT NULL auto_increment, `context` varchar(20) NOT NULL default '', `exten` varchar(20) NOT NULL default '', `priority` tinyint(4) NOT NULL default '0', `app` varchar(20) NOT NULL default '', `appdata` varchar(128) NOT NULL default '', `accountcode` varchar(20) default NULL, `notes` varchar(255) default NULL, PRIMARY KEY (`context`,`exten`,`priority`), KEY `id` (`id`) ); CREATE TABLE `voicemail` ( `uniqueid` int(11) NOT NULL auto_increment, `customer_id` varchar(11) NOT NULL default '0', `context` varchar(50) NOT NULL default '', `mailbox` varchar(11) NOT NULL default '0', `password` varchar(5) NOT NULL default '0', `fullname` varchar(150) NOT NULL default '', `email` varchar(50) NOT NULL default '', `pager` varchar(50) NOT NULL default '', `tz` varchar(10) NOT NULL default 'central', `attach` varchar(4) NOT NULL default 'yes', `saycid` varchar(4) NOT NULL default 'yes', `dialout` varchar(10) NOT NULL default '', `callback` varchar(10) NOT NULL default '', `review` varchar(4) NOT NULL default 'no', `operator` varchar(4) NOT NULL default 'no', `envelope` varchar(4) NOT NULL default 'no', `sayduration` varchar(4) NOT NULL default 'no', `saydurationm` tinyint(4) NOT NULL default '1', `sendvoicemail` varchar(4) NOT NULL default 'no', `delete` varchar(4) NOT NULL default 'no', `nextaftercmd` varchar(4) NOT NULL default 'yes', `forcename` varchar(4) NOT NULL default 'no', `forcegreetings` varchar(4) NOT NULL default 'no', `hidefromdir` varchar(4) NOT NULL default 'yes', PRIMARY KEY (`uniqueid`), KEY `mailbox_context` (`mailbox`,`context`) ); CREATE TABLE `sip` ( `id` int(11) NOT NULL auto_increment, `name` varchar(80) NOT NULL default '', `accountcode` varchar(20) default NULL, `amaflags` varchar(13) default NULL, `callgroup` varchar(10) default NULL, `callerid` varchar(80) default NULL, `canreinvite` char(3) default 'yes', `context` varchar(80) default NULL, `defaultip` varchar(15) default NULL, `dtmfmode` varchar(7) default NULL, `fromuser` varchar(80) default NULL, `fromdomain` varchar(80) default NULL, `host` varchar(31) NOT NULL default '', `insecure` varchar(4) default NULL, `language` char(2) default NULL, `mailbox` varchar(50) default NULL, `md5secret` varchar(80) default NULL, `nat` varchar(5) NOT NULL default 'no', `deny` varchar(95) default NULL, `permit` varchar(95) default NULL, `mask` varchar(95) default NULL, `pickupgroup` varchar(10) default NULL, `port` varchar(5) NOT NULL default '', `qualify` char(3) default NULL, `restrictcid` char(1) default NULL, `rtptimeout` char(3) default NULL, `rtpholdtimeout` char(3) default NULL, `secret` varchar(80) default NULL, `type` varchar(6) NOT NULL default 'friend', `username` varchar(80) NOT NULL default '', `disallow` varchar(100) default 'all', `allow` varchar(100) default 'gsm;ulaw;alaw', `musiconhold` varchar(100) default NULL, `regseconds` int(11) NOT NULL default '0', `ipaddr` varchar(15) NOT NULL default '', `regexten` varchar(80) NOT NULL default '', `cancallforward` char(3) default 'yes', `setvar` varchar(100) NOT NULL default '', `fullcontact` varchar(80) default NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`), KEY `name_2` (`name`) ); CREATE TABLE `pins` ( `id` int(11) NOT NULL auto_increment, `company` varchar(20) NOT NULL default '', `pin` varchar(10) NOT NULL default '', `active` varchar(5) NOT NULL default 'no', `accountcode` varchar(20) NOT NULL default '', `notes` varchar(255) default NULL, PRIMARY KEY (`company`,`pin`), KEY `id` (`id`) ); ================================================ FILE: resources/mysql/asterisk-realtime-setup.sql ================================================ use kamailio; insert into domain values (null,'test.ca','test.ca',NOW()); /* Most likely we will need to add a setid to this table so that we can load balance between multiple PBX's */ insert into dsip_domain_mapping values (null,0,1,'','',1); /* Type 0=integer 2=string */ insert into domain_attrs values (null,'test.ca','dispatcher_setid',0,'1',NOW()); insert into domain_attrs values (null,'test.ca','dispatcher_inv_alg',0,'4',NOW()); insert into domain_attrs values (null,'test.ca','dispatcher_reg_alg',0,'4',NOW()); insert into domain_attrs values (null,'test.ca','db_host',2,'realtime-settings.mysql.database.test.ca',NOW()); insert into domain_attrs values (null,'test.ca','db_user',2,'vmrealtime@realtime-settings',NOW()); insert into domain_attrs values (null,'test.ca','db_pass',2,'SecretPasssword',NOW()); insert into domain_attrs values (null,'test.ca','db_name',2,'asterisk',NOW()); insert into domain_attrs values (null,'test.ca','db_table',2,'sipusers',NOW()); insert into domain_attrs values (null,'test.ca','db_userfield',2,'name',NOW()); insert into domain_attrs values (null,'test.ca','db_passfield',2,'secret',NOW()); insert into domain_attrs values (null,'test.ca','db_pass_alg',2,'secret',NOW()); /* dispatcher with Setid being 1 */ insert into dispatcher values (null,1,'sip:208.79.81.99',0,0,'',''); insert into dispatcher values (null,1,'sip:192.73.246.131',0,0,'',''); insert into dispatcher values (null,1,'sip:162.248.220.121',0,0,'',''); ================================================ FILE: resources/stir_shaken/generate_self_signed_cert.sh ================================================ # Install dependencies apt -y install openssl coreutils TMP_CERT_DIR=/tmp/stir-shaken-ca DSIP_CERT_DIR=/etc/dsiprouter/certs/stirshaken # Generate Root Certificate Private Key mkdir $TMP_CERT_DIR cd $TMP_CERT_DIR openssl ecparam -noout -name prime256v1 -genkey -out ca-key.pem # Generate Public Key openssl req -x509 -new -nodes -key ca-key.pem -sha256 -days 1825 -out ca-cert.pem #Generate EC Private Key openssl ecparam -noout -name prime256v1 -genkey -out sp-key.pem #Generate an openssl.conf file which includes a hex-encoded TNAuthList extension cat >TNAuthList.conf << EOF asn1=SEQUENCE:tn_auth_list [tn_auth_list] field1=EXP:0,IA5:1001 EOF openssl asn1parse -genconf TNAuthList.conf -out TNAuthList.der cat >openssl.conf << EOF [ req ] distinguished_name = req_distinguished_name req_extensions = v3_req [ req_distinguished_name ] commonName = "SHAKEN" [ v3_req ] EOF od -An -t x1 -w TNAuthList.der | sed -e 's/ /:/g' -e 's/^/1.3.6.1.5.5.7.1.26=DER/' >>openssl.conf # Generate a Certificate Signing Request, which includes our required TNAuthorizationList openssl req -new -nodes -key sp-key.pem -keyform PEM \ -subj '/C=US/ST=VA/L=Somewhere/O=AcmeTelecom, Inc./OU=VOIP/CN=SHAKEN' \ -sha256 -config openssl.conf \ -out sp-csr.pem # On the CA side, generate the certificate (User-CSR + CA-cert + CA-key => User-cert) openssl x509 -req -in sp-csr.pem -CA ../stir-shaken-ca/ca-cert.pem -CAkey ../stir-shaken-ca/ca-key.pem -CAcreateserial \ -days 825 -sha256 -extfile openssl.conf -extensions v3_req -out sp-cert.pem # verify that the SP certificate contains the TNAuthList extension openssl x509 -in sp-cert.pem -text -noout # Copy Key and Certificate to /opt/dsiprouter cp $TMP_CERT_DIR/sp-cert.pem $DSIP_CERT_DIR cp $TMP_CERT_DIR/sp-key.pem $DSIP_CERT_DIR #Change the ownership of the certificates and key chown -R dsiprouter:kamailio $DSIP_CERT_DIR #Change permissions chmod -R 755 $DSIP_CERT_DIR ================================================ FILE: resources/syslog/consul.conf ================================================ local3.* /var/log/consul.log & stop ================================================ FILE: resources/syslog/dsiprouter.conf ================================================ $EscapeControlCharactersOnReceive off $Escape8BitCharactersOnReceive off $template dsipFormat,"%rawmsg:6:$:%\n" local2.* /var/log/dsiprouter.log;dsipFormat & stop ================================================ FILE: resources/syslog/kamailio.conf ================================================ local0.* /var/log/kamailio.log & stop ================================================ FILE: resources/syslog/rsyslog.conf ================================================ ############################################################# # dSIPRouter rsyslog.conf(5) - rsyslogd(8) configuration file ############################################################# ############################################################# # MODULES ############################################################# # provides support for local system logging module(load="imuxsock") # provides kernel logging support module(load="imklog") # provides --MARK-- message capability #module(load="immark") # provides UDP syslog reception #module(load="imudp") #input(type="imudp" port="514") # provides TCP syslog reception #module(load="imtcp") #input(type="imtcp" port="514") ############################################################# # GLOBAL DIRECTIVES ############################################################# # Use traditional timestamp format # For high precision timestamps comment this line out $ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat # File syncing capability is disabled by default # Usually not required and an extreme performance hit #$ActionFileEnableSync on # Set the default permissions for all log files $FileOwner root $FileGroup adm $FileCreateMode 0640 $DirCreateMode 0755 $Umask 0022 # Where to place spool and state files $WorkDirectory /var/spool/rsyslog # Include all config files in /etc/rsyslog.d/ $IncludeConfig /etc/rsyslog.d/*.conf ############################################################# # RULES ############################################################# *.*;auth.none;authpriv.none;cron.none /var/log/syslog auth.*,authpriv.* /var/log/auth.log cron.* /var/log/cron.log daemon.* /var/log/daemon.log kern.* /var/log/kern.log lpr.* /var/log/lpr.log mail.* /var/log/mail.log user.* /var/log/user.log *.emerg :omusrmsg:* ================================================ FILE: resources/syslog/rtpengine.conf ================================================ local1.* /var/log/rtpengine.log & stop ================================================ FILE: resources/terraform/do/.gitignore ================================================ .terraform .terraform* terraform.tfvars terraform.tfstate terraform.tfstate.backup ================================================ FILE: resources/terraform/do/README.md ================================================ ## Installing dSIPRouter Using Terraform on Digital Ocean 1. Generate an SSH key if you don't already have one 2. Configure the SSH key into your Digital Ocean Account 3. Obtain a Digital Ocean API key and store the key as an environment variable ``` export DIGITALOCEAN_TOKEN='put your token here' ``` 4. Copy terraform.tfvars.sample to terraform.tfvars ``` cp terraform.tfvars.sample terraform.tfvars ``` 5. Modify terraform.tfvars so that it overrides your variables, which is located in variables.tf. The pvt_key_path is the location of your private key. The pub_key_name is the name of the public key you defined when you uploaded your SSH key. The dsiprouter_prefix is the prefix that will be concatenated to the name of the droplet that will be created. The number_of_environments is used to specify how many instances will be crated. ``` pvt_key_path="your path to key" dns_domain="dsiprouter.net" dns_hostname="training" number_of_environments=1 pub_key_name="dopensource-training" additional_commands="echo" ``` All of the variables and any default values can be found in variables.tf. 6. Create a new instance of dSIPRouter The following command will create a new instance of dSIPRouter based on the master branch. The OS image will be Debian 11 and the dsiprouter_prefix will be overriden by demo. ``` terraform apply -var branch=master ``` If you want to create a demo instance of dSIPRouter in your Digitalocean environment with a DNS record use this. Note, you will need to change the dns_demo_domain variable to a domain that you have hosted with DigitalOcean. ``` terraform apply -var branch=master -var dns_hostname=test -var dns_demo_domain=dsiprouter.org -var additional_commands='dsiprouter setcredentials -dc admin:ZmIwMTdmY2I5NjE4' ``` 7. Destroy your instance if you are done with it by using the terraform destroy command ``` terraform destroy ``` ## Need Help? We offer paid support for this Terraform script! You can purchase 2 hours of support from [here](https://dopensource.com/product-category/prepaid-support/) ================================================ FILE: resources/terraform/do/main.tf ================================================ terraform { required_providers { digitalocean = { source = "digitalocean/digitalocean" version = "2.60.0" } } } provider "digitalocean" {} data "digitalocean_ssh_key" "ssh_key" { name = var.pub_key_name } resource "digitalocean_reserved_ip" "floating_ip" { region = var.region } resource "digitalocean_record" "dns_record" { domain = var.dns_domain type = "A" name = var.dns_hostname value = digitalocean_reserved_ip.floating_ip.ip_address } resource "digitalocean_droplet" "dsiprouter" { name = digitalocean_record.dns_record.fqdn region = var.region size = var.image_size image = var.image ssh_keys = [data.digitalocean_ssh_key.ssh_key.fingerprint] } # we w for the assignment to complete and then run provisioner # this allows for us to have a valid DNS record prior to install resource "digitalocean_reserved_ip_assignment" "floating_ip_assn" { ip_address = digitalocean_reserved_ip.floating_ip.ip_address droplet_id = digitalocean_droplet.dsiprouter.id provisioner "remote-exec" { connection { type = "ssh" user = "root" host = digitalocean_reserved_ip.floating_ip.ip_address private_key = file(var.pvt_key_path) timeout = "5m" } inline = [ "for i in `seq 0 10`; do [ $i -eq 10 ] && { echo 'failed waiting on cloud-init boot'; exit 1; } || { [ -f /var/lib/cloud/instance/boot-finished ] && break; sleep 3; }; done", "apt-get update -y", "for i in `seq 0 10`; do [ $i -eq 10 ] && { echo 'failed waiting on DNS record'; exit 1; } || { getent hosts ${digitalocean_record.dns_record.fqdn} >/dev/null && break; sleep 3; }; done", "apt-get install -y git", var.pull_request != "" ? "git clone https://github.com/dOpensource/dsiprouter.git /opt/dsiprouter && cd /opt/dsiprouter && git fetch origin pull/${var.pull_request}/head:pr_${var.pull_request}; git switch pr_${var.pull_request}" : "git clone -b ${var.branch} https://github.com/dOpensource/dsiprouter.git /opt/dsiprouter", "/opt/dsiprouter/dsiprouter.sh install -all", "${var.additional_commands}" ] } } ================================================ FILE: resources/terraform/do/terraform.tfvars.sample ================================================ pvt_key_path="/Users/mackhendricks/.ssh/dopensource-training" dns_domain="dsiprouter.net" dns_hostname="training" number_of_environments=1 pub_key_name="dopensource-training" additional_commands="echo" ================================================ FILE: resources/terraform/do/variables.tf ================================================ variable "pvt_key_path" { type=string } variable "pub_key_name" { type=string } variable "dns_domain" { type=string default="" } variable "dns_hostname" { type=string default="demo" } variable "branch" { type=string default="master" } variable "pull_request" { type=string default="" } variable "region" { type=string default="tor1" } variable "image" { type=string default="debian-12-x64" } variable "image_size" { type=string default="2gb" } variable "additional_commands" { type=string default="echo" } ================================================ FILE: resources/upgrade/v0.72/scripts/bootstrap.sh ================================================ #!/usr/bin/env bash export BOOTSTRAPPING_UPGRADE=1 export DSIP_PROJECT_DIR='/tmp/dsiprouter' TAG_NAME='v0.72-rel' REPO_URL='https://github.com/dOpensource/dsiprouter.git' rm -f /etc/dsiprouter/.requirementsinstalled rm -rf "$DSIP_PROJECT_DIR" 2>/dev/null git clone --depth 1 -c advice.detachedHead=false -b "$TAG_NAME" "$REPO_URL" "$DSIP_PROJECT_DIR" ${DSIP_PROJECT_DIR}/dsiprouter.sh upgrade -rel v0.72 ================================================ FILE: resources/upgrade/v0.72/scripts/migrate.sh ================================================ #!/usr/bin/env bash # set project dir (where src files are located) export DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # import dsip_lib utility / shared functions if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi printdbg 'backing up configs just in case the upgrade fails' BACKUP_DIR="/var/backups" CURR_BACKUP_DIR="${BACKUP_DIR}/$(date '+%s')" mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,var/lib/mysql,${HOME},etc/dsiprouter,etc/kamailio,etc/rtpengine} cp -rf /opt/dsiprouter/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -rf /etc/kamailio/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -rf /var/lib/mysql/. ${CURR_BACKUP_DIR}/var/lib/mysql/ cp -f /etc/my.cnf ${CURR_BACKUP_DIR}/etc/ 2>/dev/null cp -rf /etc/mysql/. ${CURR_BACKUP_DIR}/etc/mysql/ cp -f ${HOME}/.my.cnf ${CURR_BACKUP_DIR}/${HOME}/ 2>/dev/null # TODO: backup current systemd service files printdbg 'retrieving current settings' export DSIP_ID=$(cat /etc/machine-id | hashCreds) export DSIP_CLUSTER_ID=$(getConfigAttrib "DSIP_CLUSTER_ID" "/etc/dsiprouter/gui/settings.py") export DSIP_CLUSTER_SYNC=$(getConfigAttrib "DSIP_CLUSTER_SYNC" "/etc/dsiprouter/gui/settings.py") export DSIP_PROTO=$(getConfigAttrib "DSIP_PROTO" "/etc/dsiprouter/gui/settings.py") export DSIP_PORT=$(getConfigAttrib "DSIP_PORT" "/etc/dsiprouter/gui/settings.py") export DSIP_USERNAME=$(getConfigAttrib "DSIP_USERNAME" "/etc/dsiprouter/gui/settings.py") export DSIP_API_PROTO=$(getConfigAttrib "DSIP_API_PROTO" "/etc/dsiprouter/gui/settings.py") export DSIP_API_PORT=$(getConfigAttrib "DSIP_API_PORT" "/etc/dsiprouter/gui/settings.py") export DSIP_PRIV_KEY=$(getConfigAttrib "DSIP_PRIV_KEY" "/etc/dsiprouter/gui/settings.py") export DSIP_PID_FILE=$(getConfigAttrib "DSIP_PID_FILE" "/etc/dsiprouter/gui/settings.py") export DSIP_UNIX_SOCK=$(getConfigAttrib "DSIP_UNIX_SOCK" "/etc/dsiprouter/gui/settings.py") export DSIP_IPC_SOCK=$(getConfigAttrib "DSIP_IPC_SOCK" "/etc/dsiprouter/gui/settings.py") export DSIP_LOG_LEVEL=$(getConfigAttrib "DSIP_LOG_LEVEL" "/etc/dsiprouter/gui/settings.py") export DSIP_LOG_FACILITY=$(getConfigAttrib "DSIP_LOG_FACILITY" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_KEY=$(getConfigAttrib "DSIP_SSL_KEY" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_CERT=$(getConfigAttrib "DSIP_SSL_CERT" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_CA=$(getConfigAttrib "DSIP_SSL_CA" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_EMAIL=$(getConfigAttrib "DSIP_SSL_EMAIL" "/etc/dsiprouter/gui/settings.py") export DSIP_CERTS_DIR=$(getConfigAttrib "DSIP_CERTS_DIR" "/etc/dsiprouter/gui/settings.py") export ROLE=$(getConfigAttrib "ROLE" "/etc/dsiprouter/gui/settings.py") export GUI_INACTIVE_TIMEOUT=$(getConfigAttrib "GUI_INACTIVE_TIMEOUT" "/etc/dsiprouter/gui/settings.py") export KAM_DB_HOST=$(getConfigAttrib "KAM_DB_HOST" "/etc/dsiprouter/gui/settings.py") export KAM_DB_DRIVER=$(getConfigAttrib "KAM_DB_DRIVER" "/etc/dsiprouter/gui/settings.py") export KAM_DB_TYPE=$(getConfigAttrib "KAM_DB_TYPE" "/etc/dsiprouter/gui/settings.py") export KAM_DB_PORT=$(getConfigAttrib "KAM_DB_PORT" "/etc/dsiprouter/gui/settings.py") export KAM_DB_NAME=$(getConfigAttrib "KAM_DB_NAME" "/etc/dsiprouter/gui/settings.py") export KAM_DB_USER=$(getConfigAttrib "KAM_DB_USER" "/etc/dsiprouter/gui/settings.py") export KAM_KAMCMD_PATH=$(getConfigAttrib "KAM_KAMCMD_PATH" "/etc/dsiprouter/gui/settings.py") export KAM_CFG_PATH=$(getConfigAttrib "KAM_CFG_PATH" "/etc/dsiprouter/gui/settings.py") export KAM_TLSCFG_PATH=$(getConfigAttrib "KAM_TLSCFG_PATH" "/etc/dsiprouter/gui/settings.py") export RTP_CFG_PATH=$(getConfigAttrib "RTP_CFG_PATH" "/etc/dsiprouter/gui/settings.py") export FLT_CARRIER=$(getConfigAttrib "FLT_CARRIER" "/etc/dsiprouter/gui/settings.py") export FLT_PBX=$(getConfigAttrib "FLT_PBX" "/etc/dsiprouter/gui/settings.py") export FLT_MSTEAMS=$(getConfigAttrib "FLT_MSTEAMS" "/etc/dsiprouter/gui/settings.py") export FLT_OUTBOUND=$(getConfigAttrib "FLT_OUTBOUND" "/etc/dsiprouter/gui/settings.py") export FLT_INBOUND=$(getConfigAttrib "FLT_INBOUND" "/etc/dsiprouter/gui/settings.py") export FLT_LCR_MIN=$(getConfigAttrib "FLT_LCR_MIN" "/etc/dsiprouter/gui/settings.py") export FLT_FWD_MIN=$(getConfigAttrib "FLT_FWD_MIN" "/etc/dsiprouter/gui/settings.py") export DEFAULT_AUTH_DOMAIN=$(getConfigAttrib "DEFAULT_AUTH_DOMAIN" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_ENABLED=$(getConfigAttrib "TELEBLOCK_GW_ENABLED" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_IP=$(getConfigAttrib "TELEBLOCK_GW_IP" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_PORT=$(getConfigAttrib "TELEBLOCK_GW_PORT" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_MEDIA_IP=$(getConfigAttrib "TELEBLOCK_MEDIA_IP" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_MEDIA_PORT=$(getConfigAttrib "TELEBLOCK_MEDIA_PORT" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_ACCESS_KEY=$(getConfigAttrib "FLOWROUTE_ACCESS_KEY" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_SECRET_KEY=$(getConfigAttrib "FLOWROUTE_SECRET_KEY" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_API_ROOT_URL=$(getConfigAttrib "FLOWROUTE_API_ROOT_URL" "/etc/dsiprouter/gui/settings.py") export HOMER_ID=$(cat /etc/machine-id | hashCreds -l 4 | dd if=/dev/stdin of=/dev/stdout bs=1 count=8 2>/dev/null | hextoint) export HOMER_HEP_HOST=$(getConfigAttrib "HOMER_HEP_HOST" "/etc/dsiprouter/gui/settings.py") export HOMER_HEP_PORT=$(getConfigAttrib "HOMER_HEP_PORT" "/etc/dsiprouter/gui/settings.py") export NETWORK_MODE='0' export UPLOAD_FOLDER=$(getConfigAttrib "UPLOAD_FOLDER" "/etc/dsiprouter/gui/settings.py") export MAIL_SERVER=$(getConfigAttrib "MAIL_SERVER" "/etc/dsiprouter/gui/settings.py") export MAIL_PORT=$(getConfigAttrib "MAIL_PORT" "/etc/dsiprouter/gui/settings.py") export MAIL_USE_TLS=$(getConfigAttrib "MAIL_USE_TLS" "/etc/dsiprouter/gui/settings.py") export MAIL_USERNAME=$(getConfigAttrib "MAIL_USERNAME" "/etc/dsiprouter/gui/settings.py") export MAIL_ASCII_ATTACHMENTS=$(getConfigAttrib "MAIL_ASCII_ATTACHMENTS" "/etc/dsiprouter/gui/settings.py") export MAIL_DEFAULT_SENDER=$(getConfigAttrib "MAIL_DEFAULT_SENDER" "/etc/dsiprouter/gui/settings.py") export MAIL_DEFAULT_SUBJECT=$(getConfigAttrib "MAIL_DEFAULT_SUBJECT" "/etc/dsiprouter/gui/settings.py") export BACKUP_FOLDER=$(getConfigAttrib "BACKUP_FOLDER" "/etc/dsiprouter/gui/settings.py") export TRANSNEXUS_AUTHSERVICE_HOST=$(getConfigAttrib "TRANSNEXUS_AUTHSERVICE_HOST" "/etc/dsiprouter/gui/settings.py") export TRANSNEXUS_VERIFYSERVICE_HOST=$(getConfigAttrib "TRANSNEXUS_VERIFYSERVICE_HOST" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_PREFIX_A=$(getConfigAttrib "STIR_SHAKEN_PREFIX_A" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_PREFIX_B=$(getConfigAttrib "STIR_SHAKEN_PREFIX_B" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_PREFIX_C=$(getConfigAttrib "STIR_SHAKEN_PREFIX_C" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_PREFIX_INVALID=$(getConfigAttrib "STIR_SHAKEN_PREFIX_INVALID" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_BLOCK_INVALID=$(getConfigAttrib "STIR_SHAKEN_BLOCK_INVALID" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_CERT_URL=$(getConfigAttrib "STIR_SHAKEN_CERT_URL" "/etc/dsiprouter/gui/settings.py") export STIR_SHAKEN_KEY_PATH=$(getConfigAttrib "STIR_SHAKEN_KEY_PATH" "/etc/dsiprouter/gui/settings.py") export DSIP_DOCS_DIR="${DSIP_PROJECT_DIR}/docs" export ROOT_DB_USER=$(getConfigAttrib "ROOT_DB_USER" "/etc/dsiprouter/gui/settings.py") export ROOT_DB_NAME=$(getConfigAttrib "ROOT_DB_NAME" "/etc/dsiprouter/gui/settings.py") export LOAD_SETTINGS_FROM=$(getConfigAttrib "LOAD_SETTINGS_FROM" "/etc/dsiprouter/gui/settings.py") # TODO: currently no way of easily transferring the license keys to the upgraded platform #TRANSNEXUS_LICENSE_KEY -> DSIP_TRANSNEXUS_LICENSE getCredentials() { local SALT_LEN='64' local DK_LEN_DEFAULT='64' local CREDS_MAX_LEN='64' local HASH_ITERATIONS='10000' local HASHED_CREDS_ENCODED_MAX_LEN='256' local AESCTR_CREDS_ENCODED_MAX_LEN='160' printwarn 'dSIPRouter admin password hash can not be undone, generating new one' export DSIP_PASSWORD=$(urandomChars 64) printdbg "temporary password: $DSIP_PASSWORD" export DSIP_API_TOKEN=$(decryptConfigAttrib "DSIP_API_TOKEN" "/etc/dsiprouter/gui/settings.py") export DSIP_IPC_PASS=$(decryptConfigAttrib "DSIP_IPC_PASS" "/etc/dsiprouter/gui/settings.py") export KAM_DB_PASS=$(decryptConfigAttrib "KAM_DB_PASS" "/etc/dsiprouter/gui/settings.py") export MAIL_PASSWORD=$(decryptConfigAttrib "MAIL_PASSWORD" "/etc/dsiprouter/gui/settings.py") export ROOT_DB_PASS=$(decryptConfigAttrib "ROOT_DB_PASS" "/etc/dsiprouter/gui/settings.py") } getCredentials encryptCreds() { ( if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then cd /tmp/dsiprouter/gui else cd ${DSIP_PROJECT_DIR}/gui fi python3 -c "from util.security import AES_CTR; print(AES_CTR.encrypt('$1').decode('utf-8'), end='');" ) } DSIP_PASSWORD_HASH=$(hashCreds "$DSIP_PASSWORD") DSIP_API_TOKEN_CIPHERTEXT=$(encryptCreds "$DSIP_API_TOKEN") DSIP_IPC_PASS_CIPHERTEXT=$(encryptCreds "$DSIP_IPC_PASS") KAM_DB_PASS_CIPHERTEXT=$(encryptCreds "$KAM_DB_PASS") MAIL_PASSWORD_CIPHERTEXT=$(encryptCreds "$MAIL_PASSWORD") ROOT_DB_PASS_CIPHERTEXT=$(encryptCreds "$ROOT_DB_PASS") printdbg 'migrating database schema' ( cat <<'EOF' ALTER TABLE address MODIFY tag VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dispatcher MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_gateways MODIFY pri_prefix VARCHAR(64) NOT NULL DEFAULT '', MODIFY attrs VARCHAR(255) NOT NULL DEFAULT '', MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_gw_lists MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_rules MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dsip_cdrinfo MODIFY email VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE subscriber ADD IF NOT EXISTS email_address VARCHAR(128) NOT NULL DEFAULT '', ADD IF NOT EXISTS rpid VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE `acc` MODIFY `from_tag` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `to_tag` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `callid` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `sip_reason` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `time` DATETIME NOT NULL DEFAULT NOW(), MODIFY `dst_ouser` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `dst_user` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `dst_domain` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `src_user` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `src_domain` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `src_gwgroupid` VARCHAR (10) NOT NULL DEFAULT '', MODIFY `dst_gwgroupid` VARCHAR (10) NOT NULL DEFAULT ''; ALTER TABLE `cdrs` MODIFY `cdr_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, MODIFY `src_username` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `src_domain` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `dst_username` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `dst_domain` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `dst_ousername` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `call_start_time` DATETIME NOT NULL, MODIFY `sip_call_id` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `created` DATETIME NOT NULL DEFAULT NOW(), MODIFY `src_gwgroupid` VARCHAR(10) NOT NULL DEFAULT '', MODIFY `dst_gwgroupid` VARCHAR(10) NOT NULL DEFAULT ''; DROP PROCEDURE IF EXISTS `kamailio_cdrs`; DELIMITER // CREATE PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done INT DEFAULT 0; DECLARE bye_record INT DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_callid,v_from_tag, v_to_tag,v_src_ip,v_calltype VARCHAR(255); DECLARE v_src_gwgroupid, v_dst_gwgroupid INT(11); DECLARE v_inv_time, v_bye_time DATETIME; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, time, callid, from_tag, to_tag, src_ip, calltype, src_gwgroupid, dst_gwgroupid FROM acc WHERE method = 'INVITE' AND cdr_id = '0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip, v_calltype, v_src_gwgroupid, v_dst_gwgroupid; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method = 'BYE' AND callid = v_callid AND ((from_tag = v_from_tag AND to_tag = v_to_tag) OR (from_tag = v_to_tag AND to_tag = v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username, src_domain, dst_username, dst_domain, call_start_time, duration, sip_call_id, sip_from_tag, sip_to_tag, src_ip, created, calltype, src_gwgroupid, dst_gwgroupid) VALUES (v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, UNIX_TIMESTAMP(v_bye_time) - UNIX_TIMESTAMP(v_inv_time), v_callid, v_from_tag, v_to_tag, v_src_ip, NOW(), v_calltype, v_src_gwgroupid, v_dst_gwgroupid); UPDATE acc SET cdr_id=LAST_INSERT_ID() WHERE callid = v_callid AND from_tag = v_from_tag AND to_tag = v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END // DELIMITER ; DROP PROCEDURE IF EXISTS `kamailio_rating`; DELIMITER // CREATE PROCEDURE `kamailio_rating`(`rgroup` VARCHAR(64)) BEGIN DECLARE done, rate_record, vx_cost INT DEFAULT 0; DECLARE v_cdr_id BIGINT DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit INT DEFAULT 0; DECLARE v_dst_username VARCHAR(255); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated = 0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group = rgroup AND v_dst_username LIKE CONCAT(prefix, '%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration / v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id = v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END // DELIMITER ; EOF ) | sqlAsTransaction --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$KAM_DB_HOST" --port="$KAM_DB_PORT" if (( $? != 0 )); then printerr 'Failed merging DB schema' exit 1 fi if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then PROJECT_DSIP_DEFAULTS_DIR='/tmp/dsiprouter/kamailio/defaults' else PROJECT_DSIP_DEFAULTS_DIR='/opt/dsiprouter/kamailio/defaults' fi perl -e "\$hlen='$HASHED_CREDS_ENCODED_MAX_LEN'; \$clen='$AESCTR_CREDS_ENCODED_MAX_LEN';" \ -pe 's%\@HASHED_CREDS_ENCODED_MAX_LEN%$hlen%g; s%\@AESCTR_CREDS_ENCODED_MAX_LEN%$clen%g;' \ ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_settings.sql | mysql -s -N --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$KAM_DB_HOST" --port="$KAM_DB_PORT" "$KAM_DB_NAME" if (( $? != 0 )); then printerr 'Failed merging DB schema' exit 1 fi printdbg 'configuring dsiprouter GUI' if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then # a few stragglers that need copied over cp -f /opt/dsiprouter/gui/modules/fusionpbx/certs/cert.key /tmp/dsiprouter/gui/modules/fusionpbx/certs/cert.key cp -f /opt/dsiprouter/gui/modules/fusionpbx/certs/cert_combined.crt /tmp/dsiprouter/gui/modules/fusionpbx/certs/cert.key # use the bootstrap repo instead cloning again rm -rf /opt/dsiprouter mv -f /tmp/dsiprouter /opt/dsiprouter else # fresh repo coming up rm -rf /opt/dsiprouter git clone --depth 1 -c advice.detachedHead=false -b v0.72-rel https://github.com/dOpensource/dsiprouter.git /opt/dsiprouter fi export DSIP_PROJECT_DIR=/opt/dsiprouter printdbg 'installing python dependencies for the GUI' python3 -m pip install -U Flask~=2.0 psycopg2_binary requests SQLAlchemy~=2.0 Werkzeug~=2.0 printdbg 'generating dynamic config files for the GUI' dsiprouter configuredsip && setConfigAttrib 'DSIP_USERNAME' "$DSIP_USERNAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'DSIP_PASSWORD' "$DSIP_PASSWORD_HASH" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'DSIP_API_TOKEN' "$DSIP_API_TOKEN_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'DSIP_IPC_PASS' "$DSIP_IPC_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'KAM_DB_USER' "$KAM_DB_USER" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_PASS' "$KAM_DB_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'KAM_DB_HOST' "$KAM_DB_HOST" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_PORT' "$KAM_DB_PORT" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_NAME' "$KAM_DB_NAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'MAIL_USERNAME' "$MAIL_USERNAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'MAIL_PASSWORD' "$MAIL_PASSWORD_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'ROOT_DB_USER' "$ROOT_DB_USER" /etc/dsiprouter/gui/settings.py -q && { if ! grep -q -oP '(b""".*"""|'"b'''.*'''"'|b".*"|'"b'.*')" <<<"$ROOT_DB_PASS"; then setConfigAttrib 'ROOT_DB_PASS' "$ROOT_DB_PASS" /etc/dsiprouter/gui/settings.py -q else setConfigAttrib 'ROOT_DB_PASS' "$ROOT_DB_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb fi } && setConfigAttrib 'ROOT_DB_NAME' "$ROOT_DB_NAME" /etc/dsiprouter/gui/settings.py -q && printdbg 'successfully generated new settings file' || { printerr 'failed generating new settings file' exit 1 } if [[ "$LOAD_SETTINGS_FROM" == "db" ]]; then printdbg 'updating dsip_settings table, the GUI will be restarted multiple times...' setConfigAttrib 'LOAD_SETTINGS_FROM' 'file' /etc/dsiprouter/gui/settings.py && systemctl restart dsiprouter && setConfigAttrib 'LOAD_SETTINGS_FROM' 'db' /etc/dsiprouter/gui/settings.py && systemctl restart dsiprouter || { printerr 'failed updating dsip_settings DB table' exit 1 } else printdbg 'the dsip_settings table will be updated when the GUI service is restarted..' fi printdbg 'generating documentation for the GUI' ( cd ${DSIP_PROJECT_DIR}/docs make html >/dev/null 2>&1 ) printdbg 'generating documentation for the CLI' cp -f ${DSIP_PROJECT_DIR}/resources/man/dsiprouter.1 /usr/share/man/man1/ gzip -f /usr/share/man/man1/dsiprouter.1 mandb cp -f ${DSIP_PROJECT_DIR}/dsiprouter/dsip_completion.sh /etc/bash_completion.d/dsiprouter printdbg 'upgrading systemd service configurations' export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") export INTERNAL_IP_ADDR=$(getInternalIP -4) export INTERNAL_IP_NET=$(getInternalCIDR -4) export INTERNAL_IP6_ADDR=$(getInternalIP -6) export INTERNAL_IP_NET6=$(getInternalCIDR -6) EXTERNAL_IP_ADDR=$(getExternalIP -4) export EXTERNAL_IP_ADDR=${EXTERNAL_IP_ADDR:-$INTERNAL_IP_ADDR} EXTERNAL_IP6_ADDR=$(getExternalIP -6) export EXTERNAL_IP6_ADDR=${EXTERNAL_IP6_ADDR:-$INTERNAL_IP6_ADDR} export INTERNAL_FQDN=$(getInternalFQDN) export EXTERNAL_FQDN=$(getExternalFQDN) if [[ -z "$EXTERNAL_FQDN" ]] || ! checkConn "$EXTERNAL_FQDN"; then export EXTERNAL_FQDN="$INTERNAL_FQDN" fi case "$DISTRO" in debian|ubuntu) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Wants=nss-lookup.target Before=nss-lookup.target DefaultDependencies=no [Service] Type=forking PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test # We run dnsmasq via the /etc/init.d/dnsmasq script which acts as a # wrapper picking up extra configuration files and then execs dnsmasq # itself, when called with the "systemd-exec" function. ExecStart=/etc/init.d/dnsmasq systemd-exec # The systemd-*-resolvconf functions configure (and deconfigure) # resolvconf to work with the dnsmasq DNS server. They're called like # this to get correct error handling (ie don't start-resolvconf if the # dnsmasq daemon fails to start. ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf ExecStop=/etc/init.d/dnsmasq systemd-stop-resolvconf ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; almalinux|rocky) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; amzn|rhel) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PermissionsStartOnly=true PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; esac for SERVICE in kamailio nginx dsiprouter; do if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.${SERVICE}installed" ]]; then SVC_FILE=$(grep -oP "$SERVICE-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/$SERVICE/${DISTRO}/${DISTRO_MAJOR_VER}.sh) cp -f ${DSIP_PROJECT_DIR}/$SERVICE/systemd/$SVC_FILE /etc/systemd/system/$SERVICE.service fi done if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then SVC_FILE=$(grep -m 1 -oP "rtpengine-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/rtpengine/${DISTRO}/install.sh) cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/$SVC_FILE /etc/systemd/system/rtpengine.service fi DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" DSIP_CERTS_DIR="${DSIP_SYSTEM_CONFIG_DIR}/certs" cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.service /etc/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/etc/systemd/system/nginx-watcher.path chmod 644 /etc/systemd/system/nginx-watcher.service chmod 644 /etc/systemd/system/nginx-watcher.path systemctl daemon-reload # generate mysql service if needed reconfigureMysqlSystemdService printdbg 'upgrading kamailio configs' dsiprouter configurekam printdbg 'upgrading rtpengine configs' dsiprouter updatertpconfig printdbg 'upgrading dnsmasq configs' dsiprouter updatednsconfig printdbg 'updating file permissions' dsiprouter chown printdbg 'restarting services' systemctl restart dnsmasq systemctl restart kamailio systemctl restart nginx systemctl restart dsiprouter systemctl restart rtpengine exit 0 ================================================ FILE: resources/upgrade/v0.72/settings.json ================================================ { "version": "0.72", "depends": "0.70", "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.721/scripts/bootstrap.sh ================================================ #!/usr/bin/env bash export BOOTSTRAPPING_UPGRADE=1 export DSIP_PROJECT_DIR='/tmp/dsiprouter' TAG_NAME='v0.721-rel' REPO_URL='https://github.com/dOpensource/dsiprouter.git' rm -f /etc/dsiprouter/.requirementsinstalled rm -rf "$DSIP_PROJECT_DIR" 2>/dev/null git clone --depth 1 -c advice.detachedHead=false -b "$TAG_NAME" "$REPO_URL" "$DSIP_PROJECT_DIR" ${DSIP_PROJECT_DIR}/dsiprouter.sh upgrade -rel v0.721 ================================================ FILE: resources/upgrade/v0.721/scripts/migrate.sh ================================================ #!/usr/bin/env bash # set project dir (where src files are located) export DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # import dsip_lib utility / shared functions if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # function defs decryptOldSetting() { ( if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then cd /opt/dsiprouter/gui else cd ${DSIP_PROJECT_DIR}/gui fi VALUE=$(grep -oP '^(?!#)(?:'$1')[ \t]*=[ \t]*\K(?:\w+\(.*\)[ \t\v]*$|[\w\d\.]+[ \t]*$|\{.*\}|\[.*\][ \t]*$|\(.*\)[ \t]*$|b?""".*"""[ \t]*$|'"b?'''.*'''"'[ \v]*$|b?".*"[ \t]*$|'"b?'.*'"')' /etc/dsiprouter/gui/settings.py) if ! printf '%s' "${VALUE}" | grep -q -oP '(b""".*"""|'"b'''.*'''"'|b".*"|'"b'.*')"; then printf '%s' "${VALUE}" | perl -0777 -pe 's~^b?["'"'"']+(.*?)["'"'"']+$|(.*)~\1\2~g' else python3 -c "import sys; sys.path.insert(0, '/etc/dsiprouter/gui'); import settings; from util.security import AES_CTR; print(AES_CTR.decrypt(settings.$1).decode('utf-8'), end='')" fi ) } encryptNewCreds() { ( if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then cd /tmp/dsiprouter/gui else cd ${DSIP_PROJECT_DIR}/gui fi python3 -c "from util.security import AES_CTR; print(AES_CTR.encrypt('$1').decode('utf-8'), end='');" ) } printdbg 'verifying version requirements' CURRENT_VERSION=$(getConfigAttrib "VERSION" "/etc/dsiprouter/gui/settings.py") UPGRADE_DEPENDS=$(jq -r '.depends' <"$(dirname "$(dirname "$(readlink -f "$0")")")/settings.json") if [[ "$CURRENT_VERSION" != "$UPGRADE_DEPENDS" ]]; then printerr "unsupported upgrade scenario ($CURRENT_VERSION -> 0.721)" exit 1 fi printdbg 'backing up configs just in case the upgrade fails' BACKUP_DIR="/var/backups" CURR_BACKUP_DIR="${BACKUP_DIR}/$(date '+%s')" mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,var/lib/mysql,${HOME},etc/dsiprouter,etc/kamailio,etc/rtpengine,etc/systemd/system,lib/systemd/system,etc/default} cp -rf /opt/dsiprouter/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -rf /etc/kamailio/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -rf /var/lib/mysql/. ${CURR_BACKUP_DIR}/var/lib/mysql/ cp -f /etc/my.cnf ${CURR_BACKUP_DIR}/etc/ 2>/dev/null cp -rf /etc/mysql/. ${CURR_BACKUP_DIR}/etc/mysql/ cp -f ${HOME}/.my.cnf ${CURR_BACKUP_DIR}/${HOME}/ 2>/dev/null cp -f /etc/systemd/system/{dnsmasq.service,kamailio.service,nginx.service,rtpengine.service} ${CURR_BACKUP_DIR}/etc/systemd/system/ cp -f /lib/systemd/system/dsiprouter.service ${CURR_BACKUP_DIR}/lib/systemd/system/ cp -f /etc/default/kamailio ${CURR_BACKUP_DIR}/etc/default/ printdbg "files were backed up here: ${CURR_BACKUP_DIR}/" printdbg 'retrieving system info' export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") printdbg 'retrieving current settings' MACHINE_ID=$(</etc/machine-id) export DSIP_ID=$(hashCreds "$MACHINE_ID") export DSIP_CLUSTER_ID=$(getConfigAttrib "DSIP_CLUSTER_ID" "/etc/dsiprouter/gui/settings.py") export DSIP_CLUSTER_SYNC=$(getConfigAttrib "DSIP_CLUSTER_SYNC" "/etc/dsiprouter/gui/settings.py") export DSIP_PROTO=$(getConfigAttrib "DSIP_PROTO" "/etc/dsiprouter/gui/settings.py") export DSIP_PORT=$(getConfigAttrib "DSIP_PORT" "/etc/dsiprouter/gui/settings.py") export DSIP_USERNAME=$(getConfigAttrib "DSIP_USERNAME" "/etc/dsiprouter/gui/settings.py") export DSIP_API_PROTO=$(getConfigAttrib "DSIP_API_PROTO" "/etc/dsiprouter/gui/settings.py") export DSIP_API_PORT=$(getConfigAttrib "DSIP_API_PORT" "/etc/dsiprouter/gui/settings.py") export DSIP_PRIV_KEY=$(getConfigAttrib "DSIP_PRIV_KEY" "/etc/dsiprouter/gui/settings.py") export DSIP_PID_FILE=$(getConfigAttrib "DSIP_PID_FILE" "/etc/dsiprouter/gui/settings.py") export DSIP_UNIX_SOCK=$(getConfigAttrib "DSIP_UNIX_SOCK" "/etc/dsiprouter/gui/settings.py") export DSIP_IPC_SOCK=$(getConfigAttrib "DSIP_IPC_SOCK" "/etc/dsiprouter/gui/settings.py") export DSIP_LOG_LEVEL=$(getConfigAttrib "DSIP_LOG_LEVEL" "/etc/dsiprouter/gui/settings.py") export DSIP_LOG_FACILITY=$(getConfigAttrib "DSIP_LOG_FACILITY" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_KEY=$(getConfigAttrib "DSIP_SSL_KEY" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_CERT=$(getConfigAttrib "DSIP_SSL_CERT" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_CA=$(getConfigAttrib "DSIP_SSL_CA" "/etc/dsiprouter/gui/settings.py") export DSIP_SSL_EMAIL=$(getConfigAttrib "DSIP_SSL_EMAIL" "/etc/dsiprouter/gui/settings.py") export DSIP_CERTS_DIR=$(getConfigAttrib "DSIP_CERTS_DIR" "/etc/dsiprouter/gui/settings.py") export ROLE=$(getConfigAttrib "ROLE" "/etc/dsiprouter/gui/settings.py") export GUI_INACTIVE_TIMEOUT=$(getConfigAttrib "GUI_INACTIVE_TIMEOUT" "/etc/dsiprouter/gui/settings.py") export KAM_DB_HOST=$(getConfigAttrib "KAM_DB_HOST" "/etc/dsiprouter/gui/settings.py") export KAM_DB_DRIVER=$(getConfigAttrib "KAM_DB_DRIVER" "/etc/dsiprouter/gui/settings.py") export KAM_DB_TYPE=$(getConfigAttrib "KAM_DB_TYPE" "/etc/dsiprouter/gui/settings.py") export KAM_DB_PORT=$(getConfigAttrib "KAM_DB_PORT" "/etc/dsiprouter/gui/settings.py") export KAM_DB_NAME=$(getConfigAttrib "KAM_DB_NAME" "/etc/dsiprouter/gui/settings.py") export KAM_DB_USER=$(getConfigAttrib "KAM_DB_USER" "/etc/dsiprouter/gui/settings.py") export KAM_KAMCMD_PATH=$(getConfigAttrib "KAM_KAMCMD_PATH" "/etc/dsiprouter/gui/settings.py") export KAM_CFG_PATH=$(getConfigAttrib "KAM_CFG_PATH" "/etc/dsiprouter/gui/settings.py") export KAM_TLSCFG_PATH=$(getConfigAttrib "KAM_TLSCFG_PATH" "/etc/dsiprouter/gui/settings.py") export RTP_CFG_PATH=$(getConfigAttrib "RTP_CFG_PATH" "/etc/dsiprouter/gui/settings.py") export FLT_CARRIER=$(getConfigAttrib "FLT_CARRIER" "/etc/dsiprouter/gui/settings.py") export FLT_PBX=$(getConfigAttrib "FLT_PBX" "/etc/dsiprouter/gui/settings.py") export FLT_MSTEAMS=$(getConfigAttrib "FLT_MSTEAMS" "/etc/dsiprouter/gui/settings.py") export FLT_OUTBOUND=$(getConfigAttrib "FLT_OUTBOUND" "/etc/dsiprouter/gui/settings.py") export FLT_INBOUND=$(getConfigAttrib "FLT_INBOUND" "/etc/dsiprouter/gui/settings.py") export FLT_LCR_MIN=$(getConfigAttrib "FLT_LCR_MIN" "/etc/dsiprouter/gui/settings.py") export FLT_FWD_MIN=$(getConfigAttrib "FLT_FWD_MIN" "/etc/dsiprouter/gui/settings.py") export DEFAULT_AUTH_DOMAIN=$(getConfigAttrib "DEFAULT_AUTH_DOMAIN" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_ENABLED=$(getConfigAttrib "TELEBLOCK_GW_ENABLED" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_IP=$(getConfigAttrib "TELEBLOCK_GW_IP" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_GW_PORT=$(getConfigAttrib "TELEBLOCK_GW_PORT" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_MEDIA_IP=$(getConfigAttrib "TELEBLOCK_MEDIA_IP" "/etc/dsiprouter/gui/settings.py") export TELEBLOCK_MEDIA_PORT=$(getConfigAttrib "TELEBLOCK_MEDIA_PORT" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_ACCESS_KEY=$(getConfigAttrib "FLOWROUTE_ACCESS_KEY" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_SECRET_KEY=$(getConfigAttrib "FLOWROUTE_SECRET_KEY" "/etc/dsiprouter/gui/settings.py") export FLOWROUTE_API_ROOT_URL=$(getConfigAttrib "FLOWROUTE_API_ROOT_URL" "/etc/dsiprouter/gui/settings.py") export HOMER_ID=$(cat /etc/machine-id | hashCreds -l 4 | dd if=/dev/stdin of=/dev/stdout bs=1 count=8 2>/dev/null | hextoint) export HOMER_HEP_HOST=$(getConfigAttrib "HOMER_HEP_HOST" "/etc/dsiprouter/gui/settings.py") export HOMER_HEP_PORT=$(getConfigAttrib "HOMER_HEP_PORT" "/etc/dsiprouter/gui/settings.py") export NETWORK_MODE='0' export UPLOAD_FOLDER=$(getConfigAttrib "UPLOAD_FOLDER" "/etc/dsiprouter/gui/settings.py") export MAIL_SERVER=$(getConfigAttrib "MAIL_SERVER" "/etc/dsiprouter/gui/settings.py") export MAIL_PORT=$(getConfigAttrib "MAIL_PORT" "/etc/dsiprouter/gui/settings.py") export MAIL_USE_TLS=$(getConfigAttrib "MAIL_USE_TLS" "/etc/dsiprouter/gui/settings.py") export MAIL_USERNAME=$(getConfigAttrib "MAIL_USERNAME" "/etc/dsiprouter/gui/settings.py") export MAIL_ASCII_ATTACHMENTS=$(getConfigAttrib "MAIL_ASCII_ATTACHMENTS" "/etc/dsiprouter/gui/settings.py") export MAIL_DEFAULT_SENDER=$(getConfigAttrib "MAIL_DEFAULT_SENDER" "/etc/dsiprouter/gui/settings.py") export MAIL_DEFAULT_SUBJECT=$(getConfigAttrib "MAIL_DEFAULT_SUBJECT" "/etc/dsiprouter/gui/settings.py") export BACKUP_FOLDER=$(getConfigAttrib "BACKUP_FOLDER" "/etc/dsiprouter/gui/settings.py") TRANSNEXUS_AUTHSERVICE_ENABLED=$(getConfigAttrib "TRANSNEXUS_AUTHSERVICE_ENABLED" "/etc/dsiprouter/gui/settings.py") TRANSNEXUS_VERIFYSERVICE_ENABLED=$(getConfigAttrib "TRANSNEXUS_VERIFYSERVICE_ENABLED" "/etc/dsiprouter/gui/settings.py") TRANSNEXUS_AUTHSERVICE_HOST=$(getConfigAttrib "TRANSNEXUS_AUTHSERVICE_HOST" "/etc/dsiprouter/gui/settings.py") TRANSNEXUS_VERIFYSERVICE_HOST=$(getConfigAttrib "TRANSNEXUS_VERIFYSERVICE_HOST" "/etc/dsiprouter/gui/settings.py") TRANSNEXUS_LICENSE_KEY=$(getConfigAttrib "TRANSNEXUS_LICENSE_KEY" "/etc/dsiprouter/gui/settings.py") if [[ -n "$TRANSNEXUS_LICENSE_KEY" ]]; then DSIP_TRANSNEXUS_LICENSE=$(encryptNewCreds "${TRANSNEXUS_LICENSE_KEY}${MACHINE_ID}") else if (( $TRANSNEXUS_AUTHSERVICE_ENABLED == 1 )) || (( $TRANSNEXUS_VERIFYSERVICE_ENABLED == 1 )); then printwarn 'transnexus support requires a license, add a valid license and activate via the GUI' fi TRANSNEXUS_AUTHSERVICE_ENABLED=0 TRANSNEXUS_VERIFYSERVICE_ENABLED=0 fi STIR_SHAKEN_PREFIX_A=$(getConfigAttrib "STIR_SHAKEN_PREFIX_A" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_PREFIX_B=$(getConfigAttrib "STIR_SHAKEN_PREFIX_B" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_PREFIX_C=$(getConfigAttrib "STIR_SHAKEN_PREFIX_C" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_PREFIX_INVALID=$(getConfigAttrib "STIR_SHAKEN_PREFIX_INVALID" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_BLOCK_INVALID=$(getConfigAttrib "STIR_SHAKEN_BLOCK_INVALID" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_CERT_URL=$(getConfigAttrib "STIR_SHAKEN_CERT_URL" "/etc/dsiprouter/gui/settings.py") STIR_SHAKEN_KEY_PATH=$(getConfigAttrib "STIR_SHAKEN_KEY_PATH" "/etc/dsiprouter/gui/settings.py") export DSIP_DOCS_DIR="${DSIP_PROJECT_DIR}/docs" export ROOT_DB_USER=$(getConfigAttrib "ROOT_DB_USER" "/etc/dsiprouter/gui/settings.py") export ROOT_DB_NAME=$(getConfigAttrib "ROOT_DB_NAME" "/etc/dsiprouter/gui/settings.py") export LOAD_SETTINGS_FROM=$(getConfigAttrib "LOAD_SETTINGS_FROM" "/etc/dsiprouter/gui/settings.py") printdbg 'updating select settings' export DSIP_API_TOKEN=$(decryptOldSetting "DSIP_API_TOKEN") export DSIP_IPC_PASS=$(decryptOldSetting "DSIP_IPC_PASS") export KAM_DB_PASS=$(decryptOldSetting "KAM_DB_PASS") export MAIL_PASSWORD=$(decryptOldSetting "MAIL_PASSWORD") export ROOT_DB_PASS=$(decryptOldSetting "ROOT_DB_PASS") printwarn 'dSIPRouter admin password hash can not be undone, generating new one' export DSIP_PASSWORD=$(urandomChars 64) printdbg "temporary password: $DSIP_PASSWORD" DSIP_PASSWORD_HASH=$(hashCreds "$DSIP_PASSWORD") DSIP_API_TOKEN_CIPHERTEXT=$(encryptNewCreds "$DSIP_API_TOKEN") DSIP_IPC_PASS_CIPHERTEXT=$(encryptNewCreds "$DSIP_IPC_PASS") KAM_DB_PASS_CIPHERTEXT=$(encryptNewCreds "$KAM_DB_PASS") MAIL_PASSWORD_CIPHERTEXT=$(encryptNewCreds "$MAIL_PASSWORD") ROOT_DB_PASS_CIPHERTEXT=$(encryptNewCreds "$ROOT_DB_PASS") export INTERNAL_IP_ADDR=$(getInternalIP -4) export INTERNAL_IP_NET=$(getInternalCIDR -4) export INTERNAL_IP6_ADDR=$(getInternalIP -6) export INTERNAL_IP_NET6=$(getInternalCIDR -6) EXTERNAL_IP_ADDR=$(getExternalIP -4) export EXTERNAL_IP_ADDR=${EXTERNAL_IP_ADDR:-$INTERNAL_IP_ADDR} EXTERNAL_IP6_ADDR=$(getExternalIP -6) export EXTERNAL_IP6_ADDR=${EXTERNAL_IP6_ADDR:-$INTERNAL_IP6_ADDR} export INTERNAL_FQDN=$(getInternalFQDN) export EXTERNAL_FQDN=$(getExternalFQDN) if [[ -z "$EXTERNAL_FQDN" ]] || ! checkConn "$EXTERNAL_FQDN"; then export EXTERNAL_FQDN="$INTERNAL_FQDN" fi printdbg 'migrating database schema' ( cat <<'EOF' ALTER TABLE address MODIFY tag VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dispatcher MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_gateways MODIFY pri_prefix VARCHAR(64) NOT NULL DEFAULT '', MODIFY attrs VARCHAR(255) NOT NULL DEFAULT '', MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_gw_lists MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dr_rules MODIFY description VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE dsip_cdrinfo MODIFY email VARCHAR(255) NOT NULL DEFAULT ''; ALTER TABLE subscriber ADD IF NOT EXISTS email_address VARCHAR(128) NOT NULL DEFAULT '', ADD IF NOT EXISTS rpid VARCHAR(128) NOT NULL DEFAULT ''; ALTER TABLE `acc` MODIFY `from_tag` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `to_tag` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `callid` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `sip_reason` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `time` DATETIME NOT NULL DEFAULT NOW(), MODIFY `dst_ouser` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `dst_user` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `dst_domain` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `src_user` VARCHAR (128) NOT NULL DEFAULT '', MODIFY `src_domain` VARCHAR (255) NOT NULL DEFAULT '', MODIFY `src_gwgroupid` VARCHAR (10) NOT NULL DEFAULT '', MODIFY `dst_gwgroupid` VARCHAR (10) NOT NULL DEFAULT ''; ALTER TABLE `cdrs` MODIFY `cdr_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, MODIFY `src_username` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `src_domain` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `dst_username` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `dst_domain` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `dst_ousername` VARCHAR(128) NOT NULL DEFAULT '', MODIFY `call_start_time` DATETIME NOT NULL, MODIFY `sip_call_id` VARCHAR(255) NOT NULL DEFAULT '', MODIFY `created` DATETIME NOT NULL DEFAULT NOW(), MODIFY `src_gwgroupid` VARCHAR(10) NOT NULL DEFAULT '', MODIFY `dst_gwgroupid` VARCHAR(10) NOT NULL DEFAULT ''; DROP PROCEDURE IF EXISTS `kamailio_cdrs`; DELIMITER // CREATE PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done INT DEFAULT 0; DECLARE bye_record INT DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_callid,v_from_tag, v_to_tag,v_src_ip,v_calltype VARCHAR(255); DECLARE v_src_gwgroupid, v_dst_gwgroupid INT(11); DECLARE v_inv_time, v_bye_time DATETIME; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, time, callid, from_tag, to_tag, src_ip, calltype, src_gwgroupid, dst_gwgroupid FROM acc WHERE method = 'INVITE' AND cdr_id = '0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip, v_calltype, v_src_gwgroupid, v_dst_gwgroupid; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method = 'BYE' AND callid = v_callid AND ((from_tag = v_from_tag AND to_tag = v_to_tag) OR (from_tag = v_to_tag AND to_tag = v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username, src_domain, dst_username, dst_domain, call_start_time, duration, sip_call_id, sip_from_tag, sip_to_tag, src_ip, created, calltype, src_gwgroupid, dst_gwgroupid) VALUES (v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, UNIX_TIMESTAMP(v_bye_time) - UNIX_TIMESTAMP(v_inv_time), v_callid, v_from_tag, v_to_tag, v_src_ip, NOW(), v_calltype, v_src_gwgroupid, v_dst_gwgroupid); UPDATE acc SET cdr_id=LAST_INSERT_ID() WHERE callid = v_callid AND from_tag = v_from_tag AND to_tag = v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END // DELIMITER ; DROP PROCEDURE IF EXISTS `kamailio_rating`; DELIMITER // CREATE PROCEDURE `kamailio_rating`(`rgroup` VARCHAR(64)) BEGIN DECLARE done, rate_record, vx_cost INT DEFAULT 0; DECLARE v_cdr_id BIGINT DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit INT DEFAULT 0; DECLARE v_dst_username VARCHAR(255); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated = 0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group = rgroup AND v_dst_username LIKE CONCAT(prefix, '%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration / v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id = v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END // DELIMITER ; EOF ) | sqlAsTransaction --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$KAM_DB_HOST" --port="$KAM_DB_PORT" if (( $? != 0 )); then printerr 'Failed merging DB schema' exit 1 fi if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then PROJECT_DSIP_DEFAULTS_DIR='/tmp/dsiprouter/kamailio/defaults' else PROJECT_DSIP_DEFAULTS_DIR='/opt/dsiprouter/kamailio/defaults' fi perl -e "\$hlen='$HASHED_CREDS_ENCODED_MAX_LEN'; \$clen='$AESCTR_CREDS_ENCODED_MAX_LEN';" \ -pe 's%\@HASHED_CREDS_ENCODED_MAX_LEN%$hlen%g; s%\@AESCTR_CREDS_ENCODED_MAX_LEN%$clen%g;' \ ${PROJECT_DSIP_DEFAULTS_DIR}/dsip_settings.sql | mysql -s -N --user="$ROOT_DB_USER" --password="$ROOT_DB_PASS" --host="$KAM_DB_HOST" --port="$KAM_DB_PORT" "$KAM_DB_NAME" if (( $? != 0 )); then printerr 'Failed merging DB schema' exit 1 fi printdbg 'configuring dsiprouter GUI' if (( ${BOOTSTRAPPING_UPGRADE:-0} == 1 )); then # a few stragglers that need copied over cp -rf /opt/dsiprouter/gui/modules/fusionpbx/certs/. /tmp/dsiprouter/gui/modules/fusionpbx/certs/ # use the bootstrap repo instead cloning again rm -rf /opt/dsiprouter mv -f /tmp/dsiprouter /opt/dsiprouter else # fresh repo coming up rm -rf /opt/dsiprouter git clone --depth 1 -c advice.detachedHead=false -b v0.721-rel https://github.com/dOpensource/dsiprouter.git /opt/dsiprouter fi export DSIP_PROJECT_DIR=/opt/dsiprouter printdbg 'installing python dependencies for the GUI' python3 -m pip install -U Flask~=2.2.0 psycopg2_binary requests SQLAlchemy~=2.0 Werkzeug~=2.0 if cmdExists 'apt-get'; then apt-get remove -y *certbot* apt-get install -y python3-venv elif cmdExists 'dnf'; then dnf remove -y *certbot* dnf install -y python3-virtualenv elif cmdExists 'yum'; then yum remove -y *certbot* yum install -y python3-virtualenv fi python3 -m pip remove certbot 2>/dev/null python3 -m pip install -U acme josepy python3 -m venv /opt/certbot/ /opt/certbot/bin/pip install --upgrade pip /opt/certbot/bin/pip install certbot ln -sf /opt/certbot/bin/certbot /usr/bin/certbot printdbg 'generating dynamic config files for the GUI' dsiprouter configuredsip && setConfigAttrib 'DSIP_USERNAME' "$DSIP_USERNAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'DSIP_PASSWORD' "$DSIP_PASSWORD_HASH" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'DSIP_API_TOKEN' "$DSIP_API_TOKEN_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'DSIP_IPC_PASS' "$DSIP_IPC_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'KAM_DB_USER' "$KAM_DB_USER" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_PASS' "$KAM_DB_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'KAM_DB_HOST' "$KAM_DB_HOST" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_PORT' "$KAM_DB_PORT" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'KAM_DB_NAME' "$KAM_DB_NAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'MAIL_USERNAME' "$MAIL_USERNAME" /etc/dsiprouter/gui/settings.py -q && setConfigAttrib 'MAIL_PASSWORD' "$MAIL_PASSWORD_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb && setConfigAttrib 'ROOT_DB_USER' "$ROOT_DB_USER" /etc/dsiprouter/gui/settings.py -q && { if ! grep -q -oP '(b""".*"""|'"b'''.*'''"'|b".*"|'"b'.*')" <<<"$ROOT_DB_PASS"; then setConfigAttrib 'ROOT_DB_PASS' "$ROOT_DB_PASS" /etc/dsiprouter/gui/settings.py -q else setConfigAttrib 'ROOT_DB_PASS' "$ROOT_DB_PASS_CIPHERTEXT" /etc/dsiprouter/gui/settings.py -qb fi } && setConfigAttrib 'ROOT_DB_NAME' "$ROOT_DB_NAME" /etc/dsiprouter/gui/settings.py -q && ( setConfigAttrib 'TRANSNEXUS_AUTHSERVICE_ENABLED' "$TRANSNEXUS_AUTHSERVICE_ENABLED" /etc/dsiprouter/gui/settings.py || exit 1 setConfigAttrib 'TRANSNEXUS_VERIFYSERVICE_ENABLED' "$TRANSNEXUS_VERIFYSERVICE_ENABLED" /etc/dsiprouter/gui/settings.py || exit 1 setConfigAttrib 'TRANSNEXUS_AUTHSERVICE_HOST' "$TRANSNEXUS_AUTHSERVICE_HOST" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'TRANSNEXUS_VERIFYSERVICE_HOST' "$TRANSNEXUS_VERIFYSERVICE_HOST" /etc/dsiprouter/gui/settings.py -q || exit 1 # if transnexus license is present we need to reactivate it on the new server [[ -n "$DSIP_TRANSNEXUS_LICENSE" ]] && { RESPONSE=$( curl -sk -u "ck_068f510a518ff5ecf1cbdcbc7db7f9bac2331613:cs_5ae2f3decfa59f427a59b41f2e41459d18023dd7" \ "https://dopensource.com/wp-json/lmfwc/v2/licenses/${TRANSNEXUS_LICENSE_KEY}" ) if (( $? != 0 )); then printerr 'could not contact license server, check your internet connection and try again' exit 1 fi if [[ "$(jq -r '.success' <<<"$RESPONSE")" != "true" ]]; then printerr 'invalid transnexus license can not be migrated' printwarn 'licenses can be purchased here: https://dopensource.com/product/transnexus-clearip-module/' exit 0 fi NOW=$(date -u +'%s') if [[ "$(jq -r '.data.expiresAt' <<<"$RESPONSE")" != "null" ]]; then EXPIRES=$( jq -r '.data.expiresAt' <<<"$RESPONSE" \ | date -u -f /dev/stdin +'%s' ) else EXPIRES=$( jq -r '.data.createdAt' <<<"$RESPONSE" \ | date -u -f /dev/stdin +"%Y-%m-%d +$(jq -r '.data.validFor' <<<"$RESPONSE")day %H:%M:%S" \ | date -u -f /dev/stdin +'%s' ) fi if (( $(jq -r '.data.status' <<<"$RESPONSE") == 3 )) && (( $(jq -r '.data.timesActivated' <<<"$RESPONSE") > 0 )) && (( $EXPIRES > $NOW )); then setConfigAttrib 'DSIP_TRANSNEXUS_LICENSE' "$DSIP_TRANSNEXUS_LICENSE" /etc/dsiprouter/gui/settings.py -qb else RESPONSE=$( curl -sk -u "ck_068f510a518ff5ecf1cbdcbc7db7f9bac2331613:cs_5ae2f3decfa59f427a59b41f2e41459d18023dd7" \ "https://dopensource.com/wp-json/lmfwc/v2/licenses/activate/${TRANSNEXUS_LICENSE_KEY}" curl -skf -X PUT -H "Authorization: Bearer ${DSIP_API_TOKEN}" -H 'Content-Type: application/json' \ -d "{\"license_key\":\"${TRANSNEXUS_LICENSE_KEY}\"}" 'https://localhost:5000/api/v1/licensing/activate' ) if (( $? != 0 )) || [[ "$(jq -r '.success' <<<"$RESPONSE")" != "true" ]]; then printerr 'failed to activate transnexus license' printwarn 'contact dOpenSource to migrate your license: https://dopensource.com/' exit 0 fi setConfigAttrib 'DSIP_TRANSNEXUS_LICENSE' "$DSIP_TRANSNEXUS_LICENSE" /etc/dsiprouter/gui/settings.py -qb fi } exit 0 ) && ( setConfigAttrib 'STIR_SHAKEN_PREFIX_A' "$STIR_SHAKEN_PREFIX_A" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'STIR_SHAKEN_PREFIX_B' "$STIR_SHAKEN_PREFIX_B" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'STIR_SHAKEN_PREFIX_C' "$STIR_SHAKEN_PREFIX_C" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'STIR_SHAKEN_PREFIX_INVALID' "$STIR_SHAKEN_PREFIX_INVALID" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'STIR_SHAKEN_BLOCK_INVALID' "$STIR_SHAKEN_BLOCK_INVALID" /etc/dsiprouter/gui/settings.py || exit 1 setConfigAttrib 'STIR_SHAKEN_CERT_URL' "$STIR_SHAKEN_CERT_URL" /etc/dsiprouter/gui/settings.py -q || exit 1 setConfigAttrib 'STIR_SHAKEN_KEY_PATH' "$STIR_SHAKEN_KEY_PATH" /etc/dsiprouter/gui/settings.py -q || exit 1 ) && { # let user know how to upgrade ms teams license if valid if [[ $(curl -s -d '{"method": "dsiprouter.health_check", "jsonrpc": "2.0", "id": 1}' 'http://localhost:5060/api/kamailio' | jq -r '.result' 2>/dev/null) == "Health Check Succeeded" ]]; then printwarn 'your msteams license can not be converted automatically' printwarn 'contact dOpenSource to migrate your license: https://dopensource.com/' fi } && printdbg 'successfully generated new settings file' || { printerr 'failed generating new settings file' exit 1 } if [[ "$LOAD_SETTINGS_FROM" == "db" ]]; then printdbg 'updating dsip_settings table, the GUI will be restarted multiple times...' setConfigAttrib 'LOAD_SETTINGS_FROM' 'file' /etc/dsiprouter/gui/settings.py && systemctl restart dsiprouter && setConfigAttrib 'LOAD_SETTINGS_FROM' 'db' /etc/dsiprouter/gui/settings.py && systemctl restart dsiprouter || { printerr 'failed updating dsip_settings DB table' exit 1 } else printdbg 'the dsip_settings table will be updated when the GUI service is restarted..' fi printdbg 'generating documentation for the GUI' ( cd ${DSIP_PROJECT_DIR}/docs make html >/dev/null 2>&1 ) printdbg 'generating documentation for the CLI' cp -f ${DSIP_PROJECT_DIR}/resources/man/dsiprouter.1 /usr/share/man/man1/ gzip -f /usr/share/man/man1/dsiprouter.1 mandb cp -f ${DSIP_PROJECT_DIR}/dsiprouter/dsip_completion.sh /etc/bash_completion.d/dsiprouter printdbg 'upgrading systemd service configurations' # move kamailio defaults file to the new name mv -f /etc/default/kamailio /etc/default/kamailio.conf # update the default memory allocation if needed # currently the minimum system RAM is 256MB + 64MB + 512MB = 832MB if (( $(awk '/MemTotal/ { printf "%d \n", $2/1024 }' /proc/meminfo) < 832 )); then printwarn 'system has less than 832MB RAM, not updating kamailio default memory allocations' else PKG_MEM=$(grep -m 1 -oP 'PKG_MEMORY\=\K.*' /etc/default/kamailio.conf) if (( $PKG_MEM < 64 )); then perl -i -pe 's%(PKG_MEM\=).*%${1}64%' /etc/default/kamailio.conf fi SHM_MEM=$(grep -m 1 -oP 'SHM_MEMORY\=\K.*' /etc/default/kamailio.conf) if (( $SHM_MEM < 512 )); then perl -i -pe 's%(SHM_MEMORY\=).*%${1}512%' /etc/default/kamailio.conf fi fi case "$DISTRO" in debian|ubuntu) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Wants=nss-lookup.target Before=nss-lookup.target DefaultDependencies=no [Service] Type=forking PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' Environment='IGNORE_RESOLVCONF=yes' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test # We run dnsmasq via the /etc/init.d/dnsmasq script which acts as a # wrapper picking up extra configuration files and then execs dnsmasq # itself, when called with the "systemd-exec" function. ExecStart=/etc/init.d/dnsmasq systemd-exec # The systemd-*-resolvconf functions configure (and deconfigure) # resolvconf to work with the dnsmasq DNS server. They're called like # this to get correct error handling (ie don't start-resolvconf if the # dnsmasq daemon fails to start. ExecStartPost=/etc/init.d/dnsmasq systemd-start-resolvconf ExecStop=/etc/init.d/dnsmasq systemd-stop-resolvconf ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; almalinux|rocky) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=!-/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; amzn|rhel) cat << 'EOF' >/etc/systemd/system/dnsmasq.service [Unit] Description=dnsmasq - A lightweight DHCP and caching DNS server Requires=basic.target network.target After=network.target network-online.target basic.target Before=multi-user.target DefaultDependencies=no [Service] Type=simple PermissionsStartOnly=true PIDFile=/run/dnsmasq/dnsmasq.pid Environment='RUN_DIR=/run/dnsmasq' # make sure everything is setup correctly before starting ExecStartPre=/usr/bin/dsiprouter chown -dnsmasq ExecStartPre=/usr/sbin/dnsmasq --test ExecStart=/usr/sbin/dnsmasq -k ExecReload=/bin/kill -HUP $MAINPID [Install] WantedBy=multi-user.target EOF ;; esac if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then SVC_FILE=$(grep -oP "kamailio-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/kamailio/${DISTRO}/${DISTRO_MAJOR_VER}.sh) cp -f ${DSIP_PROJECT_DIR}/kamailio/systemd/$SVC_FILE /etc/systemd/system/kamailio.service fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then SVC_FILE=$(grep -oP "nginx-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/nginx/${DISTRO}/${DISTRO_MAJOR_VER}.sh) cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/$SVC_FILE /etc/systemd/system/nginx.service fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then SVC_FILE=$(grep -oP "dsiprouter-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/dsiprouter/${DISTRO}/${DISTRO_MAJOR_VER}.sh) cp -f ${DSIP_PROJECT_DIR}/dsiprouter/systemd/$SVC_FILE /lib/systemd/system/dsiprouter.service fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then SVC_FILE=$(grep -m 1 -oP "rtpengine-v[0-9]+\.service" ${DSIP_PROJECT_DIR}/rtpengine/${DISTRO}/install.sh) cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/$SVC_FILE /etc/systemd/system/rtpengine.service fi DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" DSIP_CERTS_DIR="${DSIP_SYSTEM_CONFIG_DIR}/certs" cp -f ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.service /etc/systemd/system/nginx-watcher.service perl -p \ -e "s%PathChanged\=.*%PathChanged=${DSIP_CERTS_DIR}/%;" \ ${DSIP_PROJECT_DIR}/nginx/systemd/nginx-watcher.path >/etc/systemd/system/nginx-watcher.path chmod 644 /etc/systemd/system/nginx-watcher.service chmod 644 /etc/systemd/system/nginx-watcher.path systemctl daemon-reload # generate mysql service if needed reconfigureMysqlSystemdService printdbg 'upgrading kamailio configs' dsiprouter configurekam printdbg 'upgrading rtpengine configs' dsiprouter updatertpconfig printdbg 'upgrading dnsmasq configs' dsiprouter updatednsconfig printdbg 'updating file permissions' dsiprouter chown printdbg 'restarting services' systemctl restart dnsmasq systemctl restart kamailio systemctl restart nginx systemctl restart dsiprouter systemctl restart rtpengine exit 0 ================================================ FILE: resources/upgrade/v0.721/settings.json ================================================ { "version": "0.721", "depends": "0.70", "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.73/scripts/bootstrap.sh ================================================ #!/usr/bin/env bash export BOOTSTRAPPING_UPGRADE=1 export BOOTSTRAP_DIR='/tmp/dsiprouter' TAG_NAME='v0.73-rel' REPO_URL='https://github.com/dOpensource/dsiprouter.git' [[ -e "$BOOTSTRAP_DIR" ]] && rm -rf "$BOOTSTRAP_DIR" git clone --depth 1 -c advice.detachedHead=false -b "$TAG_NAME" "$REPO_URL" "$BOOTSTRAP_DIR" ${BOOTSTRAP_DIR}/dsiprouter.sh upgrade -rel v0.73 RET=$? rm -rf ${BOOTSTRAP_DIR} exit $RET ================================================ FILE: resources/upgrade/v0.73/scripts/migrate.sh ================================================ #!/usr/bin/env bash # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # whether or not this was called from the GUI RUN_FROM_GUI=${RUN_FROM_GUI:-0} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # set project dir where previous repo was located export DSIP_PROJECT_DIR='/opt/dsiprouter' # import dsip_lib utility / shared functions (we are using old functions on purpose here) # WARNING: from here on we are explicitly using the OLD definitions of the dsip_lib funcs . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh printdbg 'retrieving system info' export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") printdbg 'validating OS support' if [[ "$(getDistroName)" == 'debian' && "$(getDistroVer)" == "9" ]]; then printerr 'debian stretch is not supported in this version of dSIPRouter' echo 'upgrade your system to a supported version of debian first' echo 'for more information see: https://dsiprouter.readthedocs.io/en/latest/upgrading.html' exit 1 fi printdbg 'retrieving current system settings' # NOTE: some magic is being done here to reset specific settings next install export PYTHON_CMD=python3 DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' DSIP_CONFIG_FILE=${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/kamailio.cfg" export ROOT_DB_PASS=$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE}) export ROOT_DB_HOST=$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE}) export ROOT_DB_PORT=$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE}) export ROOT_DB_NAME=$(getConfigAttrib 'ROOT_DB_NAME' ${DSIP_CONFIG_FILE}) export KAM_DB_NAME=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_HOST=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) export KAM_DB_HOST="$SET_KAM_DB_HOST" export KAM_DB_USER=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_PASS=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) export KAM_DB_PASS="$SET_KAM_DB_PASS" export SET_DSIP_API_TOKEN=$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE}) export DSIP_IPC_PASS="$SET_DSIP_API_TOKEN" export SET_DSIP_MAIL_PASS=$(decryptConfigAttrib 'MAIL_PASSWORD' ${DSIP_CONFIG_FILE}) export MAIL_PASSWORD="$DSIP_MAIL_PASS" export SET_DSIP_IPC_TOKEN=$(decryptConfigAttrib 'DSIP_IPC_PASS' ${DSIP_CONFIG_FILE}) export DSIP_IPC_PASS="$SET_DSIP_IPC_TOKEN" printdbg 'preparing for migration' REINSTALL_DNSMASQ=0 REINSTALL_KAMAILIO=0 REINSTALL_DSIPROUTER=0 REINSTALL_RTPENGINE=0 INSTALL_OPTS=() if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]]; then REINSTALL_DNSMASQ=1 fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then REINSTALL_KAMAILIO=1 INSTALL_OPTS+=(-kam) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then REINSTALL_DSIPROUTER=1 INSTALL_OPTS+=(-dsip) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then REINSTALL_RTPENGINE=1 INSTALL_OPTS+=(-rtp) fi mkdir -p $CURR_BACKUP_DIR # if the state files for the services to upgrade were there before # and we fail, put them back so the system can recover resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi cp -af ${CURR_BACKUP_DIR}/opt/. /etc/ cp -af ${CURR_BACKUP_DIR}/opt/. /lib/ cp -af ${CURR_BACKUP_DIR}/opt/. /opt/ cp -af ${CURR_BACKUP_DIR}/opt/. /var/ systemctl daemon-reload if (( ${KAM_DB_DROPPED:-0} == 1 )); then withRootDBConn mysql <${CURR_BACKUP_DIR}/db.sql withRootDBConn mysql <${CURR_BACKUP_DIR}/user.sql fi if (( $RUN_FROM_GUI == 0 )); then if (( $REINSTALL_DNSMASQ == 1 )); then systemctl restart dnsmasq fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine fi fi trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # always reinstalled rm -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled" 2>/dev/null rm -f "${DSIP_SYSTEM_CONFIG_DIR}/.requirementsinstalled" 2>/dev/null rm -rf ${SRC_DIR}/kamailio ${SRC_DIR}/rtpengine # conditionally reinstalled printdbg 'masking services' if (( $REINSTALL_DNSMASQ == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled # dnsmasq is not masked, we want it to restart during the install process if [[ -f /etc/systemd/system/dnsmasq.service ]]; then mv -f /etc/systemd/system/dnsmasq.service /lib/systemd/system/dnsmasq.service systemctl daemon-reload fi fi if (( $REINSTALL_KAMAILIO == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled if [[ -f /etc/systemd/system/kamailio.service ]]; then mv -f /etc/systemd/system/kamailio.service /lib/systemd/system/kamailio.service systemctl daemon-reload fi rm -f "$DSIP_KAMAILIO_CONFIG_FILE" systemctl mask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled systemctl mask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled if [[ -f /etc/systemd/system/rtpengine.service ]]; then mv -f /etc/systemd/system/rtpengine.service /lib/systemd/system/rtpengine.service systemctl daemon-reload fi systemctl mask rtpengine.service fi printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${DSIP_PROJECT_DIR}/ rm -rf ${NEW_PROJECT_DIR} cd ${DSIP_PROJECT_DIR}/ if (( $REINSTALL_DSIPROUTER == 1 )); then printdbg 'updating new dSIPRouter settings' cd ${DSIP_PROJECT_DIR}/gui && ( python3 <<'EOF' import os import settings as default_settings from importlib.util import module_from_spec, spec_from_file_location from shared import objToDict, updateConfig default_settings_dict = objToDict(default_settings) spec = spec_from_file_location('current_settings', '/etc/dsiprouter/gui/settings.py') current_settings = module_from_spec(spec) spec.loader.exec_module(current_settings) current_settings_dict = objToDict(current_settings) default_settings_dict.update(current_settings_dict) os.system(f'cp -f {default_settings.__file__} {current_settings.__file__}') updateConfig(current_settings, default_settings_dict) EOF ) || { printerr 'failed updating dSIPRouter settings' exit 1 } fi # source the new dsip_lib functions # WARNING: from here on we are explicitly using the NEW definitions of the dsip_lib funcs # NOTE: resetConfigsHandler() above will still use the new definitions (lazy loading) . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh if (( $REINSTALL_KAMAILIO == 1 )); then printdbg 'backing up kamailio database' dumpDB "$KAM_DB_NAME" >${CURR_BACKUP_DIR}/db.sql dumpDBUser "$KAM_DB_USER@$KAM_DB_NAME" >${CURR_BACKUP_DIR}/user.sql withRootDBConn mysql -e "USE $KAM_DB_NAME; DROP TABLE IF EXISTS dsip_settings;" withRootDBConn mysqldump --single-transaction --no-create-info --skip-triggers --replace "$KAM_DB_NAME" >${CURR_BACKUP_DIR}/data.sql if [[ ! -f ${CURR_BACKUP_DIR}/data.sql ]]; then printerr 'failed backing up kamailio database data' exit 1 fi printdbg 'dropping kamailio database to install new schema' withRootDBConn mysql -e "DROP DATABASE IF EXISTS $KAM_DB_NAME;" withRootDBConn mysql -e "DROP USER IF EXISTS '$KAM_DB_USER'@'%'; DROP USER IF EXISTS '$KAM_DB_USER'@'localhost';" KAM_DB_DROPPED=1 fi printdbg 'upgrading services' ${DSIP_PROJECT_DIR}/dsiprouter.sh install ${INSTALL_OPTS[@]} if (( $? != 0 )); then printerr 'failed upgrading services' exit 1 fi if (( $REINSTALL_KAMAILIO == 1 )); then printdbg 'restoring kamailio database data' withRootDBConn --db="$KAM_DB_NAME" mysql <${CURR_BACKUP_DIR}/data.sql || { printerr 'failed restoring kamailio database data' exit 1 } fi if (( $REINSTALL_DSIPROUTER == 1 )); then printdbg 'updating dSIPRouter version' setConfigAttrib 'VERSION' '0.73' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py -q || { printerr 'failed updating dSIPRouter version' exit 1 } fi printdbg 'unmasking services' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi if (( $RUN_FROM_GUI == 0 )); then printdbg 'restarting services' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio if ! systemctl is-active -q kamailio; then printerr 'could not start kamailio service' exit 1 fi fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter if ! systemctl is-active -q dsiprouter; then printerr 'could not start dsiprouter service' exit 1 fi fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine if ! systemctl is-active -q rtpengine; then printerr 'could not start rtpengine service' exit 1 fi fi else printwarn 'running from the GUI, some services require restarting' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.73/settings.json ================================================ { "version": "0.73", "depends": ["0.72", "0.721"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.74/scripts/migrate.sh ================================================ #!/usr/bin/env bash # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # whether or not this was called from the GUI RUN_FROM_GUI=${RUN_FROM_GUI:-0} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # set project dir where previous repo was located export DSIP_PROJECT_DIR='/opt/dsiprouter' # import dsip_lib utility / shared functions (we are using old functions on purpose here) # WARNING: from here on we are explicitly using the OLD definitions of the dsip_lib funcs . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh printdbg 'retrieving system info' export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") printdbg 'validating OS support' if [[ "$(getDistroName)" == 'debian' && "$(getDistroVer)" == "9" ]]; then printerr 'debian stretch is not supported in this version of dSIPRouter' echo 'upgrade your system to a supported version of debian first' echo 'for more information see: https://dsiprouter.readthedocs.io/en/latest/upgrading.html' exit 1 fi printdbg 'retrieving current system settings' # NOTE: some magic is being done here to reset specific settings next install export PYTHON_CMD=python3 DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' DSIP_CONFIG_FILE=${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/kamailio.cfg" export ROOT_DB_PASS=$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE}) export ROOT_DB_HOST=$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE}) export ROOT_DB_PORT=$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE}) export ROOT_DB_NAME=$(getConfigAttrib 'ROOT_DB_NAME' ${DSIP_CONFIG_FILE}) export KAM_DB_NAME=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_HOST=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) export KAM_DB_HOST="$SET_KAM_DB_HOST" export KAM_DB_USER=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_PASS=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) export KAM_DB_PASS="$SET_KAM_DB_PASS" export SET_DSIP_API_TOKEN=$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE}) export DSIP_API_TOKEN="$SET_DSIP_API_TOKEN" export SET_DSIP_MAIL_PASS=$(decryptConfigAttrib 'MAIL_PASSWORD' ${DSIP_CONFIG_FILE}) export MAIL_PASSWORD="$DSIP_MAIL_PASS" export SET_DSIP_IPC_TOKEN=$(decryptConfigAttrib 'DSIP_IPC_PASS' ${DSIP_CONFIG_FILE}) export DSIP_IPC_PASS="$SET_DSIP_IPC_TOKEN" printdbg 'preparing for migration' REINSTALL_DNSMASQ=0 REINSTALL_NGINX=0 REINSTALL_KAMAILIO=0 REINSTALL_DSIPROUTER=0 REINSTALL_RTPENGINE=0 INSTALL_OPTS=() if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]]; then REINSTALL_DNSMASQ=1 fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then REINSTALL_NGINX=1 fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then REINSTALL_KAMAILIO=1 INSTALL_OPTS+=(-kam) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then REINSTALL_DSIPROUTER=1 INSTALL_OPTS+=(-dsip) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then REINSTALL_RTPENGINE=1 INSTALL_OPTS+=(-rtp) fi mkdir -p $CURR_BACKUP_DIR # if the state files for the services to upgrade were there before # and we fail, put them back so the system can recover resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi cp -af ${CURR_BACKUP_DIR}/etc/. /etc/ cp -af ${CURR_BACKUP_DIR}/lib/. /lib/ cp -af ${CURR_BACKUP_DIR}/opt/. /opt/ cp -af ${CURR_BACKUP_DIR}/var/. /var/ systemctl daemon-reload if (( $REINSTALL_KAMAILIO == 1 )); then # automatically created in dsiprouter.sh when installKamailio() runs [[ -e ${CURR_BACKUP_DIR}/db.sql ]] && withRootDBConn mysql <${CURR_BACKUP_DIR}/db.sql && withRootDBConn mysql <${CURR_BACKUP_DIR}/user.sql fi # not included in the restrt() function from dsiprouter.sh # so we always restart to get config changes if (( $REINSTALL_DNSMASQ == 1 )); then systemctl restart dnsmasq fi if (( $RUN_FROM_GUI == 0 )); then if (( $REINSTALL_NGINX == 1 )); then systemctl restart nginx fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine fi fi trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # conditionally reinstalled printdbg 'masking services' if (( $REINSTALL_DNSMASQ == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled # dnsmasq is not masked, we want it to restart during the install process fi if (( $REINSTALL_NGINX == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled if [[ -f /etc/systemd/system/nginx.service ]]; then mv -f /etc/systemd/system/nginx.service /lib/systemd/system/nginx.service systemctl daemon-reload fi systemctl mask nginx.service fi if (( $REINSTALL_KAMAILIO == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled rm -rf ${SRC_DIR}/kamailio rm -f "$DSIP_KAMAILIO_CONFIG_FILE" systemctl mask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled systemctl mask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then rm -rf ${SRC_DIR}/rtpengine rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled systemctl mask rtpengine.service fi printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${DSIP_PROJECT_DIR}/ rm -rf ${NEW_PROJECT_DIR} cd ${DSIP_PROJECT_DIR}/ # source the new dsip_lib functions # WARNING: from here on we are explicitly using the NEW definitions of the dsip_lib funcs # NOTE: resetConfigsHandler() above will still use the new definitions (lazy loading) . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh printdbg 'upgrading services' ${DSIP_PROJECT_DIR}/dsiprouter.sh install ${INSTALL_OPTS[@]} if (( $? != 0 )); then printerr 'failed upgrading services' exit 1 fi if (( $REINSTALL_KAMAILIO == 1 )); then printdbg 'restoring kamailio database' withRootDBConn mysql <${CURR_BACKUP_DIR}/db.sql && withRootDBConn mysql <${CURR_BACKUP_DIR}/user.sql || { printerr 'failed restoring kamailio database' exit 1 } fi if (( $REINSTALL_DSIPROUTER == 1 )); then printdbg 'updating dSIPRouter version' setConfigAttrib 'VERSION' '0.74' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py -q || { printerr 'failed updating dSIPRouter version' exit 1 } fi printdbg 'unmasking services' if (( $REINSTALL_NGINX == 1 )); then systemctl unmask nginx.service fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi if (( $RUN_FROM_GUI == 0 )); then printdbg 'restarting services' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio if ! systemctl is-active -q kamailio; then printerr 'could not start kamailio service' exit 1 fi fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter if ! systemctl is-active -q dsiprouter; then printerr 'could not start dsiprouter service' exit 1 fi fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine if ! systemctl is-active -q rtpengine; then printerr 'could not start rtpengine service' exit 1 fi fi else printwarn 'running from the GUI, some services require restarting' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.74/settings.json ================================================ { "version": "0.74", "depends": ["0.73"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.75/clear_defaults.sql ================================================ SET FOREIGN_KEY_CHECKS=0; TRUNCATE TABLE `address`; TRUNCATE TABLE `dr_gateways`; TRUNCATE TABLE `dr_gw_lists`; TRUNCATE TABLE `dr_rules`; SET FOREIGN_KEY_CHECKS=1; ================================================ FILE: resources/upgrade/v0.75/migrate_data.sql ================================================ UPDATE `dr_gateways` SET `attrs` = IF( (CHAR_LENGTH(attrs) - CHAR_LENGTH(REPLACE(attrs, ',', ''))) = 2, CONCAT(SUBSTRING_INDEX(`attrs`, ',', 3), ',proxy,proxy'), CONCAT(SUBSTRING_INDEX(`attrs`, ',', 2), ',,proxy,proxy') ); UPDATE `dispatcher` SET `attrs` = IF( `attrs` REGEXP 'weight=([0-9]+)', CONCAT('signalling=proxy;media=proxy;rweight=', CAST((REGEXP_REPLACE(`attrs`, '.*weight=([0-9]+).*', '\\1') / 100) AS CHAR)), 'signalling=proxy;media=proxy;rweight=0' ); INSERT INTO `dsip_call_settings` (`gwgroupid`, `limit`) SELECT CAST(`gwgroupid` AS UNSIGNED), CAST(`limit` AS UNSIGNED) FROM `dsip_calllimit`; DROP TABLE `dsip_calllimit`; ================================================ FILE: resources/upgrade/v0.75/pre_import_data.sql ================================================ CREATE TABLE `dsip_calllimit` ( `gwgroupid` varchar(64) NOT NULL, `limit` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT 1, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; ================================================ FILE: resources/upgrade/v0.75/scripts/bootstrap.sh ================================================ #!/usr/bin/env bash cd /opt/dsiprouter . dsiprouter/dsip_lib.sh LC_CHECK=$(decryptConfigAttrib 'DSIP_CORE_LICENSE' /etc/dsiprouter/gui/settings.py | dd if=/dev/stdin of=/dev/stdout bs=1 count=32 2>/dev/null) (( ${#LC_CHECK} == 32 )) || { echo "a core licene is required to use the auto upgrade feature" echo "without a license the upgrade will fail" echo "you can purchase a license here: https://dopensource.com/product/dsiprouter-core/" exit 1 } export BOOTSTRAPPING_UPGRADE=1 export BOOTSTRAP_DIR='/tmp/dsiprouter' TAG_NAME='v0.75-rel' REPO_URL='https://github.com/dOpensource/dsiprouter.git' [[ -e "$BOOTSTRAP_DIR" ]] && rm -rf "$BOOTSTRAP_DIR" git clone --depth 1 -c advice.detachedHead=false -b "$TAG_NAME" "$REPO_URL" "$BOOTSTRAP_DIR" && mkdir -p /opt/dsiprouter/resources/upgrade/v0.75/ && cp -rf /tmp/dsiprouter/resources/upgrade/v0.75/. /opt/dsiprouter/resources/upgrade/v0.75/ && dsiprouter upgrade -rel v0.75 ================================================ FILE: resources/upgrade/v0.75/scripts/migrate.sh ================================================ #!/usr/bin/env bash (( ${DEBUG:-0} == 1 )) && set -x # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # whether or not this was called from the GUI RUN_FROM_GUI=${RUN_FROM_GUI:-0} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # set project dir where previous repo was located export DSIP_PROJECT_DIR='/opt/dsiprouter' # import dsip_lib utility / shared functions (we are using old functions on purpose here) # WARNING: from here on we are explicitly using the OLD definitions of the dsip_lib funcs . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh # make sure the updates are downloaded and in the correct location [[ ! -e "$NEW_PROJECT_DIR" ]] && { printerr 'could not find repo to upgrade from' echo "expected updated repo to be here: $NEW_PROJECT_DIR" exit 1 } printdbg 'retrieving system info' export DISTRO=$(getDistroName) export DISTRO_VER=$(getDistroVer) export DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") export DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") printdbg 'validating OS support' if [[ "$(getDistroName)" == 'debian' && "$(getDistroVer)" == "9" ]]; then printerr 'debian stretch is not supported in this version of dSIPRouter' echo 'upgrade your system to a supported version of debian first' echo 'for more information see: https://dsiprouter.readthedocs.io/en/latest/upgrading.html' exit 1 fi printdbg 'retrieving current system settings' # NOTE: some magic is being done here to reset specific settings next install export PYTHON_CMD=python3 DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' DSIP_LIB_DIR='/var/lib/dsiprouter' DSIP_CONFIG_FILE=${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/kamailio.cfg" SYSTEM_KAMAILIO_CONFIG_DIR='/etc/kamailio' SYSTEM_RTPENGINE_CONFIG_DIR='/etc/rtpengine' export ROOT_DB_PASS=$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE}) export ROOT_DB_HOST=$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE}) export ROOT_DB_PORT=$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE}) export ROOT_DB_NAME=$(getConfigAttrib 'ROOT_DB_NAME' ${DSIP_CONFIG_FILE}) export KAM_DB_NAME=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_HOST=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) export KAM_DB_HOST="$SET_KAM_DB_HOST" export KAM_DB_USER=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_PASS=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) export KAM_DB_PASS="$SET_KAM_DB_PASS" export SET_DSIP_API_TOKEN=$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE}) export DSIP_API_TOKEN="$SET_DSIP_API_TOKEN" export SET_DSIP_MAIL_PASS=$(decryptConfigAttrib 'MAIL_PASSWORD' ${DSIP_CONFIG_FILE}) export MAIL_PASSWORD="$DSIP_MAIL_PASS" export SET_DSIP_IPC_TOKEN=$(decryptConfigAttrib 'DSIP_IPC_PASS' ${DSIP_CONFIG_FILE}) export DSIP_IPC_PASS="$SET_DSIP_IPC_TOKEN" DSIP_CORE_LICENSE=$(decryptConfigAttrib 'DSIP_CORE_LICENSE' ${DSIP_CONFIG_FILE} | dd if=/dev/stdin of=/dev/stdout bs=1 count=32 2>/dev/null) DSIP_STIRSHAKEN_LICENSE=$(decryptConfigAttrib 'DSIP_STIRSHAKEN_LICENSE' ${DSIP_CONFIG_FILE} | dd if=/dev/stdin of=/dev/stdout bs=1 count=32 2>/dev/null) DSIP_TRANSNEXUS_LICENSE=$(decryptConfigAttrib 'DSIP_TRANSNEXUS_LICENSE' ${DSIP_CONFIG_FILE} | dd if=/dev/stdin of=/dev/stdout bs=1 count=32 2>/dev/null) DSIP_MSTEAMS_LICENSE=$(decryptConfigAttrib 'DSIP_MSTEAMS_LICENSE' ${DSIP_CONFIG_FILE} | dd if=/dev/stdin of=/dev/stdout bs=1 count=32 2>/dev/null) printdbg 'validating system system configuration' if [[ -z "$DSIP_CORE_LICENSE" ]]; then printerr 'A DSIP_CORE license is required to use the auto upgrade feature' echo 'Consider supporting the hard working engineers maintaining this software if you would like to use this feature' exit 1 fi printdbg 'preparing for migration' REINSTALL_DNSMASQ=0 REINSTALL_NGINX=0 REINSTALL_KAMAILIO=0 REINSTALL_DSIPROUTER=0 REINSTALL_RTPENGINE=0 INSTALL_OPTS=() if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled" ]]; then REINSTALL_DNSMASQ=1 INSTALL_OPTS+=(-dns) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then REINSTALL_NGINX=1 fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then REINSTALL_KAMAILIO=1 INSTALL_OPTS+=(-kam) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then REINSTALL_DSIPROUTER=1 INSTALL_OPTS+=(-dsip) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then REINSTALL_RTPENGINE=1 INSTALL_OPTS+=(-rtp) fi printdbg 'backing up configs just in case the upgrade fails' mkdir -p $CURR_BACKUP_DIR # TODO: make the destination paths use our static variables as well mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,var/lib/dsiprouter,etc/dsiprouter,etc/kamailio,etc/rtpengine,etc/systemd/system,lib/systemd/system,etc/default} # mkdir -p ${CURR_BACKUP_DIR}/{var/lib/mysql,${HOME}} cp -af ${DSIP_PROJECT_DIR}/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -af ${DSIP_LIB_DIR}/. ${CURR_BACKUP_DIR}/var/lib/dsiprouter/ cp -af ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -af ${DSIP_SYSTEM_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/dsiprouter/ cp -af ${SYSTEM_RTPENGINE_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/rtpengine/ # cp -af /var/lib/mysql/. ${CURR_BACKUP_DIR}/var/lib/mysql/ # cp -af /etc/my.cnf ${CURR_BACKUP_DIR}/etc/ 2>/dev/null # cp -af /etc/mysql/. ${CURR_BACKUP_DIR}/etc/mysql/ # cp -af ${HOME}/.my.cnf ${CURR_BACKUP_DIR}/${HOME}/ 2>/dev/null cp -af /etc/dnsmasq.conf ${CURR_BACKUP_DIR}/etc/ cp -af /etc/systemd/system/{nginx,dsiprouter,dnsmasq,kamailio,rtpengine,dsip-init,mariadb}.service ${CURR_BACKUP_DIR}/etc/systemd/system/ 2>/dev/null cp -af /lib/systemd/system/{nginx,dsiprouter,dnsmasq,kamailio,rtpengine,dsip-init,mariadb}.service ${CURR_BACKUP_DIR}/lib/systemd/system/ 2>/dev/null cp -af /etc/default/{kamailio,rtpengine}* ${CURR_BACKUP_DIR}/etc/default/ printdbg "files were backed up here: ${CURR_BACKUP_DIR}/" # shim any functions that would be missing from older versions declare -F withRootDBConn >/dev/null || { function withRootDBConn() { local TMP CMD local CONN_OPTS=() case "$1" in --db=*) TMP=$(cut -d '=' -f 2- <<<"$1") [[ -n "$TMP" ]] && CONN_OPTS+=( "--database=${TMP}" ) shift CMD="$1" shift ;; *) CMD="$1" shift if [[ "$CMD" == "mysql" ]]; then [[ -n "$ROOT_DB_NAME" ]] && CONN_OPTS+=( "--database=${ROOT_DB_NAME}" ) fi ;; esac [[ -n "$ROOT_DB_HOST" ]] && CONN_OPTS+=( "--host=${ROOT_DB_HOST}" ) [[ -n "$ROOT_DB_PORT" ]] && CONN_OPTS+=( "--port=${ROOT_DB_PORT}" ) [[ -n "$ROOT_DB_USER" ]] && CONN_OPTS+=( "--user=${ROOT_DB_USER}" ) [[ -n "$ROOT_DB_PASS" ]] && CONN_OPTS+=( "--password=${ROOT_DB_PASS}" ) if [[ -p /dev/stdin ]]; then ${CMD} "${CONN_OPTS[@]}" "$@" </dev/stdin else ${CMD} "${CONN_OPTS[@]}" "$@" fi return $? } } # removes the old licenses from the database dump so we can re-add them in the new format filterLicenseFromDataDump() { perl -pe 's%(INSERT .*?INTO `dsip_settings` VALUES \()(.*?)[^,]*?,[^,]*?,[^,]*?,[^,]*?(\)\;)%\1\2'"'BQAAAAA='"'\3%g' </dev/stdin } # if the state files for the services to upgrade were there before # and we fail, put them back so the system can recover resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' if (( $REINSTALL_NGINX == 1 )); then systemctl unmask nginx.service fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi cp -af ${CURR_BACKUP_DIR}/etc/. /etc/ cp -af ${CURR_BACKUP_DIR}/lib/. /lib/ cp -af ${CURR_BACKUP_DIR}/opt/. /opt/ cp -af ${CURR_BACKUP_DIR}/var/. /var/ systemctl daemon-reload if (( $REINSTALL_KAMAILIO == 1 )); then # automatically created in dsiprouter.sh when installKamailio() runs [[ -e ${CURR_BACKUP_DIR}/db.sql ]] && withRootDBConn mysql <${CURR_BACKUP_DIR}/db.sql [[ -e ${CURR_BACKUP_DIR}/user.sql ]] && withRootDBConn mysql <${CURR_BACKUP_DIR}/user.sql fi # not included in the restart() function from dsiprouter.sh # so we always restart to get config changes if (( $REINSTALL_DNSMASQ == 1 )); then systemctl restart dnsmasq fi if (( $RUN_FROM_GUI == 0 )); then if (( $REINSTALL_NGINX == 1 )); then systemctl restart nginx fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine fi fi trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM exit 1 } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # conditionally reinstalled printdbg 'masking services' if (( $REINSTALL_DNSMASQ == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dnsmasqinstalled # dnsmasq is not masked, we want it to restart during the install process fi if (( $REINSTALL_NGINX == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled if [[ -f /etc/systemd/system/nginx.service ]]; then rm -f /etc/systemd/system/nginx.service fi systemctl mask nginx.service fi if (( $REINSTALL_KAMAILIO == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled rm -rf ${SRC_DIR}/kamailio rm -f "$DSIP_KAMAILIO_CONFIG_FILE" if [[ -f /etc/systemd/system/kamailio.service ]]; then rm -f /etc/systemd/system/kamailio.service fi systemctl mask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled systemctl mask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then rm -rf ${SRC_DIR}/rtpengine rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled if [[ -f /etc/systemd/system/rtpengine.service ]]; then rm -f /etc/systemd/system/rtpengine.service fi systemctl mask rtpengine.service fi printdbg 'storing kamailio database data' ( withRootDBConn mysqldump --single-transaction --skip-opt --skip-triggers --no-create-db --no-create-info \ --insert-ignore --hex-blob --skip-comments --databases "$KAM_DB_NAME" ) >${CURR_BACKUP_DIR}/data.sql printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${DSIP_PROJECT_DIR}/ cd ${DSIP_PROJECT_DIR}/ # source the new dsip_lib functions # WARNING: from here on we are explicitly using the NEW definitions of the dsip_lib funcs # NOTE: resetConfigsHandler() above will still use the new definitions (lazy loading) export PYTHON_CMD="${DSIP_PROJECT_DIR}/venv/bin/python" . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh printdbg 'upgrading services' # we clear environment here to make sure we get new static settings on install env -i CURR_BACKUP_DIR="$CURR_BACKUP_DIR" HOME="$HOME" LANG="$LANG" LANGUAGE="$LANGUAGE" LC_ALL="$LC_ALL" PATH="$PATH" PWD="$PWD" \ ${DSIP_PROJECT_DIR}/dsiprouter.sh install ${INSTALL_OPTS[@]} if (( $? != 0 )); then printerr 'failed upgrading services' exit 1 fi if (( $REINSTALL_KAMAILIO == 1 )); then printdbg 'migrating kamailio database' withRootDBConn --db="$KAM_DB_NAME" mysql <${CURR_BACKUP_DIR}/user.sql && withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.75/clear_defaults.sql && withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.75/pre_import_data.sql && filterLicenseFromDataDump <${CURR_BACKUP_DIR}/data.sql | withRootDBConn --db="$KAM_DB_NAME" mysql && withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.75/migrate_data.sql || { printerr 'failed migrating kamailio database' exit 1 } fi if (( $REINSTALL_DSIPROUTER == 1 )); then printdbg 'migrating dSIPRouter settings' ( # magic bash hacking function exit() { :; } export -f exit source ${CURR_BACKUP_DIR}/opt/dsiprouter/dsiprouter.sh unset -f exit setStaticScriptSettings setDynamicScriptSettings # more magic environment munging dsiprouter configuredsip ) && setConfigAttrib 'VERSION' '0.75' ${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py -q && ${PYTHON_CMD} <<EOPY || { printerr 'Failed migrating dSIPRouter settings'; exit 1; } import sys sys.path = [*['${DSIP_SYSTEM_CONFIG_DIR}/gui', '${DSIP_PROJECT_DIR}/gui'], *sys.path[1:]] from database import updateDsipSettingsTable from shared import updateConfig from modules.api.licensemanager.classes import WoocommerceLicense import settings keys = [ "$DSIP_CORE_LICENSE", "$DSIP_STIRSHAKEN_LICENSE", "$DSIP_TRANSNEXUS_LICENSE", "$DSIP_MSTEAMS_LICENSE" ] for k in keys: if len(k) == 0: continue lc = WoocommerceLicense(license_key=lc_key) settings.DSIP_LICENSE_STORE[str(lc.id)] = lc.encrypt() else: if keys[0] == '': sys.exit(1) if not keys[0].active: sys.exit(1) if settings.LOAD_SETTINGS_FROM == 'db': updateDsipSettingsTable({'DSIP_LICENSE_STORE': settings.DSIP_LICENSE_STORE}) updateConfig(settings, {'DSIP_LICENSE_STORE': settings.DSIP_LICENSE_STORE}, hot_reload=True) EOPY printdbg 'unmasking services' if (( $REINSTALL_NGINX == 1 )); then systemctl unmask nginx.service fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi if (( $RUN_FROM_GUI == 0 )); then printdbg 'restarting services' if (( $REINSTALL_NGINX == 1 )); then systemctl restart nginx if ! systemctl is-active -q nginx; then printerr 'could not start nginx service' exit 1 fi fi if (( $REINSTALL_KAMAILIO == 1 )); then systemctl restart kamailio if ! systemctl is-active -q kamailio; then printerr 'could not start kamailio service' exit 1 fi fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl restart dsiprouter if ! systemctl is-active -q dsiprouter; then printerr 'could not start dsiprouter service' exit 1 fi fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl restart rtpengine if ! systemctl is-active -q rtpengine; then printerr 'could not start rtpengine service' exit 1 fi fi else printwarn 'running from the GUI, some services require restarting' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.75/settings.json ================================================ { "version": "0.75", "depends": ["0.72", "0.721", "0.73", "0.74"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.76/scripts/migrate.sh ================================================ #!/usr/bin/env bash (( ${DEBUG:-0} == 1 )) && set -x # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # project dir where previous repo was located OLD_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # system config files for dsiprouter DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' # import dsip_lib utility / shared functions (no changes to func definitions in this revision) . ${OLD_PROJECT_DIR}/dsiprouter/dsip_lib.sh # make sure the updates are downloaded and in the correct location [[ ! -e "$NEW_PROJECT_DIR" ]] && { printerr 'could not find repo to upgrade from' echo "expected updated repo to be here: $NEW_PROJECT_DIR" exit 1 } printdbg 'preparing new repo migration' cp -af ${OLD_PROJECT_DIR}/venv/. ${NEW_PROJECT_DIR}/venv/ printdbg 'validating system configuration' if ! env DSIP_PROJECT_DIR="$NEW_PROJECT_DIR" \ DSIP_SYSTEM_CONFIG_DIR="$DSIP_SYSTEM_CONFIG_DIR" \ ${NEW_PROJECT_DIR}/dsiprouter.sh licensemanager -check tag=DSIP_CORE; then printerr 'A DSIP_CORE license is required to use the auto upgrade feature' echo 'Consider supporting the hard working engineers maintaining this software if you would like to use this feature' exit 1 fi printdbg 'backing up configs just in case the upgrade fails' mkdir -p "$CURR_BACKUP_DIR" # TODO: make the destination paths use our static variables as well mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,etc/dsiprouter,etc/kamailio} cp -af ${OLD_PROJECT_DIR}/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -af ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -af ${DSIP_SYSTEM_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/dsiprouter/ printdbg "files were backed up here: ${CURR_BACKUP_DIR}/" updateDsiprouterCli() { FROM_PROJECT_DIR="$1" cp -f ${FROM_PROJECT_DIR}/dsiprouter/dsip_completion.sh /etc/bash_completion.d/dsiprouter cp -f ${FROM_PROJECT_DIR}/resources/man/dsiprouter.1 /usr/share/man/man1/ && gzip -f /usr/share/man/man1/dsiprouter.1 && mandb } # if the state files for the services to upgrade were there before # and we fail, put them back so the system can recover resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' cp -af ${CURR_BACKUP_DIR}/etc/. /etc/ cp -af ${CURR_BACKUP_DIR}/opt/. /opt/ updateDsiprouterCli "$OLD_PROJECT_DIR" dsiprouter configurekam trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM exit 1 } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${OLD_PROJECT_DIR}/ updateDsiprouterCli "$NEW_PROJECT_DIR" dsiprouter configurekam if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then printwarn 'kamailio service requires restarting' fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then printwarn 'dsiprouter service requires restarting' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.76/settings.json ================================================ { "version": "0.76", "depends": ["0.75"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.77/clear_defaults.sql ================================================ SET FOREIGN_KEY_CHECKS=0; TRUNCATE TABLE `address`; TRUNCATE TABLE `dr_gateways`; TRUNCATE TABLE `dr_gw_lists`; TRUNCATE TABLE `dr_rules`; SET FOREIGN_KEY_CHECKS=1; ================================================ FILE: resources/upgrade/v0.77/scripts/migrate.sh ================================================ #!/usr/bin/env bash (( ${DEBUG:-0} == 1 )) && set -x # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # project dir where previous repo was located OLD_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # system config files for dsiprouter DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' # import dsip_lib utility / shared functions (no changes to func definitions in this revision) . ${OLD_PROJECT_DIR}/dsiprouter/dsip_lib.sh # make sure the updates are downloaded and in the correct location [[ ! -e "$NEW_PROJECT_DIR" ]] && { printerr 'could not find repo to upgrade from' echo "expected updated repo to be here: $NEW_PROJECT_DIR" exit 1 } printdbg 'validating system configuration' if ! dsiprouter licensemanager -check tag=DSIP_CORE; then printerr 'A DSIP_CORE license is required to use the auto upgrade feature' echo 'Consider supporting the hard working engineers maintaining this software if you would like to use this feature' exit 1 fi DISTRO=$(getDistroName) DISTRO_VER=$(getDistroVer) DISTRO_MAJOR_VER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") DISTRO_MINOR_VER=$(cut -s -d '.' -f 2 <<<"$DISTRO_VER") case "$DISTRO" in debian) case "$DISTRO_VER" in 12|11|10) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 9) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.5.7"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr9.5.5.1"} ;; esac ;; centos) case "$DISTRO_VER" in 8|9) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 7) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.7.6"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; esac ;; amzn) case "$DISTRO_VER" in 2) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.7.6"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr9.5.5.1"} ;; esac ;; ubuntu) case "$DISTRO_VER" in 24.04) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.4"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 22.04) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 20.04) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr9.5.5.1"} ;; esac ;; rhel) case "$DISTRO_MAJOR_VER" in 9) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr9.5.5.1"} ;; esac ;; almalinux) case "$DISTRO_MAJOR_VER" in 9) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; esac ;; rocky) case "$DISTRO_MAJOR_VER" in 9) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; 8) NEW_KAM_VERSION=${NEW_KAM_VERSION:-"5.8.3"} NEW_RTPENGINE_VER=${NEW_RTPENGINE_VER:-"mr11.5.1.11"} ;; esac ;; esac if [[ -z "${NEW_KAM_VERSION}${NEW_RTPENGINE_VER}" ]]; then printerr 'unsupported OS version' exit 1 fi printdbg 'retrieving current system settings' # NOTE: some magic is being done here to reset specific settings next install export PYTHON_CMD=${OLD_PROJECT_DIR}/venv/bin/python DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' DSIP_LIB_DIR='/var/lib/dsiprouter' DSIP_CONFIG_FILE=${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py DSIP_KAMAILIO_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/kamailio/kamailio.cfg" SYSTEM_KAMAILIO_CONFIG_DIR='/etc/kamailio' SYSTEM_RTPENGINE_CONFIG_DIR='/etc/rtpengine' export SET_ROOT_DB_USER=$(getConfigAttrib 'ROOT_DB_USER' ${DSIP_CONFIG_FILE}) export ROOT_DB_USER="$SET_ROOT_DB_USER" export SET_ROOT_DB_PASS=$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE}) export ROOT_DB_PASS="$SET_ROOT_DB_PASS" export ROOT_DB_HOST=$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE}) export ROOT_DB_PORT=$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE}) export ROOT_DB_NAME=$(getConfigAttrib 'ROOT_DB_NAME' ${DSIP_CONFIG_FILE}) export KAM_DB_NAME=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) export SET_KAM_DB_HOST=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) export KAM_DB_HOST="$SET_KAM_DB_HOST" export SET_KAM_DB_USER=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) export KAM_DB_USER="$SET_KAM_DB_USER" export SET_KAM_DB_PASS=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) export KAM_DB_PASS="$SET_KAM_DB_PASS" export SET_DSIP_API_TOKEN=$(decryptConfigAttrib 'DSIP_API_TOKEN' ${DSIP_CONFIG_FILE}) export DSIP_API_TOKEN="$SET_DSIP_API_TOKEN" export SET_DSIP_MAIL_PASS=$(decryptConfigAttrib 'MAIL_PASSWORD' ${DSIP_CONFIG_FILE}) export MAIL_PASSWORD="$DSIP_MAIL_PASS" export SET_DSIP_IPC_TOKEN=$(decryptConfigAttrib 'DSIP_IPC_PASS' ${DSIP_CONFIG_FILE}) export DSIP_IPC_PASS="$SET_DSIP_IPC_TOKEN" export DSIP_LICENSE_STORE=$(getConfigAttrib 'DSIP_LICENSE_STORE' ${DSIP_CONFIG_FILE}) printdbg 'preparing for migration' REINSTALL_KAMAILIO=0 REINSTALL_DSIPROUTER=0 REINSTALL_RTPENGINE=0 INSTALL_OPTS=() if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then REINSTALL_KAMAILIO=1 INSTALL_OPTS+=(-kam) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then REINSTALL_DSIPROUTER=1 INSTALL_OPTS+=(-dsip) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then OLD_RTPENGINE_VER=$(rtpengine -v 2>&1 | awk '{print $2}' | cut -d '~' -f 2) if [[ "$OLD_RTPENGINE_VER" != "$NEW_RTPENGINE_VER" ]]; then REINSTALL_RTPENGINE=1 INSTALL_OPTS+=(-rtp) fi fi printdbg 'backing up configs just in case the upgrade fails' mkdir -p "$CURR_BACKUP_DIR" mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,var/lib/dsiprouter,etc/dsiprouter,etc/kamailio,etc/rtpengine,etc/systemd/system,lib/systemd/system,etc/default} cp -afP ${OLD_PROJECT_DIR}/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -afP ${DSIP_LIB_DIR}/. ${CURR_BACKUP_DIR}/var/lib/dsiprouter/ cp -afP ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -afP ${DSIP_SYSTEM_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/dsiprouter/ cp -afP ${SYSTEM_RTPENGINE_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/rtpengine/ cp -afP /etc/systemd/system/{dsiprouter,kamailio,rtpengine,dsip-init,mariadb}.service ${CURR_BACKUP_DIR}/etc/systemd/system/ 2>/dev/null cp -afP /lib/systemd/system/{dsiprouter,kamailio,rtpengine,dsip-init,mariadb}.service ${CURR_BACKUP_DIR}/lib/systemd/system/ 2>/dev/null cp -afP /etc/default/{kamailio,rtpengine}* ${CURR_BACKUP_DIR}/etc/default/ printdbg "files were backed up here: ${CURR_BACKUP_DIR}/" # if the state files for the services to upgrade were there before # and we fail, put them back so the system can recover resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi cp -afP ${CURR_BACKUP_DIR}/etc/. /etc/ cp -afP ${CURR_BACKUP_DIR}/lib/. /lib/ cp -afP ${CURR_BACKUP_DIR}/opt/. /opt/ cp -afP ${CURR_BACKUP_DIR}/var/. /var/ systemctl daemon-reload if (( $REINSTALL_KAMAILIO == 1 )); then # automatically created in dsiprouter.sh when installKamailio() runs [[ -e ${CURR_BACKUP_DIR}/db.sql ]] && withRootDBConn mysql <${CURR_BACKUP_DIR}/db.sql [[ -e ${CURR_BACKUP_DIR}/user.sql ]] && withRootDBConn mysql <${CURR_BACKUP_DIR}/user.sql fi trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM exit 1 } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # conditionally reinstalled printdbg 'preparing service updates' if (( $REINSTALL_KAMAILIO == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled rm -rf ${SRC_DIR}/kamailio rm -f "$DSIP_KAMAILIO_CONFIG_FILE" systemctl mask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.dsiproutercliinstalled systemctl mask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then rm -rf ${SRC_DIR}/rtpengine rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled systemctl mask rtpengine.service fi printdbg 'storing kamailio database data' ( withRootDBConn mysqldump --single-transaction --skip-opt --skip-triggers --no-create-db --no-create-info \ --replace --complete-insert --hex-blob --skip-comments --databases "$KAM_DB_NAME" ) >${CURR_BACKUP_DIR}/data.sql if (( $REINSTALL_DSIPROUTER == 1 )); then printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${OLD_PROJECT_DIR}/ export DSIP_PROJECT_DIR=${OLD_PROJECT_DIR} printdbg 'migrating dSIPRouter settings' ( # magic bash hacking function exit() { :; } export -f exit source ${DSIP_PROJECT_DIR}/dsiprouter.sh &>/dev/null unset -f exit setStaticScriptSettings setDynamicScriptSettings # more magic environment munging dsiprouter configuredsip # the rest of the settings are configured during reinstall ) || { printerr 'Failed migrating dSIPRouter settings' exit 1 } rm -f ${DSIP_SYSTEM_CONFIG_DIR}/.reposconfigured rm -f /lib/systemd/system/dsip-init.service fi # source the new dsip_lib functions # WARNING: from here on we are explicitly using the NEW definitions of the dsip_lib funcs # NOTE: resetConfigsHandler() above will still use the new definitions (lazy loading) export PYTHON_CMD="${DSIP_PROJECT_DIR}/venv/bin/python" . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh printdbg 'upgrading services' # we clear environment here to make sure we get new static settings on install env -i CURR_BACKUP_DIR="$CURR_BACKUP_DIR" HOME="$HOME" LANG="$LANG" LANGUAGE="$LANGUAGE" LC_ALL="$LC_ALL" PATH="$PATH" PWD="$PWD" \ ${DSIP_PROJECT_DIR}/dsiprouter.sh install ${INSTALL_OPTS[@]} if (( $? != 0 )); then printerr 'failed upgrading services' exit 1 fi if (( $REINSTALL_KAMAILIO == 1 )); then printdbg 'migrating kamailio database' withRootDBConn --db="$KAM_DB_NAME" mysql <${CURR_BACKUP_DIR}/user.sql && withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.77/clear_defaults.sql && withRootDBConn --db="$KAM_DB_NAME" mysql <${CURR_BACKUP_DIR}/data.sql || { printerr 'failed migrating kamailio database' exit 1 } fi printdbg 'unmasking services' if (( $REINSTALL_KAMAILIO == 1 )); then systemctl unmask kamailio.service fi if (( $REINSTALL_DSIPROUTER == 1 )); then systemctl unmask dsiprouter.service fi if (( $REINSTALL_RTPENGINE == 1 )); then systemctl unmask rtpengine.service fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then printwarn 'kamailio service requires restarting' fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then printwarn 'dsiprouter service requires restarting' fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.rtpengineinstalled" ]]; then printwarn 'rtpengine service requires restarting' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.77/settings.json ================================================ { "version": "0.77", "depends": ["0.76"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/upgrade/v0.78/dsip-fwd-new.sql ================================================ DROP TABLE IF EXISTS dsip_prefix_mapping; DROP VIEW IF EXISTS dsip_prefix_mapping; CREATE VIEW dsip_prefix_mapping AS SELECT prefix, CAST(ruleid AS char) AS ruleid, CAST(priority AS char) AS priority, '0' AS key_type, '0' AS value_type FROM dr_rules WHERE groupid='9000'; ================================================ FILE: resources/upgrade/v0.78/dsip-fwd-old.sql ================================================ DROP TABLE IF EXISTS dsip_prefix_mapping; DROP VIEW IF EXISTS dsip_prefix_mapping; CREATE VIEW dsip_prefix_mapping AS SELECT prefix, CAST(ruleid AS char) AS ruleid, CAST(priority AS char) AS priority, '0' AS key_type, '0' AS value_type FROM dr_rules; ================================================ FILE: resources/upgrade/v0.78/scripts/migrate.sh ================================================ #!/usr/bin/env bash (( ${DEBUG:-0} == 1 )) && set -x # where the new project files were downloaded NEW_PROJECT_DIR=${NEW_PROJECT_DIR:-/tmp/dsiprouter} # project dir where previous repo was located OLD_PROJECT_DIR=${DSIP_PROJECT_DIR:-/opt/dsiprouter} # the backup directory set by dsiprouter.sh CURR_BACKUP_DIR=${CURR_BACKUP_DIR:-"/var/backups/dsiprouter/$(date '+%s')"} # system config files for dsiprouter DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' # import dsip_lib utility / shared functions (no changes to func definitions in this revision) . ${OLD_PROJECT_DIR}/dsiprouter/dsip_lib.sh # make sure the updates are downloaded and in the correct location [[ ! -e "$NEW_PROJECT_DIR" ]] && { printerr 'could not find repo to upgrade from' echo "expected updated repo to be here: $NEW_PROJECT_DIR" exit 1 } printdbg 'validating system configuration' if ! dsiprouter licensemanager -check tag=DSIP_CORE; then printerr 'A DSIP_CORE license is required to use the auto upgrade feature' echo 'Consider supporting the hard working engineers maintaining this software if you would like to use this feature' exit 1 fi printdbg 'retrieving current system settings' export PYTHON_CMD=${OLD_PROJECT_DIR}/venv/bin/python DSIP_SYSTEM_CONFIG_DIR='/etc/dsiprouter' DSIP_CONFIG_FILE=${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py SYSTEM_KAMAILIO_CONFIG_DIR='/etc/kamailio' export ROOT_DB_USER=$(getConfigAttrib 'ROOT_DB_USER' ${DSIP_CONFIG_FILE}) export ROOT_DB_PASS=$(decryptConfigAttrib 'ROOT_DB_PASS' ${DSIP_CONFIG_FILE}) export ROOT_DB_HOST=$(getConfigAttrib 'ROOT_DB_HOST' ${DSIP_CONFIG_FILE}) export ROOT_DB_PORT=$(getConfigAttrib 'ROOT_DB_PORT' ${DSIP_CONFIG_FILE}) export KAM_DB_NAME=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) printdbg 'preparing for migration' export DSIP_PROJECT_DIR=${OLD_PROJECT_DIR} UPDATE_KAMAILIO=0 UPDATE_DSIPROUTER=0 REQUIRED_RELOADS=() RELOAD_CMD=(dsiprouter restart) if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.kamailioinstalled" ]]; then UPDATE_KAMAILIO=1 REQUIRED_RELOADS+=(kamailio) RELOAD_CMD+=(-kam) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.dsiprouterinstalled" ]]; then UPDATE_DSIPROUTER=1 REQUIRED_RELOADS+=(dsiprouter) RELOAD_CMD+=(-dsip) fi if [[ -f "${DSIP_SYSTEM_CONFIG_DIR}/.nginxinstalled" ]]; then UPDATE_NGINX=1 fi printdbg 'backing up configs just in case the upgrade fails' mkdir -p "$CURR_BACKUP_DIR" mkdir -p ${CURR_BACKUP_DIR}/{opt/dsiprouter,etc/dsiprouter,etc/kamailio,etc/nginx} cp -afP ${OLD_PROJECT_DIR}/. ${CURR_BACKUP_DIR}/opt/dsiprouter/ cp -afP ${SYSTEM_KAMAILIO_CONFIG_DIR}/. ${CURR_BACKUP_DIR}/etc/kamailio/ cp -afP /etc/nginx/. ${CURR_BACKUP_DIR}/etc/nginx/ printdbg "files were backed up here: ${CURR_BACKUP_DIR}/" # revert the changes we made on failure resetConfigsHandler() { printwarn 'upgrade failed, resetting system to previous state' cp -afP ${CURR_BACKUP_DIR}/etc/. /etc/ cp -afP ${CURR_BACKUP_DIR}/opt/. /opt/ if (( $UPDATE_KAMAILIO == 1 )); then # DSIP_PROJECT_DIR may be the old or new project based on when this fails [[ -e "${DSIP_PROJECT_DIR}/resources/upgrade/v0.78/dsip-fwd-old.sql" ]] && { withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.78/dsip-fwd-old.sql kamcmd htable.reload prefix_to_route } fi trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM exit 1 } trap 'resetConfigsHandler $?' EXIT SIGHUP SIGINT SIGQUIT SIGTERM # we want the project files updated first if (( $UPDATE_DSIPROUTER == 1 )); then printdbg 'migrating dSIPRouter project files' cp -rf ${NEW_PROJECT_DIR}/. ${OLD_PROJECT_DIR}/ export DSIP_PROJECT_DIR=${NEW_PROJECT_DIR} printdbg 'migrating dSIPRouter settings' setConfigAttrib 'VERSION' '0.78' ${DSIP_CONFIG_FILE} -q || { printerr 'Failed migrating dSIPRouter settings' exit 1 } printdbg 'regenerating dSIPRouter documentation' ( cd ${DSIP_PROJECT_DIR}/docs && make -j $(nproc) html ) || { printerr 'Failed generating documentation' exit 1 } printdbg 'syncing fusionpbx domains' sudo -u dsiprouter ${PYTHON_CMD} ${DSIP_PROJECT_DIR}/gui/dsiprouter_cron.py fusionpbx sync || { printerr 'Failed syncing fusionpbx domains' exit 1 } fi # NOTE: no change in dsip_lib.sh so we do not need to source the new one if (( $UPDATE_KAMAILIO == 1 )); then printdbg 'updating kamailio configuration' dsiprouter configurekam kamailio -c >/dev/null || { printerr 'Failed migrating kamailio configuration to newer version' exit 1 } withRootDBConn --db="$KAM_DB_NAME" mysql <${DSIP_PROJECT_DIR}/resources/upgrade/v0.78/dsip-fwd-new.sql || { printerr 'Failed migrating kamailio database' exit 1 } fi if (( $UPDATE_NGINX == 1 )); then printdbg 'updating nginx configuration' dsiprouter chown -nginx fi if (( $UPDATE_KAMAILIO == 1 || $UPDATE_DSIPROUTER == 1 )); then printwarn 'The following services require restarting:' for SVC in ${REQUIRED_RELOADS[@]}; do echo "- $SVC" done echo '' echo "To reload these services do $(printwarn -n ONE) of the following:" echo '- press the "Reload" button in the GUI and then click "Reload dSIPRouter"' echo '- run this command in the CLI "'${RELOAD_CMD[@]}'"' echo '' fi # make sure the resetConfigsHandler() is nerfed now that we are successful trap - EXIT SIGHUP SIGINT SIGQUIT SIGTERM pprint 'upgrade completed successfully' exit 0 ================================================ FILE: resources/upgrade/v0.78/settings.json ================================================ { "version": "0.78", "depends": ["0.77"], "install_location": "/opt/dsiprouter", "dsiprouter": [ "migrate.sh" ] } ================================================ FILE: resources/uploadOutRoute.py ================================================ import requests # set per your own configs (get prefixs from gui/util/conversions.py) processed_prefixs = [ '011', ] name = 'Test Calling' gwlist = '#9' # set per your own configs host = "10.10.10.154" username = 'admin' password = 'admin' # auth for session cookie URL = 'http://{}:5000/login'.format(host) payload = { 'username': username, 'password': password, 'nextpage': '' } headers = { 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Accept-Encoding':'gzip, deflate', 'Accept-Language':'en-US,en;q=0.9', 'Cache-Control':'max-age=0', 'Connection':'keep-alive', 'Content-Type':'application/x-www-form-urlencoded', 'DNT':'1', 'Host':'{}:5000'.format(host), 'Origin':'http://{}:5000'.format(host), 'Referer':'http://{}:5000/'.format(host), 'Upgrade-Insecure-Requests':'1', 'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36' } r1 = requests.post(URL, data=payload, headers=headers) print("LOGIN: {}\n".format(r1.status_code)) # add outbound routes URL = 'http://{}:5000/outboundroutes'.format(host) payloads = [{ 'ruleid': '', 'from_prefix': '', 'timerec': '', 'priority': '', 'prefix': prefix, 'name': name, 'gwlist': gwlist } for prefix in processed_prefixs] headers = { 'Host':'{}:5000'.format(host), 'Connection':'keep-alive', 'Cache-Control':'max-age=0', 'Origin':'http://{}:5000'.format(host), 'Upgrade-Insecure-Requests':'1', 'DNT':'1', 'Content-Type':'application/x-www-form-urlencoded', 'User-Agent':'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36', 'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'Referer':'http://{}:5000/outboundroutes'.format(host), 'Accept-Encoding':'gzip, deflate', 'Accept-Language':'en-US,en;q=0.9' } for payload in payloads: r = requests.post(URL, data=payload, headers=headers, cookies=r1.cookies) print("OUTBOUNDROUTES: {}".format(r.status_code)) exit(0) ================================================ FILE: rtpengine/almalinux/install.sh ================================================ #!/usr/bin/env bash # TODO: update based off latest changes in rtpengine/amzn/install.sh # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # search for RPM using almalinux vault repos # not guaranteed to find an RPM, returns 1 if not found # arguments: # $1 == rpm to search for # options: # -af <archictecture> # -dm <distro major version> function vaultSearch() { local RPM_SEARCH DISTRO_MAJVER ARCH_FILTER REPO SEARCH_RESULTS VERSIONS_TO_SEARCH SEARCH_URL REPOS_TO_SEARCH while (( $# > 0 )); do # last arg is user and database if (( $# == 1 )); then RPM_SEARCH="$1" shift break fi case "$1" in -af) shift ARCH_FILTER="$1" shift ;; -dm) shift DISTRO_MAJVER="$1" shift ;; esac done VERSIONS_TO_SEARCH=($( curl -s https://raw.repo.almalinux.org/vault/ | perl -e "\$distro_majver='$DISTRO_MAJVER'; @matches=();" -0777 -e ' $html = do { local $/; <STDIN> }; @matches = ($html =~ m%(?<=\<a href=["'"'"'])(${distro_majver}\.[0-9a-zA-Z-]+)/(?=["'"'"']\>)%g); foreach my $match (@matches) { print "${match}\n"; } ' 2>/dev/null )) for VAULT_VER in ${VERSIONS_TO_SEARCH[@]}; do REPOS_TO_SEARCH=($( curl -s https://repo.almalinux.org/vault/${VAULT_VER}/metadata/${ARCH_FILTER}/composeinfo.json | jq -re '.payload.variants | keys[]' )) (( $? == 0 )) || continue for REPO in ${REPOS_TO_SEARCH[@]}; do SEARCH_URL="https://repo.almalinux.org/vault/${VAULT_VER}/${REPO}/${ARCH_FILTER}/os/Packages/${RPM_SEARCH}.rpm" if (( $(curl -s -I -w "%{http_code}" -o /dev/null "$SEARCH_URL") == 200 )); then echo "$SEARCH_URL" return 0 fi done done return 1 } # try installing in the following order: # 1: headers from repos # 2: headers from vault repos function installKernelDevHeaders { local DISTRO_MAJVER="$DISTRO_MAJVER" local OS_ARCH="$OS_ARCH" local OS_KERNEL="$OS_KERNEL" local KERN_DEV KERN_HDR dnf install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} || { KERN_DEV=$(vaultSearch -af $OS_ARCH -dm $DISTRO_MAJVER "kernel-devel-${OS_KERNEL}") || return 1 KERN_HDR=$(vaultSearch -af $OS_ARCH -dm $DISTRO_MAJVER "kernel-headers-${OS_KERNEL}") || return 1 dnf install -y "$KERN_DEV" && dnf install -y "$KERN_HDR" } } # compile and install rtpengine from RPM's function install { local RTPENGINE_RPM_VER BUILD_KERN_VERSIONS local REBOOT_REQUIRED=0 local OS_ARCH=$(uname -m) local OS_KERNEL=$(uname -r) local RHEL_BASE_VER=$(rpm -E %{rhel}) local DISTRO_VER=$(source /etc/os-release; echo "$VERSION_ID") local DISTRO_MAJVER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") local NPROC=$(nproc) # Install required libraries dnf install -y distribution-gpg-keys && dnf install -y epel-release && dnf install -y almalinux-release-devel && if (( ${DISTRO_MAJVER} == 9 )); then dnf config-manager -y --set-enabled crb && dnf install -y http://rpm.dsiprouter.org/dsiprouter-repo.noarch.rpm elif (( ${DISTRO_MAJVER} == 8 )); then dnf config-manager -y --set-enabled powertools && rpmkeys --import /usr/share/distribution-gpg-keys/rpmfusion/RPM-GPG-KEY-rpmfusion-free-el-${RHEL_BASE_VER} && dnf --setopt=localpkg_gpgcheck=1 install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-${RHEL_BASE_VER}.noarch.rpm fi && dnf install -y jq curl gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel libcurl libcurl-devel \ xmlrpc-c xmlrpc-c-devel libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel xmlrpc-c-devel gperf redhat-rpm-config rpm-build pkgconfig spandsp-devel pandoc \ freetype-devel fontconfig-devel libxml2-devel nc dkms logrotate rsyslog perl perl-IPC-Cmd bc libwebsockets-devel \ gperf gperftools gperftools-devel gperftools-libs gzip mariadb-devel perl-Config-Tiny spandsp librabbitmq librabbitmq-devel \ ffmpeg ffmpeg-devel libjpeg-turbo-devel mosquitto-devel opus-devel iptables-legacy-devel gcc-toolset-14 && installKernelDevHeaders if (( $? != 0 )); then printerr "Problem with installing the required libraries for RTPEngine" return 1 fi BUILD_KERN_VERSIONS=$(joinwith '' ',' '' $(rpm -q kernel-headers | sed 's/kernel-headers-//g')) # rtpengine >= mr11.3.1.1 requires curl >= 7.43.0 if versionCompare "$(tr -d '[a-zA-Z]' <<<"$RTPENGINE_VER")" gteq "11.3.1.1"; then if versionCompare "$(curl -V | head -1 | awk '{print $2}')" lt "7.43.0"; then printdbg 'curl version is not recent enough.. compiling curl 7.8.0' if [[ ! -d ${SRC_DIR}/curl ]]; then ( cd ${SRC_DIR} && curl -sL https://curl.haxx.se/download/curl-7.80.0.tar.gz 2>/dev/null | tar -xzf - --transform 's%curl-7.80.0%curl%'; ) fi ( cd ${SRC_DIR}/curl && ./configure --prefix=/usr --libdir=/usr/lib64 --with-ssl && make -j $NPROC && make -j $NPROC install && ldconfig ) if (( $? != 0 )); then printerr 'Failed to compile curl' return 1 fi fi fi # reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/el-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi RTPENGINE_RPM_VER=$(grep -oP 'Version:.+?\K[\w\.\~\+]+' ${SRC_DIR}/rtpengine/el/rtpengine.spec) RPM_BUILD_ROOT="${HOME}/rpmbuild" rm -rf ${RPM_BUILD_ROOT} 2>/dev/null mkdir -p ${RPM_BUILD_ROOT}/SOURCES && ( source scl_source enable gcc-toolset-14 && cd ${SRC_DIR} && tar -czf ${RPM_BUILD_ROOT}/SOURCES/ngcp-rtpengine-${RTPENGINE_RPM_VER}.tar.gz \ --transform="s%^rtpengine%ngcp-rtpengine-$RTPENGINE_RPM_VER%g" rtpengine/ && echo "%__make $(which make) -j $NPROC" >~/.rpmmacros && # fix for BUG: "exec_prefix: command not found" function exec_prefix() { echo -n '/usr'; } && export -f exec_prefix && # build the RPM's rpmbuild -ba --define "kversion $BUILD_KERN_VERSIONS" ${SRC_DIR}/rtpengine/el/rtpengine.spec && rm -f ~/.rpmmacros && unset -f exec_prefix && systemctl mask ngcp-rtpengine-daemon.service # install the RPM's dnf install -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm ) if (( $? != 0 )); then printerr "Problems occurred compiling rtpengine" return 1 fi # warn user if kernel module not loaded yet if (( $REBOOT_REQUIRED == 1 )); then printwarn "A reboot is required to load the RTPEngine kernel module" fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # give rtpengine permissions in selinux semanage port -a -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} || semanage port -m -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /run/rtpengine/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v3.service /lib/systemd/system/rtpengine.service chmod 644 /lib/systemd/system/rtpengine.service systemctl daemon-reload systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload yum remove -y ngcp-rtpengine\* rm -f /usr/bin/rtpengine rm -f /etc/rsyslog.d/rtpengine.conf rm -f /etc/logrotate.d/rtpengine # remove our selinux changes semanage port -D -t rtp_media_port_t -p udp # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/amzn/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # search for RPM using external APIs mirrors and archives # not guaranteed to find an RPM, outputs empty string if search fails # arguments: # $1 == rpm to search for # options: # -a <arch filter> # --arch=<arch filter> # -d <distro filter> # --distro=<distro filter> # -f <grep filter> # --filter=<grep filter> #function rpmSearch() { # local RPM_SEARCH="" DISTRO_FILTER="" ARCH_FILTER="" GREP_FILTER="" SEARCH_RESULTS="" # # while (( $# > 0 )); do # # last arg is user and database # if (( $# == 1 )); then # RPM_SEARCH="$1" # shift # break # fi # # case "$1" in # -a) # shift # ARCH_FILTER="$1" # shift # ;; # --arch=*) # ARCH_FILTER="$(echo "$1" | cut -d '=' -f 2)" # shift # ;; # -d) # shift # DISTRO_FILTER="$1" # shift # ;; # --distro=*) # DISTRO_FILTER="$(echo "$1" | cut -d '=' -f 2)" # shift # ;; # -f) # shift # GREP_FILTER="$1" # shift # ;; # --filter=*) # GREP_FILTER="$(echo "$1" | cut -d '=' -f 2)" # shift # ;; # esac # done # # # if grep filter not set it defaults to rpm search # if [[ -z "$GREP_FILTER" ]]; then # GREP_FILTER="${RPM_SEARCH}" # fi # # # grab the results of the search using an API on rpmfind.net # SEARCH_RESULTS=$( # curl -sL "https://www.rpmfind.net/linux/rpm2html/search.php?query=${RPM_SEARCH}&system=${DISTRO_FILTER}&arch=${ARCH_FILTER}" 2>/dev/null | # perl -e "\$rpmfind_base_url='https://rpmfind.net'; \$rpm_search='${RPM_SEARCH}'; @matches=(); " -0777 -e \ # '$html = do { local $/; <STDIN> }; # @matches = ($html =~ m%(?<=\<a href=["'"'"'])([-a-zA-Z0-9\@\:\%\._\+~#=/]*${rpm_search}[-a-zA-Z0-9\@\:\%\._\+\~\#\=]*\.rpm)(?=["'"'"']\>)%g); # foreach my $match (@matches) { print "${rpmfind_base_url}${match}\n"; }' 2>/dev/null | # grep -m 1 "${GREP_FILTER}" # ) # # if [[ -n "$SEARCH_RESULTS" ]]; then # echo "$SEARCH_RESULTS" # fi #} # compile and install rtpengine from RPM's function install { local RTPENGINE_RPM_VER BUILD_KERN_VERSIONS TMP local OS_ARCH=$(uname -m) local OS_KERNEL=$(uname -r) local NPROC=$(nproc) # Install required libraries amazon-linux-extras enable -y GraphicsMagick1.3 >/dev/null amazon-linux-extras enable -y redis6 >/dev/null amazon-linux-extras install -y epel >/dev/null amazon-linux-extras enable mariadb10.5 >/dev/null yum groupinstall --setopt=group_package_types=mandatory,default -y 'Development Tools' yum install -y gcc-10 gcc10-c++ glib2 glib2-devel zlib zlib-devel pcre2 pcre2-devel libcurl libcurl-devel libjpeg-turbo-devel \ xmlrpc-c xmlrpc-c-devel libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent \ libevent-devel iptables iptables-devel xmlrpc-c-devel gperf redhat-rpm-config rpm-build rpmrebuild cmake3 \ pkgconfig freetype-devel fontconfig-devel libxml2-devel nc dkms logrotate rsyslog perl perl-IPC-Cmd libtiff-devel \ bc libwebsockets-devel gperf gperftools gperftools-devel gperftools-libs gzip perl-Config-Tiny libbluray-devel \ libjpeg-turbo-devel mosquitto-devel glib2-devel xmlrpc-c-devel hiredis-devel libpcap-devel libevent-devel \ json-glib-devel gperf nasm yasm yasm-devel autoconf automake bzip2 bzip2-devel libtool make mercurial libtiff-devel \ mariadb-libs mariadb-devel pandoc if (( $? != 0 )); then printerr "Could not install the required libraries for RTPEngine" return 1 fi yum install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} || { printwarn 'could not install kernel headers for current kernel' echo 'upgrading kernel and installing new headers' printwarn 'you will need to reboot the machine for changes to take effect' yum install -y kernel-devel kernel-headers } if (( $? != 0 )); then printerr "Could not install kernel headers" return 1 fi # change to a later C/C++ toolchain for FILE in $(ls /usr/bin/gcc10-*); do TMP=$(cut -d '-' -f 2- <<<"$FILE") ln -sf "$FILE" "/usr/local/bin/$TMP" done ln -sf $(which cmake3) /usr/local/bin/cmake # make sure PATH has been updated source /etc/profile BUILD_KERN_VERSIONS=$(joinwith '' ',' '' $(rpm -q kernel-headers | sed 's/kernel-headers-//g')) ## compile and install openssl v1.1.1 (workaround for amazon linux repo conflicts) ## we must overwrite system packages (openssl/openssl-devel) otherwise python's openssl package is not supported if [[ "$(openssl version 2>/dev/null | awk '{print $2}')" != "1.1.1w" ]]; then if [[ ! -d ${SRC_DIR}/openssl ]]; then ( cd ${SRC_DIR} && curl -sL https://www.openssl.org/source/openssl-1.1.1w.tar.gz 2>/dev/null | tar -xzf - --transform 's%openssl-1.1.1w%openssl%'; ) fi ( cd ${SRC_DIR}/openssl && ./Configure --prefix=/usr linux-$(uname -m) && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile openssl' return 1 } fi ## compile and install libxh264 if [[ ! -d ${SRC_DIR}/libxh264 ]]; then git clone --depth 1 -c advice.detachedHead=false https://code.videolan.org/videolan/x264 ${SRC_DIR}/libxh264 fi ( cd ${SRC_DIR}/libxh264 && ./configure --prefix=/usr --libdir=/usr/lib64 --enable-static && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libxh264' return 1 } ## compile and install libx265 if [[ ! -d ${SRC_DIR}/libx265 ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/videolan/x265 ${SRC_DIR}/libx265 fi ( cd ${SRC_DIR}/libx265/build/linux && rm -rf ${SRC_DIR}/libx265/.git && cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DLIB_INSTALL_DIR=/usr/lib64 \ -DENABLE_SHARED=TRUE ../../source && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libx265' return 1 } ## compile and install libfdkaac if [[ ! -d ${SRC_DIR}/libfdkaac ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/mstorsjo/fdk-aac ${SRC_DIR}/libfdkaac fi ( cd ${SRC_DIR}/libfdkaac && autoreconf -i && ./configure --prefix=/usr --libdir=/usr/lib64 --disable-shared && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libfdkaac' return 1 } ## compile and install libmp3lame if [[ ! -d ${SRC_DIR}/libmp3lame ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/gypified/libmp3lame.git ${SRC_DIR}/libmp3lame fi ( cd ${SRC_DIR}/libmp3lame && ./configure --prefix=/usr --libdir=/usr/lib64 --disable-shared --enable-nasm && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libmp3lame' return 1 } ## compile and install libopus if [[ ! -d ${SRC_DIR}/libopus ]]; then git clone --depth 1 -c advice.detachedHead=false https://gitlab.xiph.org/xiph/opus.git ${SRC_DIR}/libopus || git clone --depth 1 -c advice.detachedHead=false https://github.com/xiph/opus.git ${SRC_DIR}/libopus fi ( cd ${SRC_DIR}/libopus && autoreconf -i && ./configure --prefix=/usr --libdir=/usr/lib64 --disable-shared && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libopus' return 1 } ## compile and install libvpx if [[ ! -d ${SRC_DIR}/libvpx ]]; then git clone --depth 1 -c advice.detachedHead=false https://chromium.googlesource.com/webm/libvpx.git ${SRC_DIR}/libvpx fi ( cd ${SRC_DIR}/libvpx && ./configure --prefix=/usr --libdir=/usr/lib64 --disable-examples --disable-unit-tests --enable-vp9-highbitdepth --as=yasm && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libvpx' return 1 } ## compile and install ffmpeg if [[ ! -d ${SRC_DIR}/ffmpeg ]]; then git clone --depth 1 -c advice.detachedHead=false -b n7.1.1 https://git.ffmpeg.org/ffmpeg.git ${SRC_DIR}/ffmpeg fi ( cd ${SRC_DIR}/ffmpeg && ./configure --prefix=/usr --libdir=/usr/lib64 --pkg-config-flags="--static" --extra-libs=-lpthread --extra-libs=-lm \ --enable-gpl --enable-libfdk-aac --enable-libfreetype --enable-libmp3lame --enable-libopus --enable-libvpx \ --enable-libx264 --enable-libx265 --enable-nonfree && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install ffmpeg' return 1 } ## compile and install librabbitmq if [[ ! -d ${SRC_DIR}/librabbitmq ]]; then git clone --depth 1 -c advice.detachedHead=false -b v0.11.0 https://github.com/alanxz/rabbitmq-c.git ${SRC_DIR}/librabbitmq fi ( cd ${SRC_DIR}/librabbitmq && mkdir -p build && cd build/ && cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib64 \ -DBUILD_EXAMPLES=FALSE -DBUILD_TESTS=FALSE .. && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install librabbitmq' return 1 } ## compile and install libspandsp if [[ ! -d ${SRC_DIR}/libspandsp ]]; then git clone --depth 1 -c advice.detachedHead=false https://github.com/freeswitch/spandsp.git ${SRC_DIR}/libspandsp fi ( cd ${SRC_DIR}/libspandsp && ./bootstrap.sh && ./configure --prefix=/usr --libdir=/usr/lib64 && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libspandsp' return 1 } ## compile and install libwebsockets if [[ ! -d ${SRC_DIR}/libwebsockets ]]; then git clone --depth 1 -c advice.detachedHead=false -b v4.3.3 https://github.com/warmcat/libwebsockets.git ${SRC_DIR}/libwebsockets fi ( CMAKE_ARGS='-DCMAKE_INSTALL_PREFIX=/usr -DLIB_SUFFIX=64 -DLWS_WITH_HTTP2=1' if [[ -e "${SRC_DIR}/openssl" ]]; then CMAKE_ARGS="$CMAKE_ARGS -DLWS_OPENSSL_INCLUDE_DIRS=${SRC_DIR}/openssl/include" CMAKE_ARGS="$CMAKE_ARGS -DLWS_OPENSSL_LIBRARIES=${SRC_DIR}/openssl/libssl.so;${SRC_DIR}/openssl/libcrypto.so" fi cd ${SRC_DIR}/libwebsockets && { rm -rf build/ 2>/dev/null || :; } && mkdir -p build && cd build/ && cmake $CMAKE_ARGS .. && make -j $NPROC && make -j $NPROC install ) || { printerr 'Failed to compile and install libwebsockets' return 1 } ## compile and install RTPEngine as an RPM package # create rtpengine user and group # sometimes locks aren't properly removed (this seems to happen often on VM's) rm -f /etc/passwd.lock /etc/shadow.lock /etc/group.lock /etc/gshadow.lock &>/dev/null userdel rtpengine &>/dev/null; groupdel rtpengine &>/dev/null useradd --system --user-group --shell /bin/false --comment "RTPengine RTP Proxy" rtpengine # reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/el-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi # amazon linux specific: remove the dependencies that were manually compiled sed -i --regexp-extended \ -e '/^BuildRequires:[ \t]*pkgconfig\(libwebsockets\).*/d' \ -e '/^BuildRequires:[ \t]*pkgconfig\(spandsp\).*/d' \ -e '/^BuildRequires:[ \t]*pkgconfig\(opus\).*/d' \ -e '/^BuildRequires:[ \t]*ffmpeg-devel.*/d' \ -e '/^Requires\(pre\):[ \t]*ffmpeg-libs.*/d' \ -e 's/^(BuildRequires:.*)ffmpeg-devel(.*)/\1\2/' \ ${SRC_DIR}/rtpengine/el/rtpengine.spec RTPENGINE_RPM_VER=$(grep -oP 'Version:.+?\K[\w\.\~\+]+' ${SRC_DIR}/rtpengine/el/rtpengine.spec) RPM_BUILD_ROOT="${HOME}/rpmbuild" rm -rf ${RPM_BUILD_ROOT} 2>/dev/null mkdir -p ${RPM_BUILD_ROOT}/SOURCES && ( cd ${SRC_DIR} && tar -czf ${RPM_BUILD_ROOT}/SOURCES/ngcp-rtpengine-${RTPENGINE_RPM_VER}.tar.gz \ --transform="s%^rtpengine%ngcp-rtpengine-$RTPENGINE_RPM_VER%g" rtpengine/ && echo "%__make $(which make) -j $NPROC" >~/.rpmmacros && # fix for BUG: "exec_prefix: command not found" function exec_prefix() { echo -n '/usr'; } && export -f exec_prefix && # build the RPM's rpmbuild -ba --define "kversion $BUILD_KERN_VERSIONS" ${SRC_DIR}/rtpengine/el/rtpengine.spec && # see: https://stackoverflow.com/questions/49263444/missing-libraries-in-my-rpm-but-i-know-they-are-there rpmrebuild --change-spec-requires='sed -re "/^(Requires:.*)(libspandsp\.so|libwebsockets\.so|libx265\.so).*/d"' \ -bp ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm && rm -f ~/.rpmmacros && unset -f exec_prefix && systemctl mask ngcp-rtpengine-daemon.service && # install the RPM's yum localinstall -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm ) if (( $? != 0 )); then printerr "Problems occurred compiling rtpengine" return 1 fi # make sure RTPEngine kernel module configured # skip if the kernel headers were not installed if rpm -qa | grep -q "kernel-headers-${OS_KERNEL}"; then if [[ -z "$(find /lib/modules/${OS_KERNEL}/ -name 'xt_RTPENGINE.ko' 2>/dev/null)" ]]; then printerr "Problem installing RTPEngine kernel module" return 1 fi fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld if (( $? != 0 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /var/run/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files rm -f /lib/systemd/system/rtpengine.service 2>/dev/null cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v1.service /lib/systemd/system/rtpengine.service cp -f ${DSIP_PROJECT_DIR}/rtpengine/rtpengine-{start-pre,stop-post} /usr/sbin/ chmod +x /usr/sbin/rtpengine-{start-pre,stop-post} /usr/bin/rtpengine # Reload systemd configs systemctl daemon-reload # Enable the RTPEngine to start during boot systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload yum remove -y ngcp-rtpengine\* rm -f /usr/sbin/rtpengine* /usr/bin/rtpengine /etc/rsyslog.d/rtpengine.conf \ /etc/logrotate.d/rtpengine ${SRC_DIR}/rtpengine for LIB in libxh264 libx265 libfdkaac libmp3lame libopus libvpx ffmpeg librabbitmq libspandsp libwebsockets; do ( cd ${SRC_DIR}/${LIB} && make uninstall && rm -rf ${SRC_DIR}/${LIB} ) done # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/amzn/rtpengine.spec ================================================ Name: ngcp-rtpengine Version: 9.5.5.1+0~mr9.5.5.1 Release: 1%{?dist} Summary: The Sipwise NGCP rtpengine Group: System Environment/Daemons License: GPLv3 URL: https://github.com/sipwise/rtpengine Source0: https://github.com/sipwise/rtpengine/archive/mr%{version}/%{name}-%{version}.tar.gz Conflicts: %{name}-kernel < %{version}-%{release} %global with_transcoding 1 %{?_unitdir:%define has_systemd_dirs 1} BuildRequires: gcc make pkgconfig redhat-rpm-config BuildRequires: glib2-devel libcurl-devel openssl-devel pcre-devel BuildRequires: xmlrpc-c-devel zlib-devel hiredis-devel BuildRequires: libpcap-devel libevent-devel json-glib-devel BuildRequires: gperf perl-IPC-Cmd BuildRequires: perl-podlators Requires(pre): shadow-utils Requires: nc # Remain compat with other installations Provides: ngcp-rtpengine = %{version}-%{release} %description The Sipwise NGCP rtpengine is a proxy for RTP traffic and other UDP based media traffic. It's meant to be used with the Kamailio SIP proxy and forms a drop-in replacement for any of the other available RTP and media proxies. %if 0%{?rhel} < 7 %define iptables_ipv6 1 %endif %package kernel Summary: NGCP rtpengine in-kernel packet forwarding Group: System Environment/Daemons BuildRequires: gcc make redhat-rpm-config iptables-devel Requires: iptables %{?iptables_ipv6:iptables-ipv6} Requires: %{name}%{?_isa} = %{version}-%{release} Requires: %{name}-dkms = %{version}-%{release} %description kernel %{summary}. %package dkms Summary: Kernel module for NGCP rtpengine in-kernel packet forwarding Group: System Environment/Daemons BuildArch: noarch BuildRequires: redhat-rpm-config Requires: gcc make # Define requires according to the installed kernel. %{?rhel:Requires: kernel-devel} %{?fedora:Requires: kernel-devel} %{?suse_version:Requires: kernel-source} Requires(post): dkms Requires(preun): dkms %description dkms %{summary}. %if 0%{?rhel} >= 8 %define mysql_devel_pkg mariadb-devel %else %define mysql_devel_pkg mysql-devel %endif %if 0%{?with_transcoding} > 0 %package recording Summary: NGCP rtpengine recording daemon packet Group: System Environment/Daemons BuildRequires: gcc make redhat-rpm-config %{mysql_devel_pkg} %description recording %{summary}. %endif %define binname rtpengine %prep %setup -q -n %{name}-%{version} %build %if 0%{?with_transcoding} > 0 cd daemon RTPENGINE_VERSION="\"%{version}-%{release}\"" make cd ../iptables-extension RTPENGINE_VERSION="\"%{version}-%{release}\"" make cd ../recording-daemon RTPENGINE_VERSION="\"%{version}-%{release}\"" make cd .. %else cd daemon RTPENGINE_VERSION="\"%{version}-%{release}\"" make with_transcoding=no cd ../iptables-extension RTPENGINE_VERSION="\"%{version}-%{release}\"" make with_transcoding=no cd .. %endif %install # Install the userspace daemon install -D -p -m755 daemon/%{binname} %{buildroot}%{_bindir}/%{binname} # Install CLI (command line interface) install -D -p -m755 utils/%{binname}-ctl %{buildroot}%{_bindir}/%{binname}-ctl # Install recording daemon %if 0%{?with_transcoding} > 0 install -D -p -m755 recording-daemon/%{binname}-recording %{buildroot}%{_bindir}/%{binname}-recording %endif ## Install the init.d script and configuration file %if 0%{?has_systemd_dirs} install -D -p -m644 el/%{binname}.service \ %{buildroot}%{_unitdir}/%{binname}.service %else install -D -p -m755 el/%{binname}.init \ %{buildroot}%{_initrddir}/%{name} %endif %if 0%{?with_transcoding} > 0 %if 0%{?has_systemd_dirs} install -D -p -m644 el/%{binname}-recording.service \ %{buildroot}%{_unitdir}/%{binname}-recording.service %else install -D -p -m755 el/%{binname}-recording.init \ %{buildroot}%{_initrddir}/%{name}-recording %endif %endif install -D -p -m644 el/%{binname}.sysconfig \ %{buildroot}%{_sysconfdir}/sysconfig/%{binname} %if 0%{?with_transcoding} > 0 install -D -p -m644 el/%{binname}-recording.sysconfig \ %{buildroot}%{_sysconfdir}/sysconfig/%{binname}-recording %endif mkdir -p %{buildroot}%{_sharedstatedir}/%{name} mkdir -p %{buildroot}%{_var}/spool/%{binname} # Install config files install -D -p -m644 etc/%{binname}.sample.conf \ %{buildroot}%{_sysconfdir}/%{binname}/%{binname}.conf %if 0%{?with_transcoding} > 0 install -D -p -m644 etc/%{binname}-recording.sample.conf \ %{buildroot}%{_sysconfdir}/%{binname}/%{binname}-recording.conf %endif # Install the iptables plugin install -D -p -m755 iptables-extension/libxt_RTPENGINE.so \ %{buildroot}/%{_lib}/xtables/libxt_RTPENGINE.so ## DKMS module source install install -D -p -m644 kernel-module/Makefile \ %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/Makefile install -D -p -m644 kernel-module/xt_RTPENGINE.c \ %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/xt_RTPENGINE.c install -D -p -m644 kernel-module/xt_RTPENGINE.h \ %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/xt_RTPENGINE.h mkdir -p %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release} install -D -p -m644 kernel-module/rtpengine_config.h \ %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/rtpengine_config.h install -D -p -m644 debian/dkms.conf.in %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/dkms.conf sed -i -e "s/__VERSION__/%{version}-%{release}/g" %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/dkms.conf # For RHEL 7, load the compiled kernel module on boot. %if 0%{?rhel} == 7 install -D -p -m644 kernel-module/xt_RTPENGINE.modules.load.d \ %{buildroot}%{_sysconfdir}/modules-load.d/xt_RTPENGINE.conf %endif %pre getent group %{name} >/dev/null || /usr/sbin/groupadd -r %{name} getent passwd %{name} >/dev/null || /usr/sbin/useradd -r -g %{name} \ -s /sbin/nologin -c "%{name} daemon" -d %{_sharedstatedir}/%{name} %{name} %post if [ $1 -eq 1 ]; then %if 0%{?has_systemd_dirs} systemctl daemon-reload %else /sbin/chkconfig --add %{name} || : %endif fi %post dkms # Add to DKMS registry, build, and install module # The kernel version can be overridden with "--define kversion foo" on rpmbuild, # e.g. --define "kversion 2.6.32-696.23.1.el6.x86_64" %{!?kversion: %define kversion %{nil}} %if "%{kversion}" != "" dkms add -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && dkms build -m %{name} -v %{version}-%{release} -k %{kversion} --rpm_safe_upgrade && dkms install -m %{name} -v %{version}-%{release} -k %{kversion} --rpm_safe_upgrade --force %else dkms add -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && dkms build -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && dkms install -m %{name} -v %{version}-%{release} --rpm_safe_upgrade --force %endif true %preun if [ $1 = 0 ] ; then %if 0%{?has_systemd_dirs} systemctl stop %{binname}.service systemctl disable %{binname}.service %else /sbin/service %{name} stop >/dev/null 2>&1 /sbin/chkconfig --del %{name} %endif fi %preun dkms # Remove from DKMS registry dkms remove -m %{name} -v %{version}-%{release} --rpm_safe_upgrade --all true %files # Userspace daemon %{_bindir}/%{binname} # CLI (command line interface) %{_bindir}/%{binname}-ctl # init.d script and configuration file %if 0%{?has_systemd_dirs} %{_unitdir}/%{binname}.service %else %{_initrddir}/%{name} %endif %config(noreplace) %{_sysconfdir}/sysconfig/%{binname} %attr(0750,%{name},%{name}) %dir %{_sharedstatedir}/%{name} # default config %{_sysconfdir}/%{binname}/%{binname}.conf # Documentation %doc LICENSE README.md el/README.el.md debian/changelog debian/copyright %files kernel /%{_lib}/xtables/libxt_RTPENGINE.so %files dkms %{_usrsrc}/%{name}-%{version}-%{release}/ %if 0%{?rhel} == 7 %{_sysconfdir}/modules-load.d/xt_RTPENGINE.conf %endif %if 0%{?with_transcoding} > 0 %files recording # Recording daemon %{_bindir}/%{binname}-recording # Init script %if 0%{?has_systemd_dirs} %{_unitdir}/%{binname}-recording.service %else %{_initrddir}/%{name}-recording %endif # Sysconfig %config(noreplace) %{_sysconfdir}/sysconfig/%{binname}-recording # Default config %{_sysconfdir}/%{binname}/%{binname}-recording.conf # spool directory %attr(0750,%{name},%{name}) %dir %{_var}/spool/%{binname} %endif %changelog * Thu Oct 26 2023 Tyler Moore <tmoore@dopensource.com> - update paths to match latest versions - remove amzn2 compiled libraries from checks - remove/replace "archname" variable * Tue Jul 10 2018 netaskd <netaskd@gmail.com> - 6.4.0.0-1 - update to ngcp-rtpengine version 6.4.0.0 - add packet recording * Thu Nov 24 2016 Marcel Weinberg <marcel@ng-voice.com> - Updated to ngcp-rtpengine version 4.5.0 and CentOS 7.2 - created a new variable "binname" to use rtpengine as name for the binaries (still using ngcp-rtpenginge as name of the package and daemon - aligned to the .deb packages) - fixed dependencies * Mon Nov 11 2013 Peter Dunkley <peter.dunkley@crocodilertc.net> - Updated version to 2.3.2 - Set license to GPLv3 * Thu Aug 15 2013 Peter Dunkley <peter.dunkley@crocodilertc.net> - init.d scripts and configuration file * Wed Aug 14 2013 Peter Dunkley <peter.dunkley@crocodilertc.net> - First version of .spec file - Builds and installs userspace daemon (but no init.d scripts etc yet) - Builds and installs the iptables plugin - DKMS package for the kernel module ================================================ FILE: rtpengine/centos/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi ## search for RPM using external APIs mirrors and archives ## not guaranteed to find an RPM, outputs empty string if search fails ## arguments: ## $1 == rpm to search for ## options: ## -d <distro filter> ## --distro=<distro filter> ## -f <grep filter> ## --filter=<grep filter> ## TODO: add support for searching https://linuxsoft.cern.ch as well #function rpmSearch() { # local RPM_SEARCH="" DISTRO_FILTER="$DISTRO" GREP_FILTER="" SEARCH_RESULTS="" # # while (( $# > 0 )); do # # last arg is user and database # if (( $# == 1 )); then # RPM_SEARCH="$1" # shift # break # fi # # case "$1" in # -d) # shift # DISTRO_FILTER="$1" # shift # ;; # --distro=*) # DISTRO_FILTER="$(echo "$1" | cut -d '=' -f 2)" # shift # ;; # -f) # shift # GREP_FILTER="$1" # shift # ;; # --filter=*) # GREP_FILTER="$(echo "$1" | cut -d '=' -f 2)" # shift # ;; # esac # done # # # if grep filter not set it defaults to rpm search # if [[ -z "$GREP_FILTER" ]]; then # GREP_FILTER="${RPM_SEARCH}" # fi # # # grab the results of the search using an API on rpmfind.net # SEARCH_RESULTS=$( # curl -sL "https://www.rpmfind.net/linux/rpm2html/search.php?query=${RPM_SEARCH}&system=${DISTRO_FILTER}&arch=${OS_ARCH}" 2>/dev/null | # perl -e "\$rpmfind_base_url='https://rpmfind.net'; \$rpm_search='${RPM_SEARCH}'; @matches=(); " -0777 -e \ # '$html = do { local $/; <STDIN> }; # @matches = ($html =~ m%(?<=\<a href=["'"'"'])([-a-zA-Z0-9\@\:\%\._\+~#=/]*${rpm_search}[-a-zA-Z0-9\@\:\%\._\+\~\#\=]*\.rpm)(?=["'"'"']\>)%g); # foreach my $match (@matches) { print "${rpmfind_base_url}${match}\n"; }' 2>/dev/null | # grep -m 1 "${GREP_FILTER}" # ) # # # if empty try searching the official archives on vault.centos.org # if [[ -z "$SEARCH_RESULTS" ]]; then # SEARCH_RESULTS=$( # curl --keepalive-time 5 --compressed -sL https://vault.centos.org/filelist.gz 2>/dev/null | # gunzip -c | # tac | # grep -oP ".*${OS_ARCH}.*${RPM_SEARCH}.*\.rpm" | # grep -m 1 "${GREP_FILTER}" | # perl -pe 's%^\./(.*\.rpm)$%https://vault.centos.org/\1%' # ) # fi # # if [[ -n "$SEARCH_RESULTS" ]]; then # echo "$SEARCH_RESULTS" # fi #} # compile and install rtpengine from RPM's function install { local RTPENGINE_RPM_VER TMP BUILD_KERN_VERSIONS local REBOOT_REQUIRED=0 local OS_ARCH=$(uname -m) local OS_KERNEL=$(uname -r) local DISTRO_VER=$(source /etc/os-release; echo "$VERSION_ID") local DISTRO_MAJVER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") local RHEL_BASE_VER=$(rpm -E %{rhel}) local NPROC=$(nproc) # Install required libraries if (( ${DISTRO_MAJVER} == 9 )); then dnf install -y epel-release && dnf install -y epel-next-release && dnf config-manager -y --set-enabled crb && dnf install -y http://rpm.dsiprouter.org/dsiprouter-repo.noarch.rpm && dnf install -y gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel curl libcurl libcurl-devel \ xmlrpc-c libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel gperf nc dkms perl perl-IPC-Cmd spandsp spandsp-devel logrotate rsyslog mosquitto-devel \ redhat-rpm-config rpm-build pkgconfig perl-Config-Tiny gperftools-libs gperftools gperftools-devel gzip mariadb-devel \ libwebsockets-devel iptables-legacy-devel pandoc ladspa libuv-devel xmlrpc-c-devel opus-devel ffmpeg ffmpeg-devel elif (( ${DISTRO_MAJVER} == 8 )); then dnf install -y epel-release && dnf install -y epel-next-release && dnf config-manager --enable powertools && dnf install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-${RHEL_BASE_VER}.noarch.rpm && dnf install -y https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-${RHEL_BASE_VER}.noarch.rpm && dnf install -y ffmpeg ffmpeg-devel && dnf install -y gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel curl libcurl libcurl-devel \ xmlrpc-c libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel gperf nc dkms perl perl-IPC-Cmd spandsp spandsp-devel logrotate rsyslog mosquitto-devel \ redhat-rpm-config rpm-build pkgconfig perl-Config-Tiny gperftools-libs gperftools gperftools-devel gzip \ libwebsockets-devel opus-devel xmlrpc-c-devel gcc-toolset-13 pandoc mariadb-devel mariadb-libs && source scl_source enable gcc-toolset-13 else yum-config-manager --enable centos-sclo-rh >/dev/null && yum install -y epel-release && yum install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-${RHEL_BASE_VER}.noarch.rpm && yum install -y https://mirrors.rpmfusion.org/nonfree/el/rpmfusion-nonfree-release-${RHEL_BASE_VER}.noarch.rpm && yum install -y ffmpeg ffmpeg-devel && yum install -y gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre2 pcre2-devel curl libcurl libcurl-devel \ xmlrpc-c xmlrpc-c-devel libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel xmlrpc-c-devel gperf redhat-lsb nc dkms perl perl-IPC-Cmd spandsp spandsp-devel logrotate rsyslog \ redhat-rpm-config rpm-build pkgconfig perl-Config-Tiny gperftools-libs gperftools gperftools-devel gzip libwebsockets-devel \ mosquitto-devel opus-devel devtoolset-11 pandoc mariadb-devel mariadb-libs && source scl_source enable devtoolset-11 fi if (( $? != 0 )); then printerr "Could not install the required libraries for RTPEngine" return 1 fi if (( ${DISTRO_MAJVER} >= 8 )); then dnf install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} || { REBOOT_REQUIRED=1 printwarn 'could not install kernel headers for current kernel' echo 'upgrading kernel and installing new headers' printwarn 'you will need to reboot the machine for changes to take effect' dnf install -y kernel-devel kernel-headers } else yum install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} || { REBOOT_REQUIRED=1 printwarn 'could not install kernel headers for current kernel' echo 'upgrading kernel and installing new headers' printwarn 'you will need to reboot the machine for changes to take effect' yum install -y kernel-devel kernel-headers } fi if (( $? != 0 )); then printerr "Could not install kernel headers" return 1 fi BUILD_KERN_VERSIONS=$(joinwith '' ',' '' $(rpm -q kernel-headers | sed 's/kernel-headers-//g')) # rtpengine >= mr11.3.1.1 requires curl >= 7.43.0 if versionCompare "$(tr -d '[a-zA-Z]' <<<"$RTPENGINE_VER")" gteq "11.3.1.1"; then if versionCompare "$(curl -V | head -1 | awk '{print $2}')" lt "7.43.0"; then printdbg 'curl version is not recent enough.. compiling curl 7.8.0' if [[ ! -d ${SRC_DIR}/curl ]]; then ( cd ${SRC_DIR} && curl -sL https://curl.haxx.se/download/curl-7.80.0.tar.gz 2>/dev/null | tar -xzf - --transform 's%curl-7.80.0%curl%'; ) fi ( cd ${SRC_DIR}/curl && ./configure --prefix=/usr --libdir=/usr/lib64 --with-ssl && make -j $NPROC && make -j $NPROC install && ldconfig ) if (( $? != 0 )); then printerr 'Failed to compile curl' return 1 fi fi fi # reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/el-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi RTPENGINE_RPM_VER=$(grep -oP 'Version:.+?\K[\w\.\~\+]+' ${SRC_DIR}/rtpengine/el/rtpengine.spec) RPM_BUILD_ROOT="${HOME}/rpmbuild" rm -rf ${RPM_BUILD_ROOT} 2>/dev/null mkdir -p ${RPM_BUILD_ROOT}/SOURCES && ( cd ${SRC_DIR} && tar -czf ${RPM_BUILD_ROOT}/SOURCES/ngcp-rtpengine-${RTPENGINE_RPM_VER}.tar.gz \ --transform="s%^rtpengine%ngcp-rtpengine-$RTPENGINE_RPM_VER%g" rtpengine/ && echo "%__make $(which make) -j $NPROC" >~/.rpmmacros && # fix for BUG: "exec_prefix: command not found" function exec_prefix() { echo -n '/usr'; } && export -f exec_prefix && # build the RPM's rpmbuild -ba --define "kversion $BUILD_KERN_VERSIONS" ${SRC_DIR}/rtpengine/el/rtpengine.spec && rm -f ~/.rpmmacros && unset -f exec_prefix && systemctl mask ngcp-rtpengine-daemon.service # install the RPM's if (( ${DISTRO_MAJVER} >= 8 )); then dnf install -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm else yum localinstall -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm fi ) if (( $? != 0 )); then printerr "Problems occurred compiling rtpengine" return 1 fi # warn user if kernel module not loaded yet if (( $REBOOT_REQUIRED == 1 )); then printwarn "A reboot is required to load the RTPEngine kernel module" fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld if (( $? != 0 )) && (( ${DISTRO_MAJVER} == 7 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # give rtpengine permissions in selinux semanage port -a -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} || semanage port -m -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /run/rtpengine/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files if (( ${DISTRO_MAJVER} > 7 )); then cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v3.service /lib/systemd/system/rtpengine.service else cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v2.service /lib/systemd/system/rtpengine.service fi chmod 644 /lib/systemd/system/rtpengine.service systemctl daemon-reload systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload yum remove -y ngcp-rtpengine\* rm -f /usr/bin/rtpengine rm -f /etc/rsyslog.d/rtpengine.conf rm -f /etc/logrotate.d/rtpengine # remove our selinux changes semanage port -D -t rtp_media_port_t -p udp # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/configs/default.conf ================================================ CONFIG_FILE=/etc/rtpengine/rtpengine.conf CONFIG_SECTION=rtpengine PID_FILE=/run/rtpengine/rtpengine.pid MANAGE_IPTABLES=yes SET_USER=rtpengine SET_GROUP=rtpengine ================================================ FILE: rtpengine/configs/rtpengine.conf ================================================ [rtpengine] table = 0 no-fallback = false interface = 127.0.0.1 listen-ng = 127.0.0.1:7722 port-min = 10000 port-max = 20000 #num-threads = 8 timeout = 60 silent-timeout = 3600 offer-timeout = 300 #final-timeout = 7200 #tos = 184 #control-tos = 184 #delete-delay = 30 #final-timeout = 10800 #homer = 123.234.345.456:9060 #homer-protocol = udp #homer-id = 1 #sip-source = false #dtls-passive = false log-level = 4 log-stderr = false log-facility = local1 log-facility-cdr = local1 log-facility-rtcp = local1 ================================================ FILE: rtpengine/deb-mr11.5.1.11.patch ================================================ Subject: [PATCH] change defaults config path --- Index: debian/ngcp-rtpengine-iptables-setup IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/debian/ngcp-rtpengine-iptables-setup b/debian/ngcp-rtpengine-iptables-setup --- a/debian/ngcp-rtpengine-iptables-setup (revision a6631401498937d0c03e63931e601c40fcbfa2e7) +++ b/debian/ngcp-rtpengine-iptables-setup (date 1717809128862) @@ -4,7 +4,7 @@ MODNAME=xt_RTPENGINE MANAGE_IPTABLES=yes -DEFAULTS=/etc/default/ngcp-rtpengine-daemon +DEFAULTS=/etc/default/rtpengine.conf # Load startup options if available if [ -f "$DEFAULTS" ]; then ================================================ FILE: rtpengine/debian/install.sh ================================================ #!/usr/bin/env bash # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # TODO: add support for searching packages.debian.org function debSearch() { local DEB_SEARCH="$1" SEARCH_RESULTS="" # search debian snapshots for package if [[ $(curl -sLI -w "%{http_code}" "https://snapshot.debian.org/binary/?bin=${DEB_SEARCH}" -o /dev/null) == "200" ]]; then SEARCH_RESULTS=$(curl -sL "https://snapshot.debian.org/binary/?bin=${DEB_SEARCH}" 2>/dev/null | grep -oP '<li><a href="../../\K.*(?=")' | head -1) SEARCH_RESULTS=$(curl -sL "https://snapshot.debian.org/${SEARCH_RESULTS}" 2>/dev/null | grep -oP "<a href=\"\K.*${DEB_SEARCH}.*\.deb(?=\")" | head -1) if [[ -n "$SEARCH_RESULTS" ]]; then echo "https://snapshot.debian.org${SEARCH_RESULTS}" return 0 fi fi return 1 } function aptInstallKernelHeadersFromURI() { local RET=0 local KERN_HDR_URI="$1" KERN_HDR_DEB=$(basename "$1") local KERN_HDR_COMMON_URI="" KERN_HDR_COMMON_DEB="" ( # download the .deb file cd /tmp/ curl -sLO --retry 3 "$KERN_HDR_URI" # install dependent common headers KERN_HDR_COMMON_URI=$( debSearch $( dpkg --info "$KERN_HDR_DEB" 2>/dev/null | grep 'Depends:' | cut -d ':' -f 2 | tr ',' '\n' | grep -oP 'linux-headers-.*-common' ) ) && KERN_HDR_COMMON_DEB=$(basename "$KERN_HDR_COMMON_URI") && curl -sLO --retry 3 "$KERN_HDR_COMMON_URI" && { apt-get install -y ./${KERN_HDR_COMMON_DEB} RET=$((RET + $?)) apt-get install -y -f rm -f "$KERN_HDR_COMMON_DEB" } # install the kernel headers apt-get install -y ./${KERN_HDR_DEB} RET=$((RET + $?)) rm -f "$KERN_HDR_DEB" exit $RET ) return $? } # prints $1 if not virtual or the package that provides $1 if virtual function resolveAptVirtualPkg() { apt-cache search "^$1\$" | awk '{print $1}' } # when run from root of a debian repo finds the package dependencies function getDebDependencies() { local TMP DISCRETE_PKGS CONDITIONAL_PKGS RESULT_PKGS=() TMP=$( dpkg-checkbuilddeps 2>&1 | awk -F 'Unmet build dependencies: ' '{print $2}' | perl -pe 's% \(.*?\)%%g' ) DISCRETE_PKGS=$(perl -pe 's%[^ ]+ \| [^ ]+%%g' <<<"$TMP") CONDITIONAL_PKGS=$( grep -oP '[^ ]+ \| [^ ]+' <<<"$TMP" | ( while IFS= read -r LINE; do PKG=$(resolveAptVirtualPkg $(awk -F ' | ' '{print $1}' <<<"$LINE")) if [[ -n "$(apt-cache search $PKG 2>/dev/null)" ]]; then echo "$PKG" else PKG=$(resolveAptVirtualPkg $(awk -F ' | ' '{print $2}' <<<"$LINE")) [[ -n "$(apt-cache search $PKG 2>/dev/null)" ]] && echo "$PKG" fi done ) ) for PKG in $DISCRETE_PKGS; do RESULT_PKGS+=( $(resolveAptVirtualPkg "$PKG") ) done for PKG in $CONDITIONAL_PKGS; do RESULT_PKGS+=( "$PKG" ) done echo ${RESULT_PKGS[@]} } function install { local MISSING_PKGS local NPROC=$(nproc) # Install required packages and remove conflicting packages { dpkg -l ufw &>/dev/null && apt-get remove -y ufw || :; } && case "${DISTRO_VER}" in 10) apt-get install -y git logrotate rsyslog dpkg-dev && apt-get install -y -t bullseye libbcg729-0 libbcg729-dev debhelper dkms libglib2.0-dev libncurses-dev \ zlib1g-dev default-libmysqlclient-dev libmariadb-dev firewalld python3 python3-dev python3-websockets \ perl libbencode-perl libcrypt-openssl-rsa-perl libcrypt-rijndael-perl libdigest-crc-perl libnet-interface-perl \ libsocket6-perl libdigest-hmac-perl libio-multiplex-perl libio-socket-inet6-perl libjson-perl libtest2-suite-perl ;; *) apt-get install -y git logrotate rsyslog firewalld dpkg-dev ;; esac if (( $? != 0 )); then printerr "Problem with installing the required libraries for RTPEngine" return 1 fi # try installing kernel dev headers in the following order: # 1: headers from repos # 2: headers from snapshot.debian.org # NOTE: headers should be installed for all kernels on the system # but we do not want to support ancient kernel dependencies ( RET=0 for OS_KERNEL in $(ls /lib/modules/ 2>/dev/null); do apt-get install -y linux-headers-${OS_KERNEL} || aptInstallKernelHeadersFromURI $(debSearch linux-headers-${OS_KERNEL}) RET=$((RET+$?)) done exit $RET ) # debian ver <= 10 has package conflicts with some older kernels so allow userspace forwarding if (( $? != 0 && ${DISTRO_VER} > 10 )); then printerr "Problems occurred installing one or more kernel headers" return 1 fi ## compile and install RTPEngine as a DEB package ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/deb-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi # build and install using dpkg ( cd ${SRC_DIR}/rtpengine # install all missing dependencies from the control file MISSING_PKGS=$(getDebDependencies) [[ -n "$MISSING_PKGS" ]] && apt-get install -y $MISSING_PKGS dpkg-buildpackage -us -uc -sa --jobs=$NPROC || exit 1 systemctl mask ngcp-rtpengine-daemon.service apt-get install -y ../ngcp-rtpengine-daemon_*${RTPENGINE_VER}*.deb ../ngcp-rtpengine-iptables_*${RTPENGINE_VER}*.deb \ ../ngcp-rtpengine-kernel-dkms_*${RTPENGINE_VER}*.deb ../ngcp-rtpengine-utils_*${RTPENGINE_VER}*.deb || exit 1 systemctl unmask ngcp-rtpengine-daemon.service systemctl disable ngcp-rtpengine-daemon.service exit 0 ) if (( $? != 0 )); then printerr "Problem installing RTPEngine DEB's" return 1 fi # make sure RTPEngine kernel module configured # skip this check for older versions as we allow userspace forwarding if (( ${DISTRO_VER} > 10 )); then if [[ -z "$(find /lib/modules/${OS_KERNEL}/ -name 'xt_RTPENGINE.ko' 2>/dev/null)" ]]; then printerr "Problem installing RTPEngine kernel module" return 1 fi fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G rtpengine root # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /var/run/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files rm -f /lib/systemd/system/rtpengine*.service cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v3.service /lib/systemd/system/rtpengine.service chmod 644 /lib/systemd/system/rtpengine.service systemctl daemon-reload systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload apt-get remove -y ngcp-rtpengine\* rm -f /usr/sbin/rtpengine* /usr/bin/rtpengine /etc/rsyslog.d/rtpengine.conf /etc/logrotate.d/rtpengine # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/el-mr11.5.1.11.patch ================================================ Subject: [PATCH] support multiple kernel versions change defaults config path --- Index: el/rtpengine.spec IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/el/rtpengine.spec b/el/rtpengine.spec --- a/el/rtpengine.spec (revision a6631401498937d0c03e63931e601c40fcbfa2e7) +++ b/el/rtpengine.spec (date 1717911032998) @@ -179,9 +179,9 @@ sed -i -e "s/#MODULE_VERSION#/%{version}-%{release}/g" %{buildroot}%{_usrsrc}/%{name}-%{version}-%{release}/dkms.conf %pre -getent group %{name} >/dev/null || /usr/sbin/groupadd -r %{name} -getent passwd %{name} >/dev/null || /usr/sbin/useradd -r -g %{name} \ - -s /sbin/nologin -c "%{name} daemon" -d %{_sharedstatedir}/%{name} %{name} +getent group rtpengine >/dev/null || /usr/sbin/groupadd -r rtpengine +getent passwd rtpengine >/dev/null || /usr/sbin/useradd -r -g rtpengine \ + -s /sbin/nologin -c "rtpengine daemon" -d %{_sharedstatedir}/%{name} rtpengine %post @@ -198,17 +198,23 @@ # Add to DKMS registry, build, and install module # The kernel version can be overridden with "--define kversion foo" on rpmbuild, # e.g. --define "kversion 2.6.32-696.23.1.el6.x86_64" +# Multiple kernel versions can be set by delimiting them with "," +# e.g. --define "kversion 5.14.0-325.el9.x86_64,5.14.0-325.el9.x86_64" %{!?kversion: %define kversion %{nil}} %if "%{kversion}" != "" - dkms add -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && - dkms build -m %{name} -v %{version}-%{release} -k %{kversion} --rpm_safe_upgrade && - dkms install -m %{name} -v %{version}-%{release} -k %{kversion} --rpm_safe_upgrade --force +%if 0%{?lua:print(1)} +%define kparams %{lua: t = {}; k = rpm.expand("%{kversion}"); for s in string.gmatch(k, "[^,]+") do table.insert(t, "-k "..s) end; print(table.concat(t, " "))} +%else +%define kparams %(RES=(); IFS=',' read -ra ARR <<<"%{kversion}"; for STR in "${ARR[@]}"; do RES+=("-k $STR"); done; IFS=' ' echo -n "${RES[@]}") +%endif %else - dkms add -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && - dkms build -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && - dkms install -m %{name} -v %{version}-%{release} --rpm_safe_upgrade --force +%define kparams %{nil} %endif + +dkms add -m %{name} -v %{version}-%{release} --rpm_safe_upgrade && +dkms build -m %{name} -v %{version}-%{release} %{kparams} --rpm_safe_upgrade && +dkms install -m %{name} -v %{version}-%{release} %{kparams} --rpm_safe_upgrade --force true Index: el/ngcp-rtpengine-iptables-setup IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/el/ngcp-rtpengine-iptables-setup b/el/ngcp-rtpengine-iptables-setup --- a/el/ngcp-rtpengine-iptables-setup (revision a6631401498937d0c03e63931e601c40fcbfa2e7) +++ b/el/ngcp-rtpengine-iptables-setup (date 1717809128848) @@ -4,7 +4,7 @@ MODNAME=xt_RTPENGINE MANAGE_IPTABLES=yes -DEFAULTS=/etc/sysconfig/rtpengine +DEFAULTS=/etc/default/rtpengine.conf # Load startup options if available if [ -f "$DEFAULTS" ]; then ================================================ FILE: rtpengine/rhel/install.sh ================================================ #!/usr/bin/env bash # TODO: update based off latest changes in rtpengine/amzn/install.sh # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # compile and install rtpengine from RPM's function install { local OS_ARCH=$(uname -m) local OS_KERNEL=$(uname -r) local DISTRO_VER=$(source /etc/os-release; echo "$VERSION_ID") local DISTRO_MAJVER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") # Install required libraries dnf install -y epel-release && { dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-$OS_ARCH-rpms || dnf config-manager -y --set-enabled codeready-builder-for-rhel-9-rhui-rpms } && dnf install -y http://rpm.dsiprouter.org/dsiprouter-repo.noarch.rpm && dnf install -y jq curl gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel libcurl libcurl-devel \ xmlrpc-c xmlrpc-c-devel libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel xmlrpc-c-devel gperf redhat-rpm-config rpm-build pkgconfig spandsp-devel pandoc \ freetype-devel fontconfig-devel libxml2-devel nc dkms logrotate rsyslog perl perl-IPC-Cmd bc libwebsockets-devel \ gperf gperftools gperftools-devel gperftools-libs gzip mariadb-devel perl-Config-Tiny spandsp librabbitmq librabbitmq-devel \ ffmpeg ffmpeg-devel libjpeg-turbo-devel mosquitto-devel opus-devel iptables-legacy-devel gcc-toolset-14 && dnf install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} if (( $? != 0 )); then printerr "Problem with installing the required libraries for RTPEngine" exit 1 fi BUILD_KERN_VERSIONS=$(joinwith '' ',' '' $(rpm -q kernel-headers | sed 's/kernel-headers-//g')) # rtpengine >= mr11.3.1.1 requires curl >= 7.43.0 if versionCompare "$(tr -d '[a-zA-Z]' <<<"$RTPENGINE_VER")" gteq "11.3.1.1"; then if versionCompare "$(curl -V | head -1 | awk '{print $2}')" lt "7.43.0"; then printdbg 'curl version is not recent enough.. compiling curl 7.8.0' if [[ ! -d ${SRC_DIR}/curl ]]; then ( cd ${SRC_DIR} && curl -sL https://curl.haxx.se/download/curl-7.80.0.tar.gz 2>/dev/null | tar -xzf - --transform 's%curl-7.80.0%curl%'; ) fi ( cd ${SRC_DIR}/curl && ./configure --prefix=/usr --libdir=/usr/lib64 --with-ssl && make -j $NPROC && make -j $NPROC install && ldconfig ) if (( $? != 0 )); then printerr 'Failed to compile curl' return 1 fi fi fi # reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/el-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi RTPENGINE_RPM_VER=$(grep -oP 'Version:.+?\K[\w\.\~\+]+' ${SRC_DIR}/rtpengine/el/rtpengine.spec) RPM_BUILD_ROOT="${HOME}/rpmbuild" rm -rf ${RPM_BUILD_ROOT} 2>/dev/null mkdir -p ${RPM_BUILD_ROOT}/SOURCES && ( cd ${SRC_DIR} && tar -czf ${RPM_BUILD_ROOT}/SOURCES/ngcp-rtpengine-${RTPENGINE_RPM_VER}.tar.gz \ --transform="s%^rtpengine%ngcp-rtpengine-$RTPENGINE_RPM_VER%g" rtpengine/ && echo "%__make $(which make) -j $NPROC" >~/.rpmmacros && # fix for BUG: "exec_prefix: command not found" function exec_prefix() { echo -n '/usr'; } && export -f exec_prefix && # build the RPM's rpmbuild -ba --define "kversion $BUILD_KERN_VERSIONS" ${SRC_DIR}/rtpengine/el/rtpengine.spec && rm -f ~/.rpmmacros && unset -f exec_prefix && systemctl mask ngcp-rtpengine-daemon.service # install the RPM's if (( ${DISTRO_MAJVER} >= 8 )); then dnf install -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm else yum localinstall -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm fi ) if (( $? != 0 )); then printerr "Problems occurred compiling rtpengine" return 1 fi # warn user if kernel module not loaded yet if (( $REBOOT_REQUIRED == 1 )); then printwarn "A reboot is required to load the RTPEngine kernel module" fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld if (( $? != 0 )) && (( ${DISTRO_MAJVER} == 7 )); then # fix for bug: https://bugzilla.redhat.com/show_bug.cgi?id=1575845 systemctl restart dbus systemctl restart firewalld # fix for ensuing bug: https://bugzilla.redhat.com/show_bug.cgi?id=1372925 systemctl restart systemd-logind fi # give rtpengine permissions in selinux semanage port -a -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} || semanage port -m -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /run/rtpengine/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v1.service /lib/systemd/system/rtpengine.service chmod 644 /lib/systemd/system/rtpengine.service cp -f ${DSIP_PROJECT_DIR}/rtpengine/rtpengine-{start-pre,stop-post} /usr/sbin/ chmod +x /usr/sbin/rtpengine-{start-pre,stop-post} /usr/bin/rtpengine systemctl daemon-reload systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload yum remove -y ngcp-rtpengine\* rm -f /usr/sbin/rtpengine-{start-pre,stop-post} rm -f /usr/bin/rtpengine rm -f /etc/rsyslog.d/rtpengine.conf rm -f /etc/logrotate.d/rtpengine # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/rocky/install.sh ================================================ #!/usr/bin/env bash # TODO: update based off latest changes in rtpengine/amzn/install.sh # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # search for RPM using rocky linux vault repos # not guaranteed to find an RPM, returns 1 if not found # arguments: # $1 == rpm to search for function vaultSearch() { local RPM_SEARCH RPM_PATH RPM_SEARCH="$1" RPM_PATH=$( curl -s https://dl.rockylinux.org/vault/rocky/fullfilelist | grep -m 1 "$RPM_SEARCH"'.rpm$' ) (( $? == 0 )) || return 1 VERSIONS_TO_SEARCH=($( curl -s https://dl.rockylinux.org/vault/rocky/ | perl -e "\$distro_majver='$DISTRO_MAJVER'; @matches=();" -0777 -e ' $html = do { local $/; <STDIN> }; @matches = ($html =~ m%(?<=\<a href=["'"'"'])(${distro_majver}\.[0-9a-zA-Z-]+)/(?=["'"'"']\>)%g); foreach my $match (@matches) { print "${match}\n"; } ' 2>/dev/null )) echo "https://dl.rockylinux.org/vault/rocky/${RPM_PATH}" return 0 } # try installing in the following order: # 1: headers from repos # 2: headers from vault repos function installKernelDevHeaders { local DISTRO_MAJVER="$DISTRO_MAJVER" local OS_ARCH="$OS_ARCH" local OS_KERNEL="$OS_KERNEL" local KERN_DEV KERN_HDR dnf install -y kernel-devel-${OS_KERNEL} kernel-headers-${OS_KERNEL} || { KERN_DEV=$(vaultSearch "kernel-devel-${OS_KERNEL}") || return 1 KERN_HDR=$(vaultSearch "kernel-headers-${OS_KERNEL}") || return 1 dnf install -y "$KERN_DEV" && dnf install -y "$KERN_HDR" } } # compile and install rtpengine from RPM's function install { local RTPENGINE_RPM_VER BUILD_KERN_VERSIONS local REBOOT_REQUIRED=0 local OS_ARCH=$(uname -m) local OS_KERNEL=$(uname -r) local RHEL_BASE_VER=$(rpm -E %{rhel}) local DISTRO_VER=$(source /etc/os-release; echo "$VERSION_ID") local DISTRO_MAJVER=$(cut -d '.' -f 1 <<<"$DISTRO_VER") local NPROC=$(nproc) # Install required libraries dnf install -y distribution-gpg-keys && dnf install -y epel-release && dnf config-manager --enable -y devel && if (( ${DISTRO_MAJVER} == 9 )); then dnf config-manager -y --set-enabled crb && dnf install -y http://rpm.dsiprouter.org/dsiprouter-repo.noarch.rpm elif (( ${DISTRO_MAJVER} == 8 )); then dnf config-manager -y --set-enabled powertools && rpmkeys --import /usr/share/distribution-gpg-keys/rpmfusion/RPM-GPG-KEY-rpmfusion-free-el-${RHEL_BASE_VER} && dnf --setopt=localpkg_gpgcheck=1 install -y https://mirrors.rpmfusion.org/free/el/rpmfusion-free-release-${RHEL_BASE_VER}.noarch.rpm fi && dnf install -y jq curl gcc glib2 glib2-devel zlib zlib-devel openssl openssl-devel pcre pcre-devel libcurl libcurl-devel \ xmlrpc-c xmlrpc-c-devel libpcap libpcap-devel hiredis hiredis-devel json-glib json-glib-devel libevent libevent-devel \ iptables iptables-devel xmlrpc-c-devel gperf redhat-rpm-config rpm-build pkgconfig spandsp-devel pandoc \ freetype-devel fontconfig-devel libxml2-devel nc dkms logrotate rsyslog perl perl-IPC-Cmd bc libwebsockets-devel \ gperf gperftools gperftools-devel gperftools-libs gzip mariadb-devel perl-Config-Tiny spandsp librabbitmq librabbitmq-devel \ ffmpeg ffmpeg-devel libjpeg-turbo-devel mosquitto-devel opus-devel iptables-legacy-devel gcc-toolset-14 && installKernelDevHeaders if (( $? != 0 )); then printerr "Problem with installing the required libraries for RTPEngine" return 1 fi BUILD_KERN_VERSIONS=$(joinwith '' ',' '' $(rpm -q kernel-headers | sed 's/kernel-headers-//g')) # rtpengine >= mr11.3.1.1 requires curl >= 7.43.0 if versionCompare "$(tr -d '[a-zA-Z]' <<<"$RTPENGINE_VER")" gteq "11.3.1.1"; then if versionCompare "$(curl -V | head -1 | awk '{print $2}')" lt "7.43.0"; then printdbg 'curl version is not recent enough.. compiling curl 7.8.0' if [[ ! -d ${SRC_DIR}/curl ]]; then ( cd ${SRC_DIR} && curl -sL https://curl.haxx.se/download/curl-7.80.0.tar.gz 2>/dev/null | tar -xzf - --transform 's%curl-7.80.0%curl%'; ) fi ( cd ${SRC_DIR}/curl && ./configure --prefix=/usr --libdir=/usr/lib64 --with-ssl && make -j $NPROC && make -j $NPROC install && ldconfig ) if (( $? != 0 )); then printerr 'Failed to compile curl' return 1 fi fi fi # reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/el-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi RTPENGINE_RPM_VER=$(grep -oP 'Version:.+?\K[\w\.\~\+]+' ${SRC_DIR}/rtpengine/el/rtpengine.spec) RPM_BUILD_ROOT="${HOME}/rpmbuild" rm -rf ${RPM_BUILD_ROOT} 2>/dev/null mkdir -p ${RPM_BUILD_ROOT}/SOURCES && ( source scl_source enable gcc-toolset-14 && cd ${SRC_DIR} && tar -czf ${RPM_BUILD_ROOT}/SOURCES/ngcp-rtpengine-${RTPENGINE_RPM_VER}.tar.gz \ --transform="s%^rtpengine%ngcp-rtpengine-$RTPENGINE_RPM_VER%g" rtpengine/ && echo "%__make $(which make) -j $NPROC" >~/.rpmmacros && # fix for BUG: "exec_prefix: command not found" function exec_prefix() { echo -n '/usr'; } && export -f exec_prefix && # build the RPM's rpmbuild -ba --define "kversion $BUILD_KERN_VERSIONS" ${SRC_DIR}/rtpengine/el/rtpengine.spec && rm -f ~/.rpmmacros && unset -f exec_prefix && systemctl mask ngcp-rtpengine-daemon.service # install the RPM's dnf install -y ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/noarch/ngcp-rtpengine-dkms-${RTPENGINE_RPM_VER}*.rpm \ ${RPM_BUILD_ROOT}/RPMS/${OS_ARCH}/ngcp-rtpengine-kernel-${RTPENGINE_RPM_VER}*.rpm ) if (( $? != 0 )); then printerr "Problems occurred compiling rtpengine" return 1 fi # warn user if kernel module not loaded yet if (( $REBOOT_REQUIRED == 1 )); then printwarn "A reboot is required to load the RTPEngine kernel module" fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # give rtpengine permissions in selinux semanage port -a -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} || semanage port -m -t rtp_media_port_t -p udp ${RTP_PORT_MIN}-${RTP_PORT_MAX} # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /run/rtpengine/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v3.service /lib/systemd/system/rtpengine.service chmod 644 /lib/systemd/system/rtpengine.service systemctl daemon-reload systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload yum remove -y ngcp-rtpengine\* rm -f /usr/bin/rtpengine rm -f /etc/rsyslog.d/rtpengine.conf rm -f /etc/logrotate.d/rtpengine # remove our selinux changes semanage port -D -t rtp_media_port_t -p udp # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: rtpengine/rtpengine-start-pre ================================================ #!/bin/sh # defaults if config file not provided PATH=/sbin:/bin:/usr/sbin:/usr/bin TABLE=0 MODNAME=xt_RTPENGINE MANAGE_IPTABLES=yes DAEMON_OPTIONS_FILE=/var/run/rtpengine/daemon.conf # $1 contains the path to the configuration file. It is passed in the systemd unit file # When calling the rtpengine command, default location: /etc/default/rtpengine.conf DEFAULTS="$1" # Load rtpengine options if available if [ -f $DEFAULTS ]; then . $DEFAULTS || true fi if [ "$RUN_RTPENGINE" != "yes" ]; then echo "rtpengine not yet configured. Edit $DEFAULTS first." exit 0 fi # Gradually fill the options of the command rtpengine which starts the RTPEngine daemon # The variables used are sourced from the configuration file rtpengine.conf OPTIONS="" MODPROBE_OPTIONS="" if [ ! -z "$INTERFACES" ]; then for interface in $INTERFACES; do OPTIONS="$OPTIONS --interface=$interface" done fi [ -z "$CONFIG_FILE" ] || OPTIONS="$OPTIONS --config-file=$CONFIG_FILE" [ -z "$CONFIG_SECTION" ] || OPTIONS="$OPTIONS --config-section=$CONFIG_SECTION" [ -z "$ADDRESS" ] || OPTIONS="$OPTIONS --interface=$ADDRESS" [ -z "$ADV_ADDRESS" ] || OPTIONS="$OPTIONS!$ADV_ADDRESS" [ -z "$ADDRESS_IPV6" ] || OPTIONS="$OPTIONS --interface=$ADDRESS_IPV6" [ -z "$ADV_ADDRESS_IPV6" ] || OPTIONS="$OPTIONS!$ADV_ADDRESS_IPV6" [ -z "$LISTEN_TCP" ] || OPTIONS="$OPTIONS --listen-tcp=$LISTEN_TCP" [ -z "$LISTEN_UDP" ] || OPTIONS="$OPTIONS --listen-udp=$LISTEN_UDP" [ -z "$LISTEN_NG" ] || OPTIONS="$OPTIONS --listen-ng=$LISTEN_NG" [ -z "$LISTEN_CLI" ] || OPTIONS="$OPTIONS --listen-cli=$LISTEN_CLI" [ -z "$TIMEOUT" ] || OPTIONS="$OPTIONS --timeout=$TIMEOUT" [ -z "$SILENT_TIMEOUT" ] || OPTIONS="$OPTIONS --silent-timeout=$SILENT_TIMEOUT" [ -z "$PIDFILE" ] || OPTIONS="$OPTIONS --pidfile=$PIDFILE" [ -z "$TOS" ] || OPTIONS="$OPTIONS --tos=$TOS" [ -z "$PORT_MIN" ] || OPTIONS="$OPTIONS --port-min=$PORT_MIN" [ -z "$PORT_MAX" ] || OPTIONS="$OPTIONS --port-max=$PORT_MAX" [ -z "$REDIS" ] || OPTIONS="$OPTIONS --redis=$REDIS" [ -z "$REDIS_DB" ] || OPTIONS="$OPTIONS --redis-db=$REDIS_DB" [ -z "$REDIS_READ" ] || OPTIONS="$OPTIONS --redis-read=$REDIS_READ" [ -z "$REDIS_READ_DB" ] || OPTIONS="$OPTIONS --redis-read-db=$REDIS_READ_DB" [ -z "$REDIS_WRITE" ] || OPTIONS="$OPTIONS --redis-write=$REDIS_WRITE" [ -z "$REDIS_WRITE_DB" ] || OPTIONS="$OPTIONS --redis-write-db=$REDIS_WRITE_DB" [ -z "$B2B_URL" ] || OPTIONS="$OPTIONS --b2b-url=$B2B_URL" [ -z "$NO_FALLBACK" -o \( "$NO_FALLBACK" != "1" -a "$NO_FALLBACK" != "yes" \) ] || OPTIONS="$OPTIONS --no-fallback" OPTIONS="$OPTIONS --table=$TABLE" [ -z "$LOG_LEVEL" ] || OPTIONS="$OPTIONS --log-level=$LOG_LEVEL" [ -z "$LOG_FACILITY" ] || OPTIONS="$OPTIONS --log-facility=$LOG_FACILITY" [ -z "$LOG_FACILITY_CDR" ] || OPTIONS="$OPTIONS --log-facility-cdr=$LOG_FACILITY_CDR" [ -z "$LOG_FACILITY_RTCP" ] || OPTIONS="$OPTIONS --log-facility-rtcp=$LOG_FACILITY_RTCP" [ -z "$NUM_THREADS" ] || OPTIONS="$OPTIONS --num-threads=$NUM_THREADS" [ -z "$DELETE_DELAY" ] || OPTIONS="$OPTIONS --delete-delay=$DELETE_DELAY" [ -z "$GRAPHITE" ] || OPTIONS="$OPTIONS --graphite=$GRAPHITE" [ -z "$GRAPHITE_INTERVAL" ] || OPTIONS="$OPTIONS --graphite-interval=$GRAPHITE_INTERVAL" [ -z "$GRAPHITE_PREFIX" ] || OPTIONS="$OPTIONS --graphite-prefix=$GRAPHITE_PREFIX" [ -z "$MAX_SESSIONS" ] || OPTIONS="$OPTIONS --max-sessions=$MAX_SESSIONS" [ -z "$HOMER" ] || OPTIONS="$OPTIONS --homer=$HOMER" [ -z "$HOMER_PROTOCOL" ] || OPTIONS="$OPTIONS --homer-protocol=$HOMER_PROTOCOL" [ -z "$HOMER_ID" ] || OPTIONS="$OPTIONS --homer-id=$HOMER_ID" if [ ! -z "$RECORDING_DIR" ]; then OPTIONS="$OPTIONS --recording-dir=$RECORDING_DIR" if [ ! -d "$RECORDING_DIR" ]; then mkdir "$RECORDING_DIR" 2> /dev/null chmod 700 "$RECORDING_DIR" 2> /dev/null fi fi [ -z "$RECORDING_METHOD" ] || OPTIONS="$OPTIONS --recording-method=$RECORDING_METHOD" [ -z "$RECORDING_FORMAT" ] || OPTIONS="$OPTIONS --recording-format=$RECORDING_FORMAT" [ -z "$DTLS_PASSIVE" ] || ( [ "$DTLS_PASSIVE" != "yes" ] && [ "$DTLS_PASSIVE" != "1" ] ) || OPTIONS="$OPTIONS --dtls-passive" if test "$FORK" = "no" ; then OPTIONS="$OPTIONS --foreground" fi if test "$LOG_STDERR" = "yes" ; then OPTIONS="$OPTIONS --log-stderr" fi # Handle requested setuid/setgid. if ! test -z "$SET_USER"; then PUID=$(id -u "$SET_USER" 2> /dev/null) test -z "$PUID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_uid=$PUID" if test -z "$SET_GROUP"; then PGID=$(id -g "$SET_USER" 2> /dev/null) test -z "$PGID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_gid=$PGID" fi fi if ! test -z "$SET_GROUP"; then PGID=$(grep "^$SET_GROUP:" /etc/group | cut -d: -f3 2> /dev/null) test -z "$PGID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_gid=$PGID" fi # VM / Container Specific - don't use kernel forwarding if [ -x /usr/sbin/ngcp-virt-identify ]; then if /usr/sbin/ngcp-virt-identify --type container; then VIRT="yes" fi fi firewallSetup() { if [ "$TABLE" -lt 0 ] || [ "$VIRT" = "yes" ]; then return fi if [ "$MANAGE_IPTABLES" != "yes" ]; then return fi # shellcheck disable=SC2086 modprobe $MODNAME $MODPROBE_OPTIONS # ensure that the table we want to use doesn't exist if [ -e /proc/rtpengine/control ]; then echo "del $TABLE" > /proc/rtpengine/control 2>/dev/null fi # Freshly add the iptables rules to forward the udp packets to the iptables-extension "RTPEngine": # Remember iptables table = chains, rules stored in the chains # -N (create a new chain with the name rtpengine) iptables -N rtpengine 2> /dev/null # -D: Delete the rule for the target "rtpengine" if exists. -j (target): chain name or extension name # from the table "filter" (the default -without the option '-t') iptables -D INPUT -j rtpengine 2> /dev/null # Add the rule again so the packets will go to rtpengine chain after the (filter-INPUT) hook point. iptables -I INPUT -j rtpengine # Delete and Insert a rule in the rtpengine chain to forward the UDP traffic iptables -D rtpengine -p udp -j RTPENGINE --id "$TABLE" 2>/dev/null iptables -I rtpengine -p udp -j RTPENGINE --id "$TABLE" # The same for IPv6 ip6tables -N rtpengine 2> /dev/null ip6tables -D INPUT -j rtpengine 2> /dev/null ip6tables -I INPUT -j rtpengine ip6tables -D rtpengine -p udp -j RTPENGINE --id "$TABLE" 2>/dev/null ip6tables -I rtpengine -p udp -j RTPENGINE --id "$TABLE" } firewallSetup echo "Start Command: /usr/bin/rtpengine $OPTIONS" echo "OPTIONS='$OPTIONS'" > $DAEMON_OPTIONS_FILE exit 0 ================================================ FILE: rtpengine/rtpengine-stop-post ================================================ #!/bin/sh # defaults if config file not provided PATH=/sbin:/bin:/usr/sbin:/usr/bin TABLE=0 MODNAME=xt_RTPENGINE MANAGE_IPTABLES=yes # $1 contains the path to the configuration file. It is passed in the systemd unit file # When calling the rtpengine command, default location: /etc/default/rtpengine.conf DEFAULTS="$1" # Load rtpengine options if available if [ -f $DEFAULTS ]; then . $DEFAULTS || true fi MODPROBE_OPTIONS="" # Handle requested setuid/setgid. if ! test -z "$SET_USER"; then PUID=$(id -u "$SET_USER" 2> /dev/null) test -z "$PUID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_uid=$PUID" if test -z "$SET_GROUP"; then PGID=$(id -g "$SET_USER" 2> /dev/null) test -z "$PGID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_gid=$PGID" fi fi if ! test -z "$SET_GROUP"; then PGID=$(grep "^$SET_GROUP:" /etc/group | cut -d: -f3 2> /dev/null) test -z "$PGID" || MODPROBE_OPTIONS="$MODPROBE_OPTIONS proc_gid=$PGID" fi # VM / Container Specific - don't use kernel forwarding if [ -x /usr/sbin/ngcp-virt-identify ]; then if /usr/sbin/ngcp-virt-identify --type container; then VIRT="yes" fi fi # After systemd send kill signal to the rtpengine daemon, # Wait 3 sec, then clean the iptables stuffs: # 1- delete the forwarding table, # 2- delete the iptables rules related to rtpengine # 3- Unload the kernel module xt_RTPENGINE firewallTeardown() { if [ "$TABLE" -lt 0 ] || [ "$VIRT" = "yes" ]; then return fi sleep 3 # Delete the Table if [ -e /proc/rtpengine/control ]; then echo "del $TABLE" > /proc/rtpengine/control 2>/dev/null fi if [ "$MANAGE_IPTABLES" != "yes" ]; then return fi # Remove iptables forwarding rules iptables -D rtpengine -p udp -j RTPENGINE --id "$TABLE" 2>/dev/null iptables -D INPUT -j rtpengine 2> /dev/null iptables -D rtpengine 2> /dev/null # The same for ip6tables rules ip6tables -D rtpengine -p udp -j RTPENGINE --id "$TABLE" 2>/dev/null ip6tables -D INPUT -j rtpengine 2> /dev/null ip6tables -D rtpengine 2> /dev/null # Remove kernel module if loaded if lsmod | grep -q "$MODNAME" 2>/dev/null; then rmmod $MODNAME 2>/dev/null fi } firewallTeardown exit 0 ================================================ FILE: rtpengine/systemd/dummy.service ================================================ [Unit] Description=RTPEngine Dummy Service [Service] Type=oneshot ExecStart=/bin/true RemainAfterExit=true TimeoutSec=0 [Install] WantedBy=multi-user.target ================================================ FILE: rtpengine/systemd/rtpengine-v1.service ================================================ [Unit] Description=RTPEngine proxy for RTP and other media streams Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=iptables.service redis.service rsyslog.service # iptables.service is required only if the RTPEngine uses its kernel module. # redis.service is required if the Redis server is working on the same machine along with the RTPEngine DefaultDependencies=no [Service] Type=simple PermissionsStartOnly=true Environment='CFGFILE=/etc/default/rtpengine.conf' Environment='RUNDIR=/run/rtpengine' # PIDFile requires an absolute path PIDFile=/run/rtpengine/rtpengine.pid # ExecStart* requires an absolute path for the program ExecStartPre=/usr/bin/dsiprouter chown -rtpengine ExecStartPre=/usr/sbin/rtpengine-start-pre $CFGFILE ExecStart=/usr/bin/rtpengine -f $OPTIONS ExecStopPost=/usr/sbin/rtpengine-stop-post $CFGFILE Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: rtpengine/systemd/rtpengine-v2.service ================================================ [Unit] Description=RTPEngine proxy for RTP and other media streams Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=iptables.service redis.service rsyslog.service # iptables.service is required only if the RTPEngine uses its kernel module. # redis.service is required if the Redis server is working on the same machine along with the RTPEngine DefaultDependencies=no [Service] Type=forking PermissionsStartOnly=true EnvironmentFile=/etc/default/rtpengine.conf User=rtpengine Group=rtpengine # runtime only directory /run/rtpengine RuntimeDirectory=rtpengine RuntimeDirectoryMode=0770 # PIDFile requires an absolute path PIDFile=/run/rtpengine/rtpengine.pid # process capabilities AmbientCapabilities=CAP_NET_ADMIN CAP_SYS_NICE CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_NICE # ExecStart* requires an absolute path for the program ExecStartPre=/usr/bin/dsiprouter chown -rtpengine ExecStartPre=/usr/sbin/ngcp-rtpengine-iptables-setup start ExecStart=/usr/bin/rtpengine --config-file=${CONFIG_FILE} --pidfile=${PID_FILE} ExecStopPost=/usr/sbin/ngcp-rtpengine-iptables-setup stop Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: rtpengine/systemd/rtpengine-v3.service ================================================ [Unit] Description=RTPEngine proxy for RTP and other media streams Requires=basic.target network.target After=network.target network-online.target systemd-journald.socket basic.target After=iptables.service redis.service rsyslog.service # iptables.service is required only if the RTPEngine uses its kernel module. # redis.service is required if the Redis server is working on the same machine along with the RTPEngine DefaultDependencies=no [Service] Type=forking EnvironmentFile=/etc/default/rtpengine.conf User=rtpengine Group=rtpengine # runtime only directory /run/rtpengine RuntimeDirectory=rtpengine RuntimeDirectoryMode=0770 # PIDFile requires an absolute path PIDFile=/run/rtpengine/rtpengine.pid # process capabilities AmbientCapabilities=CAP_NET_ADMIN CAP_SYS_NICE CapabilityBoundingSet=CAP_NET_ADMIN CAP_SYS_NICE # ExecStart* requires an absolute path for the program ExecStartPre=!-/usr/bin/dsiprouter chown -rtpengine ExecStartPre=+/usr/sbin/ngcp-rtpengine-iptables-setup start ExecStart=/usr/bin/rtpengine --config-file=${CONFIG_FILE} --pidfile=${PID_FILE} ExecStopPost=+/usr/sbin/ngcp-rtpengine-iptables-setup stop Restart=on-failure [Install] WantedBy=multi-user.target ================================================ FILE: rtpengine/ubuntu/install.sh ================================================ #!/usr/bin/env bash # TODO: update based off latest changes in rtpengine/debian/install.sh # Debug this script if in debug mode (( $DEBUG == 1 )) && set -x # Import dsip_lib utility / shared functions if not already if [[ "$DSIP_LIB_IMPORTED" != "1" ]]; then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi function debSearch() { local DEB_SEARCH="$1" SEARCH_URL # search security.ubuntu.com for package SEARCH_URL="http://security.ubuntu.com/ubuntu/pool/main/l/linux/${DEB_SEARCH}.deb" if [[ $(curl -sL -I -w "%{http_code}" "$SEARCH_URL" -o /dev/null) == "200" ]]; then echo "$SEARCH_URL" return 0 fi # search archive.ubuntu.com for package SEARCH_URL="http://archive.ubuntu.com/ubuntu/pool/main/l/linux/${DEB_SEARCH}.deb" if [[ $(curl -sL -I -w "%{http_code}" "$SEARCH_URL" -o /dev/null) == "200" ]]; then echo "$SEARCH_URL" return 0 fi # nowhere else we trust to search return 1 } function aptInstallKernelHeadersFromURI() { local RET=0 local KERN_HDR_URI="$1" KERN_HDR_DEB=$(basename "$1") local KERN_HDR_COMMON_URI="" KERN_HDR_COMMON_DEB="" ( # download the .deb file cd /tmp/ curl -sLO --retry 3 "$KERN_HDR_URI" # install dependent common headers KERN_HDR_COMMON_URI=$( debSearch $( dpkg --info "$KERN_HDR_DEB" 2>/dev/null | grep 'Depends:' | cut -d ':' -f 2 | tr ',' '\n' | grep -oP 'linux-headers-.*-common' ) ) && KERN_HDR_COMMON_DEB=$(basename "$KERN_HDR_COMMON_URI") && curl -sLO --retry 3 "$KERN_HDR_COMMON_URI" && { apt-get install -y ./${KERN_HDR_COMMON_DEB} RET=$((RET + $?)) apt-get install -y -f rm -f "$KERN_HDR_COMMON_DEB" } # install the kernel headers apt-get install -y ./${KERN_HDR_DEB} RET=$((RET + $?)) rm -f "$KERN_HDR_DEB" exit $RET ) return $? } # prints $1 if not virtual or the package that provides $1 if virtual function resolveAptVirtualPkg() { apt-cache search "^$1\$" | awk '{print $1}' } # when run from root of a debian repo finds the package dependencies function getDebDependencies() { local TMP DISCRETE_PKGS CONDITIONAL_PKGS RESULT_PKGS=() TMP=$( dpkg-checkbuilddeps 2>&1 | awk -F 'Unmet build dependencies: ' '{print $2}' | perl -pe 's% \(.*?\)%%g' ) DISCRETE_PKGS=$(perl -pe 's%[^ ]+ \| [^ ]+%%g' <<<"$TMP") CONDITIONAL_PKGS=$( grep -oP '[^ ]+ \| [^ ]+' <<<"$TMP" | ( while IFS= read -r LINE; do PKG=$(resolveAptVirtualPkg $(awk -F ' | ' '{print $1}' <<<"$LINE")) if [[ -n "$(apt-cache search $PKG 2>/dev/null)" ]]; then echo "$PKG" else PKG=$(resolveAptVirtualPkg $(awk -F ' | ' '{print $2}' <<<"$LINE")) [[ -n "$(apt-cache search $PKG 2>/dev/null)" ]] && echo "$PKG" fi done ) ) for PKG in $DISCRETE_PKGS; do RESULT_PKGS+=( $(resolveAptVirtualPkg "$PKG") ) done for PKG in $CONDITIONAL_PKGS; do RESULT_PKGS+=( "$PKG" ) done echo ${RESULT_PKGS[@]} } function install { local MISSING_PKGS local NPROC=$(nproc) # Install required packages and remove conflicting packages { dpkg -l ufw &>/dev/null && apt-get remove -y ufw || :; } && apt-get install -y git perl logrotate rsyslog firewalld dpkg-dev if (( $? != 0 )); then printerr "Problem with installing the required libraries for RTPEngine" return 1 fi # try installing kernel dev headers in the following order: # 1: headers from security.ubuntu.com # 2: headers from archive.ubuntu.com # NOTE: headers should be installed for all kernels on the system # but we do not want to support ancient kernel dependencies ( RET=0 for OS_KERNEL in $(ls /lib/modules/ 2>/dev/null); do apt-get install -y linux-headers-${OS_KERNEL} || aptInstallKernelHeadersFromURI $(debSearch linux-headers-${OS_KERNEL}) RET=$((RET+$?)) done exit $RET ) # require kernel module if (( $? != 0 )); then printerr "Problems occurred installing one or more kernel headers" return 1 fi ## compile and install RTPEngine as a DEB package ## reuse repo if it exists and matches version we want to install if [[ -d ${SRC_DIR}/rtpengine ]]; then if [[ "$(getGitTagFromShallowRepo ${SRC_DIR}/rtpengine)" != "${RTPENGINE_VER}" ]]; then rm -rf ${SRC_DIR}/rtpengine git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi else git clone --depth 1 -c advice.detachedHead=false -b ${RTPENGINE_VER} https://github.com/sipwise/rtpengine.git ${SRC_DIR}/rtpengine fi # apply our patches ( cd ${SRC_DIR}/rtpengine && patch -p1 -N <${DSIP_PROJECT_DIR}/rtpengine/deb-${RTPENGINE_VER}.patch ) if (( $? > 1 )); then printerr 'Failed patching RTPEngine files prior to build' return 1 fi # build and install using dpkg ( cd ${SRC_DIR}/rtpengine # install all missing dependencies from the control file MISSING_PKGS=$(getDebDependencies) [[ -n "$MISSING_PKGS" ]] && apt-get install -y $MISSING_PKGS dpkg-buildpackage -us -uc -sa --jobs=$NPROC || exit 1 systemctl mask ngcp-rtpengine-daemon.service apt-get install -y ../ngcp-rtpengine-daemon_*${RTPENGINE_VER}*.deb ../ngcp-rtpengine-iptables_*${RTPENGINE_VER}*.deb \ ../ngcp-rtpengine-kernel-dkms_*${RTPENGINE_VER}*.deb ../ngcp-rtpengine-utils_*${RTPENGINE_VER}*.deb || exit 1 systemctl unmask ngcp-rtpengine-daemon.service systemctl disable ngcp-rtpengine-daemon.service exit 0 ) if (( $? != 0 )); then printerr "Problem installing RTPEngine DEB's" return 1 fi # ensure config dirs exist mkdir -p /run/rtpengine ${SYSTEM_RTPENGINE_CONFIG_DIR} chown -R rtpengine:rtpengine /run/rtpengine # allow root to fix permissions before starting services (required to work with SELinux enabled) usermod -a -G rtpengine root # setup rtpengine defaults file cp -f ${DSIP_PROJECT_DIR}/rtpengine/configs/default.conf /etc/default/rtpengine.conf # Enable and start firewalld if not already running systemctl enable firewalld systemctl start firewalld # Setup Firewall rules for RTPEngine firewall-cmd --zone=public --add-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload # Setup RTPEngine Logging cp -f ${DSIP_PROJECT_DIR}/resources/syslog/rtpengine.conf /etc/rsyslog.d/rtpengine.conf touch /var/log/rtpengine.log systemctl restart rsyslog # Setup logrotate cp -f ${DSIP_PROJECT_DIR}/resources/logrotate/rtpengine /etc/logrotate.d/rtpengine # Setup tmp files echo "d /var/run/rtpengine.pid 0755 rtpengine rtpengine - -" > /etc/tmpfiles.d/rtpengine.conf # Reconfigure systemd service files rm -f /lib/systemd/system/rtpengine*.service cp -f ${DSIP_PROJECT_DIR}/rtpengine/systemd/rtpengine-v3.service /lib/systemd/system/rtpengine.service chmod 644 /lib/systemd/system/rtpengine.service systemctl daemon-reload systemctl enable rtpengine # Reload systemd configs systemctl daemon-reload # Enable the RTPEngine to start during boot systemctl enable rtpengine # preliminary check that rtpengine actually installed if cmdExists rtpengine; then return 0 else return 1 fi } # Remove RTPEngine function uninstall { systemctl stop rtpengine systemctl disable rtpengine rm -f /{etc,lib}/systemd/system/rtpengine.service 2>/dev/null systemctl daemon-reload apt-get remove -y ngcp-rtpengine\* rm -f /usr/sbin/rtpengine* /usr/bin/rtpengine /etc/rsyslog.d/rtpengine.conf /etc/logrotate.d/rtpengine # remove our firewall changes firewall-cmd --zone=public --remove-port=${RTP_PORT_MIN}-${RTP_PORT_MAX}/udp --permanent firewall-cmd --reload return 0 } case "$1" in install) install && exit 0 || exit 1 ;; uninstall) uninstall && exit 0 || exit 1 ;; *) printerr "Usage: $0 [install | uninstall]" exit 1 ;; esac ================================================ FILE: testing/0.sh ================================================ #!/usr/bin/env bash . include/common test="Syslog Started" # Is service started systemctl is-active --quiet rsyslog; ret=$? process_result "$test" $ret ================================================ FILE: testing/1.sh ================================================ #!/usr/bin/env bash . include/common test="Mysql Started" # Is service started systemctl is-active --quiet mariadb; ret=$? process_result "$test" $ret ================================================ FILE: testing/10.sh ================================================ #!/usr/bin/env bash . include/common test="AWS AMI Instance Requirements Satisfied" # static settings project_dir=/opt/dsiprouter cookie_file=/tmp/cookie # dynamic settings proto=$(getConfigAttrib 'DSIP_PROTO' ${DSIP_CONFIG_FILE}) port=$(getConfigAttrib 'DSIP_PORT' ${DSIP_CONFIG_FILE}) export DSIP_PASSWORD="temp" # In Accordance With AWS Marketplace Policy: # https://docs.aws.amazon.com/marketplace/latest/userguide/product-and-ami-policies.html validateInstance() { # if server is not AMI Instance then it passes if ! isInstanceAMI; then return 0 fi # check dsiprouter DSIP_PASSWORD is set to instance id [ "$(getInstanceID)" = "$DSIP_PASSWORD" ] || return 1 # check debian-sys-maint user's DSIP_PASSWORD is set to instance-id (file & runtime) if [ -f /etc/debian_version ]; then maintpass=$(grep -oP '^(?!#)DSIP_PASSWORD[ \t]*=[ \t]*\K([\w\d\.\-]+)' /etc/mysql/debian.cnf | head -1) [ "$maintpass" = "$DSIP_PASSWORD" ] || return 1 mysql --user="debian-sys-maint" --DSIP_PASSWORD="$maintpass" -e 'SELECT VERSION();' >/dev/null [ $? -ne 0 ] && return 1 fi # dsiprouter accessible via instance public ip public_ip=$(curl -s http://169.254.169.254/latest/meta-data/public-ipv4 2>/dev/null) curl -s -L -f --connect-timeout 2 "${proto}://${public_ip}:${port}" >/dev/null [ $? -ne 0 ] && return 1 # # TODO: add check for ssh pubkey access using instance key # how to pull key on running instance (install pub key creds section): # https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/building-shared-amis.html # # if we made it this far all checks passed return 0 } validateInstance; ret=$? unset DSIP_PASSWORD process_result "$test" $ret ================================================ FILE: testing/11.sh ================================================ #!/usr/bin/env bash . include/common test="dSIPRouter GUI Login" # static settings project_dir=/opt/dsiprouter cookie_file=/tmp/cookie temp_pass='temp' # dynamic settings proto=$(getConfigAttrib 'DSIP_PROTO' ${DSIP_CONFIG_FILE}) host='127.0.0.1' port=$(getConfigAttrib 'DSIP_PORT' ${DSIP_CONFIG_FILE}) username=$(getConfigAttrib 'DSIP_USERNAME' ${DSIP_CONFIG_FILE}) dsip_id=$(getConfigAttrib 'DSIP_ID' ${DSIP_CONFIG_FILE}) pid_file=$(getConfigAttrib 'DSIP_PID_FILE' ${DSIP_CONFIG_FILE}) load_from=$(getConfigAttrib 'LOAD_SETTINGS_FROM' ${DSIP_CONFIG_FILE}) kam_db_host=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) kam_db_port=$(getConfigAttrib 'KAM_DB_PORT' ${DSIP_CONFIG_FILE}) kam_db_name=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) kam_db_user=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) kam_db_pass=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) # overload password # if dsip is bound to all available addresses use localhost [ "$host" = "0.0.0.0" ] && host="localhost" # attempt to login to dsiprouter base_url="${proto}://${host}:${port}" payload="username=$(uriEncode ${username})&password=$(uriEncode ${temp_pass})&nextpage=" declare -a flat_headers=() declare -A headers=( ['Accept']='text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' ['Accept-Encoding']='gzip, deflate' ['Accept-Language']='en-US,en;q=0.9' ['Cache-Control']='max-age=0' ['Connection']='keep-alive' ['Content-Type']='application/x-www-form-urlencoded' ['DNT']='1' ['Host']="${host}:${port}" ['Origin']="${proto}://${host}:${port}" ['Referer']="${proto}://${host}:${port}/" ['Upgrade-Insecure-Requests']='1' ['User-Agent']='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36' ) for key in ${!headers[@]}; do flat_headers+=( "$key: ${headers[$key]}" ); done setLoginOverride() { # make copy of settings cp -f ${DSIP_CONFIG_FILE} ${DSIP_CONFIG_FILE}.bak # update setting if [[ "$load_from" == "file" ]]; then setConfigAttrib 'DSIP_PASSWORD' "${temp_pass}" ${DSIP_CONFIG_FILE} -q elif [[ "$load_from" == "db" ]]; then mysql --user="${kam_db_user}" --password="${kam_db_pass}" --host="${kam_db_host}" --port="${kam_db_port}" --database="${kam_db_name}" \ -e "update dsip_settings set DSIP_PASSWORD='${temp_pass}' where dsip_id=${dsip_id}" fi # sync settings kill -SIGUSR1 $(cat $pid_file) 2>/dev/null sleep 1 } unsetLoginOverride() { # revert changes mv -f ${DSIP_CONFIG_FILE}.bak ${DSIP_CONFIG_FILE} # sync settings kill -SIGUSR1 $(cat $pid_file) 2>/dev/null sleep 1 } validateDsipAuth() { # attempt to auth and store cookie, we will get a 200 OK on good auth status=$(curl -s -L --connect-timeout 3 -c "$cookie_file" -w "%{http_code}" -d "$payload" "${flat_headers[@]/#/-H}" "$base_url/login" -o /dev/null) [ ${status:-400} -ne 200 ] && return 1 # try navigating to endpoint without cookie, we should get a 302 redirect status=$(curl -X GET -s --connect-timeout 3 -w "%{http_code}" "${flat_headers[@]/#/-H}" "$base_url/carriergroups" -o /dev/null) [ ${status:-400} -ne 302 ] && return 1 # try navigating to endpoint with cookie, we should get a 200 OK unset headers['Content-Type']; flat_headers=() for key in ${!headers[@]}; do flat_headers+=( "$key: ${headers[$key]}" ); done status=$(curl -X GET -s --connect-timeout 3 -b "$cookie_file" -w "%{http_code}" "${flat_headers[@]/#/-H}" "$base_url/carriergroups" -o /dev/null) [ ${status:-400} -ne 200 ] && return 1 # cleanup, remove cookie rm -f $cookie_file # if we made it this far all checks passed return 0 } cleanupHandler() { rm -f $cookie_file unsetLoginOverride } # main trap cleanupHandler EXIT setLoginOverride validateDsipAuth; ret=$? process_result "$test" $ret ================================================ FILE: testing/12.sh.dev ================================================ #!/usr/bin/env bash set -x . include/common unitname="dSIP Custom Routing" # static settings username="smoketest" host="localhost" port="5060" # TODO: add custom routes to db # - locality prefix matching # - custom kamailio route matching # mysql -e 'insert into custom_routing ...' # mysql -e 'insert into dr_rules ...' # TODO: send invite to test routes # we should be using a template INVITE and replacing values with sed #sipsak -f INVITE.sip -s sip:$username@$host:$port -H $host -vvv >/dev/null process_result "$unitname" $ret ================================================ FILE: testing/13.sh.dev ================================================ #!/usr/bin/env bash set -x . include/common unitname="FusionPBX Sync Module" # static settings username="smoketest" host="localhost" port="5060" # TODO: check that cron entry exists # TODO: check that nginx server is running and accepts requests # TODO: test fusionpbx db syncing # - create new local db # - add some date # - add pbx to kamailio db w/ fusionpbx enabled # - check that db tables synced process_result "$unitname" $ret ================================================ FILE: testing/14.sh.dev ================================================ #!/usr/bin/env bash set -x . include/common unitname="Domain Auth" # static settings username="smoketest" host="localhost" port="5060" # TODO: add some domain entries # TODO: send invite with domain auth # we should be using a template INVITE and replacing values with sed #sipsak -f INVITE.sip -s sip:$username@$host:$port -H $host -vvv >/dev/null process_result "$unitname" $ret ================================================ FILE: testing/15.sh.dev ================================================ #!/usr/bin/env bash set -x . include/common unitname="Flowroute DID Sync Module" # static settings username="smoketest" host="localhost" port="5060" # TODO: create some DID's using flowroute's API (not ours) # TODO: test flowroute module's python functions (verify we get data we sent to flowroute) # TODO: check DB and see if those DID's were synced process_result "$unitname" $ret ================================================ FILE: testing/16.sh ================================================ #!/usr/bin/env bash . include/common unitname="JSONRPC Access to Kamailio" # static settings project_dir=/opt/dsiprouter source_ip="127.0.0.1" sip_port="5060" rpc_proto="http" host="localhost" # Send a bunch of of requests to the server curl -s -X GET --connect-timeout 3 -d '{"jsonrpc": "2.0", "method": "core.psx", "id": 1}' "${rpc_proto}://${host}:${sip_port}/api/kamailio" > /dev/null ret=$? process_result "$unitname" $ret ================================================ FILE: testing/17.sh ================================================ #!/usr/bin/env bash . include/common unitname="Domain Pass-Thru using FreePBX" # static settings username="1001" password="1RSk8l6VGKUsl0zzUFYmIwsAIFT9qARM4vGoVB0pf88=" domain="smoketest.com" host="localhost" port="5060" externalip=$(getExternalIP) internalip=$(ip route get 8.8.8.8 | awk 'NR == 1 {print $7}') $(addPBX) $(addDomain) #Reload Kamailio Modules kamcmd domain.reload kamcmd drouting.reload # Register User # Try external ip sipsak -U -C sip:$username@home.com --from sip:$username@$domain -u $username -a $password -p $externalip:$port -s sip:$username@$domain -i -vvv >/dev/null ret=$? # Try internal ip if it fails if [ "$ret" != "0" ]; then sipsak -U -C sip:$username@home.com --from sip:$username@$domain -u $username -a $password -p $internalip:$port -s sip:$username@$domain -i -vvv >/dev/null ret=$? fi #Clean Up deletePBX deleteDomain process_result "$unitname" $ret ================================================ FILE: testing/18.sh ================================================ #!/usr/bin/env bash . include/common unitname="DoS and SIP Security - Doesn't Block Known Carrier IP(s)" # settings source_ip="127.0.0.11" username="smoketest" host="localhost" # Add Carrier IP to address table mysql kamailio -e "insert into address values (null,8,'$source_ip',32,0,'name:Smoke Test Carrier,gwgroup:0');" # Reload the address table kamcmd permissions.addressReload >/dev/null # Send a bunch of of requests to the server sipsak -F -d -k $source_ip -e 500 -s sip:$username@$host >/dev/null sleep 1 # Check the ipban htable to see if the ipaddress is being blocked after sending a # bunch of SIP requests # Using '!' to negate the return code. I want return code of 1 to negate to a 0 if the source_ip is not found in the ipban table ! kamcmd htable.dump ipban | grep -q "$source_ip" ret=$? # Clean up, remove the entry kamcmd htable.delete ipban $source_ip # TODO: Add a test to validate that the Server user agent is no longer sento # Remove IP from Carrier table mysql kamailio -e "DELETE FROM address WHERE tag LIKE '%name:Smoke Test Carrier%';" process_result "$unitname" $ret ================================================ FILE: testing/19.sh ================================================ #!/usr/bin/env bash . include/common test="dSIPRouter API Test" # static settings project_dir=/opt/dsiprouter cookie_file=/tmp/cookie temp_pass='temp' temp_token='temp' # dynamic settings proto=$(getConfigAttrib 'DSIP_PROTO' ${DSIP_CONFIG_FILE}) host='127.0.0.1' port=$(getConfigAttrib 'DSIP_PORT' ${DSIP_CONFIG_FILE}) username=$(getConfigAttrib 'DSIP_USERNAME' ${DSIP_CONFIG_FILE}) dsip_id=$(getConfigAttrib 'DSIP_ID' ${DSIP_CONFIG_FILE}) pid_file=$(getConfigAttrib 'DSIP_PID_FILE' ${DSIP_CONFIG_FILE}) load_from=$(getConfigAttrib 'LOAD_SETTINGS_FROM' ${DSIP_CONFIG_FILE}) kam_db_host=$(getConfigAttrib 'KAM_DB_HOST' ${DSIP_CONFIG_FILE}) kam_db_port=$(getConfigAttrib 'KAM_DB_PORT' ${DSIP_CONFIG_FILE}) kam_db_name=$(getConfigAttrib 'KAM_DB_NAME' ${DSIP_CONFIG_FILE}) kam_db_user=$(getConfigAttrib 'KAM_DB_USER' ${DSIP_CONFIG_FILE}) kam_db_pass=$(decryptConfigAttrib 'KAM_DB_PASS' ${DSIP_CONFIG_FILE}) old_api_token=$(kamcmd cfg.get server api_token) inbound_flag=$(getConfigAttrib 'FLT_INBOUND' ${DSIP_CONFIG_FILE}) # if dsip is bound to all available addresses use localhost [ "$host" = "0.0.0.0" ] && host="localhost" # attempt to login to dsiprouter base_url="${proto}://${host}:${port}" payload="username=$(uriEncode ${username})&password=$(uriEncode ${temp_pass})&nextpage=" declare -a flat_headers=() declare -A headers=( ['Accept']='text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' ['Accept-Encoding']='gzip, deflate' ['Accept-Language']='en-US,en;q=0.9' ['Cache-Control']='max-age=0' ['Connection']='keep-alive' ['Content-Type']='application/x-www-form-urlencoded' ['DNT']='1' ['Host']="${host}:${port}" ['Origin']="${proto}://${host}:${port}" ['Referer']="${proto}://${host}:${port}/" ['Upgrade-Insecure-Requests']='1' ['User-Agent']='Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.130 Safari/537.36' ) for key in ${!headers[@]}; do flat_headers+=( "$key: ${headers[$key]}" ); done setLoginOverride() { # make copy of settings cp -f ${DSIP_CONFIG_FILE} ${DSIP_CONFIG_FILE}.bak # update setting if [[ "$load_from" == "file" ]]; then setConfigAttrib 'DSIP_PASSWORD' "${temp_pass}" ${DSIP_CONFIG_FILE} -q setConfigAttrib 'DSIP_API_TOKEN' "${temp_pass}" ${DSIP_CONFIG_FILE} -q elif [[ "$load_from" == "db" ]]; then mysql --user="${kam_db_user}" --password="${kam_db_pass}" --host="${kam_db_host}" --port="${kam_db_port}" --database="${kam_db_name}" \ -e "update dsip_settings set DSIP_PASSWORD='${temp_pass}', DSIP_API_TOKEN='${temp_token}' where dsip_id=${dsip_id}" fi # sync settings kill -SIGUSR1 $(cat $pid_file) 2>/dev/null sleep 1 } unsetLoginOverride() { # revert changes mv -f ${DSIP_CONFIG_FILE}.bak ${DSIP_CONFIG_FILE} # sync settings kill -SIGUSR1 $(cat $pid_file) 2>/dev/null sleep 1 } # TODO: update these tests for new endpoint args, etc.. validate() { # update kams api token for testing kamcmd cfg.sets server api_token $temp_token # attempt to auth and store cookie, we will get a 200 OK on good auth status=$(curl -s -L --connect-timeout 3 -c "$cookie_file" -w "%{http_code}" -d "$payload" "${flat_headers[@]/#/-H}" "$base_url/login" -o /dev/null) [ ${status:-400} -ne 200 ] && return 1 # try navigating to endpoint with cookie, we should get a 200 OK status=$(curl -X GET -s --connect-timeout 3 -b "$cookie_file" -w "%{http_code}" "${flat_headers[@]/#/-H}" "$base_url/api/v1/kamailio/stats" -o /dev/null) [ ${status:-400} -ne 200 ] && return 1 # try again with API token status=$(curl -X GET -s --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/kamailio/stats" -o /dev/null) [ ${status:-400} -ne 200 ] && return 1 # create entries for testing /api/v1/mapping endpoint prefix0='123456789' prefix1='987654321' prefix2='01234' prefix3='56789' mysql --user="${kam_db_user}" --password="${kam_db_pass}" --host="${kam_db_host}" --port="${kam_db_port}" --database="${kam_db_name}" \ -e "insert into dr_rules values (null,'$inbound_flag','$prefix0','',0,'','66,67','name:Test DID Mapping 1');" \ -e "insert into dr_rules values (null,'$inbound_flag','$prefix1','',0,'','66','name:Test DID Mapping 2');" ruleid0=$(mysql --user="${kam_db_user}" --password="${kam_db_pass}" --host="${kam_db_host}" --port="${kam_db_port}" --database="${kam_db_name}" \ -sA -e "select ruleid from dr_rules where groupid='$inbound_flag' limit 1;") # ========================== # GET /api/v1/inboundmapping # ========================== # valid requests [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?ruleid=${ruleid0}" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?did='"${prefix1}"'" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?ruleid=1000000" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?did=1000000" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?ruleid=abcdef" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?did=abcdef" -o /dev/null) -ne 200 ] && return 1 # invalid requests [ $(curl -s -X GET --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?doesntexist=123" -o /dev/null) -eq 200 ] && return 1 # =========================== # POST /api/v1/inboundmapping # =========================== # valid requests [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "'"${prefix2}"'", "servers": ["66","67"], "name": "'"${prefix2}"' DID Mapping"}' -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "'"${prefix3}"'","servers": ["66","67"]}' -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "", "servers": ["66"], "name": "Default DID Mapping"}' -o /dev/null) -ne 200 ] && return 1 # invalid requests [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"servers": ["66","67"], "name": "'"${prefix2}"' DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "0", "servers": ["66","67","68","69","70","71","71"], "name": "0 DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "00", "servers": ["",""], "name": "00 DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "000", "servers": ["abc","efg"], "name": "000 DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "0000", "servers": [], "name": "0000 DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X POST --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"did": "00000", "name": "00000 DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 # ========================== # PUT /api/v1/inboundmapping # ========================== # valid requests [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?ruleid=${ruleid0}" \ -d '{"did": "01234", "name": "01234 DID Mapping"}' -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?did='"${prefix1}"'" \ -d '{"servers": ["67"]}' -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?did=10000000" \ -d '{"did": "01234", "name": "01234 DID Mapping"}' -o /dev/null) -ne 200 ] && return 1 # invalid requests [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?doesntexist=123" \ -d '{"name": "New DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping" \ -d '{"name": "Newer DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?did='"${prefix1}"'" \ -d '{}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?did=01234" \ -d '{"doesntexist": "2"}' -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X PUT --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" --connect-timeout 3 -H "Content-Type: application/json" "$base_url/api/v1/inboundmapping?did=01234" \ -d '{"doesntexist": "2", "name": "New DID Mapping"}' -o /dev/null) -eq 200 ] && return 1 # ============================= # DELETE /api/v1/inboundmapping # ============================= # valid requests [ $(curl -s -X DELETE --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?ruleid=${ruleid0}" -o /dev/null) -ne 200 ] && return 1 [ $(curl -s -X DELETE --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?did='"${prefix1}"'" -o /dev/null) -ne 200 ] && return 1 # invalid requests [ $(curl -s -X DELETE --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping?doesntexist=123" -o /dev/null) -eq 200 ] && return 1 [ $(curl -s -X DELETE --connect-timeout 3 -H "Authorization: Bearer ${temp_token}" -w "%{http_code}" "$base_url/api/v1/inboundmapping" -o /dev/null) -eq 200 ] && return 1 # if we made it this far all checks passed return 0 } # cleanup, remove cookie, remove DB entries cleanupHandler() { rm -f $cookie_file mysql --user="${kam_db_user}" --password="${kam_db_pass}" --host="${kam_db_host}" --port="${kam_db_port}" --database="${kam_db_name}" \ -e "delete from dr_rules where groupid='$inbound_flag' and (prefix='$prefix0' or prefix='$prefix1' or prefix='$prefix2' or prefix='$prefix3');" kamcmd cfg.sets server api_token $old_api_token unsetLoginOverride } # main trap cleanupHandler EXIT setLoginOverride validate; ret=$? process_result "$test" $ret ================================================ FILE: testing/2.sh ================================================ #!/usr/bin/env bash . include/common test="dsip-init Service Started" # Is service started systemctl is-active --quiet dsip-init; ret=$? process_result "$test" $ret ================================================ FILE: testing/20.sh ================================================ #!/usr/bin/env bash . include/common test="DNSmasq Started" # Is service started systemctl is-active --quiet dnsmasq; ret=$? process_result "$test" $ret ================================================ FILE: testing/21.sh ================================================ #!/usr/bin/env bash . include/common test="DNS Resolver Test" validateResolver() { # can localhost be resolved nslookup localhost 2>&1 >/dev/null || return 1 # can DMQ domain local.cluster be resolved nslookup local.cluster 2>&1 >/dev/null || return 1 # can an external host be resolved nslookup www.google.com 2>&1 >/dev/null || return 1 return 0 } validateResolver; ret=$? process_result "$test" $ret ================================================ FILE: testing/3.sh ================================================ #!/usr/bin/env bash . include/common test="Kamailio Started" # Is service started systemctl is-active --quiet kamailio; ret=$? process_result "$test" $ret ================================================ FILE: testing/4.sh ================================================ #!/usr/bin/env bash . include/common test="dSIPRouter Started" # Is service started systemctl is-active --quiet dsiprouter; ret=$? process_result "$test" $ret ================================================ FILE: testing/5.sh ================================================ #!/usr/bin/env bash . include/common test="RTPEngine Started" # Is service started systemctl is-active --quiet rtpengine; ret=$? process_result "$test" $ret ================================================ FILE: testing/6.sh ================================================ #!/usr/bin/env bash . include/common unitname="PBX and Endpoint Registration" # settings username="smoketest" password="90dsip2432x" domain="sip.dsiprouter.org" host="localhost" port="5060" # Create a new user mysql kamailio -e "insert into subscriber values (null,'$username','$host','$password','','','','');" # Register User sipsak -U -C sip:$username@$domain -s sip:$username@$host:$port -u $username -a $password -H $host -i -vvv >/dev/null ret=$? # Clean up # Delete user mysql kamailio -e "delete from subscriber where username='$username' and password='$password';" process_result "$unitname" $ret ================================================ FILE: testing/7.sh ================================================ #!/usr/bin/env bash . include/common unitname="SIP INVITE via Carrier using Username/Password Auth" # settings username="smoketest" password="90dsip2432x" domain="sip.dsiprouter.org" host="localhost" port="5060" # Create a new user mysql kamailio -e "insert into subscriber values (null,'$username','$host','$password','','','','');" # Add Carrier Group mysql kamailio -e "insert into dr_gw_lists values (null,'','name:Smoketest CarrierGroup');" gwgroupid=`mysql kamailio -s -N -e "select id from dr_gw_lists where description regexp 'name:Smoketest CarrierGroup(,|"'$'")';"` # Add Carrier mysql kamailio -e "insert into dr_gateways values (null,8,'demo.dsiprouter.org',0,'','','name:Smoketest Carrier,gwgroup:$gwgroupid');" gwid=`mysql kamailio -s -N -e "select gwid from dr_gateways where description regexp 'name:Smoketest Carrier(,|"'$'")';"` # Update the Carrier Group with the Carrier id mysql kamailio -e "update dr_gw_lists set gwlist=$gwid where id=$gwgroupid;" # Add Carrier Username/Password Auth info externalip=$(getExternalIP) mysql kamailio -e "insert into uacreg values (null,$gwgroupid,'$username','$externalip','$username','$domain','$domain','$username','$password','','','60','1','0','');" # Test auth credentials of the user created sipsak -U -C sip:$username@$domain -s sip:$username@$host:$port -u $username -a $password -H $host -i -vvv >/dev/null ret=$? # TODO: we need to send INVITE, but sipsak won't allow us to change contact header on invite: #sipsak -I -U -C sip:$username@$domain -s sip:$username@$host:$port -u $username -a $password -H $host -i -vvv >/dev/null #sipsak -I -U -s sip:$username@$host:$port -u $username -a $password -H $host -i -vvv >/dev/null # Clean up # Delete all database entries mysql kamailio -e "delete from subscriber where username='$username' and password='$password';" mysql kamailio -e "delete from dr_gw_lists where id=$gwgroupid;" mysql kamailio -e "delete from dr_gateways where gwid=$gwid;" mysql kamailio -e "delete from uacreg where l_uuid=$gwgroupid;" process_result "$unitname" $ret ================================================ FILE: testing/8.sh ================================================ #!/usr/bin/env bash . include/common unitname="DoS and SIP Security - Block Unknown IP" # settings source_ip="127.0.0.10" username="smoketest" host="localhost" # Send a bunch of of requests to the server sipsak -F -d -k $source_ip -e 500 -s sip:$username@$host >/dev/null sleep 1 # Check the ipban htable to see if the ipaddress is being blocked after sending a # bunch of SIP requests kamcmd htable.dump ipban | grep -q "$source_ip" ret=$? # Clean up, remove the entry kamcmd htable.delete ipban $source_ip # TODO: Add a test to validate that the Server user agent is no longer sent process_result "$unitname" $ret ================================================ FILE: testing/9.sh.dev ================================================ #!/usr/bin/env bash set -x . include/common unitname="Call Detail Records" # settings username="smoketest" host="localhost" port="5060" # TODO: send invite to generate CDR's # we should be using a template INVITE and replacing values with sed #sipsak -f INVITE.sip -s sip:$username@$host:$port -H $host -vvv >/dev/null # check acc table mysql kamailio -sN -e "select * from acc;" # check cdrs table mysql kamailio -sN -e "select * from cdrs;" process_result "$unitname" $ret ================================================ FILE: testing/INVITE.sip ================================================ INVITE sip:5554443333@localhost;transport=UDP SIP/2.0 Via: SIP/2.0/UDP 10.0.0.140:43017;branch=z9hG4bK-d8754z-71c87a1a39321e30-1---d8754z- Max-Forwards: 70 Contact: <sip:smoketest@10.0.0.140:43017;transport=UDP> To: <sip:5554443333@localhost;transport=UDP> From: <sip:smoketest@localhost;transport=UDP>;tag=cbe80728 Call-ID: YWI1Mjk1OTBmZWEzY2EyYTg4OGFlYjQ5NzhhMWE0Y2I. CSeq: 1 INVITE Allow: INVITE, ACK, CANCEL, BYE, NOTIFY, REFER, MESSAGE, OPTIONS, INFO, SUBSCRIBE Content-Type: application/sdp Supported: replaces, norefersub, extended-refer, timer, X-cisco-serviceuri User-Agent: Z 3.3.21937 r21903 Allow-Events: presence, kpml Content-Length: 282 v=0 o=Z 0 0 IN IP4 10.0.0.140 s=Z c=IN IP4 10.0.0.140 t=0 0 m=audio 8000 RTP/AVP 18 3 110 8 0 98 101 a=rtpmap:18 G729/8000 a=fmtp:18 annexb=no a=rtpmap:110 speex/8000 a=rtpmap:98 iLBC/8000 a=fmtp:98 mode=20 a=rtpmap:101 telephone-event/8000 ================================================ FILE: testing/Makefile ================================================ # Makefile for running test unit # # Our own sorting function sort-files = $(shell echo -e $1 | tr ' ' '\n' | sort -V) TESTS_FILES ?= $(wildcard *.sh) TESTS_EXCLUDE ?= TESTS ?= $(call sort-files,$(filter-out $(patsubst %,%.sh,$(TESTS_EXCLUDE)), $(TESTS_FILES))) DSIP_CONFIG_FILE ?= $(shell grep -oP '^DSIP_CONFIG_FILE[ \t]*=[ \t]*"\K(.*)(?=")' include/common 2>/dev/null) DEBUG ?= $(shell grep -oP '^DEBUG\s?\=\s?\K(.*)' $(DSIP_CONFIG_FILE) 2>/dev/null) _setdebug: @if [ "$(DEBUG)" = "False" ]; then \ sed -i -r 's|^DEBUG\s?\=\s?.*|DEBUG = True|g' $(DSIP_CONFIG_FILE) ; \ systemctl restart dsiprouter ; \ fi ; _resetdebug: @if [ "$(DEBUG)" = "False" ]; then \ sed -i -r 's|^DEBUG\s?\=\s?.*|DEBUG = False|g' $(DSIP_CONFIG_FILE) ; \ systemctl restart dsiprouter ; \ fi ; _all: @for FILE in $(TESTS) ; do \ if [ -f $$FILE ] ; then \ if [ -x $$FILE ] ; then \ echo "Run test `basename $$FILE .sh`:" `head -n 2 "$$FILE" | tail -n 1 | cut -c 3-` ; \ ./$$FILE ; \ ret=$$? ; \ if [ ! "$$ret" -eq 0 ] ; then \ echo "Test unit file $$FILE: failed" ; \ else \ echo "Test unit file $$FILE: ok" ; \ fi ; \ fi ; \ fi ; \ done ; \ exit $$RES; _run: -@if [ -f $(UNIT) ] ; then \ if [ -x $(UNIT) ] ; then \ echo "Run test `basename $(UNIT) .sh`:" `head -n 2 "$(UNIT)" | tail -n 1 | cut -c 3-` ; \ ./$(UNIT) ; \ ret=$$? ; \ if [ ! "$$ret" -eq 0 ] ; then \ echo "Test unit file $(UNIT): failed" ; \ else \ echo "Test unit file $(UNIT): ok" ; \ fi ; \ fi ; \ else \ echo "Test unit file $(UNIT): not found" ; \ fi ; # run all tests all: _setdebug _all _resetdebug # run one test specified in variable UNIT # # example: make UNIT=1.sh run run: _setdebug _run _resetdebug ================================================ FILE: testing/README.md ================================================ # Testing dSIPRouter This directory contains a number of scripts that contains Unit Test for testing dSIPRouter functionality. ## Execute all Unit Tests ``` make all ``` ## Execute a Single Unit Tests ``` make run UNIT=1.sh ``` ## List of Unit Tests Contains the Unittest number and a description of what the tests validates | Unittest Number | Test Description | |:-------------------------:|:----------------------------------------:| |0|syslog is started| |1|mysql is started| |2|dsip-init is started| |3|kamailio is started| |4|dsiprouter is started| |5|rtpengine is started| |6|PBX and Endpoint Registration| |7|SIP INVITE with Username/Password Auth| |8|DoS and SIP Security - Block Unknown IP| |9|CDR data is stored on SIP INVITE **(dev)**| |10|AWS instance deployment requirements satisfied| |11|dSIPRouter GUI login| |12|dSIP Custom Routing| |13|FusionPBX Sync Module| |14|Domain Auth| |15|Flowroute DID Sync Module| |16|Sending JSONRPC Commands to Kamailio| |17|Domain Pass-Thru using FreePBX| |18|DoS and SIP Security - Doesn't Block Known Carrier IP(s)| |19|dSIPRouter API| |20|DNSmasq is started| |21|DNS Resolver Test| **More to come ================================================ FILE: testing/api/dsiprouter.postman_collection.json ================================================ { "info": { "_postman_id": "5ac300ac-1cc4-4569-b3fa-514cb76b9e38", "name": "dsiprouter", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", "_exporter_id": "1379127" }, "item": [ { "name": "endpointgroups", "item": [ { "name": "/api/v1/endpointgroups", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups" ] } }, "response": [] }, { "name": "/api/v1/endpointgroups/<int id>", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups/9", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups", "9" ] }, "description": "Get a single endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups (FusionPBX PassThru)", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"name\": \"FusionPBX PassThru\",\n \"call_settings\": {\n \"limit\": 5,\n \"timeout\": 3600\n },\n \n \"fusionpbx\": {\n \"enabled\": true,\n \"dbhost\": \"fusionpbx.dsiprouter.net\",\n \"dbuser\": \"fusionpbx\",\n \"dbpass\": \"\"\n }\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups" ] }, "description": "Create an endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups (SIP Trunk - In/Out - User/Password)", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": " {\n \"name\": \"SIP Trunk In/Out - User/Password\",\n \"call_settings\": {\n \"limit\": 5,\n \"timeout\": 3600\n },\n \"auth\": {\n \"type\": \"userpwd\",\n \"user\": \"18889072085\",\n \"pass\": \"example\",\n \"domain\": \"pbx.example.com\"\n },\n \"strip\": 0,\n \"prefix\": \"\",\n \"notifications\": {\n \"overmaxcalllimit\": \"email@example.com\",\n \"endpointfailure\": \"email@example.com\"\n }\n \n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups" ] }, "description": "Create an endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups (SIP Trunk - In/Out - User/Password) Copy", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": " {\n \"name\": \"SIP Trunk In/Out - User/Password\",\n \"call_settings\": {\n \"limit\": 5,\n \"timeout\": 3600\n },\n \"auth\": {\n \"type\": \"userpwd\",\n \"user\": \"18889072085\",\n \"pass\": \"example\",\n \"domain\": \"pbx.example.com\"\n },\n \"strip\": 0,\n \"prefix\": \"\",\n \"notifications\": {\n \"overmaxcalllimit\": \"email@example.com\",\n \"endpointfailure\": \"email@example.com\"\n }\n \n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups" ] }, "description": "Create an endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups (SIP Trunk - IP Auth)", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"name\": \"SIP Trunk IP Auth\",\n \"call_settings\": {\n \"limit\": 5,\n \"timeout\": 3600\n },\n \"auth\": {\n \"type\": \"ip\"\n },\n \"endpoints\": [\n {\n \"host\": \"50.192.97.226\",\n \"port\": 5060,\n \"signalling\": \"proxy\",\n \"media\": \"proxy\",\n \"description\": \"SIP Trunk Endpoint\",\n \"rweight\": 1\n }\n ],\n \"strip\": 0,\n \"prefix\": \"\",\n \"notifications\": {\n \"overmaxcalllimit\": \"email@example.com\",\n \"endpointfailure\": \"email@example.com\"\n }\n \n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups" ] }, "description": "Create an endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups/<int id>", "request": { "method": "DELETE", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups/53", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups", "53" ] }, "description": "Delete endpointgroup" }, "response": [] }, { "name": "/api/v1/endpointgroups/<int id> (SIP Trunk - IP Auth Update)", "request": { "method": "PUT", "header": [], "body": { "mode": "raw", "raw": "{\n \"name\": \"SIP Trunk IP Auth Update\",\n \"call_settings\": {\n \"limit\": 5,\n \"timeout\": 3600\n },\n \"auth\": {\n \"type\": \"ip\"\n },\n \"endpoints\": [\n {\n \"host\": \"50.192.97.227\",\n \"port\": 5060,\n \"signalling\": \"proxy\",\n \"media\": \"proxy\",\n \"description\": \"SIP Trunk Endpoint\",\n \"rweight\": 1\n }\n ],\n \"strip\": 0,\n \"prefix\": \"\",\n \"notifications\": {\n \"overmaxcalllimit\": \"email@example.com\",\n \"endpointfailure\": \"email@example.com\"\n }\n \n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/endpointgroups/34", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "endpointgroups", "34" ] }, "description": "Update an endpointgroup" }, "response": [] } ] }, { "name": "kamailio", "item": [ { "name": "/api/v1/reload/kamailio", "request": { "method": "POST", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/reload/kamailio", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "reload", "kamailio" ] }, "description": "Trigger a reload of Kamailio. This is needed after changes are made" }, "response": [] }, { "name": "/api/v1/kamailio/stats/", "request": { "method": "GET", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/kamailio/stats", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "kamailio", "stats" ] }, "description": "Obtain call statistics " }, "response": [] } ] }, { "name": "inboundmapping", "item": [ { "name": "/api/v1/inboundmapping", "request": { "method": "GET", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/inboundmapping", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "inboundmapping" ] }, "description": "Get a list of inboundmappings" }, "response": [] }, { "name": "/api/v1/inboundmapping", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{ \n \"did\": \"13132222223\",\n \"servers\": [\"#22\"],\n \"name\": \"Taste Pizzabar\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/inboundmapping", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "inboundmapping" ] }, "description": "Create new inboundmapping" }, "response": [] }, { "name": "/api/v1/inboundmapping?did=<string>", "request": { "method": "PUT", "header": [], "body": { "mode": "raw", "raw": "{ \n \"servers\": [\"#10\"],\n \"name\": \"Flyball\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/inboundmapping?did=13132222223", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "inboundmapping" ], "query": [ { "key": "did", "value": "13132222223" } ] }, "description": "Create new inboundmapping" }, "response": [] }, { "name": "/api/v1/inboundmapping?did=<string>", "request": { "method": "DELETE", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/inboundmapping?did=13132222223", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "inboundmapping" ], "query": [ { "key": "did", "value": "13132222223" } ] }, "description": "Create new inboundmapping" }, "response": [] } ] }, { "name": "leases", "item": [ { "name": "/api/v1/lease/endpoint/ (User/Pass)", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/lease/endpoint?email=mack@goflyball.com&ttl=5m", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "lease", "endpoint" ], "query": [ { "key": "email", "value": "mack@goflyball.com" }, { "key": "ttl", "value": "5m" } ] }, "description": "Get a single endpointgroup" }, "response": [] }, { "name": "/api/v1/lease/endpoint/ (IP Type)", "protocolProfileBehavior": { "disableBodyPruning": true }, "request": { "method": "GET", "header": [ { "key": "X-Slack-Signature", "value": "test", "disabled": true } ], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/lease/endpoint?email=mack@goflyball.com&ttl=1m&type=ip&auth_ip=172.145.24.2", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "lease", "endpoint" ], "query": [ { "key": "email", "value": "mack@goflyball.com" }, { "key": "ttl", "value": "1m" }, { "key": "type", "value": "ip" }, { "key": "auth_ip", "value": "172.145.24.2" } ] }, "description": "Get a single endpointgroup" }, "response": [] }, { "name": "/api/v1/lease/endpoint/<leaseid>/revoke", "request": { "method": "DELETE", "header": [], "body": { "mode": "raw", "raw": "", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/lease/endpoint/34/revoke", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "lease", "endpoint", "34", "revoke" ] }, "description": "Get a single endpointgroup" }, "response": [] } ] }, { "name": "carriergroups", "item": [ { "name": "/api/v1/carriergroups", "request": { "method": "GET", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/carriergroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "carriergroups" ] } }, "response": [] }, { "name": "/api/v1/carriergroups", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"name\": \"Test1\",\n \"strip\": \"\",\n \"prefix\": \"\",\n \"auth\": {\n \"type\": \"ip\",\n \"r_username\": \"\",\n \"auth_username\": \"\",\n \"auth_password\": \"\",\n \"auth_domain\": \"\",\n \"auth_proxy\": \"\"\n },\n \"plugin\" : {\n \"name\":\"\",\n \"plugin_prefix\":\"\",\n \"account_sid\": \"\",\n \"account_token\":\"\"\n },\n \"endpoints\":[\n {\n \"name\": \"proxy.detroitpbx.com\",\n \"hostname\": \"proxy.detroitpbx.com\",\n \"strip\": \"\",\n \"prefix\": \"\"\n }\n ]\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/carriergroups", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "carriergroups" ] } }, "response": [] } ] }, { "name": "auth", "item": [ { "name": "Auth / User - Add", "request": { "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"username\": \"yahoo2\",\n \"password\": \"123456\",\n \"firstname\": \"First\",\n \"lastname\": \"DeLast\",\n \"roles\": [],\n \"domains\": []\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/auth/user", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "auth", "user" ] } }, "response": [] }, { "name": "Auth / User - Update", "request": { "method": "PUT", "header": [ { "key": "Authorization", "value": "Bearer ", "type": "text", "disabled": true } ], "body": { "mode": "raw", "raw": "{\n \"username\": \"de_uzer\",\n \"password\": \"1234567\",\n \"firstname\": \"First\",\n \"lastname\": \"DeLast\",\n \"roles\": [],\n \"domains\": []\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/auth/user/2", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "auth", "user", "2" ] } }, "response": [] }, { "name": "Auth / User - Delete", "request": { "method": "DELETE", "header": [ { "key": "Authorization", "value": "Bearer ", "type": "text", "disabled": true } ], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/auth/user/2", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "auth", "user", "2" ] } }, "response": [] }, { "name": "Auth / User - List", "request": { "method": "GET", "header": [ { "key": "Authorization", "value": "", "type": "text", "disabled": true } ], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/auth/user", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "auth", "user" ] } }, "response": [] }, { "name": "Auth / Login", "request": { "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "", "type": "string" } ] }, "method": "POST", "header": [], "body": { "mode": "raw", "raw": "{\n \"username\": \"yahoo2\",\n \"password\": \"123456\"\n}", "options": { "raw": { "language": "json" } } }, "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/auth/login", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "auth", "login" ] } }, "response": [] } ] }, { "name": "cdr", "item": [ { "name": "api/v1/cdrs/endpointgroups/<<endpointgroup>>", "request": { "method": "GET", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/cdrs/endpointgroups/17?type=csv&dtfilter=2022-09-14&email=True", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "cdrs", "endpointgroups", "17" ], "query": [ { "key": "type", "value": "csv", "description": "csv or json - The default is json" }, { "key": "dtfilter", "value": "2022-09-14", "description": "Date/Time Filter - mysql format" }, { "key": "email", "value": "True", "description": "Will send the CDR to email addresses in the Endpoint Group" } ] } }, "response": [] }, { "name": "api/v1/cdrs/endpoint/<<endpoint>>", "request": { "method": "GET", "header": [], "url": { "raw": "https://{{DSIP_ADDR}}:5000/api/v1/cdrs/endpoint/54", "protocol": "https", "host": [ "{{DSIP_ADDR}}" ], "port": "5000", "path": [ "api", "v1", "cdrs", "endpoint", "54" ] } }, "response": [] } ] } ], "auth": { "type": "bearer", "bearer": [ { "key": "token", "value": "{{DSIP_TOKEN}}", "type": "string" } ] }, "event": [ { "listen": "prerequest", "script": { "type": "text/javascript", "exec": [ "" ] } }, { "listen": "test", "script": { "type": "text/javascript", "exec": [ "" ] } } ], "variable": [ { "key": "DSIP_ADDR", "value": "demo.dsiprouter.net" }, { "key": "DSIP_TOKEN", "value": "91RpJidL1f2eEDSyxUDnn83B7jipuDyl", "type": "string" } ] } ================================================ FILE: testing/include/common ================================================ #!/usr/bin/env bash DSIP_PROJECT_DIR=${DSIP_PROJECT_DIR:-$(dirname $(dirname $(dirname $(readlink -f "$BASH_SOURCE"))))} DSIP_SYSTEM_CONFIG_DIR="/etc/dsiprouter" DSIP_CONFIG_FILE="${DSIP_SYSTEM_CONFIG_DIR}/gui/settings.py" # import funcs from dsip_lib.sh if (( ${DSIP_LIB_IMPORTED:-0} == 0 )); then . ${DSIP_PROJECT_DIR}/dsiprouter/dsip_lib.sh fi # Configured by install script PATH_UPDATE_FILE="/etc/profile.d/dsip_paths.sh" # Import path updates so we don't have to reload shell to test . ${PATH_UPDATE_FILE} process_result() { [ $((${#1}%2)) -eq 0 ] && desc="$1" || desc="$1 " if [ $2 -eq 0 ]; then printf "%-*s %s %*s\n" $(( (100-${#desc}) / 2 )) "($(pprint $(basename ""$0"" | cut -d '.' -f 1)))" "$(printbold ""$desc"")" $(( (100-${#desc}) / 2 )) "[$(printdbg 'PASS')]" return 0 else printf "%-*s %s %*s\n" $(( (100-${#desc}) / 2 )) "($(pprint $(basename ""$0"" | cut -d '.' -f 1)))" "$(printbold ""$desc"")" $(( (100-${#desc}) / 2 )) "[$(printerr 'FAIL')]" return 1 fi } # $* == string to encode (prevent splitting by slurping all the args) # notes: print URI encoded string uriEncode() { perl -MURI::Escape -e 'print uri_escape shift, , q{^A-Za-z0-9\-._~/}' -- "$*" } # Add PBX # return the PBX ID addPBX() { mysql kamailio -e "insert into dr_gateways values (null,9,'18.191.20.204',0,'','','name:Smoketest PBX');" pbxid=`mysql kamailio -s -N -e "select gwid from dr_gateways where description regexp 'name:Smoketest PBX(,|"'$'")';"` return $pbxid } deletePBX() { mysql kamailio -e "delete from dr_gateways where description regexp 'name:Smoketest PBX(,|"'$'")';" } addDomain() { mysql kamailio -e "insert into domain values (null,'smoketest.com','smoketest.com',now());" mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','pbx_list',2,'64',now())"; mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','pbx_type',2,'0',now())" mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','created_by',2,'0',now());" mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','domain_auth',2,'passthru',now());" mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','description',2,'notes:Smoketest Domain',now());" mysql kamailio -e "insert into domain_attrs values (null,'smoketest.com','pbx_ip',2,'18.191.20.204',now());" } deleteDomain() { mysql kamailio -e "delete from domain where did='smoketest.com';" mysql kamailio -e "delete from domain_attrs where did='smoketest.com';" } ================================================ FILE: testing/payload.json ================================================ { "name": "Endpoint1", "calllimit": "0", "auth": { "type":"ip", "user":"mack10", "pass":"1234", "domain":"sip.dsiprouter.org" }, "endpoints": [ {"hostname":"142.131.313.1","description":"Endpoint1","maintmode":0}, {"hostname":"143.131.344.2","description":"Endpoint2","maintmode":1} ], "strip": 0, "prefix": "", "notifications": { "overmaxcalllimit": "mack.hendricks@gmail.com", "endpointfailure": "mack.hendricks@gmail.com" }, "fusionpbx": { "enabled": 1, "dbhost": "13.13.24.22", "dbuser": "fusiopbx", "dbpass": "1234" } } ================================================ FILE: testing/sql/v0.522/kamailio.sql ================================================ -- MySQL dump 10.13 Distrib 5.7.23, for osx10.14 (x86_64) -- -- Host: 127.0.0.1 Database: kamailio -- ------------------------------------------------------ -- Server version 5.7.27 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `acc` -- DROP TABLE IF EXISTS `acc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(128) NOT NULL DEFAULT '', `sip_code` char(3) NOT NULL DEFAULT '', `sip_reason` varchar(32) NOT NULL DEFAULT '', `time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `src_ip` varchar(64) NOT NULL DEFAULT '', `dst_ouser` varchar(64) NOT NULL DEFAULT '', `dst_user` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `src_user` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `cdr_id` int(11) NOT NULL DEFAULT '0', `calltype` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `acc_callid` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc` -- LOCK TABLES `acc` WRITE; /*!40000 ALTER TABLE `acc` DISABLE KEYS */; /*!40000 ALTER TABLE `acc` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `acc_cdrs` -- DROP TABLE IF EXISTS `acc_cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc_cdrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `end_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` float(10,3) NOT NULL DEFAULT '0.000', PRIMARY KEY (`id`), KEY `start_time_idx` (`start_time`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc_cdrs` -- LOCK TABLES `acc_cdrs` WRITE; /*!40000 ALTER TABLE `acc_cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `acc_cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `active_watchers` -- DROP TABLE IF EXISTS `active_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `active_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) DEFAULT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, `updated_winfo` int(11) NOT NULL, `flags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `active_watchers_idx` (`callid`,`to_tag`,`from_tag`), KEY `active_watchers_expires` (`expires`), KEY `active_watchers_pres` (`presentity_uri`,`event`), KEY `updated_idx` (`updated`), KEY `updated_winfo_idx` (`updated_winfo`,`presentity_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `active_watchers` -- LOCK TABLES `active_watchers` WRITE; /*!40000 ALTER TABLE `active_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `active_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `address` -- DROP TABLE IF EXISTS `address`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `address` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `grp` int(11) unsigned NOT NULL DEFAULT '1', `ip_addr` varchar(50) NOT NULL, `mask` int(11) NOT NULL DEFAULT '32', `port` smallint(5) unsigned NOT NULL DEFAULT '0', `tag` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `address` -- LOCK TABLES `address` WRITE; /*!40000 ALTER TABLE `address` DISABLE KEYS */; INSERT INTO `address` VALUES (1,8,'52.41.52.34',32,0,'name:Skyetel North West Inbound,gwgroup:1'),(2,8,'52.8.201.128',32,0,'name:Skyetel South West Inbound,gwgroup:1'),(3,8,'52.60.138.31',32,0,'name:Skyetel North East Inbound,gwgroup:1'),(4,8,'50.17.48.216',32,0,'name:Skyetel South East Inbound,gwgroup:1'),(5,8,'35.156.192.164',32,0,'name:Skyetel Europe Inbound,gwgroup:1'),(6,8,'term.skyetel.com',32,0,'name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(7,8,'52.41.52.34',32,0,'name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(8,8,'52.8.201.128',32,0,'name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(9,8,'50.17.48.216',32,0,'name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(10,8,'52.32.223.28',32,0,'name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(11,8,'52.4.178.107',32,0,'name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(12,8,'147.75.60.160',32,0,'name:Flowroute US-West-WA,gwgroup:2'),(13,8,'34.210.91.112',32,0,'name:Flowroute US-West-OR,gwgroup:2'),(14,8,'147.75.65.192',32,0,'name:Flowroute US-East-NJ,gwgroup:2'),(15,8,'34.226.36.32',32,0,'name:Flowroute US-East-VA,gwgroup:2'),(16,8,'81.201.82.45',32,0,'name:Voxbone Belgium,gwgroup:3'),(17,8,'81.201.84.195',32,0,'name:Voxbone LA,gwgroup:3'),(18,8,'81.201.85.45',32,0,'name:Voxbone NYC,gwgroup:3'),(19,8,'81.201.83.45',32,0,'name:Voxbone Germany,gwgroup:3'),(20,8,'81.201.86.45',32,0,'name:Voxbone Hong Kong,gwgroup:3'),(21,8,'81.201.84.195',32,0,'name:Voxbone Australia,gwgroup:3'),(22,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(23,8,'64.136.173.22',32,0,'name:VI Carrier,gwgroup:4'),(24,8,'209.166.128.200',32,0,'name:VI Carrier,gwgroup:4'),(25,8,'192.240.151.100',32,0,'name:VI Carrier,gwgroup:4'),(26,8,'64.136.173.31',32,0,'name:VI Carrier,gwgroup:4'),(27,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(28,8,'64.136.174.20',32,0,'name:VI Carrier,gwgroup:4'),(29,8,'209.166.154.70',32,0,'name:VI Carrier,gwgroup:4'),(30,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(31,8,'64.136.173.23',32,0,'name:VI Carrier,gwgroup:4'),(32,8,'209.166.128.201',32,0,'name:VI Carrier,gwgroup:4'),(33,8,'192.240.151.101',32,0,'name:VI Carrier,gwgroup:4'),(34,8,'64.136.173.65',32,0,'name:VI Carrier,gwgroup:4'),(35,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(36,8,'64.136.174.21',32,0,'name:VI Carrier,gwgroup:4'),(37,8,'209.166.154.71',32,0,'name:VI Carrier,gwgroup:4'),(38,8,'72.15.219.140',32,0,'name:Thinq Carrier,gwgroup:5'),(39,8,'216.147.191.157',32,0,'name:Voxtelesys Carrier,gwgroup:6'),(40,8,'64.34.181.47',32,0,'name:Les.net Carrier,gwgroup:7'); /*!40000 ALTER TABLE `address` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `aliases` -- DROP TABLE IF EXISTS `aliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `aliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(255) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `aliases` -- LOCK TABLES `aliases` WRITE; /*!40000 ALTER TABLE `aliases` DISABLE KEYS */; /*!40000 ALTER TABLE `aliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrier_name` -- DROP TABLE IF EXISTS `carrier_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrier_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrier_name` -- LOCK TABLES `carrier_name` WRITE; /*!40000 ALTER TABLE `carrier_name` DISABLE KEYS */; /*!40000 ALTER TABLE `carrier_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierfailureroute` -- DROP TABLE IF EXISTS `carrierfailureroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierfailureroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `host_name` varchar(128) NOT NULL DEFAULT '', `reply_code` varchar(3) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `next_domain` int(10) unsigned NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierfailureroute` -- LOCK TABLES `carrierfailureroute` WRITE; /*!40000 ALTER TABLE `carrierfailureroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierfailureroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierroute` -- DROP TABLE IF EXISTS `carrierroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `prob` float NOT NULL DEFAULT '0', `strip` int(11) unsigned NOT NULL DEFAULT '0', `rewrite_host` varchar(128) NOT NULL DEFAULT '', `rewrite_prefix` varchar(64) NOT NULL DEFAULT '', `rewrite_suffix` varchar(64) NOT NULL DEFAULT '', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierroute` -- LOCK TABLES `carrierroute` WRITE; /*!40000 ALTER TABLE `carrierroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cdrs` -- DROP TABLE IF EXISTS `cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cdrs` ( `cdr_id` bigint(20) NOT NULL AUTO_INCREMENT, `src_username` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `dst_username` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `dst_ousername` varchar(64) NOT NULL DEFAULT '', `call_start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` int(10) unsigned NOT NULL DEFAULT '0', `sip_call_id` varchar(128) NOT NULL DEFAULT '', `sip_from_tag` varchar(128) NOT NULL DEFAULT '', `sip_to_tag` varchar(128) NOT NULL DEFAULT '', `src_ip` varchar(64) NOT NULL DEFAULT '', `cost` int(11) NOT NULL DEFAULT '0', `rated` int(11) NOT NULL DEFAULT '0', `created` datetime NOT NULL, `calltype` varchar(20) DEFAULT NULL, `fraud` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`cdr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cdrs` -- LOCK TABLES `cdrs` WRITE; /*!40000 ALTER TABLE `cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cpl` -- DROP TABLE IF EXISTS `cpl`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cpl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL DEFAULT '', `cpl_xml` text, `cpl_bin` text, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cpl` -- LOCK TABLES `cpl` WRITE; /*!40000 ALTER TABLE `cpl` DISABLE KEYS */; /*!40000 ALTER TABLE `cpl` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dbaliases` -- DROP TABLE IF EXISTS `dbaliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dbaliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `alias_username` varchar(64) NOT NULL DEFAULT '', `alias_domain` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `alias_user_idx` (`alias_username`), KEY `alias_idx` (`alias_username`,`alias_domain`), KEY `target_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dbaliases` -- LOCK TABLES `dbaliases` WRITE; /*!40000 ALTER TABLE `dbaliases` DISABLE KEYS */; /*!40000 ALTER TABLE `dbaliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog` -- DROP TABLE IF EXISTS `dialog`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `callid` varchar(255) NOT NULL, `from_uri` varchar(128) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_uri` varchar(128) NOT NULL, `to_tag` varchar(64) NOT NULL, `caller_cseq` varchar(20) NOT NULL, `callee_cseq` varchar(20) NOT NULL, `caller_route_set` varchar(512) DEFAULT NULL, `callee_route_set` varchar(512) DEFAULT NULL, `caller_contact` varchar(128) NOT NULL, `callee_contact` varchar(128) NOT NULL, `caller_sock` varchar(64) NOT NULL, `callee_sock` varchar(64) NOT NULL, `state` int(10) unsigned NOT NULL, `start_time` int(10) unsigned NOT NULL, `timeout` int(10) unsigned NOT NULL DEFAULT '0', `sflags` int(10) unsigned NOT NULL DEFAULT '0', `iflags` int(10) unsigned NOT NULL DEFAULT '0', `toroute_name` varchar(32) DEFAULT NULL, `req_uri` varchar(128) NOT NULL, `xdata` varchar(512) DEFAULT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog` -- LOCK TABLES `dialog` WRITE; /*!40000 ALTER TABLE `dialog` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog_vars` -- DROP TABLE IF EXISTS `dialog_vars`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog_vars` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `dialog_key` varchar(128) NOT NULL, `dialog_value` varchar(512) NOT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog_vars` -- LOCK TABLES `dialog_vars` WRITE; /*!40000 ALTER TABLE `dialog_vars` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog_vars` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialplan` -- DROP TABLE IF EXISTS `dialplan`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialplan` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `dpid` int(11) NOT NULL, `pr` int(11) NOT NULL, `match_op` int(11) NOT NULL, `match_exp` varchar(64) NOT NULL, `match_len` int(11) NOT NULL, `subst_exp` varchar(64) NOT NULL, `repl_exp` varchar(256) NOT NULL, `attrs` varchar(64) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialplan` -- LOCK TABLES `dialplan` WRITE; /*!40000 ALTER TABLE `dialplan` DISABLE KEYS */; /*!40000 ALTER TABLE `dialplan` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dispatcher` -- DROP TABLE IF EXISTS `dispatcher`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dispatcher` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(11) NOT NULL DEFAULT '0', `destination` varchar(192) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `priority` int(11) NOT NULL DEFAULT '0', `attrs` varchar(128) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dispatcher` -- LOCK TABLES `dispatcher` WRITE; /*!40000 ALTER TABLE `dispatcher` DISABLE KEYS */; /*!40000 ALTER TABLE `dispatcher` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain` -- DROP TABLE IF EXISTS `domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) NOT NULL, `did` varchar(64) DEFAULT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain` -- LOCK TABLES `domain` WRITE; /*!40000 ALTER TABLE `domain` DISABLE KEYS */; /*!40000 ALTER TABLE `domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_attrs` -- DROP TABLE IF EXISTS `domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `type` int(10) unsigned NOT NULL, `value` varchar(255) NOT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `domain_attrs_idx` (`did`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_attrs` -- LOCK TABLES `domain_attrs` WRITE; /*!40000 ALTER TABLE `domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_name` -- DROP TABLE IF EXISTS `domain_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_name` -- LOCK TABLES `domain_name` WRITE; /*!40000 ALTER TABLE `domain_name` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domainpolicy` -- DROP TABLE IF EXISTS `domainpolicy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domainpolicy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rule` varchar(255) NOT NULL, `type` varchar(255) NOT NULL, `att` varchar(255) DEFAULT NULL, `val` varchar(128) DEFAULT NULL, `description` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rav_idx` (`rule`,`att`,`val`), KEY `rule_idx` (`rule`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domainpolicy` -- LOCK TABLES `domainpolicy` WRITE; /*!40000 ALTER TABLE `domainpolicy` DISABLE KEYS */; /*!40000 ALTER TABLE `domainpolicy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_custom_rules` -- DROP TABLE IF EXISTS `dr_custom_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_custom_rules` ( `dr_ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `locality` varchar(64) NOT NULL DEFAULT '', `ppm` decimal(10,2) NOT NULL DEFAULT '0.00', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`dr_ruleid`), CONSTRAINT `dr_custom_rules_ibfk_1` FOREIGN KEY (`dr_ruleid`) REFERENCES `dr_rules` (`ruleid`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_custom_rules` -- LOCK TABLES `dr_custom_rules` WRITE; /*!40000 ALTER TABLE `dr_custom_rules` DISABLE KEYS */; /*!40000 ALTER TABLE `dr_custom_rules` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gateways` -- DROP TABLE IF EXISTS `dr_gateways`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gateways` ( `gwid` int(10) unsigned NOT NULL AUTO_INCREMENT, `type` int(11) unsigned NOT NULL DEFAULT '0', `address` varchar(128) NOT NULL, `strip` int(11) unsigned NOT NULL DEFAULT '0', `pri_prefix` varchar(64) DEFAULT NULL, `attrs` varchar(255) DEFAULT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`gwid`) ) ENGINE=InnoDB AUTO_INCREMENT=41 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gateways` -- LOCK TABLES `dr_gateways` WRITE; /*!40000 ALTER TABLE `dr_gateways` DISABLE KEYS */; INSERT INTO `dr_gateways` VALUES (1,8,'52.41.52.34',0,'','','name:Skyetel North West Inbound,gwgroup:1'),(2,8,'52.8.201.128',0,'','','name:Skyetel South West Inbound,gwgroup:1'),(3,8,'52.60.138.31',0,'','','name:Skyetel North East Inbound,gwgroup:1'),(4,8,'50.17.48.216',0,'','','name:Skyetel South East Inbound,gwgroup:1'),(5,8,'35.156.192.164',0,'','','name:Skyetel Europe Inbound,gwgroup:1'),(6,8,'term.skyetel.com',0,'','','name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(7,8,'52.41.52.34',0,'','','name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(8,8,'52.8.201.128',0,'','','name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(9,8,'50.17.48.216',0,'','','name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(10,8,'52.32.223.28',0,'','','name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(11,8,'52.4.178.107',0,'','','name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(12,8,'147.75.60.160',0,'','','name:Flowroute US-West-WA,gwgroup:2'),(13,8,'34.210.91.112',0,'','','name:Flowroute US-West-OR,gwgroup:2'),(14,8,'147.75.65.192',0,'','','name:Flowroute US-East-NJ,gwgroup:2'),(15,8,'34.226.36.32',0,'','','name:Flowroute US-East-VA,gwgroup:2'),(16,8,'81.201.82.45',0,'','','name:Voxbone Belgium,gwgroup:3'),(17,8,'81.201.84.195',0,'','','name:Voxbone LA,gwgroup:3'),(18,8,'81.201.85.45',0,'','','name:Voxbone NYC,gwgroup:3'),(19,8,'81.201.83.45',0,'','','name:Voxbone Germany,gwgroup:3'),(20,8,'81.201.86.45',0,'','','name:Voxbone Hong Kong,gwgroup:3'),(21,8,'81.201.84.195',0,'','','name:Voxbone Australia,gwgroup:3'),(22,8,'64.136.174.30',0,'','','name:VI Carrier,gwgroup:4'),(23,8,'64.136.173.22',0,'','','name:VI Carrier,gwgroup:4'),(24,8,'209.166.128.200',0,'','','name:VI Carrier,gwgroup:4'),(25,8,'192.240.151.100',0,'','','name:VI Carrier,gwgroup:4'),(26,8,'64.136.173.31',0,'','','name:VI Carrier,gwgroup:4'),(27,8,'64.136.174.30',0,'','','name:VI Carrier,gwgroup:4'),(28,8,'64.136.174.20',0,'','','name:VI Carrier,gwgroup:4'),(29,8,'209.166.154.70',0,'','','name:VI Carrier,gwgroup:4'),(30,8,'64.136.174.65',0,'','','name:VI Carrier,gwgroup:4'),(31,8,'64.136.173.23',0,'','','name:VI Carrier,gwgroup:4'),(32,8,'209.166.128.201',0,'','','name:VI Carrier,gwgroup:4'),(33,8,'192.240.151.101',0,'','','name:VI Carrier,gwgroup:4'),(34,8,'64.136.173.65',0,'','','name:VI Carrier,gwgroup:4'),(35,8,'64.136.174.65',0,'','','name:VI Carrier,gwgroup:4'),(36,8,'64.136.174.21',0,'','','name:VI Carrier,gwgroup:4'),(37,8,'209.166.154.71',0,'','','name:VI Carrier,gwgroup:4'),(38,8,'72.15.219.140',0,'','','name:Thinq Carrier,gwgroup:5'),(39,8,'216.147.191.157',0,'','','name:Voxtelesys Carrier,gwgroup:6'),(40,8,'64.34.181.47',0,'','','name:Les.net Carrier,gwgroup:7'); /*!40000 ALTER TABLE `dr_gateways` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_groups` -- DROP TABLE IF EXISTS `dr_groups`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_groups` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', `groupid` int(11) unsigned NOT NULL DEFAULT '0', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_groups` -- LOCK TABLES `dr_groups` WRITE; /*!40000 ALTER TABLE `dr_groups` DISABLE KEYS */; /*!40000 ALTER TABLE `dr_groups` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gw_lists` -- DROP TABLE IF EXISTS `dr_gw_lists`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gw_lists` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gw_lists` -- LOCK TABLES `dr_gw_lists` WRITE; /*!40000 ALTER TABLE `dr_gw_lists` DISABLE KEYS */; INSERT INTO `dr_gw_lists` VALUES (1,'1,2,3,4,5,6,7,8,9,10,11','name:Skyetel CarrierGroup,type:8'),(2,'12,13,14,15','name:Flowroute CarrierGroup,type:8'),(3,'16,17,18,19,20,21','name:Voxbone CarrierGroup,type:8'),(4,'22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37','name:VI CarrierGroup,type:8'),(5,'38','name:Thinq CarrierGroup,type:8'),(6,'39','name:Voxtelesys CarrierGroup,type:8'),(7,'40','name:Les.net CarrierGroup,type:8'); /*!40000 ALTER TABLE `dr_gw_lists` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_rules` -- DROP TABLE IF EXISTS `dr_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_rules` ( `ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `groupid` varchar(255) NOT NULL, `prefix` varchar(64) NOT NULL, `timerec` varchar(255) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', `routeid` varchar(64) NOT NULL, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`ruleid`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_rules` -- LOCK TABLES `dr_rules` WRITE; /*!40000 ALTER TABLE `dr_rules` DISABLE KEYS */; INSERT INTO `dr_rules` VALUES (1,'8000','','',0,'','1,2','name:Default Outbound Route'); /*!40000 ALTER TABLE `dr_rules` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_calllimit` -- DROP TABLE IF EXISTS `dsip_calllimit`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_calllimit` ( `gwid` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `limit` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`gwid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_calllimit` -- LOCK TABLES `dsip_calllimit` WRITE; /*!40000 ALTER TABLE `dsip_calllimit` DISABLE KEYS */; INSERT INTO `dsip_calllimit` VALUES ('64','0','9','1',1),('72','0','2','1',1),('73','0','2','1',1),('74','0','2','0',1); /*!40000 ALTER TABLE `dsip_calllimit` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_domain_mapping` -- DROP TABLE IF EXISTS `dsip_domain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_domain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `domain_id` int(10) NOT NULL, `attr_list` varchar(255) NOT NULL, `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_domain_mapping` -- LOCK TABLES `dsip_domain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_domain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_domain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_endpoint_lease` -- DROP TABLE IF EXISTS `dsip_endpoint_lease`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_endpoint_lease` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwid` int(10) unsigned NOT NULL, `sid` int(10) unsigned NOT NULL, `expiration` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_endpoint_lease` -- LOCK TABLES `dsip_endpoint_lease` WRITE; /*!40000 ALTER TABLE `dsip_endpoint_lease` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_endpoint_lease` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_lcr` -- DROP TABLE IF EXISTS `dsip_lcr`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_lcr` ( `pattern` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `dr_groupid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `cost` decimal(3,2) NOT NULL DEFAULT '0.00', `from_prefix` varchar(64) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`pattern`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_lcr` -- LOCK TABLES `dsip_lcr` WRITE; /*!40000 ALTER TABLE `dsip_lcr` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_lcr` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_maintmode` -- DROP TABLE IF EXISTS `dsip_maintmode`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_maintmode` ( `ipaddr` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `gwid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`ipaddr`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_maintmode` -- LOCK TABLES `dsip_maintmode` WRITE; /*!40000 ALTER TABLE `dsip_maintmode` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_maintmode` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_multidomain_mapping` -- DROP TABLE IF EXISTS `dsip_multidomain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_multidomain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `db_host` varchar(20) NOT NULL, `db_username` varchar(40) NOT NULL, `db_password` varchar(40) NOT NULL, `domain_list` varchar(255) NOT NULL DEFAULT '', `attr_list` varchar(255) NOT NULL DEFAULT '', `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', `lastsync` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `syncstatus` tinyint(1) NOT NULL DEFAULT '0', `syncerror` varchar(200) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_multidomain_mapping` -- LOCK TABLES `dsip_multidomain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_multidomain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_multidomain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_notification` -- DROP TABLE IF EXISTS `dsip_notification`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_notification` ( `gwgroupid` int(11) NOT NULL, `type` int(11) NOT NULL, `method` int(11) DEFAULT NULL, `value` varchar(255) DEFAULT NULL, `createdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`gwgroupid`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_notification` -- LOCK TABLES `dsip_notification` WRITE; /*!40000 ALTER TABLE `dsip_notification` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_notification` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `globalblacklist` -- DROP TABLE IF EXISTS `globalblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `globalblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `globalblacklist_idx` (`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `globalblacklist` -- LOCK TABLES `globalblacklist` WRITE; /*!40000 ALTER TABLE `globalblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `globalblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `grp` -- DROP TABLE IF EXISTS `grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `grp` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_group_idx` (`username`,`domain`,`grp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `grp` -- LOCK TABLES `grp` WRITE; /*!40000 ALTER TABLE `grp` DISABLE KEYS */; /*!40000 ALTER TABLE `grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `htable` -- DROP TABLE IF EXISTS `htable`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `htable` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `key_name` varchar(64) NOT NULL DEFAULT '', `key_type` int(11) NOT NULL DEFAULT '0', `value_type` int(11) NOT NULL DEFAULT '0', `key_value` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `htable` -- LOCK TABLES `htable` WRITE; /*!40000 ALTER TABLE `htable` DISABLE KEYS */; /*!40000 ALTER TABLE `htable` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_members` -- DROP TABLE IF EXISTS `imc_members`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_members` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `room` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_room_idx` (`username`,`domain`,`room`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_members` -- LOCK TABLES `imc_members` WRITE; /*!40000 ALTER TABLE `imc_members` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_members` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_rooms` -- DROP TABLE IF EXISTS `imc_rooms`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_rooms` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name_domain_idx` (`name`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_rooms` -- LOCK TABLES `imc_rooms` WRITE; /*!40000 ALTER TABLE `imc_rooms` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_rooms` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_gw` -- DROP TABLE IF EXISTS `lcr_gw`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_gw` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `gw_name` varchar(128) DEFAULT NULL, `ip_addr` varchar(50) DEFAULT NULL, `hostname` varchar(64) DEFAULT NULL, `port` smallint(5) unsigned DEFAULT NULL, `params` varchar(64) DEFAULT NULL, `uri_scheme` tinyint(3) unsigned DEFAULT NULL, `transport` tinyint(3) unsigned DEFAULT NULL, `strip` tinyint(3) unsigned DEFAULT NULL, `prefix` varchar(16) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `defunct` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_gw` -- LOCK TABLES `lcr_gw` WRITE; /*!40000 ALTER TABLE `lcr_gw` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_gw` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule` -- DROP TABLE IF EXISTS `lcr_rule`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `prefix` varchar(16) DEFAULT NULL, `from_uri` varchar(64) DEFAULT NULL, `request_uri` varchar(64) DEFAULT NULL, `mt_tvalue` varchar(128) DEFAULT NULL, `stopper` int(10) unsigned NOT NULL DEFAULT '0', `enabled` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `lcr_id_prefix_from_uri_idx` (`lcr_id`,`prefix`,`from_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule` -- LOCK TABLES `lcr_rule` WRITE; /*!40000 ALTER TABLE `lcr_rule` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule_target` -- DROP TABLE IF EXISTS `lcr_rule_target`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule_target` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `rule_id` int(10) unsigned NOT NULL, `gw_id` int(10) unsigned NOT NULL, `priority` tinyint(3) unsigned NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `rule_id_gw_id_idx` (`rule_id`,`gw_id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule_target` -- LOCK TABLES `lcr_rule_target` WRITE; /*!40000 ALTER TABLE `lcr_rule_target` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule_target` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `locale_lookup` -- DROP TABLE IF EXISTS `locale_lookup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `locale_lookup` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `locale` varchar(64) NOT NULL DEFAULT '', `fprefix` varchar(64) NOT NULL DEFAULT '0', `tprefix` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `locale_lookup` -- LOCK TABLES `locale_lookup` WRITE; /*!40000 ALTER TABLE `locale_lookup` DISABLE KEYS */; /*!40000 ALTER TABLE `locale_lookup` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location` -- DROP TABLE IF EXISTS `location`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(512) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`), KEY `expires_idx` (`expires`), KEY `connection_idx` (`server_id`,`connection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location` -- LOCK TABLES `location` WRITE; /*!40000 ALTER TABLE `location` DISABLE KEYS */; /*!40000 ALTER TABLE `location` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location_attrs` -- DROP TABLE IF EXISTS `location_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `aname` varchar(64) NOT NULL DEFAULT '', `atype` int(11) NOT NULL DEFAULT '0', `avalue` varchar(255) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `account_record_idx` (`username`,`domain`,`ruid`), KEY `last_modified_idx` (`last_modified`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location_attrs` -- LOCK TABLES `location_attrs` WRITE; /*!40000 ALTER TABLE `location_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `location_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `missed_calls` -- DROP TABLE IF EXISTS `missed_calls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `missed_calls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(255) NOT NULL DEFAULT '', `sip_code` varchar(3) NOT NULL DEFAULT '', `sip_reason` varchar(128) NOT NULL DEFAULT '', `time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `missed_calls` -- LOCK TABLES `missed_calls` WRITE; /*!40000 ALTER TABLE `missed_calls` DISABLE KEYS */; /*!40000 ALTER TABLE `missed_calls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqcalls` -- DROP TABLE IF EXISTS `mohqcalls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqcalls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `mohq_id` int(10) unsigned NOT NULL, `call_id` varchar(100) NOT NULL, `call_status` int(10) unsigned NOT NULL, `call_from` varchar(100) NOT NULL, `call_contact` varchar(100) DEFAULT NULL, `call_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqcalls_idx` (`call_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqcalls` -- LOCK TABLES `mohqcalls` WRITE; /*!40000 ALTER TABLE `mohqcalls` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqcalls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqueues` -- DROP TABLE IF EXISTS `mohqueues`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqueues` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) NOT NULL, `uri` varchar(100) NOT NULL, `mohdir` varchar(100) DEFAULT NULL, `mohfile` varchar(100) NOT NULL, `debug` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqueue_uri_idx` (`uri`), UNIQUE KEY `mohqueue_name_idx` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqueues` -- LOCK TABLES `mohqueues` WRITE; /*!40000 ALTER TABLE `mohqueues` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqueues` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtree` -- DROP TABLE IF EXISTS `mtree`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtree` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tprefix_idx` (`tprefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtree` -- LOCK TABLES `mtree` WRITE; /*!40000 ALTER TABLE `mtree` DISABLE KEYS */; /*!40000 ALTER TABLE `mtree` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtrees` -- DROP TABLE IF EXISTS `mtrees`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtrees` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tname` varchar(128) NOT NULL DEFAULT '', `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tname_tprefix_tvalue_idx` (`tname`,`tprefix`,`tvalue`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtrees` -- LOCK TABLES `mtrees` WRITE; /*!40000 ALTER TABLE `mtrees` DISABLE KEYS */; /*!40000 ALTER TABLE `mtrees` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pdt` -- DROP TABLE IF EXISTS `pdt`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pdt` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sdomain` varchar(128) NOT NULL, `prefix` varchar(32) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `sdomain_prefix_idx` (`sdomain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pdt` -- LOCK TABLES `pdt` WRITE; /*!40000 ALTER TABLE `pdt` DISABLE KEYS */; /*!40000 ALTER TABLE `pdt` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pl_pipes` -- DROP TABLE IF EXISTS `pl_pipes`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pl_pipes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pipeid` varchar(64) NOT NULL DEFAULT '', `algorithm` varchar(32) NOT NULL DEFAULT '', `plimit` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pl_pipes` -- LOCK TABLES `pl_pipes` WRITE; /*!40000 ALTER TABLE `pl_pipes` DISABLE KEYS */; /*!40000 ALTER TABLE `pl_pipes` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `presentity` -- DROP TABLE IF EXISTS `presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL, `etag` varchar(64) NOT NULL, `expires` int(11) NOT NULL, `received_time` int(11) NOT NULL, `body` blob NOT NULL, `sender` varchar(128) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `presentity_idx` (`username`,`domain`,`event`,`etag`), KEY `presentity_expires` (`expires`), KEY `account_idx` (`username`,`domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `presentity` -- LOCK TABLES `presentity` WRITE; /*!40000 ALTER TABLE `presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pua` -- DROP TABLE IF EXISTS `pua`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pua` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pres_uri` varchar(128) NOT NULL, `pres_id` varchar(255) NOT NULL, `event` int(11) NOT NULL, `expires` int(11) NOT NULL, `desired_expires` int(11) NOT NULL, `flag` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `tuple_id` varchar(64) DEFAULT NULL, `watcher_uri` varchar(128) NOT NULL, `call_id` varchar(255) NOT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `cseq` int(11) NOT NULL, `record_route` text, `contact` varchar(128) NOT NULL, `remote_contact` varchar(128) NOT NULL, `version` int(11) NOT NULL, `extra_headers` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `pua_idx` (`etag`,`tuple_id`,`call_id`,`from_tag`), KEY `expires_idx` (`expires`), KEY `dialog1_idx` (`pres_id`,`pres_uri`), KEY `dialog2_idx` (`call_id`,`from_tag`), KEY `record_idx` (`pres_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pua` -- LOCK TABLES `pua` WRITE; /*!40000 ALTER TABLE `pua` DISABLE KEYS */; /*!40000 ALTER TABLE `pua` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `purplemap` -- DROP TABLE IF EXISTS `purplemap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `purplemap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sip_user` varchar(128) NOT NULL, `ext_user` varchar(128) NOT NULL, `ext_prot` varchar(16) NOT NULL, `ext_pass` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `purplemap` -- LOCK TABLES `purplemap` WRITE; /*!40000 ALTER TABLE `purplemap` DISABLE KEYS */; /*!40000 ALTER TABLE `purplemap` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `re_grp` -- DROP TABLE IF EXISTS `re_grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `re_grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `reg_exp` varchar(128) NOT NULL DEFAULT '', `group_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `group_idx` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `re_grp` -- LOCK TABLES `re_grp` WRITE; /*!40000 ALTER TABLE `re_grp` DISABLE KEYS */; /*!40000 ALTER TABLE `re_grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_presentity` -- DROP TABLE IF EXISTS `rls_presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rlsubs_did` varchar(255) NOT NULL, `resource_uri` varchar(128) NOT NULL, `content_type` varchar(255) NOT NULL, `presence_state` blob NOT NULL, `expires` int(11) NOT NULL, `updated` int(11) NOT NULL, `auth_state` int(11) NOT NULL, `reason` varchar(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_presentity_idx` (`rlsubs_did`,`resource_uri`), KEY `rlsubs_idx` (`rlsubs_did`), KEY `updated_idx` (`updated`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_presentity` -- LOCK TABLES `rls_presentity` WRITE; /*!40000 ALTER TABLE `rls_presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_watchers` -- DROP TABLE IF EXISTS `rls_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) NOT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_watcher_idx` (`callid`,`to_tag`,`from_tag`), KEY `rls_watchers_update` (`watcher_username`,`watcher_domain`,`event`), KEY `rls_watchers_expires` (`expires`), KEY `updated_idx` (`updated`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_watchers` -- LOCK TABLES `rls_watchers` WRITE; /*!40000 ALTER TABLE `rls_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpengine` -- DROP TABLE IF EXISTS `rtpengine`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpengine` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(10) unsigned NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', `disabled` int(1) NOT NULL DEFAULT '0', `stamp` datetime NOT NULL DEFAULT '1900-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `rtpengine_nodes` (`setid`,`url`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpengine` -- LOCK TABLES `rtpengine` WRITE; /*!40000 ALTER TABLE `rtpengine` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpengine` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpproxy` -- DROP TABLE IF EXISTS `rtpproxy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpproxy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` varchar(32) NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `weight` int(11) NOT NULL DEFAULT '1', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpproxy` -- LOCK TABLES `rtpproxy` WRITE; /*!40000 ALTER TABLE `rtpproxy` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpproxy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sca_subscriptions` -- DROP TABLE IF EXISTS `sca_subscriptions`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sca_subscriptions` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `subscriber` varchar(255) NOT NULL, `aor` varchar(255) NOT NULL, `event` int(11) NOT NULL DEFAULT '0', `expires` int(11) NOT NULL DEFAULT '0', `state` int(11) NOT NULL DEFAULT '0', `app_idx` int(11) NOT NULL DEFAULT '0', `call_id` varchar(255) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_tag` varchar(64) NOT NULL, `record_route` text, `notify_cseq` int(11) NOT NULL, `subscribe_cseq` int(11) NOT NULL, `server_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `sca_subscriptions_idx` (`subscriber`,`call_id`,`from_tag`,`to_tag`), KEY `sca_expires_idx` (`server_id`,`expires`), KEY `sca_subscribers_idx` (`subscriber`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sca_subscriptions` -- LOCK TABLES `sca_subscriptions` WRITE; /*!40000 ALTER TABLE `sca_subscriptions` DISABLE KEYS */; /*!40000 ALTER TABLE `sca_subscriptions` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `silo` -- DROP TABLE IF EXISTS `silo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `silo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_addr` varchar(128) NOT NULL DEFAULT '', `dst_addr` varchar(128) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `inc_time` int(11) NOT NULL DEFAULT '0', `exp_time` int(11) NOT NULL DEFAULT '0', `snd_time` int(11) NOT NULL DEFAULT '0', `ctype` varchar(32) NOT NULL DEFAULT 'text/plain', `body` blob, `extra_hdrs` text, `callid` varchar(128) NOT NULL DEFAULT '', `status` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `silo` -- LOCK TABLES `silo` WRITE; /*!40000 ALTER TABLE `silo` DISABLE KEYS */; /*!40000 ALTER TABLE `silo` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sip_trace` -- DROP TABLE IF EXISTS `sip_trace`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sip_trace` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `time_stamp` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `time_us` int(10) unsigned NOT NULL DEFAULT '0', `callid` varchar(255) NOT NULL DEFAULT '', `traced_user` varchar(128) NOT NULL DEFAULT '', `msg` mediumtext NOT NULL, `method` varchar(50) NOT NULL DEFAULT '', `status` varchar(128) NOT NULL DEFAULT '', `fromip` varchar(50) NOT NULL DEFAULT '', `toip` varchar(50) NOT NULL DEFAULT '', `fromtag` varchar(64) NOT NULL DEFAULT '', `totag` varchar(64) NOT NULL DEFAULT '', `direction` varchar(4) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `traced_user_idx` (`traced_user`), KEY `date_idx` (`time_stamp`), KEY `fromip_idx` (`fromip`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sip_trace` -- LOCK TABLES `sip_trace` WRITE; /*!40000 ALTER TABLE `sip_trace` DISABLE KEYS */; /*!40000 ALTER TABLE `sip_trace` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `speed_dial` -- DROP TABLE IF EXISTS `speed_dial`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `speed_dial` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `sd_username` varchar(64) NOT NULL DEFAULT '', `sd_domain` varchar(64) NOT NULL DEFAULT '', `new_uri` varchar(128) NOT NULL DEFAULT '', `fname` varchar(64) NOT NULL DEFAULT '', `lname` varchar(64) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `speed_dial_idx` (`username`,`domain`,`sd_domain`,`sd_username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `speed_dial` -- LOCK TABLES `speed_dial` WRITE; /*!40000 ALTER TABLE `speed_dial` DISABLE KEYS */; /*!40000 ALTER TABLE `speed_dial` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `subscriber` -- DROP TABLE IF EXISTS `subscriber`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `subscriber` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `password` varchar(64) NOT NULL DEFAULT '', `ha1` varchar(128) NOT NULL DEFAULT '', `ha1b` varchar(128) NOT NULL DEFAULT '', `email_address` varchar(128) DEFAULT NULL, `rpid` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`), KEY `username_idx` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `subscriber` -- LOCK TABLES `subscriber` WRITE; /*!40000 ALTER TABLE `subscriber` DISABLE KEYS */; /*!40000 ALTER TABLE `subscriber` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_d` -- DROP TABLE IF EXISTS `topos_d`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_d` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_rr` mediumtext, `b_rr` mediumtext, `s_rr` mediumtext, `iflags` int(10) unsigned NOT NULL DEFAULT '0', `a_uri` varchar(128) NOT NULL DEFAULT '', `b_uri` varchar(128) NOT NULL DEFAULT '', `r_uri` varchar(128) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `a_uuid_idx` (`a_uuid`), KEY `b_uuid_idx` (`b_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_d` -- LOCK TABLES `topos_d` WRITE; /*!40000 ALTER TABLE `topos_d` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_d` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_t` -- DROP TABLE IF EXISTS `topos_t`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `direction` int(11) NOT NULL DEFAULT '0', `x_via` mediumtext, `x_vbranch` varchar(255) NOT NULL DEFAULT '', `x_rr` mediumtext, `y_rr` mediumtext, `s_rr` mediumtext, `x_uri` varchar(128) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `x_tag` varchar(255) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `x_vbranch_idx` (`x_vbranch`), KEY `a_uuid_idx` (`a_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_t` -- LOCK TABLES `topos_t` WRITE; /*!40000 ALTER TABLE `topos_t` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_t` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `trusted` -- DROP TABLE IF EXISTS `trusted`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `trusted` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_ip` varchar(50) NOT NULL, `proto` varchar(4) NOT NULL, `from_pattern` varchar(64) DEFAULT NULL, `ruri_pattern` varchar(64) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `peer_idx` (`src_ip`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `trusted` -- LOCK TABLES `trusted` WRITE; /*!40000 ALTER TABLE `trusted` DISABLE KEYS */; /*!40000 ALTER TABLE `trusted` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uacreg` -- DROP TABLE IF EXISTS `uacreg`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uacreg` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `l_uuid` varchar(64) NOT NULL DEFAULT '', `l_username` varchar(64) NOT NULL DEFAULT '', `l_domain` varchar(64) NOT NULL DEFAULT '', `r_username` varchar(64) NOT NULL DEFAULT '', `r_domain` varchar(64) NOT NULL DEFAULT '', `realm` varchar(64) NOT NULL DEFAULT '', `auth_username` varchar(64) NOT NULL DEFAULT '', `auth_password` varchar(64) NOT NULL DEFAULT '', `auth_ha1` varchar(128) NOT NULL DEFAULT '', `auth_proxy` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', `flags` int(11) NOT NULL DEFAULT '0', `reg_delay` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `l_uuid_idx` (`l_uuid`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uacreg` -- LOCK TABLES `uacreg` WRITE; /*!40000 ALTER TABLE `uacreg` DISABLE KEYS */; INSERT INTO `uacreg` VALUES (1,'1','1','67.205.143.99','','','','','','','',60,1,0),(2,'2','2','67.205.143.99','','','','','','','',60,1,0),(3,'3','3','67.205.143.99','','','','','','','',60,1,0),(4,'4','4','67.205.143.99','','','','','','','',60,1,0),(5,'5','5','67.205.143.99','','','','','','','',60,1,0),(6,'6','6','67.205.143.99','','','','','','','',60,1,0),(7,'7','7','67.205.143.99','','','','','','','',60,1,0); /*!40000 ALTER TABLE `uacreg` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_credentials` -- DROP TABLE IF EXISTS `uid_credentials`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_credentials` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `auth_username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL DEFAULT '_default', `realm` varchar(64) NOT NULL, `password` varchar(28) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `ha1` varchar(32) NOT NULL, `ha1b` varchar(32) NOT NULL DEFAULT '', `uid` varchar(64) NOT NULL, PRIMARY KEY (`id`), KEY `cred_idx` (`auth_username`,`did`), KEY `uid` (`uid`), KEY `did_idx` (`did`), KEY `realm_idx` (`realm`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_credentials` -- LOCK TABLES `uid_credentials` WRITE; /*!40000 ALTER TABLE `uid_credentials` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_credentials` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain` -- DROP TABLE IF EXISTS `uid_domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`), KEY `did_idx` (`did`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain` -- LOCK TABLES `uid_domain` WRITE; /*!40000 ALTER TABLE `uid_domain` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain_attrs` -- DROP TABLE IF EXISTS `uid_domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) DEFAULT NULL, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_attr_idx` (`did`,`name`,`value`), KEY `domain_did` (`did`,`flags`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain_attrs` -- LOCK TABLES `uid_domain_attrs` WRITE; /*!40000 ALTER TABLE `uid_domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_global_attrs` -- DROP TABLE IF EXISTS `uid_global_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_global_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `global_attrs_idx` (`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_global_attrs` -- LOCK TABLES `uid_global_attrs` WRITE; /*!40000 ALTER TABLE `uid_global_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_global_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri` -- DROP TABLE IF EXISTS `uid_uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `username` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), KEY `uri_idx1` (`username`,`did`,`scheme`), KEY `uri_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri` -- LOCK TABLES `uid_uri` WRITE; /*!40000 ALTER TABLE `uid_uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri_attrs` -- DROP TABLE IF EXISTS `uid_uri_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), UNIQUE KEY `uriattrs_idx` (`username`,`did`,`name`,`value`,`scheme`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri_attrs` -- LOCK TABLES `uid_uri_attrs` WRITE; /*!40000 ALTER TABLE `uid_uri_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_user_attrs` -- DROP TABLE IF EXISTS `uid_user_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_user_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `userattrs_idx` (`uid`,`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_user_attrs` -- LOCK TABLES `uid_user_attrs` WRITE; /*!40000 ALTER TABLE `uid_user_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_user_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uri` -- DROP TABLE IF EXISTS `uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `uri_user` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`,`uri_user`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uri` -- LOCK TABLES `uri` WRITE; /*!40000 ALTER TABLE `uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `userblacklist` -- DROP TABLE IF EXISTS `userblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `userblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `userblacklist_idx` (`username`,`domain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `userblacklist` -- LOCK TABLES `userblacklist` WRITE; /*!40000 ALTER TABLE `userblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `userblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `usr_preferences` -- DROP TABLE IF EXISTS `usr_preferences`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `usr_preferences` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(64) NOT NULL DEFAULT '', `username` varchar(128) NOT NULL DEFAULT '0', `domain` varchar(64) NOT NULL DEFAULT '', `attribute` varchar(32) NOT NULL DEFAULT '', `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `ua_idx` (`uuid`,`attribute`), KEY `uda_idx` (`username`,`domain`,`attribute`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `usr_preferences` -- LOCK TABLES `usr_preferences` WRITE; /*!40000 ALTER TABLE `usr_preferences` DISABLE KEYS */; /*!40000 ALTER TABLE `usr_preferences` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `version` -- DROP TABLE IF EXISTS `version`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `version` ( `table_name` varchar(32) NOT NULL, `table_version` int(10) unsigned NOT NULL DEFAULT '0', UNIQUE KEY `table_name_idx` (`table_name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `version` -- LOCK TABLES `version` WRITE; /*!40000 ALTER TABLE `version` DISABLE KEYS */; INSERT INTO `version` VALUES ('acc',5),('acc_cdrs',2),('active_watchers',12),('address',6),('aliases',8),('carrierfailureroute',2),('carrierroute',3),('carrier_name',1),('cpl',1),('dbaliases',1),('dialog',7),('dialog_vars',1),('dialplan',2),('dispatcher',4),('domain',2),('domainpolicy',2),('domain_attrs',1),('domain_name',1),('dr_gateways',3),('dr_groups',2),('dr_gw_lists',1),('dr_rules',3),('globalblacklist',1),('grp',2),('htable',2),('imc_members',1),('imc_rooms',1),('lcr_gw',3),('lcr_rule',3),('lcr_rule_target',1),('location',9),('location_attrs',1),('missed_calls',4),('mohqcalls',1),('mohqueues',1),('mtree',1),('mtrees',2),('pdt',1),('pl_pipes',1),('presentity',4),('pua',7),('purplemap',1),('re_grp',1),('rls_presentity',1),('rls_watchers',3),('rtpengine',1),('rtpproxy',1),('sca_subscriptions',2),('silo',8),('sip_trace',4),('speed_dial',2),('subscriber',7),('topos_d',1),('topos_t',1),('trusted',6),('uacreg',3),('uid_credentials',7),('uid_domain',2),('uid_domain_attrs',1),('uid_global_attrs',1),('uid_uri',3),('uid_uri_attrs',2),('uid_user_attrs',3),('uri',1),('userblacklist',1),('usr_preferences',2),('version',1),('watchers',3),('xcap',4); /*!40000 ALTER TABLE `version` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `watchers` -- DROP TABLE IF EXISTS `watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `status` int(11) NOT NULL, `reason` varchar(64) DEFAULT NULL, `inserted_time` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `watcher_idx` (`presentity_uri`,`watcher_username`,`watcher_domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `watchers` -- LOCK TABLES `watchers` WRITE; /*!40000 ALTER TABLE `watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `xcap` -- DROP TABLE IF EXISTS `xcap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `xcap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `doc` mediumblob NOT NULL, `doc_type` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `source` int(11) NOT NULL, `doc_uri` varchar(255) NOT NULL, `port` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `doc_uri_idx` (`doc_uri`), KEY `account_doc_type_idx` (`username`,`domain`,`doc_type`), KEY `account_doc_type_uri_idx` (`username`,`domain`,`doc_type`,`doc_uri`), KEY `account_doc_uri_idx` (`username`,`domain`,`doc_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `xcap` -- LOCK TABLES `xcap` WRITE; /*!40000 ALTER TABLE `xcap` DISABLE KEYS */; /*!40000 ALTER TABLE `xcap` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2019-08-20 21:30:19 ================================================ FILE: testing/sql/v0.523+ent/grants.sql ================================================ -- Grants dumped for users: kamailio,kamailioro GRANT USAGE ON *.* TO 'kamailio'@'%' IDENTIFIED BY PASSWORD '*2870144497941F902294EDABA260A0A4A15078E4'; GRANT ALL PRIVILEGES ON `kamailio`.* TO 'kamailio'@'%'; GRANT USAGE ON *.* TO 'kamailio'@'localhost' IDENTIFIED BY PASSWORD '*2870144497941F902294EDABA260A0A4A15078E4'; GRANT ALL PRIVILEGES ON `kamailio`.* TO 'kamailio'@'localhost'; GRANT USAGE ON *.* TO 'kamailioro'@'%' IDENTIFIED BY PASSWORD '*CF29822D11D49E1965F110105EACFBE9202F1A31'; GRANT SELECT ON `kamailio`.* TO 'kamailioro'@'%'; GRANT USAGE ON *.* TO 'kamailioro'@'localhost' IDENTIFIED BY PASSWORD '*CF29822D11D49E1965F110105EACFBE9202F1A31'; GRANT SELECT ON `kamailio`.* TO 'kamailioro'@'localhost'; FLUSH PRIVILEGES; ================================================ FILE: testing/sql/v0.523+ent/kamailio.sql ================================================ -- MySQL dump 10.16 Distrib 10.1.41-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: kamailio -- ------------------------------------------------------ -- Server version 10.1.41-MariaDB-0+deb9u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `acc` -- DROP TABLE IF EXISTS `acc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(128) NOT NULL DEFAULT '', `sip_code` char(3) NOT NULL DEFAULT '', `sip_reason` varchar(32) NOT NULL DEFAULT '', `time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `src_ip` varchar(64) NOT NULL DEFAULT '', `dst_ouser` varchar(64) NOT NULL DEFAULT '', `dst_user` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `src_user` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `cdr_id` int(11) NOT NULL DEFAULT '0', `calltype` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `acc_callid` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc` -- LOCK TABLES `acc` WRITE; /*!40000 ALTER TABLE `acc` DISABLE KEYS */; /*!40000 ALTER TABLE `acc` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `acc_cdrs` -- DROP TABLE IF EXISTS `acc_cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc_cdrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `end_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` float(10,3) NOT NULL DEFAULT '0.000', PRIMARY KEY (`id`), KEY `start_time_idx` (`start_time`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc_cdrs` -- LOCK TABLES `acc_cdrs` WRITE; /*!40000 ALTER TABLE `acc_cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `acc_cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `active_watchers` -- DROP TABLE IF EXISTS `active_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `active_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) DEFAULT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, `updated_winfo` int(11) NOT NULL, `flags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `active_watchers_idx` (`callid`,`to_tag`,`from_tag`), KEY `active_watchers_expires` (`expires`), KEY `active_watchers_pres` (`presentity_uri`,`event`), KEY `updated_idx` (`updated`), KEY `updated_winfo_idx` (`updated_winfo`,`presentity_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `active_watchers` -- LOCK TABLES `active_watchers` WRITE; /*!40000 ALTER TABLE `active_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `active_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `address` -- DROP TABLE IF EXISTS `address`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `address` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `grp` int(11) unsigned NOT NULL DEFAULT '1', `ip_addr` varchar(50) NOT NULL, `mask` int(11) NOT NULL DEFAULT '32', `port` smallint(5) unsigned NOT NULL DEFAULT '0', `tag` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=72 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `address` -- LOCK TABLES `address` WRITE; /*!40000 ALTER TABLE `address` DISABLE KEYS */; INSERT INTO `address` VALUES (1,8,'52.41.52.34',32,0,'name:Skyetel North West Inbound,gwgroup:1'),(2,8,'52.8.201.128',32,0,'name:Skyetel South West Inbound,gwgroup:1'),(3,8,'52.60.138.31',32,0,'name:Skyetel North East Inbound,gwgroup:1'),(4,8,'50.17.48.216',32,0,'name:Skyetel South East Inbound,gwgroup:1'),(5,8,'35.156.192.164',32,0,'name:Skyetel Europe Inbound,gwgroup:1'),(6,8,'term.skyetel.com',32,0,'name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(7,8,'52.41.52.34',32,0,'name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(8,8,'52.8.201.128',32,0,'name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(9,8,'50.17.48.216',32,0,'name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(10,8,'52.32.223.28',32,0,'name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(11,8,'52.4.178.107',32,0,'name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(12,8,'147.75.60.160',32,0,'name:Flowroute US-West-WA,gwgroup:2'),(13,8,'34.210.91.112',32,0,'name:Flowroute US-West-OR,gwgroup:2'),(14,8,'147.75.65.192',32,0,'name:Flowroute US-East-NJ,gwgroup:2'),(15,8,'34.226.36.32',32,0,'name:Flowroute US-East-VA,gwgroup:2'),(16,8,'81.201.82.45',32,0,'name:Voxbone Belgium,gwgroup:3'),(17,8,'81.201.84.195',32,0,'name:Voxbone LA,gwgroup:3'),(18,8,'81.201.85.45',32,0,'name:Voxbone NYC,gwgroup:3'),(19,8,'81.201.83.45',32,0,'name:Voxbone Germany,gwgroup:3'),(20,8,'81.201.86.45',32,0,'name:Voxbone Hong Kong,gwgroup:3'),(21,8,'81.201.84.195',32,0,'name:Voxbone Australia,gwgroup:3'),(22,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(23,8,'64.136.173.22',32,0,'name:VI Carrier,gwgroup:4'),(24,8,'209.166.128.200',32,0,'name:VI Carrier,gwgroup:4'),(25,8,'192.240.151.100',32,0,'name:VI Carrier,gwgroup:4'),(26,8,'64.136.173.31',32,0,'name:VI Carrier,gwgroup:4'),(27,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(28,8,'64.136.174.20',32,0,'name:VI Carrier,gwgroup:4'),(29,8,'209.166.154.70',32,0,'name:VI Carrier,gwgroup:4'),(30,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(31,8,'64.136.173.23',32,0,'name:VI Carrier,gwgroup:4'),(32,8,'209.166.128.201',32,0,'name:VI Carrier,gwgroup:4'),(33,8,'192.240.151.101',32,0,'name:VI Carrier,gwgroup:4'),(34,8,'64.136.173.65',32,0,'name:VI Carrier,gwgroup:4'),(35,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(36,8,'64.136.174.21',32,0,'name:VI Carrier,gwgroup:4'),(37,8,'209.166.154.71',32,0,'name:VI Carrier,gwgroup:4'),(38,8,'72.15.219.140',32,0,'name:Thinq Carrier,gwgroup:5'),(39,8,'216.147.191.157',32,0,'name:Voxtelesys Carrier,gwgroup:6'),(40,8,'64.34.181.47',32,0,'name:Les.net Carrier,gwgroup:7'),(64,8,'10.10.10.89',32,0,'name:vpn conn,gwgroup:8'),(65,9,'127.0.0.2',32,0,'name:,gwgroup:9'),(66,9,'127.0.0.3',32,0,'name:,gwgroup:10'),(67,9,'127.0.0.4',32,0,'name:,gwgroup:11'),(68,9,'127.0.0.4',32,0,'name:,gwgroup:12'),(69,8,'50.253.243.17',32,0,'name:vpn ext,gwgroup:8'),(70,9,'127.0.0.6',32,0,'name:,gwgroup:13'),(71,9,'127.0.0.6',32,0,'name:,gwgroup:14'); /*!40000 ALTER TABLE `address` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `aliases` -- DROP TABLE IF EXISTS `aliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `aliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(255) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `aliases` -- LOCK TABLES `aliases` WRITE; /*!40000 ALTER TABLE `aliases` DISABLE KEYS */; /*!40000 ALTER TABLE `aliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrier_name` -- DROP TABLE IF EXISTS `carrier_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrier_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrier_name` -- LOCK TABLES `carrier_name` WRITE; /*!40000 ALTER TABLE `carrier_name` DISABLE KEYS */; /*!40000 ALTER TABLE `carrier_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierfailureroute` -- DROP TABLE IF EXISTS `carrierfailureroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierfailureroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `host_name` varchar(128) NOT NULL DEFAULT '', `reply_code` varchar(3) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `next_domain` int(10) unsigned NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierfailureroute` -- LOCK TABLES `carrierfailureroute` WRITE; /*!40000 ALTER TABLE `carrierfailureroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierfailureroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierroute` -- DROP TABLE IF EXISTS `carrierroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `prob` float NOT NULL DEFAULT '0', `strip` int(11) unsigned NOT NULL DEFAULT '0', `rewrite_host` varchar(128) NOT NULL DEFAULT '', `rewrite_prefix` varchar(64) NOT NULL DEFAULT '', `rewrite_suffix` varchar(64) NOT NULL DEFAULT '', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierroute` -- LOCK TABLES `carrierroute` WRITE; /*!40000 ALTER TABLE `carrierroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cdrs` -- DROP TABLE IF EXISTS `cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cdrs` ( `cdr_id` bigint(20) NOT NULL AUTO_INCREMENT, `src_username` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `dst_username` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `dst_ousername` varchar(64) NOT NULL DEFAULT '', `call_start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` int(10) unsigned NOT NULL DEFAULT '0', `sip_call_id` varchar(128) NOT NULL DEFAULT '', `sip_from_tag` varchar(128) NOT NULL DEFAULT '', `sip_to_tag` varchar(128) NOT NULL DEFAULT '', `src_ip` varchar(64) NOT NULL DEFAULT '', `cost` int(11) NOT NULL DEFAULT '0', `rated` int(11) NOT NULL DEFAULT '0', `created` datetime NOT NULL, `calltype` varchar(20) DEFAULT NULL, `fraud` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`cdr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cdrs` -- LOCK TABLES `cdrs` WRITE; /*!40000 ALTER TABLE `cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cpl` -- DROP TABLE IF EXISTS `cpl`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cpl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL DEFAULT '', `cpl_xml` text, `cpl_bin` text, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cpl` -- LOCK TABLES `cpl` WRITE; /*!40000 ALTER TABLE `cpl` DISABLE KEYS */; /*!40000 ALTER TABLE `cpl` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dbaliases` -- DROP TABLE IF EXISTS `dbaliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dbaliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `alias_username` varchar(64) NOT NULL DEFAULT '', `alias_domain` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `alias_user_idx` (`alias_username`), KEY `alias_idx` (`alias_username`,`alias_domain`), KEY `target_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dbaliases` -- LOCK TABLES `dbaliases` WRITE; /*!40000 ALTER TABLE `dbaliases` DISABLE KEYS */; /*!40000 ALTER TABLE `dbaliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog` -- DROP TABLE IF EXISTS `dialog`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `callid` varchar(255) NOT NULL, `from_uri` varchar(128) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_uri` varchar(128) NOT NULL, `to_tag` varchar(64) NOT NULL, `caller_cseq` varchar(20) NOT NULL, `callee_cseq` varchar(20) NOT NULL, `caller_route_set` varchar(512) DEFAULT NULL, `callee_route_set` varchar(512) DEFAULT NULL, `caller_contact` varchar(128) NOT NULL, `callee_contact` varchar(128) NOT NULL, `caller_sock` varchar(64) NOT NULL, `callee_sock` varchar(64) NOT NULL, `state` int(10) unsigned NOT NULL, `start_time` int(10) unsigned NOT NULL, `timeout` int(10) unsigned NOT NULL DEFAULT '0', `sflags` int(10) unsigned NOT NULL DEFAULT '0', `iflags` int(10) unsigned NOT NULL DEFAULT '0', `toroute_name` varchar(32) DEFAULT NULL, `req_uri` varchar(128) NOT NULL, `xdata` varchar(512) DEFAULT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog` -- LOCK TABLES `dialog` WRITE; /*!40000 ALTER TABLE `dialog` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog_vars` -- DROP TABLE IF EXISTS `dialog_vars`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog_vars` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `dialog_key` varchar(128) NOT NULL, `dialog_value` varchar(512) NOT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog_vars` -- LOCK TABLES `dialog_vars` WRITE; /*!40000 ALTER TABLE `dialog_vars` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog_vars` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialplan` -- DROP TABLE IF EXISTS `dialplan`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialplan` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `dpid` int(11) NOT NULL, `pr` int(11) NOT NULL, `match_op` int(11) NOT NULL, `match_exp` varchar(64) NOT NULL, `match_len` int(11) NOT NULL, `subst_exp` varchar(64) NOT NULL, `repl_exp` varchar(256) NOT NULL, `attrs` varchar(64) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialplan` -- LOCK TABLES `dialplan` WRITE; /*!40000 ALTER TABLE `dialplan` DISABLE KEYS */; /*!40000 ALTER TABLE `dialplan` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dispatcher` -- DROP TABLE IF EXISTS `dispatcher`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dispatcher` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(11) NOT NULL DEFAULT '0', `destination` varchar(192) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `priority` int(11) NOT NULL DEFAULT '0', `attrs` varchar(128) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dispatcher` -- LOCK TABLES `dispatcher` WRITE; /*!40000 ALTER TABLE `dispatcher` DISABLE KEYS */; /*!40000 ALTER TABLE `dispatcher` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain` -- DROP TABLE IF EXISTS `domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) NOT NULL, `did` varchar(64) DEFAULT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain` -- LOCK TABLES `domain` WRITE; /*!40000 ALTER TABLE `domain` DISABLE KEYS */; /*!40000 ALTER TABLE `domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_attrs` -- DROP TABLE IF EXISTS `domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `type` int(10) unsigned NOT NULL, `value` varchar(255) NOT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `domain_attrs_idx` (`did`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_attrs` -- LOCK TABLES `domain_attrs` WRITE; /*!40000 ALTER TABLE `domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_name` -- DROP TABLE IF EXISTS `domain_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_name` -- LOCK TABLES `domain_name` WRITE; /*!40000 ALTER TABLE `domain_name` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domainpolicy` -- DROP TABLE IF EXISTS `domainpolicy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domainpolicy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rule` varchar(255) NOT NULL, `type` varchar(255) NOT NULL, `att` varchar(255) DEFAULT NULL, `val` varchar(128) DEFAULT NULL, `description` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rav_idx` (`rule`,`att`,`val`), KEY `rule_idx` (`rule`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domainpolicy` -- LOCK TABLES `domainpolicy` WRITE; /*!40000 ALTER TABLE `domainpolicy` DISABLE KEYS */; /*!40000 ALTER TABLE `domainpolicy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_custom_rules` -- DROP TABLE IF EXISTS `dr_custom_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_custom_rules` ( `dr_ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `locality` varchar(64) NOT NULL DEFAULT '', `ppm` decimal(10,2) NOT NULL DEFAULT '0.00', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`dr_ruleid`), CONSTRAINT `dr_custom_rules_ibfk_1` FOREIGN KEY (`dr_ruleid`) REFERENCES `dr_rules` (`ruleid`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_custom_rules` -- LOCK TABLES `dr_custom_rules` WRITE; /*!40000 ALTER TABLE `dr_custom_rules` DISABLE KEYS */; /*!40000 ALTER TABLE `dr_custom_rules` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gateways` -- DROP TABLE IF EXISTS `dr_gateways`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gateways` ( `gwid` int(10) unsigned NOT NULL AUTO_INCREMENT, `type` int(11) unsigned NOT NULL DEFAULT '0', `address` varchar(128) NOT NULL, `strip` int(11) unsigned NOT NULL DEFAULT '0', `pri_prefix` varchar(64) DEFAULT NULL, `attrs` varchar(255) DEFAULT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`gwid`) ) ENGINE=InnoDB AUTO_INCREMENT=73 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gateways` -- LOCK TABLES `dr_gateways` WRITE; /*!40000 ALTER TABLE `dr_gateways` DISABLE KEYS */; INSERT INTO `dr_gateways` VALUES (1,8,'52.41.52.34',0,'','1,8','name:Skyetel North West Inbound,gwgroup:1'),(2,8,'52.8.201.128',0,'','2,8','name:Skyetel South West Inbound,gwgroup:1'),(3,8,'52.60.138.31',0,'','3,8','name:Skyetel North East Inbound,gwgroup:1'),(4,8,'50.17.48.216',0,'','4,8','name:Skyetel South East Inbound,gwgroup:1'),(5,8,'35.156.192.164',0,'','5,8','name:Skyetel Europe Inbound,gwgroup:1'),(6,8,'term.skyetel.com',0,'','6,8','name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(7,8,'52.41.52.34',0,'','7,8','name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(8,8,'52.8.201.128',0,'','8,8','name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(9,8,'50.17.48.216',0,'','9,8','name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(10,8,'52.32.223.28',0,'','10,8','name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(11,8,'52.4.178.107',0,'','11,8','name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(12,8,'147.75.60.160',0,'','12,8','name:Flowroute US-West-WA,gwgroup:2'),(13,8,'34.210.91.112',0,'','13,8','name:Flowroute US-West-OR,gwgroup:2'),(14,8,'147.75.65.192',0,'','14,8','name:Flowroute US-East-NJ,gwgroup:2'),(15,8,'34.226.36.32',0,'','15,8','name:Flowroute US-East-VA,gwgroup:2'),(16,8,'81.201.82.45',0,'','16,8','name:Voxbone Belgium,gwgroup:3'),(17,8,'81.201.84.195',0,'','17,8','name:Voxbone LA,gwgroup:3'),(18,8,'81.201.85.45',0,'','18,8','name:Voxbone NYC,gwgroup:3'),(19,8,'81.201.83.45',0,'','19,8','name:Voxbone Germany,gwgroup:3'),(20,8,'81.201.86.45',0,'','20,8','name:Voxbone Hong Kong,gwgroup:3'),(21,8,'81.201.84.195',0,'','21,8','name:Voxbone Australia,gwgroup:3'),(22,8,'64.136.174.30',0,'','22,8','name:VI Carrier,gwgroup:4'),(23,8,'64.136.173.22',0,'','23,8','name:VI Carrier,gwgroup:4'),(24,8,'209.166.128.200',0,'','24,8','name:VI Carrier,gwgroup:4'),(25,8,'192.240.151.100',0,'','25,8','name:VI Carrier,gwgroup:4'),(26,8,'64.136.173.31',0,'','26,8','name:VI Carrier,gwgroup:4'),(27,8,'64.136.174.30',0,'','27,8','name:VI Carrier,gwgroup:4'),(28,8,'64.136.174.20',0,'','28,8','name:VI Carrier,gwgroup:4'),(29,8,'209.166.154.70',0,'','29,8','name:VI Carrier,gwgroup:4'),(30,8,'64.136.174.65',0,'','30,8','name:VI Carrier,gwgroup:4'),(31,8,'64.136.173.23',0,'','31,8','name:VI Carrier,gwgroup:4'),(32,8,'209.166.128.201',0,'','32,8','name:VI Carrier,gwgroup:4'),(33,8,'192.240.151.101',0,'','33,8','name:VI Carrier,gwgroup:4'),(34,8,'64.136.173.65',0,'','34,8','name:VI Carrier,gwgroup:4'),(35,8,'64.136.174.65',0,'','35,8','name:VI Carrier,gwgroup:4'),(36,8,'64.136.174.21',0,'','36,8','name:VI Carrier,gwgroup:4'),(37,8,'209.166.154.71',0,'','37,8','name:VI Carrier,gwgroup:4'),(38,8,'72.15.219.140',0,'','38,8','name:Thinq Carrier,gwgroup:5'),(39,8,'216.147.191.157',0,'','39,8','name:Voxtelesys Carrier,gwgroup:6'),(40,8,'64.34.181.47',0,'','40,8','name:Les.net Carrier,gwgroup:7'),(64,8,'10.10.10.89',0,'','64,8','name:vpn conn,gwgroup:8'),(65,9,'127.0.0.2',0,'','65,9','name:,gwgroup:9'),(66,9,'127.0.0.3',0,'','66,9','name:,gwgroup:10'),(67,9,'127.0.0.4',0,'','67,9','name:,gwgroup:11'),(68,9,'127.0.0.5',0,'','68,9','gwgroup:12,name:,type:9'),(69,8,'50.253.243.17',0,'','69,8','name:vpn ext,gwgroup:8'); /*!40000 ALTER TABLE `dr_gateways` ENABLE KEYS */; UNLOCK TABLES; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER insert_dr_gateways BEFORE INSERT ON dr_gateways FOR EACH ROW BEGIN DECLARE new_gwid int; SET new_gwid := ( SELECT auto_increment FROM information_schema.tables WHERE table_name = 'dr_gateways' AND table_schema = DATABASE()); SET NEW.attrs = CONCAT(CAST(new_gwid AS char), ',', CAST(NEW.type AS char)); END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER update_dr_gateways BEFORE UPDATE ON dr_gateways FOR EACH ROW BEGIN IF NOT (NEW.gwid <=> OLD.gwid) THEN SET NEW.attrs = CONCAT(CAST(NEW.gwid AS char), ',', CAST(NEW.type AS char)); END IF; END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -- -- Table structure for table `dr_groups` -- DROP TABLE IF EXISTS `dr_groups`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_groups` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', `groupid` int(11) unsigned NOT NULL DEFAULT '0', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_groups` -- LOCK TABLES `dr_groups` WRITE; /*!40000 ALTER TABLE `dr_groups` DISABLE KEYS */; /*!40000 ALTER TABLE `dr_groups` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gw_lists` -- DROP TABLE IF EXISTS `dr_gw_lists`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gw_lists` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gw_lists` -- LOCK TABLES `dr_gw_lists` WRITE; /*!40000 ALTER TABLE `dr_gw_lists` DISABLE KEYS */; INSERT INTO `dr_gw_lists` VALUES (1,'1,2,3,4,5,6,7,8,9,10,11','name:Skyetel CarrierGroup,type:8'),(2,'12,13,14,15','name:Flowroute CarrierGroup,type:8'),(3,'16,17,18,19,20,21','name:Voxbone CarrierGroup,type:8'),(4,'22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37','name:VI CarrierGroup,type:8'),(5,'38','name:Thinq CarrierGroup,type:8'),(6,'39','name:Voxtelesys CarrierGroup,type:8'),(7,'40','name:Les.net CarrierGroup,type:8'),(8,'64,69','name:tyler testing,type:8'),(9,'65','name:test no fwd,type:9'),(10,'66','name:test hard fwd,type:9'),(11,'67','name:test fail fwd,type:9'),(12,'68','name:test call limit,type:9'); /*!40000 ALTER TABLE `dr_gw_lists` ENABLE KEYS */; UNLOCK TABLES; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER insert_gw2gwgroup AFTER INSERT ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.gwlist, ',', gw_index), ',', -1), cast(NEW.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER update_gw2gwgroup AFTER UPDATE ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; IF NOT (NEW.gwlist <=> OLD.gwlist) THEN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(OLD.id AS char(64)); IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(NEW.gwlist, ',', gw_index), ',', -1), cast(NEW.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; ELSEIF NOT (NEW.id <=> OLD.id) THEN UPDATE dsip_gw2gwgroup SET gwgroupid = cast(NEW.id AS char(64)) WHERE gwgroupid = cast(OLD.id AS char(64)); END IF; END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER delete_gw2gwgroup AFTER DELETE ON dr_gw_lists FOR EACH ROW BEGIN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(OLD.id AS char(64)); END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -- -- Table structure for table `dr_rules` -- DROP TABLE IF EXISTS `dr_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_rules` ( `ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `groupid` varchar(255) NOT NULL, `prefix` varchar(64) NOT NULL, `timerec` varchar(255) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', `routeid` varchar(64) NOT NULL, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`ruleid`) ) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_rules` -- LOCK TABLES `dr_rules` WRITE; /*!40000 ALTER TABLE `dr_rules` DISABLE KEYS */; INSERT INTO `dr_rules` VALUES (1,'8000','','',0,'','1,2','name:Default Outbound Route'),(2,'9000','123456','',0,'','#9','name:test no fwd'),(3,'9000','1234567','',0,'','','name:test hard fwd'),(4,'9000','12345678','',0,'','#9','name:test fail fwd'),(5,'9000','123456789','',0,'','','name:test fail fwd + hard fwd'),(6,'9000','012345','',0,'','','name:test hard fwd + gwgroup'),(7,'20000','','',0,'','#10','name:Hard Forward from 012345 to DID 5555'),(8,'9000','0123456','',0,'','#9','name:test fail fwd + gwgroup'),(9,'20001','','',0,'','#11','name:Failover Forward from 0123456 to DID 6666'),(10,'9000','01234567','',0,'','','name:test hard fwd + fail fwd + gwgroups'),(11,'20002','','',0,'','#10','name:Hard Forward from 01234567 to DID 5555'),(12,'20003','','',0,'','#11','name:Failover Forward from 01234567 to DID 6666'),(17,'9000','012345678','',0,'','#12','name:test call limit'); /*!40000 ALTER TABLE `dr_rules` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_calllimit` -- DROP TABLE IF EXISTS `dsip_calllimit`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_calllimit` ( `gwgroupid` varchar(64) NOT NULL, `limit` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '1', `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_calllimit` -- LOCK TABLES `dsip_calllimit` WRITE; /*!40000 ALTER TABLE `dsip_calllimit` DISABLE KEYS */; INSERT INTO `dsip_calllimit` VALUES ('12','0',1,'0','0'); /*!40000 ALTER TABLE `dsip_calllimit` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_domain_mapping` -- DROP TABLE IF EXISTS `dsip_domain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_domain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `domain_id` int(10) NOT NULL, `attr_list` varchar(255) NOT NULL, `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_domain_mapping` -- LOCK TABLES `dsip_domain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_domain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_domain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_endpoint_lease` -- DROP TABLE IF EXISTS `dsip_endpoint_lease`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_endpoint_lease` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwid` int(10) unsigned NOT NULL, `sid` int(10) unsigned NOT NULL, `expiration` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_endpoint_lease` -- LOCK TABLES `dsip_endpoint_lease` WRITE; /*!40000 ALTER TABLE `dsip_endpoint_lease` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_endpoint_lease` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_failfwd` -- DROP TABLE IF EXISTS `dsip_failfwd`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_failfwd` ( `dr_ruleid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `dr_groupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`dr_ruleid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_failfwd` -- LOCK TABLES `dsip_failfwd` WRITE; /*!40000 ALTER TABLE `dsip_failfwd` DISABLE KEYS */; INSERT INTO `dsip_failfwd` VALUES ('10','6666','20003','0','0'),('4','6666','8000','0','0'),('5','6666','8000','0','0'),('8','6666','20001','0','0'); /*!40000 ALTER TABLE `dsip_failfwd` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_gw2gwgroup` -- DROP TABLE IF EXISTS `dsip_gw2gwgroup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_gw2gwgroup` ( `gwid` varchar(64) NOT NULL, `gwgroupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`gwid`,`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_gw2gwgroup` -- LOCK TABLES `dsip_gw2gwgroup` WRITE; /*!40000 ALTER TABLE `dsip_gw2gwgroup` DISABLE KEYS */; INSERT INTO `dsip_gw2gwgroup` VALUES ('1','1','0','0'),('10','1','0','0'),('11','1','0','0'),('12','2','0','0'),('13','2','0','0'),('14','2','0','0'),('15','2','0','0'),('16','3','0','0'),('17','3','0','0'),('18','3','0','0'),('19','3','0','0'),('2','1','0','0'),('20','3','0','0'),('21','3','0','0'),('22','4','0','0'),('23','4','0','0'),('24','4','0','0'),('25','4','0','0'),('26','4','0','0'),('27','4','0','0'),('28','4','0','0'),('29','4','0','0'),('3','1','0','0'),('30','4','0','0'),('31','4','0','0'),('32','4','0','0'),('33','4','0','0'),('34','4','0','0'),('35','4','0','0'),('36','4','0','0'),('37','4','0','0'),('38','5','0','0'),('39','6','0','0'),('4','1','0','0'),('40','7','0','0'),('5','1','0','0'),('6','1','0','0'),('64','8','0','0'),('65','9','0','0'),('66','10','0','0'),('67','11','0','0'),('68','12','0','0'),('69','8','0','0'),('7','1','0','0'),('8','1','0','0'),('9','1','0','0'); /*!40000 ALTER TABLE `dsip_gw2gwgroup` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_hardfwd` -- DROP TABLE IF EXISTS `dsip_hardfwd`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_hardfwd` ( `dr_ruleid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `dr_groupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`dr_ruleid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_hardfwd` -- LOCK TABLES `dsip_hardfwd` WRITE; /*!40000 ALTER TABLE `dsip_hardfwd` DISABLE KEYS */; INSERT INTO `dsip_hardfwd` VALUES ('10','5555','20002','0','0'),('3','5555','8000','0','0'),('5','5555','8000','0','0'),('6','5555','20000','0','0'); /*!40000 ALTER TABLE `dsip_hardfwd` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_lcr` -- DROP TABLE IF EXISTS `dsip_lcr`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_lcr` ( `pattern` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `dr_groupid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `cost` decimal(3,2) NOT NULL DEFAULT '0.00', `from_prefix` varchar(64) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`pattern`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_lcr` -- LOCK TABLES `dsip_lcr` WRITE; /*!40000 ALTER TABLE `dsip_lcr` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_lcr` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_maintmode` -- DROP TABLE IF EXISTS `dsip_maintmode`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_maintmode` ( `ipaddr` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `gwid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`ipaddr`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_maintmode` -- LOCK TABLES `dsip_maintmode` WRITE; /*!40000 ALTER TABLE `dsip_maintmode` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_maintmode` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_multidomain_mapping` -- DROP TABLE IF EXISTS `dsip_multidomain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_multidomain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `db_host` varchar(20) NOT NULL, `db_username` varchar(40) NOT NULL, `db_password` varchar(40) NOT NULL, `domain_list` varchar(255) NOT NULL DEFAULT '', `domain_list_hash` varchar(255) NOT NULL DEFAULT '', `attr_list` varchar(255) NOT NULL DEFAULT '', `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', `lastsync` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `syncstatus` tinyint(1) NOT NULL DEFAULT '0', `syncerror` varchar(200) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_multidomain_mapping` -- LOCK TABLES `dsip_multidomain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_multidomain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_multidomain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_notification` -- DROP TABLE IF EXISTS `dsip_notification`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_notification` ( `gwgroupid` int(11) NOT NULL, `type` int(11) NOT NULL, `method` int(11) DEFAULT NULL, `value` varchar(255) DEFAULT NULL, `createdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`gwgroupid`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_notification` -- LOCK TABLES `dsip_notification` WRITE; /*!40000 ALTER TABLE `dsip_notification` DISABLE KEYS */; INSERT INTO `dsip_notification` VALUES (9,0,0,'','2019-09-30 19:07:46'),(9,1,0,'','2019-09-30 19:07:46'),(10,0,0,'','2019-09-30 19:08:23'),(10,1,0,'','2019-09-30 19:08:23'),(11,0,0,'','2019-09-30 19:08:33'),(11,1,0,'','2019-09-30 19:08:33'),(12,0,0,'tmoore@goflyball.com','2019-09-30 23:20:24'),(12,1,0,'tmoore@goflyball.com','2019-09-30 23:20:24'); /*!40000 ALTER TABLE `dsip_notification` ENABLE KEYS */; UNLOCK TABLES; -- -- Temporary table structure for view `dsip_prefix_mapping` -- DROP TABLE IF EXISTS `dsip_prefix_mapping`; /*!50001 DROP VIEW IF EXISTS `dsip_prefix_mapping`*/; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; /*!50001 CREATE TABLE `dsip_prefix_mapping` ( `prefix` tinyint NOT NULL, `ruleid` tinyint NOT NULL, `key_type` tinyint NOT NULL, `value_type` tinyint NOT NULL ) ENGINE=MyISAM */; SET character_set_client = @saved_cs_client; -- -- Table structure for table `dsip_settings` -- DROP TABLE IF EXISTS `dsip_settings`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_settings` ( `DSIP_ID` int(10) unsigned NOT NULL DEFAULT '1', `FLT_CARRIER` int(11) NOT NULL DEFAULT '8', `FLT_PBX` int(11) NOT NULL DEFAULT '9', `FLT_OUTBOUND` int(11) NOT NULL DEFAULT '8000', `FLT_INBOUND` int(11) NOT NULL DEFAULT '9000', `FLT_LCR_MIN` int(11) NOT NULL DEFAULT '10000', `FLT_FWD_MIN` int(11) NOT NULL DEFAULT '20000', PRIMARY KEY (`DSIP_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 MIN_ROWS=1 MAX_ROWS=1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_settings` -- LOCK TABLES `dsip_settings` WRITE; /*!40000 ALTER TABLE `dsip_settings` DISABLE KEYS */; INSERT INTO `dsip_settings` VALUES (1,8,9,8000,9000,10000,20000); /*!40000 ALTER TABLE `dsip_settings` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `globalblacklist` -- DROP TABLE IF EXISTS `globalblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `globalblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `globalblacklist_idx` (`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `globalblacklist` -- LOCK TABLES `globalblacklist` WRITE; /*!40000 ALTER TABLE `globalblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `globalblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `grp` -- DROP TABLE IF EXISTS `grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `grp` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_group_idx` (`username`,`domain`,`grp`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `grp` -- LOCK TABLES `grp` WRITE; /*!40000 ALTER TABLE `grp` DISABLE KEYS */; /*!40000 ALTER TABLE `grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `htable` -- DROP TABLE IF EXISTS `htable`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `htable` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `key_name` varchar(64) NOT NULL DEFAULT '', `key_type` int(11) NOT NULL DEFAULT '0', `value_type` int(11) NOT NULL DEFAULT '0', `key_value` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `htable` -- LOCK TABLES `htable` WRITE; /*!40000 ALTER TABLE `htable` DISABLE KEYS */; /*!40000 ALTER TABLE `htable` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_members` -- DROP TABLE IF EXISTS `imc_members`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_members` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `room` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_room_idx` (`username`,`domain`,`room`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_members` -- LOCK TABLES `imc_members` WRITE; /*!40000 ALTER TABLE `imc_members` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_members` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_rooms` -- DROP TABLE IF EXISTS `imc_rooms`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_rooms` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name_domain_idx` (`name`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_rooms` -- LOCK TABLES `imc_rooms` WRITE; /*!40000 ALTER TABLE `imc_rooms` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_rooms` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_gw` -- DROP TABLE IF EXISTS `lcr_gw`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_gw` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `gw_name` varchar(128) DEFAULT NULL, `ip_addr` varchar(50) DEFAULT NULL, `hostname` varchar(64) DEFAULT NULL, `port` smallint(5) unsigned DEFAULT NULL, `params` varchar(64) DEFAULT NULL, `uri_scheme` tinyint(3) unsigned DEFAULT NULL, `transport` tinyint(3) unsigned DEFAULT NULL, `strip` tinyint(3) unsigned DEFAULT NULL, `prefix` varchar(16) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `defunct` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_gw` -- LOCK TABLES `lcr_gw` WRITE; /*!40000 ALTER TABLE `lcr_gw` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_gw` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule` -- DROP TABLE IF EXISTS `lcr_rule`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `prefix` varchar(16) DEFAULT NULL, `from_uri` varchar(64) DEFAULT NULL, `request_uri` varchar(64) DEFAULT NULL, `mt_tvalue` varchar(128) DEFAULT NULL, `stopper` int(10) unsigned NOT NULL DEFAULT '0', `enabled` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `lcr_id_prefix_from_uri_idx` (`lcr_id`,`prefix`,`from_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule` -- LOCK TABLES `lcr_rule` WRITE; /*!40000 ALTER TABLE `lcr_rule` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule_target` -- DROP TABLE IF EXISTS `lcr_rule_target`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule_target` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `rule_id` int(10) unsigned NOT NULL, `gw_id` int(10) unsigned NOT NULL, `priority` tinyint(3) unsigned NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `rule_id_gw_id_idx` (`rule_id`,`gw_id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule_target` -- LOCK TABLES `lcr_rule_target` WRITE; /*!40000 ALTER TABLE `lcr_rule_target` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule_target` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `locale_lookup` -- DROP TABLE IF EXISTS `locale_lookup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `locale_lookup` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `locale` varchar(64) NOT NULL DEFAULT '', `fprefix` varchar(64) NOT NULL DEFAULT '0', `tprefix` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `locale_lookup` -- LOCK TABLES `locale_lookup` WRITE; /*!40000 ALTER TABLE `locale_lookup` DISABLE KEYS */; /*!40000 ALTER TABLE `locale_lookup` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location` -- DROP TABLE IF EXISTS `location`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(512) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`), KEY `expires_idx` (`expires`), KEY `connection_idx` (`server_id`,`connection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location` -- LOCK TABLES `location` WRITE; /*!40000 ALTER TABLE `location` DISABLE KEYS */; /*!40000 ALTER TABLE `location` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location_attrs` -- DROP TABLE IF EXISTS `location_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `aname` varchar(64) NOT NULL DEFAULT '', `atype` int(11) NOT NULL DEFAULT '0', `avalue` varchar(255) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `account_record_idx` (`username`,`domain`,`ruid`), KEY `last_modified_idx` (`last_modified`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location_attrs` -- LOCK TABLES `location_attrs` WRITE; /*!40000 ALTER TABLE `location_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `location_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `missed_calls` -- DROP TABLE IF EXISTS `missed_calls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `missed_calls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(255) NOT NULL DEFAULT '', `sip_code` varchar(3) NOT NULL DEFAULT '', `sip_reason` varchar(128) NOT NULL DEFAULT '', `time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `missed_calls` -- LOCK TABLES `missed_calls` WRITE; /*!40000 ALTER TABLE `missed_calls` DISABLE KEYS */; /*!40000 ALTER TABLE `missed_calls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqcalls` -- DROP TABLE IF EXISTS `mohqcalls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqcalls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `mohq_id` int(10) unsigned NOT NULL, `call_id` varchar(100) NOT NULL, `call_status` int(10) unsigned NOT NULL, `call_from` varchar(100) NOT NULL, `call_contact` varchar(100) DEFAULT NULL, `call_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqcalls_idx` (`call_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqcalls` -- LOCK TABLES `mohqcalls` WRITE; /*!40000 ALTER TABLE `mohqcalls` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqcalls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqueues` -- DROP TABLE IF EXISTS `mohqueues`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqueues` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) NOT NULL, `uri` varchar(100) NOT NULL, `mohdir` varchar(100) DEFAULT NULL, `mohfile` varchar(100) NOT NULL, `debug` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqueue_uri_idx` (`uri`), UNIQUE KEY `mohqueue_name_idx` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqueues` -- LOCK TABLES `mohqueues` WRITE; /*!40000 ALTER TABLE `mohqueues` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqueues` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtree` -- DROP TABLE IF EXISTS `mtree`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtree` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tprefix_idx` (`tprefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtree` -- LOCK TABLES `mtree` WRITE; /*!40000 ALTER TABLE `mtree` DISABLE KEYS */; /*!40000 ALTER TABLE `mtree` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtrees` -- DROP TABLE IF EXISTS `mtrees`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtrees` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tname` varchar(128) NOT NULL DEFAULT '', `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tname_tprefix_tvalue_idx` (`tname`,`tprefix`,`tvalue`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtrees` -- LOCK TABLES `mtrees` WRITE; /*!40000 ALTER TABLE `mtrees` DISABLE KEYS */; /*!40000 ALTER TABLE `mtrees` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pdt` -- DROP TABLE IF EXISTS `pdt`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pdt` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sdomain` varchar(128) NOT NULL, `prefix` varchar(32) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `sdomain_prefix_idx` (`sdomain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pdt` -- LOCK TABLES `pdt` WRITE; /*!40000 ALTER TABLE `pdt` DISABLE KEYS */; /*!40000 ALTER TABLE `pdt` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pl_pipes` -- DROP TABLE IF EXISTS `pl_pipes`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pl_pipes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pipeid` varchar(64) NOT NULL DEFAULT '', `algorithm` varchar(32) NOT NULL DEFAULT '', `plimit` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pl_pipes` -- LOCK TABLES `pl_pipes` WRITE; /*!40000 ALTER TABLE `pl_pipes` DISABLE KEYS */; /*!40000 ALTER TABLE `pl_pipes` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `presentity` -- DROP TABLE IF EXISTS `presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL, `etag` varchar(64) NOT NULL, `expires` int(11) NOT NULL, `received_time` int(11) NOT NULL, `body` blob NOT NULL, `sender` varchar(128) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `presentity_idx` (`username`,`domain`,`event`,`etag`), KEY `presentity_expires` (`expires`), KEY `account_idx` (`username`,`domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `presentity` -- LOCK TABLES `presentity` WRITE; /*!40000 ALTER TABLE `presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pua` -- DROP TABLE IF EXISTS `pua`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pua` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pres_uri` varchar(128) NOT NULL, `pres_id` varchar(255) NOT NULL, `event` int(11) NOT NULL, `expires` int(11) NOT NULL, `desired_expires` int(11) NOT NULL, `flag` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `tuple_id` varchar(64) DEFAULT NULL, `watcher_uri` varchar(128) NOT NULL, `call_id` varchar(255) NOT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `cseq` int(11) NOT NULL, `record_route` text, `contact` varchar(128) NOT NULL, `remote_contact` varchar(128) NOT NULL, `version` int(11) NOT NULL, `extra_headers` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `pua_idx` (`etag`,`tuple_id`,`call_id`,`from_tag`), KEY `expires_idx` (`expires`), KEY `dialog1_idx` (`pres_id`,`pres_uri`), KEY `dialog2_idx` (`call_id`,`from_tag`), KEY `record_idx` (`pres_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pua` -- LOCK TABLES `pua` WRITE; /*!40000 ALTER TABLE `pua` DISABLE KEYS */; /*!40000 ALTER TABLE `pua` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `purplemap` -- DROP TABLE IF EXISTS `purplemap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `purplemap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sip_user` varchar(128) NOT NULL, `ext_user` varchar(128) NOT NULL, `ext_prot` varchar(16) NOT NULL, `ext_pass` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `purplemap` -- LOCK TABLES `purplemap` WRITE; /*!40000 ALTER TABLE `purplemap` DISABLE KEYS */; /*!40000 ALTER TABLE `purplemap` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `re_grp` -- DROP TABLE IF EXISTS `re_grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `re_grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `reg_exp` varchar(128) NOT NULL DEFAULT '', `group_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `group_idx` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `re_grp` -- LOCK TABLES `re_grp` WRITE; /*!40000 ALTER TABLE `re_grp` DISABLE KEYS */; /*!40000 ALTER TABLE `re_grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_presentity` -- DROP TABLE IF EXISTS `rls_presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rlsubs_did` varchar(255) NOT NULL, `resource_uri` varchar(128) NOT NULL, `content_type` varchar(255) NOT NULL, `presence_state` blob NOT NULL, `expires` int(11) NOT NULL, `updated` int(11) NOT NULL, `auth_state` int(11) NOT NULL, `reason` varchar(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_presentity_idx` (`rlsubs_did`,`resource_uri`), KEY `rlsubs_idx` (`rlsubs_did`), KEY `updated_idx` (`updated`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_presentity` -- LOCK TABLES `rls_presentity` WRITE; /*!40000 ALTER TABLE `rls_presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_watchers` -- DROP TABLE IF EXISTS `rls_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) NOT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_watcher_idx` (`callid`,`to_tag`,`from_tag`), KEY `rls_watchers_update` (`watcher_username`,`watcher_domain`,`event`), KEY `rls_watchers_expires` (`expires`), KEY `updated_idx` (`updated`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_watchers` -- LOCK TABLES `rls_watchers` WRITE; /*!40000 ALTER TABLE `rls_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpengine` -- DROP TABLE IF EXISTS `rtpengine`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpengine` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(10) unsigned NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', `disabled` int(1) NOT NULL DEFAULT '0', `stamp` datetime NOT NULL DEFAULT '1900-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `rtpengine_nodes` (`setid`,`url`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpengine` -- LOCK TABLES `rtpengine` WRITE; /*!40000 ALTER TABLE `rtpengine` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpengine` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpproxy` -- DROP TABLE IF EXISTS `rtpproxy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpproxy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` varchar(32) NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `weight` int(11) NOT NULL DEFAULT '1', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpproxy` -- LOCK TABLES `rtpproxy` WRITE; /*!40000 ALTER TABLE `rtpproxy` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpproxy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sca_subscriptions` -- DROP TABLE IF EXISTS `sca_subscriptions`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sca_subscriptions` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `subscriber` varchar(255) NOT NULL, `aor` varchar(255) NOT NULL, `event` int(11) NOT NULL DEFAULT '0', `expires` int(11) NOT NULL DEFAULT '0', `state` int(11) NOT NULL DEFAULT '0', `app_idx` int(11) NOT NULL DEFAULT '0', `call_id` varchar(255) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_tag` varchar(64) NOT NULL, `record_route` text, `notify_cseq` int(11) NOT NULL, `subscribe_cseq` int(11) NOT NULL, `server_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `sca_subscriptions_idx` (`subscriber`,`call_id`,`from_tag`,`to_tag`), KEY `sca_expires_idx` (`server_id`,`expires`), KEY `sca_subscribers_idx` (`subscriber`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sca_subscriptions` -- LOCK TABLES `sca_subscriptions` WRITE; /*!40000 ALTER TABLE `sca_subscriptions` DISABLE KEYS */; /*!40000 ALTER TABLE `sca_subscriptions` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `silo` -- DROP TABLE IF EXISTS `silo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `silo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_addr` varchar(128) NOT NULL DEFAULT '', `dst_addr` varchar(128) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `inc_time` int(11) NOT NULL DEFAULT '0', `exp_time` int(11) NOT NULL DEFAULT '0', `snd_time` int(11) NOT NULL DEFAULT '0', `ctype` varchar(32) NOT NULL DEFAULT 'text/plain', `body` blob, `extra_hdrs` text, `callid` varchar(128) NOT NULL DEFAULT '', `status` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `silo` -- LOCK TABLES `silo` WRITE; /*!40000 ALTER TABLE `silo` DISABLE KEYS */; /*!40000 ALTER TABLE `silo` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sip_trace` -- DROP TABLE IF EXISTS `sip_trace`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sip_trace` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `time_stamp` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `time_us` int(10) unsigned NOT NULL DEFAULT '0', `callid` varchar(255) NOT NULL DEFAULT '', `traced_user` varchar(128) NOT NULL DEFAULT '', `msg` mediumtext NOT NULL, `method` varchar(50) NOT NULL DEFAULT '', `status` varchar(128) NOT NULL DEFAULT '', `fromip` varchar(50) NOT NULL DEFAULT '', `toip` varchar(50) NOT NULL DEFAULT '', `fromtag` varchar(64) NOT NULL DEFAULT '', `totag` varchar(64) NOT NULL DEFAULT '', `direction` varchar(4) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `traced_user_idx` (`traced_user`), KEY `date_idx` (`time_stamp`), KEY `fromip_idx` (`fromip`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sip_trace` -- LOCK TABLES `sip_trace` WRITE; /*!40000 ALTER TABLE `sip_trace` DISABLE KEYS */; /*!40000 ALTER TABLE `sip_trace` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `speed_dial` -- DROP TABLE IF EXISTS `speed_dial`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `speed_dial` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `sd_username` varchar(64) NOT NULL DEFAULT '', `sd_domain` varchar(64) NOT NULL DEFAULT '', `new_uri` varchar(128) NOT NULL DEFAULT '', `fname` varchar(64) NOT NULL DEFAULT '', `lname` varchar(64) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `speed_dial_idx` (`username`,`domain`,`sd_domain`,`sd_username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `speed_dial` -- LOCK TABLES `speed_dial` WRITE; /*!40000 ALTER TABLE `speed_dial` DISABLE KEYS */; /*!40000 ALTER TABLE `speed_dial` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `subscriber` -- DROP TABLE IF EXISTS `subscriber`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `subscriber` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `password` varchar(64) NOT NULL DEFAULT '', `ha1` varchar(128) NOT NULL DEFAULT '', `ha1b` varchar(128) NOT NULL DEFAULT '', `email_address` varchar(128) DEFAULT NULL, `rpid` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`), KEY `username_idx` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `subscriber` -- LOCK TABLES `subscriber` WRITE; /*!40000 ALTER TABLE `subscriber` DISABLE KEYS */; /*!40000 ALTER TABLE `subscriber` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_d` -- DROP TABLE IF EXISTS `topos_d`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_d` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_rr` mediumtext, `b_rr` mediumtext, `s_rr` mediumtext, `iflags` int(10) unsigned NOT NULL DEFAULT '0', `a_uri` varchar(128) NOT NULL DEFAULT '', `b_uri` varchar(128) NOT NULL DEFAULT '', `r_uri` varchar(128) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `a_uuid_idx` (`a_uuid`), KEY `b_uuid_idx` (`b_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_d` -- LOCK TABLES `topos_d` WRITE; /*!40000 ALTER TABLE `topos_d` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_d` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_t` -- DROP TABLE IF EXISTS `topos_t`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `direction` int(11) NOT NULL DEFAULT '0', `x_via` mediumtext, `x_vbranch` varchar(255) NOT NULL DEFAULT '', `x_rr` mediumtext, `y_rr` mediumtext, `s_rr` mediumtext, `x_uri` varchar(128) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `x_tag` varchar(255) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `x_vbranch_idx` (`x_vbranch`), KEY `a_uuid_idx` (`a_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_t` -- LOCK TABLES `topos_t` WRITE; /*!40000 ALTER TABLE `topos_t` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_t` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `trusted` -- DROP TABLE IF EXISTS `trusted`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `trusted` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_ip` varchar(50) NOT NULL, `proto` varchar(4) NOT NULL, `from_pattern` varchar(64) DEFAULT NULL, `ruri_pattern` varchar(64) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `peer_idx` (`src_ip`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `trusted` -- LOCK TABLES `trusted` WRITE; /*!40000 ALTER TABLE `trusted` DISABLE KEYS */; /*!40000 ALTER TABLE `trusted` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uacreg` -- DROP TABLE IF EXISTS `uacreg`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uacreg` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `l_uuid` varchar(64) NOT NULL DEFAULT '', `l_username` varchar(64) NOT NULL DEFAULT '', `l_domain` varchar(64) NOT NULL DEFAULT '', `r_username` varchar(64) NOT NULL DEFAULT '', `r_domain` varchar(64) NOT NULL DEFAULT '', `realm` varchar(64) NOT NULL DEFAULT '', `auth_username` varchar(64) NOT NULL DEFAULT '', `auth_password` varchar(64) NOT NULL DEFAULT '', `auth_ha1` varchar(128) NOT NULL DEFAULT '', `auth_proxy` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', `flags` int(11) NOT NULL DEFAULT '0', `reg_delay` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `l_uuid_idx` (`l_uuid`) ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uacreg` -- LOCK TABLES `uacreg` WRITE; /*!40000 ALTER TABLE `uacreg` DISABLE KEYS */; INSERT INTO `uacreg` VALUES (1,'1','1','50.253.243.17','','','','','','','',60,1,0),(2,'2','2','50.253.243.17','','','','','','','',60,1,0),(3,'3','3','50.253.243.17','','','','','','','',60,1,0),(4,'4','4','50.253.243.17','','','','','','','',60,1,0),(5,'5','5','50.253.243.17','','','','','','','',60,1,0),(6,'6','6','50.253.243.17','','','','','','','',60,1,0),(7,'7','7','50.253.243.17','','','','','','','',60,1,0),(8,'8','8','50.253.243.17','','','','','','','',60,1,0); /*!40000 ALTER TABLE `uacreg` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_credentials` -- DROP TABLE IF EXISTS `uid_credentials`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_credentials` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `auth_username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL DEFAULT '_default', `realm` varchar(64) NOT NULL, `password` varchar(28) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `ha1` varchar(32) NOT NULL, `ha1b` varchar(32) NOT NULL DEFAULT '', `uid` varchar(64) NOT NULL, PRIMARY KEY (`id`), KEY `cred_idx` (`auth_username`,`did`), KEY `uid` (`uid`), KEY `did_idx` (`did`), KEY `realm_idx` (`realm`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_credentials` -- LOCK TABLES `uid_credentials` WRITE; /*!40000 ALTER TABLE `uid_credentials` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_credentials` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain` -- DROP TABLE IF EXISTS `uid_domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`), KEY `did_idx` (`did`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain` -- LOCK TABLES `uid_domain` WRITE; /*!40000 ALTER TABLE `uid_domain` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain_attrs` -- DROP TABLE IF EXISTS `uid_domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) DEFAULT NULL, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_attr_idx` (`did`,`name`,`value`), KEY `domain_did` (`did`,`flags`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain_attrs` -- LOCK TABLES `uid_domain_attrs` WRITE; /*!40000 ALTER TABLE `uid_domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_global_attrs` -- DROP TABLE IF EXISTS `uid_global_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_global_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `global_attrs_idx` (`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_global_attrs` -- LOCK TABLES `uid_global_attrs` WRITE; /*!40000 ALTER TABLE `uid_global_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_global_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri` -- DROP TABLE IF EXISTS `uid_uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `username` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), KEY `uri_idx1` (`username`,`did`,`scheme`), KEY `uri_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri` -- LOCK TABLES `uid_uri` WRITE; /*!40000 ALTER TABLE `uid_uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri_attrs` -- DROP TABLE IF EXISTS `uid_uri_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), UNIQUE KEY `uriattrs_idx` (`username`,`did`,`name`,`value`,`scheme`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri_attrs` -- LOCK TABLES `uid_uri_attrs` WRITE; /*!40000 ALTER TABLE `uid_uri_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_user_attrs` -- DROP TABLE IF EXISTS `uid_user_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_user_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `userattrs_idx` (`uid`,`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_user_attrs` -- LOCK TABLES `uid_user_attrs` WRITE; /*!40000 ALTER TABLE `uid_user_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_user_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uri` -- DROP TABLE IF EXISTS `uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `uri_user` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`,`uri_user`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uri` -- LOCK TABLES `uri` WRITE; /*!40000 ALTER TABLE `uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `userblacklist` -- DROP TABLE IF EXISTS `userblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `userblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `userblacklist_idx` (`username`,`domain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `userblacklist` -- LOCK TABLES `userblacklist` WRITE; /*!40000 ALTER TABLE `userblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `userblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `usr_preferences` -- DROP TABLE IF EXISTS `usr_preferences`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `usr_preferences` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(64) NOT NULL DEFAULT '', `username` varchar(128) NOT NULL DEFAULT '0', `domain` varchar(64) NOT NULL DEFAULT '', `attribute` varchar(32) NOT NULL DEFAULT '', `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `ua_idx` (`uuid`,`attribute`), KEY `uda_idx` (`username`,`domain`,`attribute`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `usr_preferences` -- LOCK TABLES `usr_preferences` WRITE; /*!40000 ALTER TABLE `usr_preferences` DISABLE KEYS */; /*!40000 ALTER TABLE `usr_preferences` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `version` -- DROP TABLE IF EXISTS `version`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `version` ( `table_name` varchar(32) NOT NULL, `table_version` int(10) unsigned NOT NULL DEFAULT '0', UNIQUE KEY `table_name_idx` (`table_name`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `version` -- LOCK TABLES `version` WRITE; /*!40000 ALTER TABLE `version` DISABLE KEYS */; INSERT INTO `version` VALUES ('acc',5),('acc_cdrs',2),('active_watchers',12),('address',6),('aliases',8),('carrierfailureroute',2),('carrierroute',3),('carrier_name',1),('cpl',1),('dbaliases',1),('dialog',7),('dialog_vars',1),('dialplan',2),('dispatcher',4),('domain',2),('domainpolicy',2),('domain_attrs',1),('domain_name',1),('dr_gateways',3),('dr_groups',2),('dr_gw_lists',1),('dr_rules',3),('globalblacklist',1),('grp',2),('htable',2),('imc_members',1),('imc_rooms',1),('lcr_gw',3),('lcr_rule',3),('lcr_rule_target',1),('location',9),('location_attrs',1),('missed_calls',4),('mohqcalls',1),('mohqueues',1),('mtree',1),('mtrees',2),('pdt',1),('pl_pipes',1),('presentity',4),('pua',7),('purplemap',1),('re_grp',1),('rls_presentity',1),('rls_watchers',3),('rtpengine',1),('rtpproxy',1),('sca_subscriptions',2),('silo',8),('sip_trace',4),('speed_dial',2),('subscriber',7),('topos_d',1),('topos_t',1),('trusted',6),('uacreg',3),('uid_credentials',7),('uid_domain',2),('uid_domain_attrs',1),('uid_global_attrs',1),('uid_uri',3),('uid_uri_attrs',2),('uid_user_attrs',3),('uri',1),('userblacklist',1),('usr_preferences',2),('version',1),('watchers',3),('xcap',4); /*!40000 ALTER TABLE `version` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `watchers` -- DROP TABLE IF EXISTS `watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `status` int(11) NOT NULL, `reason` varchar(64) DEFAULT NULL, `inserted_time` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `watcher_idx` (`presentity_uri`,`watcher_username`,`watcher_domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `watchers` -- LOCK TABLES `watchers` WRITE; /*!40000 ALTER TABLE `watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `xcap` -- DROP TABLE IF EXISTS `xcap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `xcap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `doc` mediumblob NOT NULL, `doc_type` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `source` int(11) NOT NULL, `doc_uri` varchar(255) NOT NULL, `port` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `doc_uri_idx` (`doc_uri`), KEY `account_doc_type_idx` (`username`,`domain`,`doc_type`), KEY `account_doc_type_uri_idx` (`username`,`domain`,`doc_type`,`doc_uri`), KEY `account_doc_uri_idx` (`username`,`domain`,`doc_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `xcap` -- LOCK TABLES `xcap` WRITE; /*!40000 ALTER TABLE `xcap` DISABLE KEYS */; /*!40000 ALTER TABLE `xcap` ENABLE KEYS */; UNLOCK TABLES; -- -- Dumping events for database 'kamailio' -- -- -- Dumping routines for database 'kamailio' -- /*!50003 DROP PROCEDURE IF EXISTS `kamailio_cdrs` */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8 */ ; /*!50003 SET character_set_results = utf8 */ ; /*!50003 SET collation_connection = utf8_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = '' */ ; DELIMITER ;; CREATE DEFINER=`root`@`localhost` PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done INT DEFAULT 0; DECLARE bye_record INT DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_callid,v_from_tag, v_to_tag,v_src_ip,v_calltype VARCHAR(64); DECLARE v_inv_time, v_bye_time DATETIME; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, time, callid,from_tag, to_tag, src_ip, calltype FROM acc where method='INVITE' and cdr_id='0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip, v_calltype; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method='BYE' AND callid=v_callid AND ((from_tag=v_from_tag AND to_tag=v_to_tag) OR (from_tag=v_to_tag AND to_tag=v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username,src_domain,dst_username, dst_domain,call_start_time,duration,sip_call_id,sip_from_tag, sip_to_tag,src_ip,created,calltype) VALUES (v_src_user,v_src_domain, v_dst_user,v_dst_domain,v_inv_time, UNIX_TIMESTAMP(v_bye_time)-UNIX_TIMESTAMP(v_inv_time), v_callid,v_from_tag,v_to_tag,v_src_ip,NOW(),v_calltype); UPDATE acc SET cdr_id=last_insert_id() WHERE callid=v_callid AND from_tag=v_from_tag AND to_tag=v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 DROP PROCEDURE IF EXISTS `kamailio_rating` */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8 */ ; /*!50003 SET character_set_results = utf8 */ ; /*!50003 SET collation_connection = utf8_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = '' */ ; DELIMITER ;; CREATE DEFINER=`kamailio`@`localhost` PROCEDURE `kamailio_rating`(`rgroup` varchar(64)) BEGIN DECLARE done, rate_record, vx_cost INT DEFAULT 0; DECLARE v_cdr_id BIGINT DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit INT DEFAULT 0; DECLARE v_dst_username VARCHAR(64); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated=0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group=rgroup AND v_dst_username LIKE concat(prefix, '%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration/v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id=v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -- -- Final view structure for view `dsip_prefix_mapping` -- /*!50001 DROP TABLE IF EXISTS `dsip_prefix_mapping`*/; /*!50001 DROP VIEW IF EXISTS `dsip_prefix_mapping`*/; /*!50001 SET @saved_cs_client = @@character_set_client */; /*!50001 SET @saved_cs_results = @@character_set_results */; /*!50001 SET @saved_col_connection = @@collation_connection */; /*!50001 SET character_set_client = utf8 */; /*!50001 SET character_set_results = utf8 */; /*!50001 SET collation_connection = utf8mb4_general_ci */; /*!50001 CREATE ALGORITHM=UNDEFINED */ /*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */ /*!50001 VIEW `dsip_prefix_mapping` AS select `dr_rules`.`prefix` AS `prefix`,cast(`dr_rules`.`ruleid` as char(64) charset utf8mb4) AS `ruleid`,'0' AS `key_type`,'0' AS `value_type` from `dr_rules` where (`dr_rules`.`groupid` = (select `dsip_settings`.`FLT_INBOUND` from `dsip_settings` where (`dsip_settings`.`DSIP_ID` = 1) limit 1)) */; /*!50001 SET character_set_client = @saved_cs_client */; /*!50001 SET character_set_results = @saved_cs_results */; /*!50001 SET collation_connection = @saved_col_connection */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2019-09-30 19:43:05 ================================================ FILE: testing/sql/v0.60+ent/grants.sql ================================================ GRANT USAGE ON *.* TO 'kamailio'@'localhost' IDENTIFIED BY PASSWORD '*2870144497941F902294EDABA260A0A4A15078E4'; GRANT ALL PRIVILEGES ON `kamailio`.* TO 'kamailio'@'localhost'; GRANT USAGE ON *.* TO 'kamailioro'@'localhost' IDENTIFIED BY PASSWORD '*CF29822D11D49E1965F110105EACFBE9202F1A31'; GRANT SELECT ON `kamailio`.* TO 'kamailioro'@'localhost'; GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED VIA unix_socket WITH GRANT OPTION; GRANT PROXY ON ''@'%' TO 'root'@'localhost' WITH GRANT OPTION; ================================================ FILE: testing/sql/v0.60+ent/kamailio.sql ================================================ -- MySQL dump 10.16 Distrib 10.1.41-MariaDB, for debian-linux-gnu (x86_64) -- -- Host: localhost Database: kamailio -- ------------------------------------------------------ -- Server version 10.1.41-MariaDB-0+deb9u1 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `acc` -- DROP TABLE IF EXISTS `acc`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(128) NOT NULL DEFAULT '', `sip_code` char(3) NOT NULL DEFAULT '', `sip_reason` varchar(32) NOT NULL DEFAULT '', `time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `src_ip` varchar(64) NOT NULL DEFAULT '', `dst_ouser` varchar(64) NOT NULL DEFAULT '', `dst_user` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `src_user` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `cdr_id` int(11) NOT NULL DEFAULT '0', `calltype` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`), KEY `acc_callid` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc` -- LOCK TABLES `acc` WRITE; /*!40000 ALTER TABLE `acc` DISABLE KEYS */; /*!40000 ALTER TABLE `acc` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `acc_cdrs` -- DROP TABLE IF EXISTS `acc_cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `acc_cdrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `end_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` float(10,3) NOT NULL DEFAULT '0.000', PRIMARY KEY (`id`), KEY `start_time_idx` (`start_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `acc_cdrs` -- LOCK TABLES `acc_cdrs` WRITE; /*!40000 ALTER TABLE `acc_cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `acc_cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `active_watchers` -- DROP TABLE IF EXISTS `active_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `active_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) DEFAULT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, `updated_winfo` int(11) NOT NULL, `flags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `active_watchers_idx` (`callid`,`to_tag`,`from_tag`), KEY `active_watchers_expires` (`expires`), KEY `active_watchers_pres` (`presentity_uri`,`event`), KEY `updated_idx` (`updated`), KEY `updated_winfo_idx` (`updated_winfo`,`presentity_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `active_watchers` -- LOCK TABLES `active_watchers` WRITE; /*!40000 ALTER TABLE `active_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `active_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `address` -- DROP TABLE IF EXISTS `address`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `address` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `grp` int(11) unsigned NOT NULL DEFAULT '1', `ip_addr` varchar(50) NOT NULL, `mask` int(11) NOT NULL DEFAULT '32', `port` smallint(5) unsigned NOT NULL DEFAULT '0', `tag` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=190 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `address` -- LOCK TABLES `address` WRITE; /*!40000 ALTER TABLE `address` DISABLE KEYS */; INSERT INTO `address` VALUES (127,8,'52.41.52.34',32,0,'name:Skyetel North West Inbound,gwgroup:1'),(128,8,'52.8.201.128',32,0,'name:Skyetel South West Inbound,gwgroup:1'),(129,8,'52.60.138.31',32,0,'name:Skyetel North East Inbound,gwgroup:1'),(130,8,'50.17.48.216',32,0,'name:Skyetel South East Inbound,gwgroup:1'),(131,8,'35.156.192.164',32,0,'name:Skyetel Europe Inbound,gwgroup:1'),(132,8,'term.skyetel.com',32,0,'name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(133,8,'52.41.52.34',32,0,'name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(134,8,'52.8.201.128',32,0,'name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(135,8,'50.17.48.216',32,0,'name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(136,8,'52.32.223.28',32,0,'name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(137,8,'52.4.178.107',32,0,'name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(138,8,'147.75.60.160',32,0,'name:Flowroute US-West-WA,gwgroup:2'),(139,8,'34.210.91.112',32,0,'name:Flowroute US-West-OR,gwgroup:2'),(140,8,'147.75.65.192',32,0,'name:Flowroute US-East-NJ,gwgroup:2'),(141,8,'34.226.36.32',32,0,'name:Flowroute US-East-VA,gwgroup:2'),(142,8,'81.201.82.45',32,0,'name:Voxbone Belgium,gwgroup:3'),(143,8,'81.201.84.195',32,0,'name:Voxbone LA,gwgroup:3'),(144,8,'81.201.85.45',32,0,'name:Voxbone NYC,gwgroup:3'),(145,8,'81.201.83.45',32,0,'name:Voxbone Germany,gwgroup:3'),(146,8,'81.201.86.45',32,0,'name:Voxbone Hong Kong,gwgroup:3'),(147,8,'81.201.84.195',32,0,'name:Voxbone Australia,gwgroup:3'),(148,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(149,8,'64.136.173.22',32,0,'name:VI Carrier,gwgroup:4'),(150,8,'209.166.128.200',32,0,'name:VI Carrier,gwgroup:4'),(151,8,'192.240.151.100',32,0,'name:VI Carrier,gwgroup:4'),(152,8,'64.136.173.31',32,0,'name:VI Carrier,gwgroup:4'),(153,8,'64.136.174.30',32,0,'name:VI Carrier,gwgroup:4'),(154,8,'64.136.174.20',32,0,'name:VI Carrier,gwgroup:4'),(155,8,'209.166.154.70',32,0,'name:VI Carrier,gwgroup:4'),(156,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(157,8,'64.136.173.23',32,0,'name:VI Carrier,gwgroup:4'),(158,8,'209.166.128.201',32,0,'name:VI Carrier,gwgroup:4'),(159,8,'192.240.151.101',32,0,'name:VI Carrier,gwgroup:4'),(160,8,'64.136.173.65',32,0,'name:VI Carrier,gwgroup:4'),(161,8,'64.136.174.65',32,0,'name:VI Carrier,gwgroup:4'),(162,8,'64.136.174.21',32,0,'name:VI Carrier,gwgroup:4'),(163,8,'209.166.154.71',32,0,'name:VI Carrier,gwgroup:4'),(164,8,'72.15.219.140',32,0,'name:Thinq Carrier,gwgroup:5'),(165,8,'216.147.191.157',32,0,'name:Voxtelesys Carrier,gwgroup:6'),(166,8,'64.34.181.47',32,0,'name:Les.net Carrier,gwgroup:7'); /*!40000 ALTER TABLE `address` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `aliases` -- DROP TABLE IF EXISTS `aliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `aliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(255) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `aliases` -- LOCK TABLES `aliases` WRITE; /*!40000 ALTER TABLE `aliases` DISABLE KEYS */; /*!40000 ALTER TABLE `aliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrier_name` -- DROP TABLE IF EXISTS `carrier_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrier_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrier_name` -- LOCK TABLES `carrier_name` WRITE; /*!40000 ALTER TABLE `carrier_name` DISABLE KEYS */; /*!40000 ALTER TABLE `carrier_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierfailureroute` -- DROP TABLE IF EXISTS `carrierfailureroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierfailureroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `host_name` varchar(128) NOT NULL DEFAULT '', `reply_code` varchar(3) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `next_domain` int(10) unsigned NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierfailureroute` -- LOCK TABLES `carrierfailureroute` WRITE; /*!40000 ALTER TABLE `carrierfailureroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierfailureroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `carrierroute` -- DROP TABLE IF EXISTS `carrierroute`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `carrierroute` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `carrier` int(10) unsigned NOT NULL DEFAULT '0', `domain` int(10) unsigned NOT NULL DEFAULT '0', `scan_prefix` varchar(64) NOT NULL DEFAULT '', `flags` int(11) unsigned NOT NULL DEFAULT '0', `mask` int(11) unsigned NOT NULL DEFAULT '0', `prob` float NOT NULL DEFAULT '0', `strip` int(11) unsigned NOT NULL DEFAULT '0', `rewrite_host` varchar(128) NOT NULL DEFAULT '', `rewrite_prefix` varchar(64) NOT NULL DEFAULT '', `rewrite_suffix` varchar(64) NOT NULL DEFAULT '', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `carrierroute` -- LOCK TABLES `carrierroute` WRITE; /*!40000 ALTER TABLE `carrierroute` DISABLE KEYS */; /*!40000 ALTER TABLE `carrierroute` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cdrs` -- DROP TABLE IF EXISTS `cdrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cdrs` ( `cdr_id` bigint(20) NOT NULL AUTO_INCREMENT, `src_username` varchar(64) NOT NULL DEFAULT '', `src_domain` varchar(128) NOT NULL DEFAULT '', `dst_username` varchar(64) NOT NULL DEFAULT '', `dst_domain` varchar(128) NOT NULL DEFAULT '', `dst_ousername` varchar(64) NOT NULL DEFAULT '', `call_start_time` datetime NOT NULL DEFAULT '2000-01-01 00:00:00', `duration` int(10) unsigned NOT NULL DEFAULT '0', `sip_call_id` varchar(128) NOT NULL DEFAULT '', `sip_from_tag` varchar(128) NOT NULL DEFAULT '', `sip_to_tag` varchar(128) NOT NULL DEFAULT '', `src_ip` varchar(64) NOT NULL DEFAULT '', `cost` int(11) NOT NULL DEFAULT '0', `rated` int(11) NOT NULL DEFAULT '0', `created` datetime NOT NULL, `calltype` varchar(20) DEFAULT NULL, `fraud` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`cdr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cdrs` -- LOCK TABLES `cdrs` WRITE; /*!40000 ALTER TABLE `cdrs` DISABLE KEYS */; /*!40000 ALTER TABLE `cdrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `cpl` -- DROP TABLE IF EXISTS `cpl`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `cpl` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL DEFAULT '', `cpl_xml` text, `cpl_bin` text, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `cpl` -- LOCK TABLES `cpl` WRITE; /*!40000 ALTER TABLE `cpl` DISABLE KEYS */; /*!40000 ALTER TABLE `cpl` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dbaliases` -- DROP TABLE IF EXISTS `dbaliases`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dbaliases` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `alias_username` varchar(64) NOT NULL DEFAULT '', `alias_domain` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `alias_user_idx` (`alias_username`), KEY `alias_idx` (`alias_username`,`alias_domain`), KEY `target_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dbaliases` -- LOCK TABLES `dbaliases` WRITE; /*!40000 ALTER TABLE `dbaliases` DISABLE KEYS */; /*!40000 ALTER TABLE `dbaliases` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog` -- DROP TABLE IF EXISTS `dialog`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `callid` varchar(255) NOT NULL, `from_uri` varchar(128) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_uri` varchar(128) NOT NULL, `to_tag` varchar(64) NOT NULL, `caller_cseq` varchar(20) NOT NULL, `callee_cseq` varchar(20) NOT NULL, `caller_route_set` varchar(512) DEFAULT NULL, `callee_route_set` varchar(512) DEFAULT NULL, `caller_contact` varchar(128) NOT NULL, `callee_contact` varchar(128) NOT NULL, `caller_sock` varchar(64) NOT NULL, `callee_sock` varchar(64) NOT NULL, `state` int(10) unsigned NOT NULL, `start_time` int(10) unsigned NOT NULL, `timeout` int(10) unsigned NOT NULL DEFAULT '0', `sflags` int(10) unsigned NOT NULL DEFAULT '0', `iflags` int(10) unsigned NOT NULL DEFAULT '0', `toroute_name` varchar(32) DEFAULT NULL, `req_uri` varchar(128) NOT NULL, `xdata` varchar(512) DEFAULT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog` -- LOCK TABLES `dialog` WRITE; /*!40000 ALTER TABLE `dialog` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialog_vars` -- DROP TABLE IF EXISTS `dialog_vars`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialog_vars` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `hash_entry` int(10) unsigned NOT NULL, `hash_id` int(10) unsigned NOT NULL, `dialog_key` varchar(128) NOT NULL, `dialog_value` varchar(512) NOT NULL, PRIMARY KEY (`id`), KEY `hash_idx` (`hash_entry`,`hash_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialog_vars` -- LOCK TABLES `dialog_vars` WRITE; /*!40000 ALTER TABLE `dialog_vars` DISABLE KEYS */; /*!40000 ALTER TABLE `dialog_vars` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dialplan` -- DROP TABLE IF EXISTS `dialplan`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dialplan` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `dpid` int(11) NOT NULL, `pr` int(11) NOT NULL, `match_op` int(11) NOT NULL, `match_exp` varchar(64) NOT NULL, `match_len` int(11) NOT NULL, `subst_exp` varchar(64) NOT NULL, `repl_exp` varchar(256) NOT NULL, `attrs` varchar(64) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dialplan` -- LOCK TABLES `dialplan` WRITE; /*!40000 ALTER TABLE `dialplan` DISABLE KEYS */; /*!40000 ALTER TABLE `dialplan` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dispatcher` -- DROP TABLE IF EXISTS `dispatcher`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dispatcher` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(11) NOT NULL DEFAULT '0', `destination` varchar(192) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `priority` int(11) NOT NULL DEFAULT '0', `attrs` varchar(128) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dispatcher` -- LOCK TABLES `dispatcher` WRITE; /*!40000 ALTER TABLE `dispatcher` DISABLE KEYS */; /*!40000 ALTER TABLE `dispatcher` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain` -- DROP TABLE IF EXISTS `domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) NOT NULL, `did` varchar(64) DEFAULT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain` -- LOCK TABLES `domain` WRITE; /*!40000 ALTER TABLE `domain` DISABLE KEYS */; /*!40000 ALTER TABLE `domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_attrs` -- DROP TABLE IF EXISTS `domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `type` int(10) unsigned NOT NULL, `value` varchar(255) NOT NULL, `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `domain_attrs_idx` (`did`,`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_attrs` -- LOCK TABLES `domain_attrs` WRITE; /*!40000 ALTER TABLE `domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domain_name` -- DROP TABLE IF EXISTS `domain_name`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domain_name` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `domain` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domain_name` -- LOCK TABLES `domain_name` WRITE; /*!40000 ALTER TABLE `domain_name` DISABLE KEYS */; /*!40000 ALTER TABLE `domain_name` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `domainpolicy` -- DROP TABLE IF EXISTS `domainpolicy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `domainpolicy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rule` varchar(255) NOT NULL, `type` varchar(255) NOT NULL, `att` varchar(255) DEFAULT NULL, `val` varchar(128) DEFAULT NULL, `description` varchar(255) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rav_idx` (`rule`,`att`,`val`), KEY `rule_idx` (`rule`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `domainpolicy` -- LOCK TABLES `domainpolicy` WRITE; /*!40000 ALTER TABLE `domainpolicy` DISABLE KEYS */; /*!40000 ALTER TABLE `domainpolicy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gateways` -- DROP TABLE IF EXISTS `dr_gateways`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gateways` ( `gwid` int(10) unsigned NOT NULL AUTO_INCREMENT, `type` int(11) unsigned NOT NULL DEFAULT '0', `address` varchar(128) NOT NULL, `strip` int(11) unsigned NOT NULL DEFAULT '0', `pri_prefix` varchar(64) DEFAULT NULL, `attrs` varchar(255) DEFAULT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`gwid`) ) ENGINE=InnoDB AUTO_INCREMENT=64 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gateways` -- LOCK TABLES `dr_gateways` WRITE; /*!40000 ALTER TABLE `dr_gateways` DISABLE KEYS */; INSERT INTO `dr_gateways` VALUES (1,8,'52.41.52.34',0,'','','name:Skyetel North West Inbound,gwgroup:1'),(2,8,'52.8.201.128',0,'','','name:Skyetel South West Inbound,gwgroup:1'),(3,8,'52.60.138.31',0,'','','name:Skyetel North East Inbound,gwgroup:1'),(4,8,'50.17.48.216',0,'','','name:Skyetel South East Inbound,gwgroup:1'),(5,8,'35.156.192.164',0,'','','name:Skyetel Europe Inbound,gwgroup:1'),(6,8,'term.skyetel.com',0,'','','name:Skyetel 1st Priority Outbound Call,gwgroup:1'),(7,8,'52.41.52.34',0,'','','name:Skyetel 2nd Priority Outbound Call,gwgroup:1'),(8,8,'52.8.201.128',0,'','','name:Skyetel 3rd Priority Outbound Call,gwgroup:1'),(9,8,'50.17.48.216',0,'','','name:Skyetel 4rd Priority Outbound Call,gwgroup:1'),(10,8,'52.32.223.28',0,'','','name:Skyetel North West High Cost Outbound Traffic,gwgroup:1'),(11,8,'52.4.178.107',0,'','','name:Skyetel South East High Cost Outbound Traffic,gwgroup:1'),(12,8,'147.75.60.160',0,'','','name:Flowroute US-West-WA,gwgroup:2'),(13,8,'34.210.91.112',0,'','','name:Flowroute US-West-OR,gwgroup:2'),(14,8,'147.75.65.192',0,'','','name:Flowroute US-East-NJ,gwgroup:2'),(15,8,'34.226.36.32',0,'','','name:Flowroute US-East-VA,gwgroup:2'),(16,8,'81.201.82.45',0,'','','name:Voxbone Belgium,gwgroup:3'),(17,8,'81.201.84.195',0,'','','name:Voxbone LA,gwgroup:3'),(18,8,'81.201.85.45',0,'','','name:Voxbone NYC,gwgroup:3'),(19,8,'81.201.83.45',0,'','','name:Voxbone Germany,gwgroup:3'),(20,8,'81.201.86.45',0,'','','name:Voxbone Hong Kong,gwgroup:3'),(21,8,'81.201.84.195',0,'','','name:Voxbone Australia,gwgroup:3'),(22,8,'64.136.174.30',0,'','','name:VI Carrier,gwgroup:4'),(23,8,'64.136.173.22',0,'','','name:VI Carrier,gwgroup:4'),(24,8,'209.166.128.200',0,'','','name:VI Carrier,gwgroup:4'),(25,8,'192.240.151.100',0,'','','name:VI Carrier,gwgroup:4'),(26,8,'64.136.173.31',0,'','','name:VI Carrier,gwgroup:4'),(27,8,'64.136.174.30',0,'','','name:VI Carrier,gwgroup:4'),(28,8,'64.136.174.20',0,'','','name:VI Carrier,gwgroup:4'),(29,8,'209.166.154.70',0,'','','name:VI Carrier,gwgroup:4'),(30,8,'64.136.174.65',0,'','','name:VI Carrier,gwgroup:4'),(31,8,'64.136.173.23',0,'','','name:VI Carrier,gwgroup:4'),(32,8,'209.166.128.201',0,'','','name:VI Carrier,gwgroup:4'),(33,8,'192.240.151.101',0,'','','name:VI Carrier,gwgroup:4'),(34,8,'64.136.173.65',0,'','','name:VI Carrier,gwgroup:4'),(35,8,'64.136.174.65',0,'','','name:VI Carrier,gwgroup:4'),(36,8,'64.136.174.21',0,'','','name:VI Carrier,gwgroup:4'),(37,8,'209.166.154.71',0,'','','name:VI Carrier,gwgroup:4'),(38,8,'72.15.219.140',0,'','','name:Thinq Carrier,gwgroup:5'),(39,8,'216.147.191.157',0,'','','name:Voxtelesys Carrier,gwgroup:6'),(40,8,'64.34.181.47',0,'','','name:Les.net Carrier,gwgroup:7'); /*!40000 ALTER TABLE `dr_gateways` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_groups` -- DROP TABLE IF EXISTS `dr_groups`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_groups` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', `groupid` int(11) unsigned NOT NULL DEFAULT '0', `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_groups` -- LOCK TABLES `dr_groups` WRITE; /*!40000 ALTER TABLE `dr_groups` DISABLE KEYS */; /*!40000 ALTER TABLE `dr_groups` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dr_gw_lists` -- DROP TABLE IF EXISTS `dr_gw_lists`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_gw_lists` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_gw_lists` -- LOCK TABLES `dr_gw_lists` WRITE; /*!40000 ALTER TABLE `dr_gw_lists` DISABLE KEYS */; INSERT INTO `dr_gw_lists` VALUES (1,'1,2,3,4,5,6,7,8,9,10,11','name:Skyetel CarrierGroup,type:8'),(2,'12,13,14,15','name:Flowroute CarrierGroup,type:8'),(3,'16,17,18,19,20,21','name:Voxbone CarrierGroup,type:8'),(4,'22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37','name:VI CarrierGroup,type:8'),(5,'38','name:Thinq CarrierGroup,type:8'),(6,'39','name:Voxtelesys CarrierGroup,type:8'),(7,'40','name:Les.net CarrierGroup,type:8'); /*!40000 ALTER TABLE `dr_gw_lists` ENABLE KEYS */; UNLOCK TABLES; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER insert_gw2gwgroup AFTER INSERT ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(new.gwlist, ',', gw_index), ',', -1), cast(new.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER update_gw2gwgroup AFTER UPDATE ON dr_gw_lists FOR EACH ROW BEGIN DECLARE num_gws int DEFAULT 0; DECLARE gw_index int DEFAULT 1; IF NOT (NEW.gwlist <=> OLD.gwlist) THEN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(old.id AS char(64)); IF CHAR_LENGTH(NEW.gwlist) > 0 THEN SET num_gws := (CHAR_LENGTH(NEW.gwlist) - CHAR_LENGTH(REPLACE(NEW.gwlist, ',', '')) + 1); WHILE gw_index <= num_gws DO INSERT IGNORE INTO dsip_gw2gwgroup VALUES (SUBSTRING_INDEX(SUBSTRING_INDEX(new.gwlist, ',', gw_index), ',', -1), cast(new.id AS char(64)), DEFAULT, DEFAULT); SET gw_index := gw_index + 1; END WHILE; END IF; ELSEIF NOT (NEW.id <=> OLD.id) THEN UPDATE dsip_gw2gwgroup SET gwgroupid = cast(new.id AS char(64)) WHERE gwgroupid = cast(old.id AS char(64)); END IF; END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8mb4 */ ; /*!50003 SET character_set_results = utf8mb4 */ ; /*!50003 SET collation_connection = utf8mb4_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = 'NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ; DELIMITER ;; /*!50003 CREATE*/ /*!50017 DEFINER=`root`@`localhost`*/ /*!50003 TRIGGER delete_gw2gwgroup AFTER DELETE ON dr_gw_lists FOR EACH ROW BEGIN DELETE FROM dsip_gw2gwgroup WHERE gwgroupid = cast(old.id AS char(64)); END */;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -- -- Table structure for table `dr_rules` -- DROP TABLE IF EXISTS `dr_rules`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dr_rules` ( `ruleid` int(10) unsigned NOT NULL AUTO_INCREMENT, `groupid` varchar(255) NOT NULL, `prefix` varchar(64) NOT NULL, `timerec` varchar(255) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', `routeid` varchar(64) NOT NULL, `gwlist` varchar(255) NOT NULL, `description` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`ruleid`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dr_rules` -- LOCK TABLES `dr_rules` WRITE; /*!40000 ALTER TABLE `dr_rules` DISABLE KEYS */; INSERT INTO `dr_rules` VALUES (1,'8000','','',0,'','1,2','name:Default Outbound Route'); /*!40000 ALTER TABLE `dr_rules` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_calllimit` -- DROP TABLE IF EXISTS `dsip_calllimit`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_calllimit` ( `gwgroupid` varchar(64) NOT NULL, `limit` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(1) NOT NULL DEFAULT '1', `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_calllimit` -- LOCK TABLES `dsip_calllimit` WRITE; /*!40000 ALTER TABLE `dsip_calllimit` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_calllimit` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_domain_mapping` -- DROP TABLE IF EXISTS `dsip_domain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_domain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `domain_id` int(10) NOT NULL, `attr_list` varchar(255) NOT NULL, `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_domain_mapping` -- LOCK TABLES `dsip_domain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_domain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_domain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_endpoint_lease` -- DROP TABLE IF EXISTS `dsip_endpoint_lease`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_endpoint_lease` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `gwid` int(10) unsigned NOT NULL, `sid` int(10) unsigned NOT NULL, `expiration` datetime NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_endpoint_lease` -- LOCK TABLES `dsip_endpoint_lease` WRITE; /*!40000 ALTER TABLE `dsip_endpoint_lease` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_endpoint_lease` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_failfwd` -- DROP TABLE IF EXISTS `dsip_failfwd`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_failfwd` ( `dr_ruleid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `dr_groupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`dr_ruleid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_failfwd` -- LOCK TABLES `dsip_failfwd` WRITE; /*!40000 ALTER TABLE `dsip_failfwd` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_failfwd` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_gw2gwgroup` -- DROP TABLE IF EXISTS `dsip_gw2gwgroup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_gw2gwgroup` ( `gwid` varchar(64) NOT NULL, `gwgroupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`gwid`,`gwgroupid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_gw2gwgroup` -- LOCK TABLES `dsip_gw2gwgroup` WRITE; /*!40000 ALTER TABLE `dsip_gw2gwgroup` DISABLE KEYS */; INSERT INTO `dsip_gw2gwgroup` VALUES ('1','1','0','0'),('10','1','0','0'),('11','1','0','0'),('12','2','0','0'),('13','2','0','0'),('14','2','0','0'),('15','2','0','0'),('16','3','0','0'),('17','3','0','0'),('18','3','0','0'),('19','3','0','0'),('2','1','0','0'),('20','3','0','0'),('21','3','0','0'),('22','4','0','0'),('23','4','0','0'),('24','4','0','0'),('25','4','0','0'),('26','4','0','0'),('27','4','0','0'),('28','4','0','0'),('29','4','0','0'),('3','1','0','0'),('30','4','0','0'),('31','4','0','0'),('32','4','0','0'),('33','4','0','0'),('34','4','0','0'),('35','4','0','0'),('36','4','0','0'),('37','4','0','0'),('38','5','0','0'),('39','6','0','0'),('4','1','0','0'),('40','7','0','0'),('5','1','0','0'),('6','1','0','0'),('7','1','0','0'),('8','1','0','0'),('9','1','0','0'); /*!40000 ALTER TABLE `dsip_gw2gwgroup` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_hardfwd` -- DROP TABLE IF EXISTS `dsip_hardfwd`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_hardfwd` ( `dr_ruleid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `dr_groupid` varchar(64) NOT NULL, `key_type` varchar(64) NOT NULL DEFAULT '0', `value_type` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`dr_ruleid`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_hardfwd` -- LOCK TABLES `dsip_hardfwd` WRITE; /*!40000 ALTER TABLE `dsip_hardfwd` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_hardfwd` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_lcr` -- DROP TABLE IF EXISTS `dsip_lcr`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_lcr` ( `pattern` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `dr_groupid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `cost` decimal(3,2) NOT NULL DEFAULT '0.00', `from_prefix` varchar(64) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`pattern`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_lcr` -- LOCK TABLES `dsip_lcr` WRITE; /*!40000 ALTER TABLE `dsip_lcr` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_lcr` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_maintmode` -- DROP TABLE IF EXISTS `dsip_maintmode`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_maintmode` ( `ipaddr` varchar(64) NOT NULL DEFAULT '', `key_type` varchar(64) NOT NULL DEFAULT '0', `gwid` varchar(64) NOT NULL DEFAULT '', `value_type` varchar(64) NOT NULL DEFAULT '0', `status` tinyint(4) NOT NULL DEFAULT '1', PRIMARY KEY (`ipaddr`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_maintmode` -- LOCK TABLES `dsip_maintmode` WRITE; /*!40000 ALTER TABLE `dsip_maintmode` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_maintmode` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_multidomain_mapping` -- DROP TABLE IF EXISTS `dsip_multidomain_mapping`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_multidomain_mapping` ( `id` int(10) NOT NULL AUTO_INCREMENT, `pbx_id` int(10) NOT NULL, `db_host` varchar(20) NOT NULL, `db_username` varchar(40) NOT NULL, `db_password` varchar(40) NOT NULL, `domain_list` varchar(255) NOT NULL DEFAULT '', `domain_list_hash` varchar(255) NOT NULL DEFAULT '', `attr_list` varchar(255) NOT NULL DEFAULT '', `type` tinyint(3) NOT NULL DEFAULT '0', `enabled` tinyint(1) NOT NULL DEFAULT '0', `lastsync` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `syncstatus` tinyint(1) NOT NULL DEFAULT '0', `syncerror` varchar(200) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_multidomain_mapping` -- LOCK TABLES `dsip_multidomain_mapping` WRITE; /*!40000 ALTER TABLE `dsip_multidomain_mapping` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_multidomain_mapping` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `dsip_notification` -- DROP TABLE IF EXISTS `dsip_notification`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_notification` ( `gwgroupid` int(11) NOT NULL, `type` int(11) NOT NULL, `method` int(11) DEFAULT NULL, `value` varchar(255) DEFAULT NULL, `createdate` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`gwgroupid`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_notification` -- LOCK TABLES `dsip_notification` WRITE; /*!40000 ALTER TABLE `dsip_notification` DISABLE KEYS */; /*!40000 ALTER TABLE `dsip_notification` ENABLE KEYS */; UNLOCK TABLES; -- -- Temporary table structure for view `dsip_prefix_mapping` -- DROP TABLE IF EXISTS `dsip_prefix_mapping`; /*!50001 DROP VIEW IF EXISTS `dsip_prefix_mapping`*/; SET @saved_cs_client = @@character_set_client; SET character_set_client = utf8; /*!50001 CREATE TABLE `dsip_prefix_mapping` ( `prefix` tinyint NOT NULL, `ruleid` tinyint NOT NULL, `key_type` tinyint NOT NULL, `value_type` tinyint NOT NULL ) ENGINE=MyISAM */; SET character_set_client = @saved_cs_client; -- -- Table structure for table `dsip_settings` -- DROP TABLE IF EXISTS `dsip_settings`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `dsip_settings` ( `DSIP_ID` int(10) unsigned NOT NULL DEFAULT '1', `DSIP_PROTO` varchar(255) NOT NULL DEFAULT 'http', `DSIP_HOST` varchar(255) NOT NULL DEFAULT '0.0.0.0', `DSIP_PORT` int(11) NOT NULL DEFAULT '5000', `DSIP_USERNAME` varchar(255) NOT NULL DEFAULT 'admin', `DSIP_PASSWORD` binary(128) NOT NULL, `DSIP_SALT` binary(128) NOT NULL, `DSIP_API_PROTO` varchar(255) NOT NULL DEFAULT 'http', `DSIP_API_HOST` varchar(255) NOT NULL DEFAULT '127.0.0.1', `DSIP_API_PORT` int(11) NOT NULL DEFAULT '5000', `DSIP_API_TOKEN` varbinary(160) NOT NULL, `DSIP_LOG_LEVEL` int(11) NOT NULL DEFAULT '3', `DSIP_LOG_FACILITY` int(11) NOT NULL DEFAULT '18', `DSIP_SSL_KEY` varchar(255) NOT NULL DEFAULT '', `DSIP_SSL_CERT` varchar(255) NOT NULL DEFAULT '', `DSIP_SSL_EMAIL` varchar(255) NOT NULL DEFAULT '', `VERSION` varchar(255) NOT NULL DEFAULT '0.523+ent', `DEBUG` tinyint(1) NOT NULL DEFAULT '0', `ROLE` varchar(255) NOT NULL DEFAULT '', `KAM_DB_HOST` varchar(255) NOT NULL DEFAULT 'localhost', `KAM_DB_DRIVER` varchar(255) NOT NULL DEFAULT '', `KAM_DB_TYPE` varchar(255) NOT NULL DEFAULT 'mysql', `KAM_DB_PORT` varchar(255) NOT NULL DEFAULT '3306', `KAM_DB_NAME` varchar(255) NOT NULL DEFAULT 'kamailio', `KAM_DB_USER` varchar(255) NOT NULL DEFAULT 'kamailio', `KAM_DB_PASS` varbinary(160) NOT NULL, `KAM_KAMCMD_PATH` varchar(255) NOT NULL DEFAULT '/usr/sbin/kamcmd', `KAM_CFG_PATH` varchar(255) NOT NULL DEFAULT '/etc/kamailio/kamailio.cfg', `RTP_CFG_PATH` varchar(255) NOT NULL DEFAULT '/etc/kamailio/kamailio.cfg', `FLT_CARRIER` int(11) NOT NULL DEFAULT '8', `FLT_PBX` int(11) NOT NULL DEFAULT '9', `FLT_OUTBOUND` int(11) NOT NULL DEFAULT '8000', `FLT_INBOUND` int(11) NOT NULL DEFAULT '9000', `FLT_LCR_MIN` int(11) NOT NULL DEFAULT '10000', `FLT_FWD_MIN` int(11) NOT NULL DEFAULT '20000', `DEFAULT_AUTH_DOMAIN` varchar(255) NOT NULL DEFAULT 'sip.dsiprouter.org', `TELEBLOCK_GW_ENABLED` int(11) NOT NULL DEFAULT '0', `TELEBLOCK_GW_IP` varchar(255) NOT NULL DEFAULT '62.34.24.22', `TELEBLOCK_GW_PORT` varchar(255) NOT NULL DEFAULT '5066', `TELEBLOCK_MEDIA_IP` varchar(255) NOT NULL DEFAULT '', `TELEBLOCK_MEDIA_PORT` varchar(255) NOT NULL DEFAULT '', `FLOWROUTE_ACCESS_KEY` varchar(255) NOT NULL DEFAULT '', `FLOWROUTE_SECRET_KEY` varchar(255) NOT NULL DEFAULT '', `FLOWROUTE_API_ROOT_URL` varchar(255) NOT NULL DEFAULT 'https://api.flowroute.com/v2', `UPLOAD_FOLDER` varchar(255) NOT NULL DEFAULT '/tmp', `CLOUD_PLATFORM` varchar(255) NOT NULL DEFAULT '', `MAIL_SERVER` varchar(255) NOT NULL DEFAULT 'smtp.gmail.com', `MAIL_PORT` int(11) NOT NULL DEFAULT '587', `MAIL_USE_TLS` tinyint(1) NOT NULL DEFAULT '1', `MAIL_USERNAME` varchar(255) NOT NULL DEFAULT '', `MAIL_PASSWORD` varbinary(160) NOT NULL, `MAIL_ASCII_ATTACHMENTS` tinyint(1) NOT NULL DEFAULT '0', `MAIL_DEFAULT_SENDER` varchar(255) NOT NULL DEFAULT 'DoNotReply@smtp.gmail.com', `MAIL_DEFAULT_SUBJECT` varchar(255) NOT NULL DEFAULT 'dSIPRouter System Notification', PRIMARY KEY (`DSIP_ID`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 MIN_ROWS=1 MAX_ROWS=1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `dsip_settings` -- LOCK TABLES `dsip_settings` WRITE; /*!40000 ALTER TABLE `dsip_settings` DISABLE KEYS */; INSERT INTO `dsip_settings` VALUES (1,'http','0.0.0.0',5000,'admin','97652880df95b6fc581f24d5f61189bbc40d9684da22efce32f4161140001bc78f6dceb3e3d95630a420d0fc8388c9fc9914830fff3777792da0c3a4467893c8','f8d5442c876afda0cf72c7f0e29b7815325c94a712788bb570857c28d8132ac99ffead3c6a01667e12f9463d380888e6567aa0f30f9878e2fc68d0b968d25425','http','127.0.0.1',5000,'accf0cc8bffbc746dc4b5a03ec4174dea0818a6eb7e7aa26b799b0c1c1ea8f29',3,18,'','','','0.60+ent',0,'','localhost','','mysql','3306','kamailio','kamailio','d67225cd123dffb2cd4acf6a49a53d55','/usr/sbin/kamcmd','/etc/kamailio/kamailio.cfg','/etc/kamailio/kamailio.cfg',0,0,8,9,8000,9000,10000,20000,'sip.dsiprouter.org',0,'62.34.24.22','5066','','','','','https://api.flowroute.com/v2','/tmp','','smtp.gmail.com',587,1,'','',0,'DoNotReply@smtp.gmail.com','dSIPRouter System Notification'); /*!40000 ALTER TABLE `dsip_settings` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `globalblacklist` -- DROP TABLE IF EXISTS `globalblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `globalblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', `description` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), KEY `globalblacklist_idx` (`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `globalblacklist` -- LOCK TABLES `globalblacklist` WRITE; /*!40000 ALTER TABLE `globalblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `globalblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `grp` -- DROP TABLE IF EXISTS `grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `grp` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_group_idx` (`username`,`domain`,`grp`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `grp` -- LOCK TABLES `grp` WRITE; /*!40000 ALTER TABLE `grp` DISABLE KEYS */; /*!40000 ALTER TABLE `grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `htable` -- DROP TABLE IF EXISTS `htable`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `htable` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `key_name` varchar(64) NOT NULL DEFAULT '', `key_type` int(11) NOT NULL DEFAULT '0', `value_type` int(11) NOT NULL DEFAULT '0', `key_value` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `htable` -- LOCK TABLES `htable` WRITE; /*!40000 ALTER TABLE `htable` DISABLE KEYS */; /*!40000 ALTER TABLE `htable` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_members` -- DROP TABLE IF EXISTS `imc_members`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_members` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `room` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_room_idx` (`username`,`domain`,`room`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_members` -- LOCK TABLES `imc_members` WRITE; /*!40000 ALTER TABLE `imc_members` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_members` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `imc_rooms` -- DROP TABLE IF EXISTS `imc_rooms`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `imc_rooms` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flag` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name_domain_idx` (`name`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `imc_rooms` -- LOCK TABLES `imc_rooms` WRITE; /*!40000 ALTER TABLE `imc_rooms` DISABLE KEYS */; /*!40000 ALTER TABLE `imc_rooms` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_gw` -- DROP TABLE IF EXISTS `lcr_gw`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_gw` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `gw_name` varchar(128) DEFAULT NULL, `ip_addr` varchar(50) DEFAULT NULL, `hostname` varchar(64) DEFAULT NULL, `port` smallint(5) unsigned DEFAULT NULL, `params` varchar(64) DEFAULT NULL, `uri_scheme` tinyint(3) unsigned DEFAULT NULL, `transport` tinyint(3) unsigned DEFAULT NULL, `strip` tinyint(3) unsigned DEFAULT NULL, `prefix` varchar(16) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `defunct` int(10) unsigned DEFAULT NULL, PRIMARY KEY (`id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_gw` -- LOCK TABLES `lcr_gw` WRITE; /*!40000 ALTER TABLE `lcr_gw` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_gw` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule` -- DROP TABLE IF EXISTS `lcr_rule`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `prefix` varchar(16) DEFAULT NULL, `from_uri` varchar(64) DEFAULT NULL, `request_uri` varchar(64) DEFAULT NULL, `mt_tvalue` varchar(128) DEFAULT NULL, `stopper` int(10) unsigned NOT NULL DEFAULT '0', `enabled` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `lcr_id_prefix_from_uri_idx` (`lcr_id`,`prefix`,`from_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule` -- LOCK TABLES `lcr_rule` WRITE; /*!40000 ALTER TABLE `lcr_rule` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `lcr_rule_target` -- DROP TABLE IF EXISTS `lcr_rule_target`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `lcr_rule_target` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `lcr_id` smallint(5) unsigned NOT NULL, `rule_id` int(10) unsigned NOT NULL, `gw_id` int(10) unsigned NOT NULL, `priority` tinyint(3) unsigned NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', PRIMARY KEY (`id`), UNIQUE KEY `rule_id_gw_id_idx` (`rule_id`,`gw_id`), KEY `lcr_id_idx` (`lcr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `lcr_rule_target` -- LOCK TABLES `lcr_rule_target` WRITE; /*!40000 ALTER TABLE `lcr_rule_target` DISABLE KEYS */; /*!40000 ALTER TABLE `lcr_rule_target` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `locale_lookup` -- DROP TABLE IF EXISTS `locale_lookup`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `locale_lookup` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `locale` varchar(64) NOT NULL DEFAULT '', `fprefix` varchar(64) NOT NULL DEFAULT '0', `tprefix` varchar(64) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `locale_lookup` -- LOCK TABLES `locale_lookup` WRITE; /*!40000 ALTER TABLE `locale_lookup` DISABLE KEYS */; /*!40000 ALTER TABLE `locale_lookup` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location` -- DROP TABLE IF EXISTS `location`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `contact` varchar(512) NOT NULL DEFAULT '', `received` varchar(128) DEFAULT NULL, `path` varchar(512) DEFAULT NULL, `expires` datetime NOT NULL DEFAULT '2030-05-28 21:32:15', `q` float(10,2) NOT NULL DEFAULT '1.00', `callid` varchar(255) NOT NULL DEFAULT 'Default-Call-ID', `cseq` int(11) NOT NULL DEFAULT '1', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `flags` int(11) NOT NULL DEFAULT '0', `cflags` int(11) NOT NULL DEFAULT '0', `user_agent` varchar(255) NOT NULL DEFAULT '', `socket` varchar(64) DEFAULT NULL, `methods` int(11) DEFAULT NULL, `instance` varchar(255) DEFAULT NULL, `reg_id` int(11) NOT NULL DEFAULT '0', `server_id` int(11) NOT NULL DEFAULT '0', `connection_id` int(11) NOT NULL DEFAULT '0', `keepalive` int(11) NOT NULL DEFAULT '0', `partition` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `ruid_idx` (`ruid`), KEY `account_contact_idx` (`username`,`domain`,`contact`(255)), KEY `expires_idx` (`expires`), KEY `connection_idx` (`server_id`,`connection_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location` -- LOCK TABLES `location` WRITE; /*!40000 ALTER TABLE `location` DISABLE KEYS */; /*!40000 ALTER TABLE `location` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `location_attrs` -- DROP TABLE IF EXISTS `location_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `location_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `ruid` varchar(64) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) DEFAULT NULL, `aname` varchar(64) NOT NULL DEFAULT '', `atype` int(11) NOT NULL DEFAULT '0', `avalue` varchar(255) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `account_record_idx` (`username`,`domain`,`ruid`), KEY `last_modified_idx` (`last_modified`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `location_attrs` -- LOCK TABLES `location_attrs` WRITE; /*!40000 ALTER TABLE `location_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `location_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `missed_calls` -- DROP TABLE IF EXISTS `missed_calls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `missed_calls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `method` varchar(16) NOT NULL DEFAULT '', `from_tag` varchar(64) NOT NULL DEFAULT '', `to_tag` varchar(64) NOT NULL DEFAULT '', `callid` varchar(255) NOT NULL DEFAULT '', `sip_code` varchar(3) NOT NULL DEFAULT '', `sip_reason` varchar(128) NOT NULL DEFAULT '', `time` datetime NOT NULL, PRIMARY KEY (`id`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `missed_calls` -- LOCK TABLES `missed_calls` WRITE; /*!40000 ALTER TABLE `missed_calls` DISABLE KEYS */; /*!40000 ALTER TABLE `missed_calls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqcalls` -- DROP TABLE IF EXISTS `mohqcalls`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqcalls` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `mohq_id` int(10) unsigned NOT NULL, `call_id` varchar(100) NOT NULL, `call_status` int(10) unsigned NOT NULL, `call_from` varchar(100) NOT NULL, `call_contact` varchar(100) DEFAULT NULL, `call_time` datetime NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqcalls_idx` (`call_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqcalls` -- LOCK TABLES `mohqcalls` WRITE; /*!40000 ALTER TABLE `mohqcalls` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqcalls` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mohqueues` -- DROP TABLE IF EXISTS `mohqueues`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mohqueues` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(25) NOT NULL, `uri` varchar(100) NOT NULL, `mohdir` varchar(100) DEFAULT NULL, `mohfile` varchar(100) NOT NULL, `debug` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `mohqueue_uri_idx` (`uri`), UNIQUE KEY `mohqueue_name_idx` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mohqueues` -- LOCK TABLES `mohqueues` WRITE; /*!40000 ALTER TABLE `mohqueues` DISABLE KEYS */; /*!40000 ALTER TABLE `mohqueues` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtree` -- DROP TABLE IF EXISTS `mtree`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtree` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tprefix_idx` (`tprefix`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtree` -- LOCK TABLES `mtree` WRITE; /*!40000 ALTER TABLE `mtree` DISABLE KEYS */; /*!40000 ALTER TABLE `mtree` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `mtrees` -- DROP TABLE IF EXISTS `mtrees`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `mtrees` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `tname` varchar(128) NOT NULL DEFAULT '', `tprefix` varchar(32) NOT NULL DEFAULT '', `tvalue` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `tname_tprefix_tvalue_idx` (`tname`,`tprefix`,`tvalue`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `mtrees` -- LOCK TABLES `mtrees` WRITE; /*!40000 ALTER TABLE `mtrees` DISABLE KEYS */; /*!40000 ALTER TABLE `mtrees` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pdt` -- DROP TABLE IF EXISTS `pdt`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pdt` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sdomain` varchar(128) NOT NULL, `prefix` varchar(32) NOT NULL, `domain` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `sdomain_prefix_idx` (`sdomain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pdt` -- LOCK TABLES `pdt` WRITE; /*!40000 ALTER TABLE `pdt` DISABLE KEYS */; /*!40000 ALTER TABLE `pdt` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pl_pipes` -- DROP TABLE IF EXISTS `pl_pipes`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pl_pipes` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pipeid` varchar(64) NOT NULL DEFAULT '', `algorithm` varchar(32) NOT NULL DEFAULT '', `plimit` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pl_pipes` -- LOCK TABLES `pl_pipes` WRITE; /*!40000 ALTER TABLE `pl_pipes` DISABLE KEYS */; /*!40000 ALTER TABLE `pl_pipes` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `presentity` -- DROP TABLE IF EXISTS `presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL, `etag` varchar(64) NOT NULL, `expires` int(11) NOT NULL, `received_time` int(11) NOT NULL, `body` blob NOT NULL, `sender` varchar(128) NOT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `presentity_idx` (`username`,`domain`,`event`,`etag`), KEY `presentity_expires` (`expires`), KEY `account_idx` (`username`,`domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `presentity` -- LOCK TABLES `presentity` WRITE; /*!40000 ALTER TABLE `presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `pua` -- DROP TABLE IF EXISTS `pua`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `pua` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `pres_uri` varchar(128) NOT NULL, `pres_id` varchar(255) NOT NULL, `event` int(11) NOT NULL, `expires` int(11) NOT NULL, `desired_expires` int(11) NOT NULL, `flag` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `tuple_id` varchar(64) DEFAULT NULL, `watcher_uri` varchar(128) NOT NULL, `call_id` varchar(255) NOT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `cseq` int(11) NOT NULL, `record_route` text, `contact` varchar(128) NOT NULL, `remote_contact` varchar(128) NOT NULL, `version` int(11) NOT NULL, `extra_headers` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `pua_idx` (`etag`,`tuple_id`,`call_id`,`from_tag`), KEY `expires_idx` (`expires`), KEY `dialog1_idx` (`pres_id`,`pres_uri`), KEY `dialog2_idx` (`call_id`,`from_tag`), KEY `record_idx` (`pres_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `pua` -- LOCK TABLES `pua` WRITE; /*!40000 ALTER TABLE `pua` DISABLE KEYS */; /*!40000 ALTER TABLE `pua` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `purplemap` -- DROP TABLE IF EXISTS `purplemap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `purplemap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `sip_user` varchar(128) NOT NULL, `ext_user` varchar(128) NOT NULL, `ext_prot` varchar(16) NOT NULL, `ext_pass` varchar(64) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `purplemap` -- LOCK TABLES `purplemap` WRITE; /*!40000 ALTER TABLE `purplemap` DISABLE KEYS */; /*!40000 ALTER TABLE `purplemap` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `re_grp` -- DROP TABLE IF EXISTS `re_grp`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `re_grp` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `reg_exp` varchar(128) NOT NULL DEFAULT '', `group_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `group_idx` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `re_grp` -- LOCK TABLES `re_grp` WRITE; /*!40000 ALTER TABLE `re_grp` DISABLE KEYS */; /*!40000 ALTER TABLE `re_grp` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_presentity` -- DROP TABLE IF EXISTS `rls_presentity`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_presentity` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rlsubs_did` varchar(255) NOT NULL, `resource_uri` varchar(128) NOT NULL, `content_type` varchar(255) NOT NULL, `presence_state` blob NOT NULL, `expires` int(11) NOT NULL, `updated` int(11) NOT NULL, `auth_state` int(11) NOT NULL, `reason` varchar(64) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_presentity_idx` (`rlsubs_did`,`resource_uri`), KEY `rlsubs_idx` (`rlsubs_did`), KEY `updated_idx` (`updated`), KEY `expires_idx` (`expires`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_presentity` -- LOCK TABLES `rls_presentity` WRITE; /*!40000 ALTER TABLE `rls_presentity` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_presentity` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rls_watchers` -- DROP TABLE IF EXISTS `rls_watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rls_watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `to_user` varchar(64) NOT NULL, `to_domain` varchar(64) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `event_id` varchar(64) DEFAULT NULL, `to_tag` varchar(64) NOT NULL, `from_tag` varchar(64) NOT NULL, `callid` varchar(255) NOT NULL, `local_cseq` int(11) NOT NULL, `remote_cseq` int(11) NOT NULL, `contact` varchar(128) NOT NULL, `record_route` text, `expires` int(11) NOT NULL, `status` int(11) NOT NULL DEFAULT '2', `reason` varchar(64) NOT NULL, `version` int(11) NOT NULL DEFAULT '0', `socket_info` varchar(64) NOT NULL, `local_contact` varchar(128) NOT NULL, `from_user` varchar(64) NOT NULL, `from_domain` varchar(64) NOT NULL, `updated` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `rls_watcher_idx` (`callid`,`to_tag`,`from_tag`), KEY `rls_watchers_update` (`watcher_username`,`watcher_domain`,`event`), KEY `rls_watchers_expires` (`expires`), KEY `updated_idx` (`updated`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rls_watchers` -- LOCK TABLES `rls_watchers` WRITE; /*!40000 ALTER TABLE `rls_watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `rls_watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpengine` -- DROP TABLE IF EXISTS `rtpengine`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpengine` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` int(10) unsigned NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL, `weight` int(10) unsigned NOT NULL DEFAULT '1', `disabled` int(1) NOT NULL DEFAULT '0', `stamp` datetime NOT NULL DEFAULT '1900-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `rtpengine_nodes` (`setid`,`url`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpengine` -- LOCK TABLES `rtpengine` WRITE; /*!40000 ALTER TABLE `rtpengine` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpengine` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `rtpproxy` -- DROP TABLE IF EXISTS `rtpproxy`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `rtpproxy` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `setid` varchar(32) NOT NULL DEFAULT '0', `url` varchar(64) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `weight` int(11) NOT NULL DEFAULT '1', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `rtpproxy` -- LOCK TABLES `rtpproxy` WRITE; /*!40000 ALTER TABLE `rtpproxy` DISABLE KEYS */; /*!40000 ALTER TABLE `rtpproxy` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sca_subscriptions` -- DROP TABLE IF EXISTS `sca_subscriptions`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sca_subscriptions` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `subscriber` varchar(255) NOT NULL, `aor` varchar(255) NOT NULL, `event` int(11) NOT NULL DEFAULT '0', `expires` int(11) NOT NULL DEFAULT '0', `state` int(11) NOT NULL DEFAULT '0', `app_idx` int(11) NOT NULL DEFAULT '0', `call_id` varchar(255) NOT NULL, `from_tag` varchar(64) NOT NULL, `to_tag` varchar(64) NOT NULL, `record_route` text, `notify_cseq` int(11) NOT NULL, `subscribe_cseq` int(11) NOT NULL, `server_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `sca_subscriptions_idx` (`subscriber`,`call_id`,`from_tag`,`to_tag`), KEY `sca_expires_idx` (`server_id`,`expires`), KEY `sca_subscribers_idx` (`subscriber`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sca_subscriptions` -- LOCK TABLES `sca_subscriptions` WRITE; /*!40000 ALTER TABLE `sca_subscriptions` DISABLE KEYS */; /*!40000 ALTER TABLE `sca_subscriptions` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `silo` -- DROP TABLE IF EXISTS `silo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `silo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_addr` varchar(128) NOT NULL DEFAULT '', `dst_addr` varchar(128) NOT NULL DEFAULT '', `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `inc_time` int(11) NOT NULL DEFAULT '0', `exp_time` int(11) NOT NULL DEFAULT '0', `snd_time` int(11) NOT NULL DEFAULT '0', `ctype` varchar(32) NOT NULL DEFAULT 'text/plain', `body` blob, `extra_hdrs` text, `callid` varchar(128) NOT NULL DEFAULT '', `status` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `account_idx` (`username`,`domain`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `silo` -- LOCK TABLES `silo` WRITE; /*!40000 ALTER TABLE `silo` DISABLE KEYS */; /*!40000 ALTER TABLE `silo` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `sip_trace` -- DROP TABLE IF EXISTS `sip_trace`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `sip_trace` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `time_stamp` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', `time_us` int(10) unsigned NOT NULL DEFAULT '0', `callid` varchar(255) NOT NULL DEFAULT '', `traced_user` varchar(128) NOT NULL DEFAULT '', `msg` mediumtext NOT NULL, `method` varchar(50) NOT NULL DEFAULT '', `status` varchar(128) NOT NULL DEFAULT '', `fromip` varchar(50) NOT NULL DEFAULT '', `toip` varchar(50) NOT NULL DEFAULT '', `fromtag` varchar(64) NOT NULL DEFAULT '', `totag` varchar(64) NOT NULL DEFAULT '', `direction` varchar(4) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `traced_user_idx` (`traced_user`), KEY `date_idx` (`time_stamp`), KEY `fromip_idx` (`fromip`), KEY `callid_idx` (`callid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `sip_trace` -- LOCK TABLES `sip_trace` WRITE; /*!40000 ALTER TABLE `sip_trace` DISABLE KEYS */; /*!40000 ALTER TABLE `sip_trace` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `speed_dial` -- DROP TABLE IF EXISTS `speed_dial`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `speed_dial` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `sd_username` varchar(64) NOT NULL DEFAULT '', `sd_domain` varchar(64) NOT NULL DEFAULT '', `new_uri` varchar(128) NOT NULL DEFAULT '', `fname` varchar(64) NOT NULL DEFAULT '', `lname` varchar(64) NOT NULL DEFAULT '', `description` varchar(64) NOT NULL DEFAULT '', PRIMARY KEY (`id`), UNIQUE KEY `speed_dial_idx` (`username`,`domain`,`sd_domain`,`sd_username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `speed_dial` -- LOCK TABLES `speed_dial` WRITE; /*!40000 ALTER TABLE `speed_dial` DISABLE KEYS */; /*!40000 ALTER TABLE `speed_dial` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `subscriber` -- DROP TABLE IF EXISTS `subscriber`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `subscriber` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `password` varchar(64) NOT NULL DEFAULT '', `ha1` varchar(128) NOT NULL DEFAULT '', `ha1b` varchar(128) NOT NULL DEFAULT '', `email_address` varchar(128) DEFAULT NULL, `rpid` varchar(128) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`), KEY `username_idx` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `subscriber` -- LOCK TABLES `subscriber` WRITE; /*!40000 ALTER TABLE `subscriber` DISABLE KEYS */; /*!40000 ALTER TABLE `subscriber` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_d` -- DROP TABLE IF EXISTS `topos_d`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_d` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_rr` mediumtext, `b_rr` mediumtext, `s_rr` mediumtext, `iflags` int(10) unsigned NOT NULL DEFAULT '0', `a_uri` varchar(128) NOT NULL DEFAULT '', `b_uri` varchar(128) NOT NULL DEFAULT '', `r_uri` varchar(128) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `a_uuid_idx` (`a_uuid`), KEY `b_uuid_idx` (`b_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_d` -- LOCK TABLES `topos_d` WRITE; /*!40000 ALTER TABLE `topos_d` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_d` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `topos_t` -- DROP TABLE IF EXISTS `topos_t`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `topos_t` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `rectime` datetime NOT NULL, `s_method` varchar(64) NOT NULL DEFAULT '', `s_cseq` varchar(64) NOT NULL DEFAULT '', `a_callid` varchar(255) NOT NULL DEFAULT '', `a_uuid` varchar(255) NOT NULL DEFAULT '', `b_uuid` varchar(255) NOT NULL DEFAULT '', `direction` int(11) NOT NULL DEFAULT '0', `x_via` mediumtext, `x_vbranch` varchar(255) NOT NULL DEFAULT '', `x_rr` mediumtext, `y_rr` mediumtext, `s_rr` mediumtext, `x_uri` varchar(128) NOT NULL DEFAULT '', `a_contact` varchar(128) NOT NULL DEFAULT '', `b_contact` varchar(128) NOT NULL DEFAULT '', `as_contact` varchar(128) NOT NULL DEFAULT '', `bs_contact` varchar(128) NOT NULL DEFAULT '', `x_tag` varchar(255) NOT NULL DEFAULT '', `a_tag` varchar(255) NOT NULL DEFAULT '', `b_tag` varchar(255) NOT NULL DEFAULT '', `a_srcaddr` varchar(128) NOT NULL DEFAULT '', `b_srcaddr` varchar(128) NOT NULL DEFAULT '', `a_socket` varchar(128) NOT NULL DEFAULT '', `b_socket` varchar(128) NOT NULL DEFAULT '', PRIMARY KEY (`id`), KEY `rectime_idx` (`rectime`), KEY `a_callid_idx` (`a_callid`), KEY `x_vbranch_idx` (`x_vbranch`), KEY `a_uuid_idx` (`a_uuid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `topos_t` -- LOCK TABLES `topos_t` WRITE; /*!40000 ALTER TABLE `topos_t` DISABLE KEYS */; /*!40000 ALTER TABLE `topos_t` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `trusted` -- DROP TABLE IF EXISTS `trusted`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `trusted` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `src_ip` varchar(50) NOT NULL, `proto` varchar(4) NOT NULL, `from_pattern` varchar(64) DEFAULT NULL, `ruri_pattern` varchar(64) DEFAULT NULL, `tag` varchar(64) DEFAULT NULL, `priority` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `peer_idx` (`src_ip`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `trusted` -- LOCK TABLES `trusted` WRITE; /*!40000 ALTER TABLE `trusted` DISABLE KEYS */; /*!40000 ALTER TABLE `trusted` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uacreg` -- DROP TABLE IF EXISTS `uacreg`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uacreg` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `l_uuid` varchar(64) NOT NULL DEFAULT '', `l_username` varchar(64) NOT NULL DEFAULT '', `l_domain` varchar(64) NOT NULL DEFAULT '', `r_username` varchar(64) NOT NULL DEFAULT '', `r_domain` varchar(64) NOT NULL DEFAULT '', `realm` varchar(64) NOT NULL DEFAULT '', `auth_username` varchar(64) NOT NULL DEFAULT '', `auth_password` varchar(64) NOT NULL DEFAULT '', `auth_ha1` varchar(128) NOT NULL DEFAULT '', `auth_proxy` varchar(128) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT '0', `flags` int(11) NOT NULL DEFAULT '0', `reg_delay` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `l_uuid_idx` (`l_uuid`) ) ENGINE=InnoDB AUTO_INCREMENT=10 DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uacreg` -- LOCK TABLES `uacreg` WRITE; /*!40000 ALTER TABLE `uacreg` DISABLE KEYS */; INSERT INTO `uacreg` VALUES (1,'1','1','50.253.243.17','','','','','','','',60,1,0),(2,'2','2','50.253.243.17','','','','','','','',60,1,0),(3,'3','3','50.253.243.17','','','','','','','',60,1,0),(4,'4','4','50.253.243.17','','','','','','','',60,1,0),(5,'5','5','50.253.243.17','','','','','','','',60,1,0),(6,'6','6','50.253.243.17','','','','','','','',60,1,0),(7,'7','7','50.253.243.17','','','','','','','',60,1,0); /*!40000 ALTER TABLE `uacreg` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_credentials` -- DROP TABLE IF EXISTS `uid_credentials`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_credentials` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `auth_username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL DEFAULT '_default', `realm` varchar(64) NOT NULL, `password` varchar(28) NOT NULL DEFAULT '', `flags` int(11) NOT NULL DEFAULT '0', `ha1` varchar(32) NOT NULL, `ha1b` varchar(32) NOT NULL DEFAULT '', `uid` varchar(64) NOT NULL, PRIMARY KEY (`id`), KEY `cred_idx` (`auth_username`,`did`), KEY `uid` (`uid`), KEY `did_idx` (`did`), KEY `realm_idx` (`realm`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_credentials` -- LOCK TABLES `uid_credentials` WRITE; /*!40000 ALTER TABLE `uid_credentials` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_credentials` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain` -- DROP TABLE IF EXISTS `uid_domain`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_idx` (`domain`), KEY `did_idx` (`did`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain` -- LOCK TABLES `uid_domain` WRITE; /*!40000 ALTER TABLE `uid_domain` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_domain_attrs` -- DROP TABLE IF EXISTS `uid_domain_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_domain_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `did` varchar(64) DEFAULT NULL, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `domain_attr_idx` (`did`,`name`,`value`), KEY `domain_did` (`did`,`flags`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_domain_attrs` -- LOCK TABLES `uid_domain_attrs` WRITE; /*!40000 ALTER TABLE `uid_domain_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_domain_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_global_attrs` -- DROP TABLE IF EXISTS `uid_global_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_global_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) DEFAULT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `global_attrs_idx` (`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_global_attrs` -- LOCK TABLES `uid_global_attrs` WRITE; /*!40000 ALTER TABLE `uid_global_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_global_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri` -- DROP TABLE IF EXISTS `uid_uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `username` varchar(64) NOT NULL, `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), KEY `uri_idx1` (`username`,`did`,`scheme`), KEY `uri_uid` (`uid`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri` -- LOCK TABLES `uid_uri` WRITE; /*!40000 ALTER TABLE `uid_uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_uri_attrs` -- DROP TABLE IF EXISTS `uid_uri_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_uri_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `did` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', `scheme` varchar(8) NOT NULL DEFAULT 'sip', PRIMARY KEY (`id`), UNIQUE KEY `uriattrs_idx` (`username`,`did`,`name`,`value`,`scheme`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_uri_attrs` -- LOCK TABLES `uid_uri_attrs` WRITE; /*!40000 ALTER TABLE `uid_uri_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_uri_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uid_user_attrs` -- DROP TABLE IF EXISTS `uid_user_attrs`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uid_user_attrs` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uid` varchar(64) NOT NULL, `name` varchar(32) NOT NULL, `value` varchar(128) DEFAULT NULL, `type` int(11) NOT NULL DEFAULT '0', `flags` int(10) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `userattrs_idx` (`uid`,`name`,`value`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uid_user_attrs` -- LOCK TABLES `uid_user_attrs` WRITE; /*!40000 ALTER TABLE `uid_user_attrs` DISABLE KEYS */; /*!40000 ALTER TABLE `uid_user_attrs` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `uri` -- DROP TABLE IF EXISTS `uri`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `uri` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `uri_user` varchar(64) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), UNIQUE KEY `account_idx` (`username`,`domain`,`uri_user`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `uri` -- LOCK TABLES `uri` WRITE; /*!40000 ALTER TABLE `uri` DISABLE KEYS */; /*!40000 ALTER TABLE `uri` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `userblacklist` -- DROP TABLE IF EXISTS `userblacklist`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `userblacklist` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL DEFAULT '', `domain` varchar(64) NOT NULL DEFAULT '', `prefix` varchar(64) NOT NULL DEFAULT '', `whitelist` tinyint(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), KEY `userblacklist_idx` (`username`,`domain`,`prefix`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `userblacklist` -- LOCK TABLES `userblacklist` WRITE; /*!40000 ALTER TABLE `userblacklist` DISABLE KEYS */; /*!40000 ALTER TABLE `userblacklist` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `usr_preferences` -- DROP TABLE IF EXISTS `usr_preferences`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `usr_preferences` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `uuid` varchar(64) NOT NULL DEFAULT '', `username` varchar(128) NOT NULL DEFAULT '0', `domain` varchar(64) NOT NULL DEFAULT '', `attribute` varchar(32) NOT NULL DEFAULT '', `type` int(11) NOT NULL DEFAULT '0', `value` varchar(128) NOT NULL DEFAULT '', `last_modified` datetime NOT NULL DEFAULT '2000-01-01 00:00:01', PRIMARY KEY (`id`), KEY `ua_idx` (`uuid`,`attribute`), KEY `uda_idx` (`username`,`domain`,`attribute`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `usr_preferences` -- LOCK TABLES `usr_preferences` WRITE; /*!40000 ALTER TABLE `usr_preferences` DISABLE KEYS */; /*!40000 ALTER TABLE `usr_preferences` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `version` -- DROP TABLE IF EXISTS `version`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `version` ( `table_name` varchar(32) NOT NULL, `table_version` int(10) unsigned NOT NULL DEFAULT '0', UNIQUE KEY `table_name_idx` (`table_name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `version` -- LOCK TABLES `version` WRITE; /*!40000 ALTER TABLE `version` DISABLE KEYS */; INSERT INTO `version` VALUES ('acc',5),('acc_cdrs',2),('active_watchers',12),('address',6),('aliases',8),('carrierfailureroute',2),('carrierroute',3),('carrier_name',1),('cpl',1),('dbaliases',1),('dialog',7),('dialog_vars',1),('dialplan',2),('dispatcher',4),('domain',2),('domainpolicy',2),('domain_attrs',1),('domain_name',1),('dr_gateways',3),('dr_groups',2),('dr_gw_lists',1),('dr_rules',3),('globalblacklist',1),('grp',2),('htable',2),('imc_members',1),('imc_rooms',1),('lcr_gw',3),('lcr_rule',3),('lcr_rule_target',1),('location',9),('location_attrs',1),('missed_calls',4),('mohqcalls',1),('mohqueues',1),('mtree',1),('mtrees',2),('pdt',1),('pl_pipes',1),('presentity',4),('pua',7),('purplemap',1),('re_grp',1),('rls_presentity',1),('rls_watchers',3),('rtpengine',1),('rtpproxy',1),('sca_subscriptions',2),('silo',8),('sip_trace',4),('speed_dial',2),('subscriber',7),('topos_d',1),('topos_t',1),('trusted',6),('uacreg',3),('uid_credentials',7),('uid_domain',2),('uid_domain_attrs',1),('uid_global_attrs',1),('uid_uri',3),('uid_uri_attrs',2),('uid_user_attrs',3),('uri',1),('userblacklist',1),('usr_preferences',2),('version',1),('watchers',3),('xcap',4); /*!40000 ALTER TABLE `version` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `watchers` -- DROP TABLE IF EXISTS `watchers`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `watchers` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `presentity_uri` varchar(128) NOT NULL, `watcher_username` varchar(64) NOT NULL, `watcher_domain` varchar(64) NOT NULL, `event` varchar(64) NOT NULL DEFAULT 'presence', `status` int(11) NOT NULL, `reason` varchar(64) DEFAULT NULL, `inserted_time` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `watcher_idx` (`presentity_uri`,`watcher_username`,`watcher_domain`,`event`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `watchers` -- LOCK TABLES `watchers` WRITE; /*!40000 ALTER TABLE `watchers` DISABLE KEYS */; /*!40000 ALTER TABLE `watchers` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `xcap` -- DROP TABLE IF EXISTS `xcap`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `xcap` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `username` varchar(64) NOT NULL, `domain` varchar(64) NOT NULL, `doc` mediumblob NOT NULL, `doc_type` int(11) NOT NULL, `etag` varchar(64) NOT NULL, `source` int(11) NOT NULL, `doc_uri` varchar(255) NOT NULL, `port` int(11) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `doc_uri_idx` (`doc_uri`), KEY `account_doc_type_idx` (`username`,`domain`,`doc_type`), KEY `account_doc_type_uri_idx` (`username`,`domain`,`doc_type`,`doc_uri`), KEY `account_doc_uri_idx` (`username`,`domain`,`doc_uri`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `xcap` -- LOCK TABLES `xcap` WRITE; /*!40000 ALTER TABLE `xcap` DISABLE KEYS */; /*!40000 ALTER TABLE `xcap` ENABLE KEYS */; UNLOCK TABLES; -- -- Dumping events for database 'kamailio' -- -- -- Dumping routines for database 'kamailio' -- /*!50003 DROP PROCEDURE IF EXISTS `kamailio_cdrs` */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = utf8 */ ; /*!50003 SET character_set_results = utf8 */ ; /*!50003 SET collation_connection = utf8_general_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = '' */ ; DELIMITER ;; CREATE DEFINER=`root`@`localhost` PROCEDURE `kamailio_cdrs`() BEGIN DECLARE done INT DEFAULT 0; DECLARE bye_record INT DEFAULT 0; DECLARE v_src_user,v_src_domain,v_dst_user,v_dst_domain,v_callid,v_from_tag, v_to_tag,v_src_ip,v_calltype VARCHAR(64); DECLARE v_inv_time, v_bye_time DATETIME; DECLARE inv_cursor CURSOR FOR SELECT src_user, src_domain, dst_user, dst_domain, time, callid,from_tag, to_tag, src_ip, calltype FROM acc where method='INVITE' and cdr_id='0'; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN inv_cursor; REPEAT FETCH inv_cursor INTO v_src_user, v_src_domain, v_dst_user, v_dst_domain, v_inv_time, v_callid, v_from_tag, v_to_tag, v_src_ip, v_calltype; IF NOT done THEN SET bye_record = 0; SELECT 1, time INTO bye_record, v_bye_time FROM acc WHERE method='BYE' AND callid=v_callid AND ((from_tag=v_from_tag AND to_tag=v_to_tag) OR (from_tag=v_to_tag AND to_tag=v_from_tag)) ORDER BY time ASC LIMIT 1; IF bye_record = 1 THEN INSERT INTO cdrs (src_username,src_domain,dst_username, dst_domain,call_start_time,duration,sip_call_id,sip_from_tag, sip_to_tag,src_ip,created,calltype) VALUES (v_src_user,v_src_domain, v_dst_user,v_dst_domain,v_inv_time, UNIX_TIMESTAMP(v_bye_time)-UNIX_TIMESTAMP(v_inv_time), v_callid,v_from_tag,v_to_tag,v_src_ip,NOW(),v_calltype); UPDATE acc SET cdr_id=last_insert_id() WHERE callid=v_callid AND from_tag=v_from_tag AND to_tag=v_to_tag; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; /*!50003 DROP PROCEDURE IF EXISTS `kamailio_rating` */; /*!50003 SET @saved_cs_client = @@character_set_client */ ; /*!50003 SET @saved_cs_results = @@character_set_results */ ; /*!50003 SET @saved_col_connection = @@collation_connection */ ; /*!50003 SET character_set_client = latin1 */ ; /*!50003 SET character_set_results = latin1 */ ; /*!50003 SET collation_connection = latin1_swedish_ci */ ; /*!50003 SET @saved_sql_mode = @@sql_mode */ ; /*!50003 SET sql_mode = '' */ ; DELIMITER ;; CREATE DEFINER=`kamailio`@`localhost` PROCEDURE `kamailio_rating`(`rgroup` varchar(64)) BEGIN DECLARE done, rate_record, vx_cost INT DEFAULT 0; DECLARE v_cdr_id BIGINT DEFAULT 0; DECLARE v_duration, v_rate_unit, v_time_unit INT DEFAULT 0; DECLARE v_dst_username VARCHAR(64); DECLARE cdrs_cursor CURSOR FOR SELECT cdr_id, dst_username, duration FROM cdrs WHERE rated=0; DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1; OPEN cdrs_cursor; REPEAT FETCH cdrs_cursor INTO v_cdr_id, v_dst_username, v_duration; IF NOT done THEN SET rate_record = 0; SELECT 1, rate_unit, time_unit INTO rate_record, v_rate_unit, v_time_unit FROM billing_rates WHERE rate_group=rgroup AND v_dst_username LIKE concat(prefix, '%') ORDER BY prefix DESC LIMIT 1; IF rate_record = 1 THEN SET vx_cost = v_rate_unit * CEIL(v_duration/v_time_unit); UPDATE cdrs SET rated=1, cost=vx_cost WHERE cdr_id=v_cdr_id; END IF; SET done = 0; END IF; UNTIL done END REPEAT; END ;; DELIMITER ; /*!50003 SET sql_mode = @saved_sql_mode */ ; /*!50003 SET character_set_client = @saved_cs_client */ ; /*!50003 SET character_set_results = @saved_cs_results */ ; /*!50003 SET collation_connection = @saved_col_connection */ ; -- -- Final view structure for view `dsip_prefix_mapping` -- /*!50001 DROP TABLE IF EXISTS `dsip_prefix_mapping`*/; /*!50001 DROP VIEW IF EXISTS `dsip_prefix_mapping`*/; /*!50001 SET @saved_cs_client = @@character_set_client */; /*!50001 SET @saved_cs_results = @@character_set_results */; /*!50001 SET @saved_col_connection = @@collation_connection */; /*!50001 SET character_set_client = utf8 */; /*!50001 SET character_set_results = utf8 */; /*!50001 SET collation_connection = utf8mb4_general_ci */; /*!50001 CREATE ALGORITHM=UNDEFINED */ /*!50013 DEFINER=`root`@`localhost` SQL SECURITY DEFINER */ /*!50001 VIEW `dsip_prefix_mapping` AS select `dr_rules`.`prefix` AS `prefix`,cast(`dr_rules`.`ruleid` as char(64) charset utf8mb4) AS `ruleid`,'0' AS `key_type`,'0' AS `value_type` from `dr_rules` where (`dr_rules`.`groupid` = (select `dsip_settings`.`FLT_INBOUND` from `dsip_settings` where `DSIP_ID` = 1 limit 1)) */; /*!50001 SET character_set_client = @saved_cs_client */; /*!50001 SET character_set_results = @saved_cs_results */; /*!50001 SET collation_connection = @saved_col_connection */; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2019-09-20 14:59:18