├── .gitignore ├── 10-intro.md ├── 20-basics.md ├── 30-security.md ├── 40-interconnecting.md ├── 50-encryption.md ├── 60-database.md ├── 70-audio.md ├── _config.yml ├── _includes ├── footer.html ├── head.html └── header.html ├── _layouts ├── default.html ├── page.html └── post.html ├── _sass ├── _base.scss ├── _layout.scss └── _syntax-highlighting.scss └── css └── main.scss /.gitignore: -------------------------------------------------------------------------------- 1 | _site 2 | .sass-cache 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /10-intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Introduction 4 | permalink: / 5 | --- 6 | 7 | ## About 8 | This document is a collection of practical solutions and ideas - *recipes* - related to the [Yate telephony engine](http://yate.null.ro/). Recipes give specific solutions to specific problems encountered in real life. Some recipes were derived from practical experience during the 29th and 30th Chaos Communication Congress in Hamburg, Germany. Other solutions were encountered while setting up and maintaining our public VoIP provider [EPVPN](https://eventphone.de/epvpn) (Eventphone Virtual Phone Network). 9 | 10 | The technical minded person may find a jump start into Yate or use it as a reference guide. 11 | Personally I prefer to write down experiences before they slip my mind and may be lost forever. 12 | Please feel free to share this document (under the terms of this document's license). Information wants to be free. 13 | 14 | ## The Author 15 | 16 | Ben Fuhrmannek is a computer scientist with a background in software development and IT security. In his spare time, among other things, he likes to indulge in open communication. 17 | 18 | For more information check out my list of [presentations and papers](http://www.fuhrmannek.de/pnp/). 19 | 20 | ## Alternatives to Yate 21 | 22 | The eventphone team has been evaluating VoIP software since 2004. And we came across quite a few working solutions for our use case: Provide secure, stable and reliable VoIP connectivity for more than 1000 hackers during conferences. This is my personal opinion: 23 | 24 | * **PBX4Linux, now LCR:** LCR comes with the idea of an ISDN PBX, now with SIP connectivity. We are using LCR as our central routing mechanism to interconnect DECT, Yate, Asterisk, GSM and Dialin/Dialout. 25 | * **Asterisk (and many forks):** This PBX opened my eyes in regard to programming VoIP applications. AGI and AEL are quite powerful interfaces and very intuitively programmed. Complex IVRs, games, automated calls and many more features make this PBX software almost perfect for every use case. However, there has not been a single hacker event without the need for a *while-true* loop to restart asterisk every hour or so. A similar setup with Yate lasted the whole 29c3 and 30c3 without a single unscheduled restart. The people of the rather popular [Gemeinschaft PBX](http://amooma.de/gemeinschaft) seem to come to a similar [conclusion](http://www.freeswitch.org/node/373) and switched from Asterisk to FreeSWITCH: "FreeSWITCH is better, more stable, more secure and gives higher performance." 26 | * **FreeSWITCH:** This PBX caught my attention because it took me about 10 minutes form download to the first call. Configuration files are XML, there are many (maybe even a lot of) modules available and the PBX can be scripted in every popular scripting language. Hardware support seemed a bit limited at the time - in 2005 I found only Sangoma ISDN card drivers. An alpha version of mISDN worked just fine when I tried it. Just like VIM vs. Emacs, RISC vs. CISC, for me this is a tie between FreeSWITH and Yate. 27 | * **OpenSER / Kamaillo / OpenSIPS:** These *Open Source SIP Servers* seem to focus on SIP and do their job well. 28 | * **YXA:** As a friend of software written in Erlang, particularly PBX software, this SIP router could have been a good successor for Asterisk. However after a day or so of configuration efforts I chose to focus on alternatives. 29 | 30 | 31 | ## License 32 | 33 | This work is licensed under a [Creative Commons Attribution 4.0 International License](http://creativecommons.org/licenses/by/4.0/). 34 | 35 | You are free to: 36 | 37 | * **Share** — copy and redistribute the material in any medium or format 38 | * **Adapt** — remix, transform, and build upon the material 39 | 40 | for any purpose, even commercially. 41 | 42 | Under the following terms: 43 | 44 | * **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 45 | 46 | **No additional restrictions** — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 47 | 48 | ## Referencing Guideline 49 | 50 | When quoting or referencing this document, it is good style to mention 51 | 52 | * **the author's name:** Ben Fuhrmannek - *Note: Derivative and contributed work may have a different author or several authors as indicated on the page or chapter.* The github username - here @bef - is sufficient when referencing on github. 53 | * **document title:** {{ site.title }} 54 | * **document version, revision or date**: see [https://github.com/{{ site.github_project }}](https://github.com/{{ site.github_project }}) for the latest change. If unsure, use the access date. 55 | * **original URL:** https://bef.github.io{{ site.baseurl }} 56 | 57 | *Note:* Be aware, that the document, URLs and the document structure may change over time. It's best not to share a specific URL only, but rather a descriptive reference including chapter name. 58 | 59 | ### Examples: 60 | 61 | * Example reference in print: 62 | 63 | Fuhrmannek, B. (2014-10-01) {{ site.title }}. (Internet). Available from {{ site.projecturl }}. Accessed 29.06.2048. 64 | 65 | * Formal web reference: 66 | 67 | {{ site.title }} by Ben Fuhrmannek 68 | 69 | * Casual web reference (specific chapter) when referencing from github: 70 | 71 | See {{ site.title }} / Security. 72 | 73 | ### Bad Examples: 74 | 75 | * See here. *(no title)* 76 | * Look what I did: ... *(invalid attribution)* 77 | * Ad. Ad. (this work)... Ad. Ad. *(bad context: The document has been released with a commercial use license. But using my name or work in connection with link spam sites is just wrong.)* 78 | 79 | -------------------------------------------------------------------------------- /20-basics.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Yate Basics 4 | permalink: /basics/ 5 | --- 6 | 7 | This chapter will provide a basic understanding of how to install, configure and debug Yate. Please feel free to skip to the next chapter if your Yate installation is already up and running properly. 8 | 9 | ## Documentation 10 | 11 | Where can I get more information? 12 | 13 | * [http://docs.yate.ro/](http://docs.yate.ro/) - up-to-date documentation wiki with lots of articles 14 | * [http://yate.null.ro/](http://yate.null.ro/) - slightly out-of-date documentation wiki, but still useful 15 | * Source Code / RTFS - The source code is well structured C++ and very easy to read. Give it a try. 16 | * [http://yate.null.ro/pmwiki/index.php?n=Main.MailList](http://yate.null.ro/pmwiki/index.php?n=Main.MailList) - the Yate mailing list and its archive 17 | 18 | ## Quick Installation 19 | 20 | You may choose to install Yate from your operating system's package repository, e.g. on Debian Linux: 21 | 22 | # apt-get install yate 23 | 24 | However, keep in mind that your OS may not include the latest Yate version, which can be problematic in regard to security and feature set. On the other hand, packages are easier to maintain. 25 | 26 | ### Install from Source 27 | 28 | 1. Download latest version from [http://yate.null.ro/](http://yate.null.ro/) and unpack the archive as usual, e.g. 29 | 30 | $ cd /usr/local/src 31 | $ wget http://yate.null.ro/tarballs/yate5/yate-XXXXX.tar.gz ## replace XXXXX with actual version number 32 | $ tar zxvf yate-XXXXX.tar.gz 33 | $ cd yate 34 | 35 | *Note:* If you are upgrading, be aware that the archive contains the directory `yate/`, which may already exist. --> `cp yate yate-xxx` first. 36 | 37 | 2. Install dependencies, e.g. on Debian/Ubuntu: 38 | 39 | # apt-get install build-essential zlib1g-dev libssl-dev libgsm1-dev pkg-config speex 40 | # apt-get install libmysqlclient-dev ## for mysql support if needed 41 | 42 | 3. Configure with flags, e.g. with custom install path, without Postgres support and with SSE2: 43 | 44 | $ ./configure --prefix=/home/poc/yate --without-libpq --enable-sse2 45 | 46 | Or simply run the default configure: 47 | 48 | $ ./configure 49 | 50 | Personally I like to keep this line and additional installation instructions and notes close to the actual installation in a file like `../COMPILE_YATE`. This way I know exactly how to re-compile or upgrade the software several months later for this particular installation. 51 | 52 | 4. Compile + Install 53 | 54 | $ make 55 | $ sudo make install 56 | 57 | Don't worry. There is a working `make uninstall`, which deletes all but configuration files. 58 | 59 | After installation, files can be found in the usual places. Assuming an installation prefix of `/usr/local`: 60 | 61 | * `/usr/local/etc/yate`: configuration files 62 | * `/usr/local/bin/yate*`: executable files 63 | * `/usr/local/share/yate/*`: support files: scripts, sounds, UI files 64 | * `/usr/local/man/man8/yate*`: manual pages 65 | * `/usr/local/share/doc/yate-*`: developer documentation 66 | * `/usr/local/lib/yate/*`: yate modules 67 | * `/usr/local/lib/libyate*`: shared libraries 68 | * `/usr/local/include/yate`: include files 69 | * depending on your configuration there may be other support files 70 | 71 | ### Upgrade from Source 72 | 73 | 1. Stop Yate, e.g. `killall yate` 74 | 2. `make uninstall` in the old Yate source directory: This will remove version specific libraries 75 | 3. `make install` in the new Yate source directory: This will install the new Yate version and not harm any existing configuration files. 76 | 4. Update configuration if needed, e.g. new modules. 77 | 5. Start Yate 78 | 79 | 80 | ### Configuration 81 | 82 | Yate configuration files are like INI files with `[sections]` and `key=value` pairs. For syntax highlighting in VIM just type `:setf dosini` and `:syntax on` or extend your `.vimrc` like so: 83 | 84 | ```vim 85 | " yate -> dosini syntax 86 | au BufNewFile,BufRead */etc/yate/*.conf setf dosini 87 | 88 | " syntax highlighting 89 | syn on 90 | ``` 91 | 92 | Config files reside in `/usr/local/etc/yate/` by default. Let's start with `yate.conf`: It is a good idea only to load those modules actually needed for your setup. Otherwise it may lead to unwanted features or protocols being enabled, which in turn broadens the attack surface from a security point of view. 93 | 94 | The following example shows a usable `yate.conf` restricted to load only a few modules on startup: 95 | 96 | ```INI 97 | [general] 98 | ; modload: boolean: Should a module be loaded by default if there is no 99 | ; reference to it in the [modules] section 100 | modload=disable 101 | 102 | ;... 103 | 104 | [modules] 105 | dumbchan.yate=true 106 | lateroute.yate=true 107 | regfile.yate=true 108 | accfile.yate=true 109 | rmanager.yate=true 110 | wavefile.yate=true 111 | pbx.yate=true 112 | tonegen.yate=true 113 | callfork.yate=true 114 | ysipchan.yate=true 115 | extmodule.yate=true 116 | yrtpchan.yate=true 117 | openssl.yate=true 118 | regexroute.yate=true 119 | ;ilbcwebrtc.yate=true 120 | ;cdrbuild.yate=true 121 | ;cdrfile.yate=true 122 | ;javascript.yate=true 123 | ;... 124 | ``` 125 | 126 | I like to add all available modules commented out to the [modules] section to be able to toggle modules with minimal effort. Try the following command: 127 | 128 | find /usr/local/lib/yate -name \*.yate |xargs -n 1 basename |xargs -n 1 -I '{}' echo ";{}=true" 129 | 130 | As a rule: If you don't know the module, best leave it commented out. See also [the Yate docu Wiki](http://docs.yate.ro/wiki/Modules) for a list of modules. 131 | 132 | *Next step:* Configure each module loaded on startup. 133 | 134 | 135 | ## Startup 136 | 137 | Yate can run as unprivileged system user. I fact, creating a dedicated user and group just for running Yate is a security recommendation. So: 138 | 139 | # groupadd yate 140 | # useradd -g yate yate 141 | # mkdir /home/yate 142 | # chown yate:yate /home/yate 143 | # su - yate 144 | $ 145 | 146 | 147 | There are several ways to start Yate: 148 | 149 | * For debugging right after compilation: There is a script in the source directory `./run` to start and debug Yate on Linux systems. 150 | 151 | * For debugging and first setup after installation: Just type `yate` or `yate -v` or even `yate -Do -vv` (`-Do` means color output; `-vv` means 2x verbose). 152 | 153 | The following error may occur: 154 | 155 | yate: error while loading shared libraries: libyate.so.4.3.0: cannot open shared object file: No such file or directory 156 | 157 | If so, try `LD_LIBRARY_PATH=/usr/local/lib yate` or add the correct library path to your dynamic loader search path, e.g. `/etc/ld.so.conf` and run `ldconfig`. On MacOSX the dynamic linker uses the environment variable `DYLD_LIBRARY_PATH`. 158 | 159 | Also quite useful in this context is [SCREEN](http://www.gnu.org/software/screen/) or any other terminal multiplexer program. 160 | 161 | * For production: The source package comes with a few sample init scripts: 162 | 163 | ./packing/deb/yate.init 164 | ./packing/portage/yate.init 165 | ./packing/rpm/yate.init 166 | 167 | 168 | After successful startup you can see where Yate is listening for connections: 169 | 170 | $ sudo netstat -lntup |grep yate 171 | 172 | On my test VM the output looks like this: 173 | 174 | ``` 175 | tcp 0 0 127.0.0.1:5060 0.0.0.0:* LISTEN 28833/yate 176 | tcp 0 0 172.16.229.129:5061 0.0.0.0:* LISTEN 28833/yate 177 | tcp 0 0 127.0.0.1:5038 0.0.0.0:* LISTEN 28833/yate 178 | tcp 0 0 127.0.0.1:5039 0.0.0.0:* LISTEN 28833/yate 179 | udp 0 0 172.16.229.129:5060 0.0.0.0:* 28833/yate 180 | udp 0 0 0.0.0.0:4569 0.0.0.0:* 28833/yate 181 | ``` 182 | 183 | Port 5060 is for SIP. Port 2061 is for SIPS (SIP over TLS). Port 4569 is IAX. Port 5038 is the rmanager and port 5039 is an extmodule listener. 184 | 185 | 186 | ## Debugging 187 | 188 | ### Restarting Yate 189 | 190 | 191 | * Full restart: `/etc/init.d/yate restart` 192 | or equivalently Ctrl-C, Cursor-UP, RETURN. 193 | 194 | * Reload Yate: Send SIGQUIT to the Yate process: 195 | 196 | killall -SIGQUIT yate 197 | 198 | Or press `Ctrl-\` on the running foreground process - however this may detach shell scripts if Yate was started using a custom startup script. 199 | 200 | Or press `Ctrl-\` in rmanager. 201 | 202 | 203 | ### rmanager 204 | 205 | The rmanager is a useful Yate console. A most common configuration is done like so in `rmanager.conf`: 206 | 207 | ```INI 208 | [general] 209 | ; Each section creates a connection listener in the Remote Manager. 210 | ; An empty (all defaults) general section is assumed only in server mode if the 211 | ; configuration file is missing. 212 | 213 | ; port: int: TCP Port to listen on, 0 to disable the listener 214 | port=5038 215 | 216 | ; addr: ipaddress: IP address to bind to 217 | addr=127.0.0.1 218 | 219 | ;... 220 | ; color: bool: Enable colorization debug as soon as connecting 221 | ; This setting is ignored if telnet negotiation is disabled 222 | color=yes 223 | ;... 224 | ``` 225 | 226 | Then, connect using telnet or netcat: 227 | 228 | ``` 229 | $ telnet localhost 5038 230 | Trying ::1... 231 | Trying 127.0.0.1... 232 | Connected to localhost. 233 | Escape character is '^]'. 234 | eventphone YATE 4.3.0-1 on devvm. 235 | ``` 236 | 237 | Try `help` and `status`. For more debugging output, try this (for verbose SIP debugging): 238 | 239 | debug on 240 | debug level 10 241 | debug sip level 10 242 | 243 | 244 | Possible debug levels can be found in `yateclass.h` line 225++: 245 | 246 | 247 | ``` 248 | enum DebugLevel { 249 | DebugFail = 0, 250 | DebugTest = 1, 251 | DebugGoOn = 2, 252 | DebugConf = 3, 253 | DebugStub = 4, 254 | DebugWarn = 5, 255 | DebugMild = 6, 256 | DebugCall = 7, 257 | DebugNote = 8, 258 | DebugInfo = 9, 259 | DebugAll = 10 260 | }; 261 | ``` 262 | 263 | 264 | ### IP level SIP debugging 265 | 266 | Usual candidates would be 267 | 268 | # ngrep -l -W byline port 5060 269 | 270 | # tcpdump -ln -i eth0 -A port 5060 271 | 272 | 273 | 274 | ## Concepts 275 | 276 | ### Engine / Message Queue 277 | 278 | The core of Yate - the engine - is a message queue. Each module can subscribe to a type of message and either process or reject the message. In addition, Yate modules can also observe the message queue without actually handling messages (watch), which is similar to a [PubSub pattern](https://en.wikipedia.org/wiki/Publish/subscribe). 279 | 280 | When subscribing to a message type, the module must provide a priority. A message is then passed to each matching module in the order of subscribed priority. 281 | 282 | Messages are human readable. Try this in rmanager: 283 | 284 | ``` 285 | machine on 286 | Machine mode: on 287 | %%::[]:[:=...] 295 | 296 | The message dumped here is of type `engine.timer` and was sent from *Engine to Application*. There is no `id`. The message has not been processed by any module (processed=false). The return value is empty. At the end there are multiple key/value pairs, notably *handlers=...*, which lists all modules subscribed to this message type and their priority. 297 | 298 | The complete message format documentation can be found [here](http://yate.null.ro/docs/extmodule.html) as well as in the source package under `docs/extmodule.html`. 299 | 300 | A common message flow for a Call from a user to an IVR would look like this: 301 | 302 | call.preroute 303 | chan.startup 304 | call.route 305 | call.update 306 | call.execute 307 | chan.rtp status=created 308 | chan.connected 309 | ... 310 | chan.rtp status=terminated 311 | chan.hangup 312 | chan.disconnected 313 | 314 | 315 | A list of standard messages can be found in the [documentation](http://docs.yate.ro/wiki/Standard_Messages). Custom modules may invent new message types and use the Yate engine to do IPC (inter-process communication). 316 | 317 | 318 | ### Extmodule 319 | 320 | Participating in the Yate engine's message queue without writing another module in C++ (which would be simple enough) is done via the *extmodule* module. `extmodule.conf`: 321 | 322 | ```INI 323 | [general] 324 | scripts_dir=/usr/local/share/yate/scripts/ 325 | priority=100 326 | timeout=10000 327 | timebomb=true 328 | ;... 329 | 330 | [listener tcp5039] 331 | type=tcp 332 | addr=127.0.0.1 333 | port=5039 334 | ``` 335 | This configuration opens a TCP socket on port 5039. It is also possible to call scripts, that communicate via stdio, like so: `regexroute.conf`: 336 | 337 | ^77$=external/nodata/foo.tcl 338 | 339 | This would execute the script `/usr/local/share/yate/scripts/foo.tcl` and interact using the extmodule protocol. 340 | 341 | *Note*: Some examples in this document are written in Tcl using the [YGI library](https://github.com/bef/yate-tcl) to interface with Yate. Libraries in other languages - PHP, Python, Perl - are available as well. Yate also comes with its own JavaScript interpreter in its own module *javascript.yate*. 342 | 343 | A simple use case for extmodule is YGI's message printer `dumpmsgs.tcl`: 344 | 345 | ``` 346 | ---------> new message: user.register true 347 | | number 3333 348 | | sip_uri sip:devvm 349 | | sip_callid 2088059556@devvm 350 | | username 3333 351 | | realm devvm 352 | | ip_transport TLS 353 | | newcall false 354 | | domain devvm 355 | | device YATE/4.0.1 356 | | driver sip 357 | | data sip/sip:3333@172.16.229.1:62928 358 | | ip_host 172.16.229.1 359 | | ip_port 62928 360 | | expires 600 361 | | sip_to sip:3333@172.16.229.1:62928 362 | | connection_id tls:172.16.229.129:5061-172.16.229.1:62928 363 | | connection_reliable true 364 | | route_params oconnection_id 365 | | oconnection_id tls:172.16.229.129:5061-172.16.229.1:62928 366 | | handlers monitoring:1,register:50 367 | ... 368 | ``` 369 | 370 | -------------------------------------------------------------------------------- /30-security.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Security 4 | permalink: /security/ 5 | --- 6 | 7 | 8 | This security checklist is a good start to harden your VoIP setup: 9 | 10 | * **Principle of minimal privilege:** Try to restrict your setup as much as possible to do exactly what you intended it to do, not more. This principle implicitly applies to all of the following points. 11 | 12 | * **Operating System:** 13 | 14 | * Use virtual environments, such as Xen, VirtualBox, OpenVZ, ... 15 | * Use a chroot environment. 16 | * Run Yate with a dedicated system user and group. 17 | * Set ulimits to prevent resource exhaustion. 18 | * Use application security systems, e.g. AppArmor. 19 | * Don't run any other server software on the system. 20 | * Don't let many users access the system. 21 | * Log admin access. 22 | 23 | 24 | * **Filesystem:** Restrict access to files: 25 | 26 | * Yate files, scripts and modules should be owned by a different system user than the user that runs Yate. E.g. 27 | 28 | chown -R root:yate /usr/local/etc/yate /usr/local/share/yate 29 | 30 | * Files should be set read-only for the user that runs Yate. E.g. 31 | 32 | chmod -R go-w /usr/local/etc/yate /usr/local/share/yate 33 | 34 | * Files containing passwords or other sensitive information should be set unreadable for others: 35 | 36 | cd /usr/local/etc/yate 37 | chmod 640 accfile.conf regfile.conf mysqldb.conf 38 | 39 | * Consider using encrypted filesystems to protect sensitive data, e.g. voicemail messages or remote VoIP account credentials. 40 | 41 | 42 | * **IP Network:** 43 | 44 | * Set up a firewall to restrict access to SIP, rmanager, extmodule, ... and don't forget IPv6. 45 | * Set up flood protection, e.g. fail2ban. 46 | * Use a VPN to restrict access to access all or parts of Yate. 47 | * Configure management services like rmanager and extmodule to listen on localhost only. 48 | * Configure a dedicated VLAN for VoIP traffic. 49 | * Protect switch ports with IEEE 802.1x if possible. 50 | * Set switch ports to be disabled after link is down. 51 | 52 | 53 | * **Database:** 54 | 55 | * Write your SQL statements with caution: Only use appropriately escaped or whitelisted values in dynamic queries in order to prevent [SQL injection](https://www.owasp.org/index.php/SQL_injection) attacks. Keep in mind, that variables may contain user provided values, such as user agent, caller ID or custom SIP headers. 56 | * Restrict Yate database user to DELETE, INSERT, SELECT, USAGE, UPDATE. There is no reason for the database to be dropped or altered by a phone call. 57 | * Think about rejecting suspicious database queries by whitelisting or blacklisting queries before execution using the *regexroute* module. 58 | 59 | 60 | * **SIP Security:** 61 | 62 | * Only allow SIP methods actually needed, e.g. disable OPTIONS. 63 | * Don't enable subscribe/notify features to unauthenticated users. 64 | * Don't leak information about server software versions to the outside. Change the default SIP header *Server:* (or *User-Agent:* for SIP clients) to omit version numbers: `ysipchan.conf`: 65 | 66 | useragent=Foo 67 | 68 | * Filter traffic to other networks, e.g. with a Session Border Controller (SBC). 69 | 70 | 71 | * **VoIP routing and dialplan considerations:** 72 | 73 | * Avoid routing loops. Yate has an internal loop detection. But bouncing calls from one VoIP server to another and back several times will exhaust resources and provide attackers with a deny-of-service attack surface. 74 | * Restrict internal numbers to authenticated clients. 75 | * Categorise clients by source IP, if possible. E.g. internal clients may always have an internal IP. 76 | * Protect your dialout. Anonymous users or SIP scanners should not be able to generate charges on your telephone bill. 77 | * Never trust an incoming caller ID. Caller IDs can be faked, is PSTN as well as in VoIP. Also: Obscure caller IDs should be rejected or rewritten at an early routing stage, e.g. allow only digits 0-9, A-D and maybe allow the international `+' character in some cases. 78 | * Do not allow users to change their caller ID, e.g. set caller ID based on the authenticated username. 79 | * Explain your dialplan. Draw diagrams. Write tables. Fill Wikis. Anything. Please. 80 | * Test your configuration. In particular, regular expressions as used to create a dialplan with the *regexroute* module can be tricky. 81 | 82 | 83 | 84 | * **Passwords:** 85 | 86 | * Generate strong and random user passwords, e.g. with [APG](http://www.adel.nursat.kz/apg/). 87 | * If possible, avoid passwords at all, but use certificates or hardware tokens instead. 88 | * Protect phone applications, e.g. voicemail, with passcodes longer than four digits. If possible, add additional checks for valid caller-IDs, user authentication credentials, IPs, time of day or other criteria. 89 | * Users must be able to change their passwords and PINs on their own. They should also be made aware of this feature. 90 | 91 | 92 | 93 | * **Update Strategy:** 94 | 95 | * Regularly check for new versions. 96 | * Know how to easily update Yate. Take notes on how to compile, deploy, install, upgrade Yate to make life easier for the future you or possibly for other administrators. Also: Store notes where they can be found, e.g. in a file `../YATE_NOTES.txt` or a documentation wiki (or even an offline notebook). 97 | 98 | 99 | 100 | * **Transport Encryption:** Consider setting up encryption if possible: 101 | 102 | * Enable SIP over TLS (SIPS). 103 | * Enable SRTP. 104 | * As a client, validate certificates in order to prevent man-in-the-middle attacks. 105 | * Consider enforcing encrypted calls - SIPS + SRTP - for some numbers, e.g. confidential conference rooms. 106 | * For performance reasons it may be better to use VPN solutions - e.g. IPSec or OpenVPN - for point-to-point links in some cases. 107 | 108 | 109 | * **No Logging:** 110 | 111 | * Log nothing unless absolutely required. For personal use, this may be unnecessary. For business use it may even be against privacy laws to store connection data. 112 | * Think about logging only statistics - e.g. usage counters - without associated names/numbers. 113 | * A cronjob should be in place to delete old data. 114 | 115 | 116 | * **Monitoring:** Set up monitoring software in order to know when something went wrong. 117 | 118 | * **Security Checks:** Implement as many security features as possible and check them on a regular basis. 119 | 120 | * **Disaster Recovery:** Keep your VoIP setup well documented and create automated backups on a regular basis. It should be well known what to do after discovering a security incident - for example: 121 | 122 | * Disconnect from the internet. 123 | * Restore VoIP setup to a defined state. 124 | * Find and fix vulnerability, e.g. upgrade software. 125 | * Change all passwords, PINs, SSH keys, ... and revoke certificates. 126 | * Inform users. 127 | 128 | -------------------------------------------------------------------------------- /40-interconnecting.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Interconnecting 4 | permalink: /interconnecting/ 5 | --- 6 | 7 | ## Connecting LCR 8 | 9 | ``` 10 | ------------- ------------- 11 | | Yate |-----------| LCR | 12 | |172.16.40.2| (SIP) |172.16.40.1| 13 | ------------- ------------- 14 | ``` 15 | 16 | Running Yate and [LCR (Linux Call Router)](http://www.linux-call-router.de) on the same host - e.g. localhost - is possible. However if the setup is more complicated - e.g. Yate + Asterisk + LCR or several Yates and LCR or several LCRs - you can save a lot of time and effort by separating each VoIP server into its own (virtual) environment with its own network interface. Just keep in mind, that real hardware access may require PCI passthru or equivalent for better timing. 17 | 18 | `LCR interface.conf`: 19 | 20 | [Yate_SIP] 21 | #sip [:] [:] 22 | sip 172.16.40.1:5061 172.16.40.2 23 | earlyb no 24 | tones yes 25 | 26 | If not already covered by the a listener define another listener in `Yate ysipchan.conf`: 27 | 28 | [listener lcr] 29 | enable=yes 30 | addr=172.16.40.2 31 | port=5060 32 | 33 | 34 | Routing from LCR to Yate is shown by the following example. Calling *01980123* will call *123* via interface Yate_SIP. Calling *01999123* will call *01999123* as the `goto` does not consume matched digits. 35 | `LCR routing.conf`: 36 | 37 | [main] 38 | dialing=01980 : extern interfaces=Yate_SIP 39 | dialing=01999 : goto ruleset=to_yate 40 | 41 | [to_yate] 42 | : extern interfaces=Yate_SIP 43 | 44 | 45 | Routing from Yate to LCR is done the usual way - assuming 0... will be routed to LCR: 46 | `Yate regexroute.conf`: 47 | 48 | ^\(0.*\)$=sip/sip:\1@172.16.40.1:5061 49 | 50 | 51 | *Note*: Try to avoid routing loops. 52 | 53 | 54 | ## Connecting Asterisk (Scenario 1) 55 | 56 | N-to-N trunking without registration or authentication via SIP protocol - (assuming VPN or other trust relationship between hosts and static IPs): 57 | 58 | ``` 59 | -------- ------------ 60 | | Yate |----SIP----| Asterisk | 61 | -------- ------------ 62 | ``` 63 | 64 | `Asterisk sip.conf` for incoming calls: 65 | 66 | ```INI 67 | [world] 68 | type=peer 69 | host=office.example.com 70 | context=default 71 | permit=192.168.88.99/255.255.255.255 72 | ``` 73 | 74 | Calling from Yate to Asterisk: - assuming a two-digit extension: `Yate regexroute.conf` 75 | 76 | ```INI 77 | ^..$=sip/sip:\0@192.168.88.98 78 | ``` 79 | 80 | Calling from Asterisk to Yate: `Asterisk extensions.ael` 81 | 82 | ``` 83 | context internal { 84 | _X. => { 85 | Dial(SIP/${EXTEN}@192.168.88.99); 86 | Hangup; 87 | }; 88 | }; 89 | ``` 90 | 91 | ## Connecting Asterisk (Scenario 2) 92 | 93 | Yate registers to Asterisk via IAX2 protocol. 94 | 95 | ``` 96 | -------- -------- ------------ --------- 97 | | PSTN |<---| Yate |--(IAX2)-->| Asterisk |--->| Phones| 98 | -------- -------- ------------ --------- 99 | ``` 100 | 101 | This is one practical solution for N-to-N trunking with registration and two way password authentication. The opposite direction where Asterisk registers to Yate would be more straight forward, however routing from Yate to Asterisk can become rather complicated. 102 | 103 | `Asterisk sip.conf`: 104 | 105 | ```INI 106 | [world] 107 | type=friend 108 | ;host=office.example.com 109 | host=dynamic 110 | secret=SECRET_PASSWORD_HERE 111 | context=default 112 | permit=192.168.88.99/255.255.255.255 113 | ``` 114 | 115 | `Yate accfile.conf` for registration and outgoing connections: 116 | 117 | ```INI 118 | [office] 119 | enabled=yes 120 | protocol=iax 121 | username=world 122 | password=SECRET_PASSWORD_HERE 123 | server=192.168.88.98 124 | interval=30 125 | ``` 126 | 127 | `Yate regfile.conf` for incoming connections: 128 | 129 | ```INI 130 | [x1] 131 | password=SECRET_PASSWORD_HERE 132 | ``` 133 | 134 | Calling from Yate to Asterisk - assuming a two-digit extension: `Yate regexroute.conf`: 135 | 136 | ```INI 137 | ^..$=line/\0;line=office 138 | ``` 139 | 140 | Calling from Asterisk to Yate: `Asterisk extensions.ael`: 141 | 142 | ``` 143 | context internal { 144 | _X. => { 145 | Dial(IAX2/x1:SECRET_PASSWORD_HERE@192.168.88.99/${EXTEN}); 146 | Hangup; 147 | }; 148 | }; 149 | ``` 150 | 151 | -------------------------------------------------------------------------------- /50-encryption.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Encryption 4 | permalink: /encryption/ 5 | --- 6 | 7 | ## TLS Encryption (SIPS/SRTP Server) 8 | 9 | 10 | These few steps will enable TLS for SIP and RTP in Yate: 11 | 12 | 1. Get or create a certificate, e.g. create a self-signed certificate: 13 | 14 | openssl genrsa -out key.pem 2048 15 | openssl req -new -key key.pem -out request.pem 16 | openssl x509 -req -days 3650 -in request.pem -signkey key.pem -out certificate.pem 17 | chmod 400 *.pem 18 | 19 | key.pem and certificate.pem can be moved to /usr/local/etc/yate/ssl. 20 | 21 | 2. Configure the openssl module: `openssl.conf` 22 | 23 | ```INI 24 | [general] 25 | 26 | [server_context] 27 | enable=yes 28 | domains=voip.example.com 29 | certificate=ssl/certificate.pem 30 | key=ssl/key.pem 31 | ``` 32 | 33 | And enable openssl.yate in yate.conf (set openssl.yate=true in section [modules]). The created [server_context] can be used by several modules: jabberserver, ysipchan, rmanager 34 | 35 | 3. Configure SIP module: `ysipchan.conf` 36 | 37 | ```INI 38 | [general] 39 | ;... 40 | secure=enable 41 | ;... 42 | [listener ssl] 43 | enable=yes 44 | type=tls 45 | ;addr=172.16.88.12 46 | port=5061 47 | sslcontext=server_context 48 | ``` 49 | 50 | 4. Configure all your clients to accept the generated certificate as valid. 51 | 52 | 53 | Optional steps: 54 | 55 | * DNS configuration: 56 | Set NAPTR and SRV records. [NAPTR](https://en.wikipedia.org/wiki/NAPTR_record) (Name Authority Pointer) records provide a way to point to the correct VoIP resource and may even contain a regular expression to rewrite the request. 57 | [SRV](https://en.wikipedia.org/wiki/SRV_record) records point services to hostnames and ports. 58 | 59 | example.com. IN NAPTR 10 0 "s" "SIPS+D2T" "" _sips._tcp.example.com. 60 | example.com. IN NAPTR 20 0 "s" "SIP+D2T" "" _sip._tcp.example.com. 61 | example.com. IN NAPTR 30 0 "s" "SIP+D2U" "" _sip._udp.example.com. 62 | _sips._tcp.example.com. IN SRV 10 0 5061 voip.example.com. 63 | _sip._udp.example.com. IN SRV 10 0 5060 voip.example.com. 64 | _sip._tcp.example.com. IN SRV 10 0 5060 voip.example.com. 65 | 66 | This example contains a complete set of entries for SIPS, SIP over UDP and SIP over TCP. If you have only enabled listeners for one or two of these services, then don't set the others. 67 | 68 | * Encourage the use of encryption by blocking some (or all) numbers for unencrypted connections: `regexroute.conf`: 69 | 70 | ```INI 71 | ^8044$=if ${ip_transport}^TLS$=if ${encryption}.=if ${crypto}.=conf/tlsonly;maxusers=23;lonely=true 72 | ``` 73 | 74 | This will allow participants to enter a conference room (`conf/tlsonly`) when the signaling connection is TLS encrypted (e.g. SIPS) and also data encryption is offered. *Note*: At this point, the client may still negotiate to use an unencrypted data channel or renegotiate the data channel later. 75 | 76 | * Store `oconnection_id` in user database: 77 | In order to call clients connected by TLS (or TCP), Yate needs to be able to find the TCP connection associated with the client. This is done by looking up (and setting) the channel variable `oconnection_id`. (Actually, the channel variable `route_params` contains a list of variables to be looked up for routing, but for this use case it's just `oconnection_id`.) The file backend (regfile module) can do this automatically. Database backends must store this field explicitly. 78 | 79 | -------------------------------------------------------------------------------- /60-database.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Database 4 | permalink: /database/ 5 | --- 6 | 7 | ## MySQL Users and Routing 8 | 9 | The *register* module provides an SQL database backend to user registration. This is an example configuration for MySQL. (The module can also be used for CDR and subscription features, which have been left out for simplicity.) 10 | 11 | `mysqldb.conf:` 12 | 13 | ```INI 14 | [default] 15 | ;host= 16 | ;port=0 17 | database=yate 18 | user=yate 19 | password=mypassword 20 | ;socket= 21 | ;compress=disable 22 | encoding=utf8 23 | poolsize=1 24 | ``` 25 | 26 | `register.conf:` 27 | 28 | ```INI 29 | [general] 30 | expires=30 31 | user.auth=yes 32 | user.register=yes 33 | user.unregister=yes 34 | engine.timer=yes 35 | call.route=yes 36 | ;... 37 | 38 | [default] 39 | priority=50 40 | account=default 41 | 42 | [user.auth] 43 | query=SELECT password FROM users WHERE username='${username}' AND password IS NOT NULL AND password<>'' AND type='user' LIMIT 1 44 | result=password 45 | 46 | [user.register] 47 | query=UPDATE users SET location='${data}',expires=NOW() + INTERVAL ${expires} SECOND,oconnection_id='${oconnection_id}' WHERE username='${username}' AND type='user' LIMIT 1 48 | 49 | [user.unregister] 50 | query=UPDATE users SET location=NULL,expires=NULL,oconnection_id=NULL WHERE expires IS NOT NULL AND username='${username}' AND type='user' LIMIT 1 51 | 52 | [engine.timer] 53 | query=UPDATE users SET location=NULL,expires=NULL,oconnection_id=NULL WHERE expires IS NOT NULL AND expires<=CURRENT_TIMESTAMP AND type='user' 54 | 55 | [call.route] 56 | query=SELECT location,(CASE WHEN location IS NULL THEN 'offline' ELSE NULL END) AS error,oconnection_id FROM users WHERE username='${called}' LIMIT 1 57 | result=location 58 | priority=120 59 | 60 | ;... 61 | ``` 62 | 63 | Priorities have to be set according to your configuration. I like to have regexroute:100 and register:120 for call.route. This way it is easy to simply {\em return} in regexroute and let register handle the routing. 64 | 65 | The field `oconnection_id` is needed for Yate to map incoming calls to open TCP and TLS connections. SIP over UDP should work just fine without this field. 66 | 67 | Tables were created like this: 68 | 69 | ```SQL 70 | -- create Mysql user 71 | CREATE USER 'yate'@'localhost' IDENTIFIED BY 'mypassword'; 72 | CREATE DATABASE yate; 73 | GRANT DELETE, INSERT, SELECT, USAGE, UPDATE ON yate.* TO 'yate'@'localhost'; 74 | FLUSH PRIVILEGES; 75 | 76 | use yate; 77 | 78 | CREATE TABLE users ( 79 | username VARCHAR(128) UNIQUE, 80 | `password` VARCHAR(128), 81 | inuse INTEGER, 82 | `location` VARCHAR(1024), 83 | expires TIMESTAMP NULL DEFAULT NULL, 84 | `type` VARCHAR(20) NULL DEFAULT NULL, 85 | oconnection_id varchar(1024) null default null); 86 | ``` 87 | -------------------------------------------------------------------------------- /70-audio.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Audio 4 | permalink: /audio/ 5 | --- 6 | 7 | ## Converting Audio for Yate 8 | 9 | Despite its name, the *wave* module (as in `wave/play/...`) does not actually support wave files. It can however process raw 8kHz single channel signed-integer PCM audio data. And this is how to create such files: 10 | 11 | sox sound.wav -t raw -r 8000 -c 1 -e signed-integer sound.slin 12 | 13 | I like to wrap things in shell scripts for simple reuse: 14 | `wav2slin.sh:` 15 | 16 | ```sh 17 | #!/bin/sh 18 | sox $1 -t raw -r 8000 -c 1 -e signed-integer $2 19 | ``` -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Site settings 2 | title: BeF's Yate Cookbook 3 | baseurl: "/yate-cookbook" # the subpath of your site, e.g. /blog/ 4 | projecturl: "https://bef.github.com/yate-cookbook" 5 | github_project: bef/yate-cookbook 6 | 7 | # Build settings 8 | markdown: redcarpet 9 | highlighter: pygments 10 | -------------------------------------------------------------------------------- /_includes/footer.html: -------------------------------------------------------------------------------- 1 | 56 | -------------------------------------------------------------------------------- /_includes/head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% if page.title %}{{ page.title }}{% else %}{{ site.title }}{% endif %} 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /_includes/header.html: -------------------------------------------------------------------------------- 1 | 28 | -------------------------------------------------------------------------------- /_layouts/default.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include head.html %} 5 | 6 | 7 | 8 | {% include header.html %} 9 | 10 |
11 |
12 | {{ content }} 13 |
14 |
15 | 16 | {% include footer.html %} 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 |
9 | 10 |
11 | {{ content }} 12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /_layouts/post.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: default 3 | --- 4 |
5 | 6 |
7 |

{{ page.title }}

8 | 9 |
10 | 11 |
12 | {{ content }} 13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /_sass/_base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Reset some basic elements 3 | */ 4 | body, h1, h2, h3, h4, h5, h6, 5 | p, blockquote, pre, hr, 6 | dl, dd, ol, ul, figure { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | 12 | 13 | /** 14 | * Basic styling 15 | */ 16 | body { 17 | font-family: $base-font-family; 18 | font-size: $base-font-size; 19 | line-height: $base-line-height; 20 | font-weight: 300; 21 | color: $text-color; 22 | background-color: $background-color; 23 | -webkit-text-size-adjust: 100%; 24 | } 25 | 26 | 27 | 28 | /** 29 | * Set `margin-bottom` to maintain vertical rhythm 30 | */ 31 | h1, h2, h3, h4, h5, h6, 32 | p, blockquote, pre, 33 | ul, ol, dl, figure, 34 | %vertical-rhythm { 35 | margin-bottom: $spacing-unit / 2; 36 | } 37 | 38 | 39 | 40 | /** 41 | * Images 42 | */ 43 | img { 44 | max-width: 100%; 45 | vertical-align: middle; 46 | } 47 | 48 | 49 | 50 | /** 51 | * Figures 52 | */ 53 | figure > img { 54 | display: block; 55 | } 56 | 57 | figcaption { 58 | font-size: $small-font-size; 59 | } 60 | 61 | 62 | 63 | /** 64 | * Lists 65 | */ 66 | ul, ol { 67 | margin-left: $spacing-unit; 68 | } 69 | 70 | li { 71 | > ul, 72 | > ol { 73 | margin-bottom: 0; 74 | } 75 | } 76 | 77 | 78 | 79 | /** 80 | * Headings 81 | */ 82 | h1, h2, h3, h4, h5, h6 { 83 | font-weight: 300; 84 | } 85 | 86 | 87 | 88 | /** 89 | * Links 90 | */ 91 | a { 92 | color: $brand-color; 93 | text-decoration: none; 94 | 95 | &:visited { 96 | color: darken($brand-color, 15%); 97 | } 98 | 99 | &:hover { 100 | color: $text-color; 101 | text-decoration: underline; 102 | } 103 | } 104 | 105 | 106 | 107 | /** 108 | * Blockquotes 109 | */ 110 | blockquote { 111 | color: $grey-color; 112 | border-left: 4px solid $grey-color-light; 113 | padding-left: $spacing-unit / 2; 114 | font-size: 18px; 115 | letter-spacing: -1px; 116 | font-style: italic; 117 | 118 | > :last-child { 119 | margin-bottom: 0; 120 | } 121 | } 122 | 123 | 124 | 125 | /** 126 | * Code formatting 127 | */ 128 | pre, 129 | code { 130 | font-size: 15px; 131 | border: 1px solid $grey-color-light; 132 | border-radius: 3px; 133 | background-color: #eef; 134 | } 135 | 136 | code { 137 | padding: 1px 5px; 138 | } 139 | 140 | pre { 141 | padding: 8px 12px; 142 | overflow-x: scroll; 143 | 144 | > code { 145 | border: 0; 146 | padding-right: 0; 147 | padding-left: 0; 148 | } 149 | } 150 | 151 | 152 | 153 | /** 154 | * Wrapper 155 | */ 156 | .wrapper { 157 | max-width: -webkit-calc(800px - (#{$spacing-unit} * 2)); 158 | max-width: calc(800px - (#{$spacing-unit} * 2)); 159 | margin-right: auto; 160 | margin-left: auto; 161 | padding-right: $spacing-unit; 162 | padding-left: $spacing-unit; 163 | @extend %clearfix; 164 | 165 | @include media-query($on-laptop) { 166 | max-width: -webkit-calc(800px - (#{$spacing-unit})); 167 | max-width: calc(800px - (#{$spacing-unit})); 168 | padding-right: $spacing-unit / 2; 169 | padding-left: $spacing-unit / 2; 170 | } 171 | } 172 | 173 | 174 | 175 | /** 176 | * Clearfix 177 | */ 178 | %clearfix { 179 | 180 | &:after { 181 | content: ""; 182 | display: table; 183 | clear: both; 184 | } 185 | } 186 | 187 | 188 | 189 | /** 190 | * Icons 191 | */ 192 | .icon { 193 | 194 | > svg { 195 | display: inline-block; 196 | width: 16px; 197 | height: 16px; 198 | vertical-align: middle; 199 | 200 | path { 201 | fill: $grey-color; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /_sass/_layout.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Site header 3 | */ 4 | .site-header { 5 | border-top: 5px solid $grey-color-dark; 6 | border-bottom: 1px solid $grey-color-light; 7 | min-height: 56px; 8 | 9 | // Positioning context for the mobile navigation icon 10 | position: relative; 11 | } 12 | 13 | .site-title { 14 | font-size: 26px; 15 | line-height: 56px; 16 | letter-spacing: -1px; 17 | margin-bottom: 0; 18 | float: left; 19 | 20 | &, 21 | &:visited { 22 | color: $grey-color-dark; 23 | } 24 | } 25 | 26 | .site-nav { 27 | float: right; 28 | line-height: 56px; 29 | 30 | .menu-icon { 31 | display: none; 32 | } 33 | 34 | .page-link { 35 | color: $text-color; 36 | line-height: $base-line-height; 37 | 38 | // Gaps between nav items, but not on the first one 39 | &:not(:first-child) { 40 | margin-left: 20px; 41 | } 42 | } 43 | 44 | @include media-query($on-palm) { 45 | position: absolute; 46 | top: 9px; 47 | right: 30px; 48 | background-color: $background-color; 49 | border: 1px solid $grey-color-light; 50 | border-radius: 5px; 51 | text-align: right; 52 | 53 | .menu-icon { 54 | display: block; 55 | float: right; 56 | width: 36px; 57 | height: 26px; 58 | line-height: 0; 59 | padding-top: 10px; 60 | text-align: center; 61 | 62 | > svg { 63 | width: 18px; 64 | height: 15px; 65 | 66 | path { 67 | fill: $grey-color-dark; 68 | } 69 | } 70 | } 71 | 72 | .trigger { 73 | clear: both; 74 | display: none; 75 | } 76 | 77 | &:hover .trigger { 78 | display: block; 79 | padding-bottom: 5px; 80 | } 81 | 82 | .page-link { 83 | display: block; 84 | padding: 5px 10px; 85 | } 86 | } 87 | } 88 | 89 | 90 | 91 | /** 92 | * Site footer 93 | */ 94 | .site-footer { 95 | border-top: 1px solid $grey-color-light; 96 | padding: $spacing-unit 0; 97 | } 98 | 99 | .footer-heading { 100 | font-size: 18px; 101 | margin-bottom: $spacing-unit / 2; 102 | } 103 | 104 | .contact-list, 105 | .social-media-list { 106 | list-style: none; 107 | margin-left: 0; 108 | } 109 | 110 | .footer-col-wrapper { 111 | font-size: 15px; 112 | color: $grey-color; 113 | margin-left: -$spacing-unit / 2; 114 | @extend %clearfix; 115 | } 116 | 117 | .footer-col { 118 | float: left; 119 | margin-bottom: $spacing-unit / 2; 120 | padding-left: $spacing-unit / 2; 121 | } 122 | 123 | .footer-col-1 { 124 | width: -webkit-calc(35% - (#{$spacing-unit} / 2)); 125 | width: calc(35% - (#{$spacing-unit} / 2)); 126 | } 127 | 128 | .footer-col-2 { 129 | width: -webkit-calc(20% - (#{$spacing-unit} / 2)); 130 | width: calc(20% - (#{$spacing-unit} / 2)); 131 | } 132 | 133 | .footer-col-3 { 134 | width: -webkit-calc(45% - (#{$spacing-unit} / 2)); 135 | width: calc(45% - (#{$spacing-unit} / 2)); 136 | } 137 | 138 | @include media-query($on-laptop) { 139 | .footer-col-1, 140 | .footer-col-2 { 141 | width: -webkit-calc(50% - (#{$spacing-unit} / 2)); 142 | width: calc(50% - (#{$spacing-unit} / 2)); 143 | } 144 | 145 | .footer-col-3 { 146 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 147 | width: calc(100% - (#{$spacing-unit} / 2)); 148 | } 149 | } 150 | 151 | @include media-query($on-palm) { 152 | .footer-col { 153 | float: none; 154 | width: -webkit-calc(100% - (#{$spacing-unit} / 2)); 155 | width: calc(100% - (#{$spacing-unit} / 2)); 156 | } 157 | } 158 | 159 | 160 | 161 | /** 162 | * Page content 163 | */ 164 | .page-content { 165 | padding: $spacing-unit 0; 166 | } 167 | 168 | .page-heading { 169 | font-size: 20px; 170 | } 171 | 172 | .post-list { 173 | margin-left: 0; 174 | list-style: none; 175 | 176 | > li { 177 | margin-bottom: $spacing-unit; 178 | } 179 | } 180 | 181 | .post-meta { 182 | font-size: $small-font-size; 183 | color: $grey-color; 184 | } 185 | 186 | .post-link { 187 | display: block; 188 | font-size: 24px; 189 | } 190 | 191 | 192 | 193 | /** 194 | * Posts 195 | */ 196 | .post-header { 197 | margin-bottom: $spacing-unit; 198 | } 199 | 200 | .post-title { 201 | font-size: 42px; 202 | letter-spacing: -1px; 203 | line-height: 1; 204 | 205 | @include media-query($on-laptop) { 206 | font-size: 36px; 207 | } 208 | } 209 | 210 | .post-content { 211 | margin-bottom: $spacing-unit; 212 | 213 | h2 { 214 | font-size: 32px; 215 | 216 | @include media-query($on-laptop) { 217 | font-size: 28px; 218 | } 219 | } 220 | 221 | h3 { 222 | font-size: 26px; 223 | 224 | @include media-query($on-laptop) { 225 | font-size: 22px; 226 | } 227 | } 228 | 229 | h4 { 230 | font-size: 20px; 231 | 232 | @include media-query($on-laptop) { 233 | font-size: 18px; 234 | } 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /_sass/_syntax-highlighting.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Syntax highlighting styles 3 | */ 4 | .highlight { 5 | background: #fff; 6 | @extend %vertical-rhythm; 7 | 8 | .c { color: #998; font-style: italic } // Comment 9 | .err { color: #a61717; background-color: #e3d2d2 } // Error 10 | .k { font-weight: bold } // Keyword 11 | .o { font-weight: bold } // Operator 12 | .cm { color: #998; font-style: italic } // Comment.Multiline 13 | .cp { color: #999; font-weight: bold } // Comment.Preproc 14 | .c1 { color: #998; font-style: italic } // Comment.Single 15 | .cs { color: #999; font-weight: bold; font-style: italic } // Comment.Special 16 | .gd { color: #000; background-color: #fdd } // Generic.Deleted 17 | .gd .x { color: #000; background-color: #faa } // Generic.Deleted.Specific 18 | .ge { font-style: italic } // Generic.Emph 19 | .gr { color: #a00 } // Generic.Error 20 | .gh { color: #999 } // Generic.Heading 21 | .gi { color: #000; background-color: #dfd } // Generic.Inserted 22 | .gi .x { color: #000; background-color: #afa } // Generic.Inserted.Specific 23 | .go { color: #888 } // Generic.Output 24 | .gp { color: #555 } // Generic.Prompt 25 | .gs { font-weight: bold } // Generic.Strong 26 | .gu { color: #aaa } // Generic.Subheading 27 | .gt { color: #a00 } // Generic.Traceback 28 | .kc { font-weight: bold } // Keyword.Constant 29 | .kd { font-weight: bold } // Keyword.Declaration 30 | .kp { font-weight: bold } // Keyword.Pseudo 31 | .kr { font-weight: bold } // Keyword.Reserved 32 | .kt { color: #458; font-weight: bold } // Keyword.Type 33 | .m { color: #099 } // Literal.Number 34 | .s { color: #d14 } // Literal.String 35 | .na { color: #008080 } // Name.Attribute 36 | .nb { color: #0086B3 } // Name.Builtin 37 | .nc { color: #458; font-weight: bold } // Name.Class 38 | .no { color: #008080 } // Name.Constant 39 | .ni { color: #800080 } // Name.Entity 40 | .ne { color: #900; font-weight: bold } // Name.Exception 41 | .nf { color: #900; font-weight: bold } // Name.Function 42 | .nn { color: #555 } // Name.Namespace 43 | .nt { color: #000080 } // Name.Tag 44 | .nv { color: #008080 } // Name.Variable 45 | .ow { font-weight: bold } // Operator.Word 46 | .w { color: #bbb } // Text.Whitespace 47 | .mf { color: #099 } // Literal.Number.Float 48 | .mh { color: #099 } // Literal.Number.Hex 49 | .mi { color: #099 } // Literal.Number.Integer 50 | .mo { color: #099 } // Literal.Number.Oct 51 | .sb { color: #d14 } // Literal.String.Backtick 52 | .sc { color: #d14 } // Literal.String.Char 53 | .sd { color: #d14 } // Literal.String.Doc 54 | .s2 { color: #d14 } // Literal.String.Double 55 | .se { color: #d14 } // Literal.String.Escape 56 | .sh { color: #d14 } // Literal.String.Heredoc 57 | .si { color: #d14 } // Literal.String.Interpol 58 | .sx { color: #d14 } // Literal.String.Other 59 | .sr { color: #009926 } // Literal.String.Regex 60 | .s1 { color: #d14 } // Literal.String.Single 61 | .ss { color: #990073 } // Literal.String.Symbol 62 | .bp { color: #999 } // Name.Builtin.Pseudo 63 | .vc { color: #008080 } // Name.Variable.Class 64 | .vg { color: #008080 } // Name.Variable.Global 65 | .vi { color: #008080 } // Name.Variable.Instance 66 | .il { color: #099 } // Literal.Number.Integer.Long 67 | } 68 | -------------------------------------------------------------------------------- /css/main.scss: -------------------------------------------------------------------------------- 1 | --- 2 | # Only the main Sass file needs front matter (the dashes are enough) 3 | --- 4 | @charset "utf-8"; 5 | 6 | 7 | 8 | // Our variables 9 | $base-font-family: Helvetica, Arial, sans-serif; 10 | $base-font-size: 16px; 11 | $small-font-size: $base-font-size * 0.875; 12 | $base-line-height: 1.5; 13 | 14 | $spacing-unit: 30px; 15 | 16 | $text-color: #111; 17 | $background-color: #fdfdfd; 18 | $brand-color: #2a7ae2; 19 | 20 | $grey-color: #828282; 21 | $grey-color-light: lighten($grey-color, 40%); 22 | $grey-color-dark: darken($grey-color, 25%); 23 | 24 | $on-palm: 600px; 25 | $on-laptop: 800px; 26 | 27 | 28 | 29 | // Using media queries with like this: 30 | // @include media-query($palm) { 31 | // .wrapper { 32 | // padding-right: $spacing-unit / 2; 33 | // padding-left: $spacing-unit / 2; 34 | // } 35 | // } 36 | @mixin media-query($device) { 37 | @media screen and (max-width: $device) { 38 | @content; 39 | } 40 | } 41 | 42 | 43 | 44 | // Import partials from `sass_dir` (defaults to `_sass`) 45 | @import 46 | "base", 47 | "layout", 48 | "syntax-highlighting" 49 | ; 50 | --------------------------------------------------------------------------------