├── .gitignore ├── COPYING ├── INSTALL ├── Makefile.am ├── README.md ├── autogen.sh ├── configure.ac ├── xoauth2_client.c ├── xoauth2_init.c ├── xoauth2_plugin.h ├── xoauth2_server.c └── xoauth2_str.c /.gitignore: -------------------------------------------------------------------------------- 1 | aclocal.m4 2 | autom4te.cache 3 | compile 4 | config.guess 5 | config.h 6 | config.h.in 7 | config.h.in~ 8 | config.log 9 | config.status 10 | config.sub 11 | configure 12 | depcomp 13 | .deps 14 | *.dylib 15 | install-sh 16 | *.la 17 | .libs 18 | libtool 19 | *.lo 20 | ltmain.sh 21 | m4 22 | Makefile 23 | Makefile.in 24 | missing 25 | *.o 26 | *.so 27 | stamp-h1 28 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Moriyoshi Koizumi 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | Installation Instructions 2 | ************************* 3 | 4 | Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, 5 | Inc. 6 | 7 | Copying and distribution of this file, with or without modification, 8 | are permitted in any medium without royalty provided the copyright 9 | notice and this notice are preserved. This file is offered as-is, 10 | without warranty of any kind. 11 | 12 | Basic Installation 13 | ================== 14 | 15 | Briefly, the shell command `./configure && make && make install' 16 | should configure, build, and install this package. The following 17 | more-detailed instructions are generic; see the `README' file for 18 | instructions specific to this package. Some packages provide this 19 | `INSTALL' file but do not implement all of the features documented 20 | below. The lack of an optional feature in a given package is not 21 | necessarily a bug. More recommendations for GNU packages can be found 22 | in *note Makefile Conventions: (standards)Makefile Conventions. 23 | 24 | The `configure' shell script attempts to guess correct values for 25 | various system-dependent variables used during compilation. It uses 26 | those values to create a `Makefile' in each directory of the package. 27 | It may also create one or more `.h' files containing system-dependent 28 | definitions. Finally, it creates a shell script `config.status' that 29 | you can run in the future to recreate the current configuration, and a 30 | file `config.log' containing compiler output (useful mainly for 31 | debugging `configure'). 32 | 33 | It can also use an optional file (typically called `config.cache' 34 | and enabled with `--cache-file=config.cache' or simply `-C') that saves 35 | the results of its tests to speed up reconfiguring. Caching is 36 | disabled by default to prevent problems with accidental use of stale 37 | cache files. 38 | 39 | If you need to do unusual things to compile the package, please try 40 | to figure out how `configure' could check whether to do them, and mail 41 | diffs or instructions to the address given in the `README' so they can 42 | be considered for the next release. If you are using the cache, and at 43 | some point `config.cache' contains results you don't want to keep, you 44 | may remove or edit it. 45 | 46 | The file `configure.ac' (or `configure.in') is used to create 47 | `configure' by a program called `autoconf'. You need `configure.ac' if 48 | you want to change it or regenerate `configure' using a newer version 49 | of `autoconf'. 50 | 51 | The simplest way to compile this package is: 52 | 53 | 1. `cd' to the directory containing the package's source code and type 54 | `./configure' to configure the package for your system. 55 | 56 | Running `configure' might take a while. While running, it prints 57 | some messages telling which features it is checking for. 58 | 59 | 2. Type `make' to compile the package. 60 | 61 | 3. Optionally, type `make check' to run any self-tests that come with 62 | the package, generally using the just-built uninstalled binaries. 63 | 64 | 4. Type `make install' to install the programs and any data files and 65 | documentation. When installing into a prefix owned by root, it is 66 | recommended that the package be configured and built as a regular 67 | user, and only the `make install' phase executed with root 68 | privileges. 69 | 70 | 5. Optionally, type `make installcheck' to repeat any self-tests, but 71 | this time using the binaries in their final installed location. 72 | This target does not install anything. Running this target as a 73 | regular user, particularly if the prior `make install' required 74 | root privileges, verifies that the installation completed 75 | correctly. 76 | 77 | 6. You can remove the program binaries and object files from the 78 | source code directory by typing `make clean'. To also remove the 79 | files that `configure' created (so you can compile the package for 80 | a different kind of computer), type `make distclean'. There is 81 | also a `make maintainer-clean' target, but that is intended mainly 82 | for the package's developers. If you use it, you may have to get 83 | all sorts of other programs in order to regenerate files that came 84 | with the distribution. 85 | 86 | 7. Often, you can also type `make uninstall' to remove the installed 87 | files again. In practice, not all packages have tested that 88 | uninstallation works correctly, even though it is required by the 89 | GNU Coding Standards. 90 | 91 | 8. Some packages, particularly those that use Automake, provide `make 92 | distcheck', which can by used by developers to test that all other 93 | targets like `make install' and `make uninstall' work correctly. 94 | This target is generally not run by end users. 95 | 96 | Compilers and Options 97 | ===================== 98 | 99 | Some systems require unusual options for compilation or linking that 100 | the `configure' script does not know about. Run `./configure --help' 101 | for details on some of the pertinent environment variables. 102 | 103 | You can give `configure' initial values for configuration parameters 104 | by setting variables in the command line or in the environment. Here 105 | is an example: 106 | 107 | ./configure CC=c99 CFLAGS=-g LIBS=-lposix 108 | 109 | *Note Defining Variables::, for more details. 110 | 111 | Compiling For Multiple Architectures 112 | ==================================== 113 | 114 | You can compile the package for more than one kind of computer at the 115 | same time, by placing the object files for each architecture in their 116 | own directory. To do this, you can use GNU `make'. `cd' to the 117 | directory where you want the object files and executables to go and run 118 | the `configure' script. `configure' automatically checks for the 119 | source code in the directory that `configure' is in and in `..'. This 120 | is known as a "VPATH" build. 121 | 122 | With a non-GNU `make', it is safer to compile the package for one 123 | architecture at a time in the source code directory. After you have 124 | installed the package for one architecture, use `make distclean' before 125 | reconfiguring for another architecture. 126 | 127 | On MacOS X 10.5 and later systems, you can create libraries and 128 | executables that work on multiple system types--known as "fat" or 129 | "universal" binaries--by specifying multiple `-arch' options to the 130 | compiler but only a single `-arch' option to the preprocessor. Like 131 | this: 132 | 133 | ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 134 | CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ 135 | CPP="gcc -E" CXXCPP="g++ -E" 136 | 137 | This is not guaranteed to produce working output in all cases, you 138 | may have to build one architecture at a time and combine the results 139 | using the `lipo' tool if you have problems. 140 | 141 | Installation Names 142 | ================== 143 | 144 | By default, `make install' installs the package's commands under 145 | `/usr/local/bin', include files under `/usr/local/include', etc. You 146 | can specify an installation prefix other than `/usr/local' by giving 147 | `configure' the option `--prefix=PREFIX', where PREFIX must be an 148 | absolute file name. 149 | 150 | You can specify separate installation prefixes for 151 | architecture-specific files and architecture-independent files. If you 152 | pass the option `--exec-prefix=PREFIX' to `configure', the package uses 153 | PREFIX as the prefix for installing programs and libraries. 154 | Documentation and other data files still use the regular prefix. 155 | 156 | In addition, if you use an unusual directory layout you can give 157 | options like `--bindir=DIR' to specify different values for particular 158 | kinds of files. Run `configure --help' for a list of the directories 159 | you can set and what kinds of files go in them. In general, the 160 | default for these options is expressed in terms of `${prefix}', so that 161 | specifying just `--prefix' will affect all of the other directory 162 | specifications that were not explicitly provided. 163 | 164 | The most portable way to affect installation locations is to pass the 165 | correct locations to `configure'; however, many packages provide one or 166 | both of the following shortcuts of passing variable assignments to the 167 | `make install' command line to change installation locations without 168 | having to reconfigure or recompile. 169 | 170 | The first method involves providing an override variable for each 171 | affected directory. For example, `make install 172 | prefix=/alternate/directory' will choose an alternate location for all 173 | directory configuration variables that were expressed in terms of 174 | `${prefix}'. Any directories that were specified during `configure', 175 | but not in terms of `${prefix}', must each be overridden at install 176 | time for the entire installation to be relocated. The approach of 177 | makefile variable overrides for each directory variable is required by 178 | the GNU Coding Standards, and ideally causes no recompilation. 179 | However, some platforms have known limitations with the semantics of 180 | shared libraries that end up requiring recompilation when using this 181 | method, particularly noticeable in packages that use GNU Libtool. 182 | 183 | The second method involves providing the `DESTDIR' variable. For 184 | example, `make install DESTDIR=/alternate/directory' will prepend 185 | `/alternate/directory' before all installation names. The approach of 186 | `DESTDIR' overrides is not required by the GNU Coding Standards, and 187 | does not work on platforms that have drive letters. On the other hand, 188 | it does better at avoiding recompilation issues, and works well even 189 | when some directory options were not specified in terms of `${prefix}' 190 | at `configure' time. 191 | 192 | Optional Features 193 | ================= 194 | 195 | If the package supports it, you can cause programs to be installed 196 | with an extra prefix or suffix on their names by giving `configure' the 197 | option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. 198 | 199 | Some packages pay attention to `--enable-FEATURE' options to 200 | `configure', where FEATURE indicates an optional part of the package. 201 | They may also pay attention to `--with-PACKAGE' options, where PACKAGE 202 | is something like `gnu-as' or `x' (for the X Window System). The 203 | `README' should mention any `--enable-' and `--with-' options that the 204 | package recognizes. 205 | 206 | For packages that use the X Window System, `configure' can usually 207 | find the X include and library files automatically, but if it doesn't, 208 | you can use the `configure' options `--x-includes=DIR' and 209 | `--x-libraries=DIR' to specify their locations. 210 | 211 | Some packages offer the ability to configure how verbose the 212 | execution of `make' will be. For these packages, running `./configure 213 | --enable-silent-rules' sets the default to minimal output, which can be 214 | overridden with `make V=1'; while running `./configure 215 | --disable-silent-rules' sets the default to verbose, which can be 216 | overridden with `make V=0'. 217 | 218 | Particular systems 219 | ================== 220 | 221 | On HP-UX, the default C compiler is not ANSI C compatible. If GNU 222 | CC is not installed, it is recommended to use the following options in 223 | order to use an ANSI C compiler: 224 | 225 | ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" 226 | 227 | and if that doesn't work, install pre-built binaries of GCC for HP-UX. 228 | 229 | HP-UX `make' updates targets which have the same time stamps as 230 | their prerequisites, which makes it generally unusable when shipped 231 | generated files such as `configure' are involved. Use GNU `make' 232 | instead. 233 | 234 | On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot 235 | parse its `' header file. The option `-nodtk' can be used as 236 | a workaround. If GNU CC is not installed, it is therefore recommended 237 | to try 238 | 239 | ./configure CC="cc" 240 | 241 | and if that doesn't work, try 242 | 243 | ./configure CC="cc -nodtk" 244 | 245 | On Solaris, don't put `/usr/ucb' early in your `PATH'. This 246 | directory contains several dysfunctional programs; working variants of 247 | these programs are available in `/usr/bin'. So, if you need `/usr/ucb' 248 | in your `PATH', put it _after_ `/usr/bin'. 249 | 250 | On Haiku, software installed for all users goes in `/boot/common', 251 | not `/usr/local'. It is recommended to use the following options: 252 | 253 | ./configure --prefix=/boot/common 254 | 255 | Specifying the System Type 256 | ========================== 257 | 258 | There may be some features `configure' cannot figure out 259 | automatically, but needs to determine by the type of machine the package 260 | will run on. Usually, assuming the package is built to be run on the 261 | _same_ architectures, `configure' can figure that out, but if it prints 262 | a message saying it cannot guess the machine type, give it the 263 | `--build=TYPE' option. TYPE can either be a short name for the system 264 | type, such as `sun4', or a canonical name which has the form: 265 | 266 | CPU-COMPANY-SYSTEM 267 | 268 | where SYSTEM can have one of these forms: 269 | 270 | OS 271 | KERNEL-OS 272 | 273 | See the file `config.sub' for the possible values of each field. If 274 | `config.sub' isn't included in this package, then this package doesn't 275 | need to know the machine type. 276 | 277 | If you are _building_ compiler tools for cross-compiling, you should 278 | use the option `--target=TYPE' to select the type of system they will 279 | produce code for. 280 | 281 | If you want to _use_ a cross compiler, that generates code for a 282 | platform different from the build platform, you should specify the 283 | "host" platform (i.e., that on which the generated programs will 284 | eventually be run) with `--host=TYPE'. 285 | 286 | Sharing Defaults 287 | ================ 288 | 289 | If you want to set default values for `configure' scripts to share, 290 | you can create a site shell script called `config.site' that gives 291 | default values for variables like `CC', `cache_file', and `prefix'. 292 | `configure' looks for `PREFIX/share/config.site' if it exists, then 293 | `PREFIX/etc/config.site' if it exists. Or, you can set the 294 | `CONFIG_SITE' environment variable to the location of the site script. 295 | A warning: not all `configure' scripts look for a site script. 296 | 297 | Defining Variables 298 | ================== 299 | 300 | Variables not defined in a site shell script can be set in the 301 | environment passed to `configure'. However, some packages may run 302 | configure again during the build, and the customized values of these 303 | variables may be lost. In order to avoid this problem, you should set 304 | them in the `configure' command line, using `VAR=value'. For example: 305 | 306 | ./configure CC=/usr/local2/bin/gcc 307 | 308 | causes the specified `gcc' to be used as the C compiler (unless it is 309 | overridden in the site shell script). 310 | 311 | Unfortunately, this technique does not work for `CONFIG_SHELL' due to 312 | an Autoconf limitation. Until the limitation is lifted, you can use 313 | this workaround: 314 | 315 | CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash 316 | 317 | `configure' Invocation 318 | ====================== 319 | 320 | `configure' recognizes the following options to control how it 321 | operates. 322 | 323 | `--help' 324 | `-h' 325 | Print a summary of all of the options to `configure', and exit. 326 | 327 | `--help=short' 328 | `--help=recursive' 329 | Print a summary of the options unique to this package's 330 | `configure', and exit. The `short' variant lists options used 331 | only in the top level, while the `recursive' variant lists options 332 | also present in any nested packages. 333 | 334 | `--version' 335 | `-V' 336 | Print the version of Autoconf used to generate the `configure' 337 | script, and exit. 338 | 339 | `--cache-file=FILE' 340 | Enable the cache: use and save the results of the tests in FILE, 341 | traditionally `config.cache'. FILE defaults to `/dev/null' to 342 | disable caching. 343 | 344 | `--config-cache' 345 | `-C' 346 | Alias for `--cache-file=config.cache'. 347 | 348 | `--quiet' 349 | `--silent' 350 | `-q' 351 | Do not print messages saying which checks are being made. To 352 | suppress all normal output, redirect it to `/dev/null' (any error 353 | messages will still be shown). 354 | 355 | `--srcdir=DIR' 356 | Look for the package's source code in directory DIR. Usually 357 | `configure' can determine that directory automatically. 358 | 359 | `--prefix=DIR' 360 | Use DIR as the installation prefix. *note Installation Names:: 361 | for more details, including other options available for fine-tuning 362 | the installation locations. 363 | 364 | `--no-create' 365 | `-n' 366 | Run the configure checks, but stop before creating any output 367 | files. 368 | 369 | `configure' also accepts some other, not widely useful, options. Run 370 | `configure --help' for more details. 371 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | ACLOCAL_AMFLAGS = -I m4 2 | CYRYS_SASL_PREFIX = @CYRUS_SASL_PREFIX@ 3 | CYRYS_SASL_CPPFLAGS = @CYRUS_SASL_CPPFLAGS@ 4 | CYRUS_SASL_LDFLAGS = @CYRUS_SASL_LDFLAGS@ 5 | 6 | pkglibdir = ${CYRUS_SASL_PREFIX}/lib/sasl2 7 | pkglib_LTLIBRARIES = libxoauth2.la 8 | libxoauth2_la_CPPFLAGS = ${CYRUS_SASL_CPPFLAGS} 9 | libxoauth2_la_LDFLAGS = ${CYRUS_SASL_LDFLAGS} -module 10 | libxoauth2_la_SOURCES = xoauth2_str.c xoauth2_init.c xoauth2_server.c xoauth2_client.c 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cyrus-sasl-xoauth2 2 | 3 | This is a plugin implementation of [XOAUTH2](https://developers.google.com/gmail/xoauth2_protocol). 4 | 5 | FYI: if you are forced to use XOAUTH2-enabled SMTP / IMAP servers by your employer and want to keep using your favorite \*nix MUA locally, the following detailed document should help a lot: http://mmogilvi.users.sourceforge.net/software/oauthbearer.html (DISCLAIMER: in contrast to the document's author, I'd rather read and write emails on my browser a lot. I haven't tested it personally) 6 | 7 | ## Releases 8 | 9 | * [v0.2 (Apr 28, 2020)](https://github.com/moriyoshi/cyrus-sasl-xoauth2/releases/tag/v0.2) 10 | * [Development (Apr 28, 2020)](https://github.com/moriyoshi/cyrus-sasl-xoauth2/releases/tag/edge) 11 | 12 | ## Building and installation 13 | 14 | ``` 15 | ./autogen.sh 16 | ./configure 17 | sudo make install 18 | ``` 19 | 20 | ## Server-side configuration 21 | 22 | ### auxprops 23 | 24 | * `oauth2BearerTokens` 25 | 26 | Stores the token values for the specified authentication ID. 27 | 28 | ### SASL2 settings 29 | 30 | * `xoauth2_scope` 31 | 32 | The name of the authorization scope that will appear in the error response. 33 | 34 | 35 | ## Example: Postfix server (smtpd) / client (smtp) authentication configuration 36 | 37 | * `main.cf`: 38 | 39 | ``` 40 | # ... OTHER SETTINGS GO HERE ... 41 | 42 | # SERVER 43 | smtpd_sasl_auth_enable = yes 44 | smtpd_sasl_path = smtpd 45 | smtpd_relay_restrictions = permit_sasl_authenticated, reject 46 | 47 | # CLIENT 48 | relayhost = [smtp.gmail.com]:587 49 | smtp_sasl_auth_enable = yes 50 | smtp_sasl_password_maps = hash:/etc/postfix/saslpasswd 51 | smtp_sasl_mechanism_filter = xoauth2 52 | smtp_sasl_security_options = 53 | smtp_tls_security_level = may 54 | smtp_tls_policy_maps = hash:/etc/postfix/tls_policy 55 | 56 | ``` 57 | 58 | * `/etc/postfix/saslpasswd`: 59 | 60 | ``` 61 | [smtp.gmail.com]:587 YOUR-ACCOUNT@gmail.com:OAUTH2-TOKEN-RETRIEVED-BY-GMAIL-OAUTH2-TOOLS 62 | ``` 63 | 64 | * `/etc/postfix/saslpasswd.db` needs to be generated with `postmap`: 65 | 66 | ``` 67 | # postmap /etc/postfix/saslpasswd 68 | ``` 69 | 70 | * Gmail OAuth2 Tools can be found [here](https://github.com/google/gmail-oauth2-tools). 71 | 72 | * `/etc/postfix/tls_policy`: 73 | 74 | ``` 75 | [smtp.gmail.com]:587 encrypt 76 | ``` 77 | 78 | * `/etc/postfix/tls_policy.db` needs to be generated with `postmap`: 79 | 80 | ``` 81 | # postmap /etc/postfix/tls_policy 82 | ``` 83 | 84 | * `${sasl_plugin_dir}/smtpd.conf`: 85 | 86 | ``` 87 | log_level: DEBUG 88 | sql_engine: sqlite3 89 | sql_database: /etc/sasldb2.sqlite3 90 | sql_select: SELECT props.value FROM users JOIN props ON users.id=props.user_id WHERE users.name='%u' AND users.realm='%r' AND props.name='%p' 91 | xoauth2_scope: https://mail.example.com/ 92 | auxprop_plugin: sql 93 | mech_list: xoauth2 94 | ``` 95 | 96 | * `/etc/sasldb2.sqlite3`: 97 | 98 | Generated from the following DDL and SQL statements: 99 | 100 | ``` 101 | PRAGMA foreign_keys=OFF; 102 | BEGIN TRANSACTION; 103 | CREATE TABLE users (id INTEGER PRIMARY KEY, name VARCHAR, password VARCHAR, realm VARCHAR); 104 | INSERT INTO "users" VALUES(1,'test','test','example.com'); 105 | CREATE TABLE props (id INTEGER PRIMARY KEY, user_id INTEGER, name VARCHAR, value VARCHAR, FOREIGN KEY (user_id) REFERENCES users (id)); 106 | INSERT INTO "props" VALUES(1,1,'userPassword','*'); 107 | INSERT INTO "props" VALUES(2,1,'oauth2BearerTokens','token'); 108 | COMMIT; 109 | ``` 110 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | libtoolize 2 | install -d m4 3 | aclocal -I m4 4 | autoheader 5 | automake -c -a --foreign 6 | autoconf 7 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | dnl Copyright (c) 2016 Moriyoshi Koizumi 2 | dnl 3 | dnl Permission is hereby granted, free of charge, to any person obtaining a 4 | dnl copy of this software and associated documentation files (the "Software"), 5 | dnl to deal in the Software without restriction, including without limitation 6 | dnl the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | dnl and/or sell copies of the Software, and to permit persons to whom the 8 | dnl Software is furnished to do so, subject to the following conditions: 9 | dnl 10 | dnl The above copyright notice and this permission notice shall be included in 11 | dnl all copies or substantial portions of the Software. 12 | dnl 13 | dnl THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | dnl IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | dnl FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | dnl AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | dnl LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | dnl FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | dnl DEALINGS IN THE SOFTWARE. 20 | 21 | AC_INIT([cyrus-sasl-xoauth2], [0.0]) 22 | AM_INIT_AUTOMAKE 23 | AC_CONFIG_MACRO_DIR([m4]) 24 | LT_INIT 25 | AC_PROG_CC 26 | AC_CONFIG_HEADERS([config.h]) 27 | AC_CONFIG_FILES([Makefile]) 28 | 29 | CYRUS_SASL_CPPFLAGS= 30 | CYRUS_SASL_PREFIX=/usr 31 | CYRUS_SASL_LDFLAGS= 32 | 33 | # Cygwin stuff 34 | case "$host" in 35 | *-*-cygwin*) 36 | CYRUS_SASL_LDFLAGS=-no-undefined 37 | ;; 38 | esac 39 | 40 | AC_ARG_WITH( 41 | [cyrus-sasl], 42 | AS_HELP_STRING([--with-cyrus-sasl=[[PREFIX]]], [Installation prefix of Cyrus-SASL (defaults to /usr)]), 43 | [CYRUS_SASL_PREFIXES=$withval], 44 | [CYRUS_SASL_PREFIXES=/usr] 45 | ) 46 | 47 | for prefix in "$CYRUS_SASL_PREFIXES"; do 48 | ac_save_CPPFLAGS="$CPPFLAGS" 49 | CPPFLAGS="-I$prefix/include $CPPFLAGS" 50 | AC_CHECK_HEADER([sasl/sasl.h], [ 51 | CYRUS_SASL_CPPFLAGS="-I$prefix/include" 52 | CYRUS_SASL_PREFIX="$prefix" 53 | ]) 54 | CPPFLAGS="$ac_save_CPPFLAGS" 55 | if test -n "$CYRUS_SASL_PREFIX"; then 56 | break 57 | fi 58 | done 59 | 60 | if test -z "$CYRUS_SASL_PREFIX"; then 61 | AC_MSG_ERROR([Cyrus-SASL not found under $CYRUS_SASL_PREFIX]) 62 | fi 63 | 64 | AC_SUBST([CYRUS_SASL_CPPFLAGS], [$CYRUS_SASL_CPPFLAGS]) 65 | AC_SUBST([CYRUS_SASL_LDFLAGS], [$CYRUS_SASL_LDFLAGS]) 66 | AC_SUBST([CYRUS_SASL_PREFIX], [$CYRUS_SASL_PREFIX]) 67 | AC_OUTPUT 68 | -------------------------------------------------------------------------------- /xoauth2_client.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Moriyoshi Koizumi 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifdef HAVE_CONFIG_H 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | #include "xoauth2_plugin.h" 29 | 30 | static int xoauth2_plugin_client_mech_new( 31 | UNUSED(void *glob_context), 32 | sasl_client_params_t *params, 33 | void **pcontext) 34 | { 35 | int err; 36 | const sasl_utils_t *utils = params->utils; 37 | xoauth2_plugin_client_context_t *context; 38 | 39 | context = SASL_malloc(sizeof(*context)); 40 | if (!context) { 41 | SASL_seterror((utils->conn, 0, "Failed to allocate memory")); 42 | return SASL_NOMEM; 43 | } 44 | 45 | context->state = 0; 46 | context->resp.buf = NULL; 47 | err = xoauth2_plugin_str_init(utils, &context->outbuf); 48 | if (err != SASL_OK) { 49 | SASL_free(context); 50 | return err; 51 | } 52 | *pcontext = context; 53 | return SASL_OK; 54 | } 55 | 56 | static int build_client_response(const sasl_utils_t *utils, xoauth2_plugin_str_t *outbuf, xoauth2_plugin_auth_response_t *resp) 57 | { 58 | int err; 59 | err = xoauth2_plugin_str_append(utils, outbuf, "user=", 5); 60 | if (err != SASL_OK) { 61 | return err; 62 | } 63 | err = xoauth2_plugin_str_append(utils, outbuf, resp->authid, resp->authid_len); 64 | if (err != SASL_OK) { 65 | return err; 66 | } 67 | err = xoauth2_plugin_str_append(utils, outbuf, "\1", 1); 68 | if (err != SASL_OK) { 69 | return err; 70 | } 71 | err = xoauth2_plugin_str_append(utils, outbuf, "auth=", 5); 72 | if (err != SASL_OK) { 73 | return err; 74 | } 75 | err = xoauth2_plugin_str_append(utils, outbuf, resp->token_type, resp->token_type_len); 76 | if (err != SASL_OK) { 77 | return err; 78 | } 79 | err = xoauth2_plugin_str_append(utils, outbuf, " ", 1); 80 | if (err != SASL_OK) { 81 | return err; 82 | } 83 | err = xoauth2_plugin_str_append(utils, outbuf, resp->token, resp->token_len); 84 | if (err != SASL_OK) { 85 | return err; 86 | } 87 | err = xoauth2_plugin_str_append(utils, outbuf, "\1\1", 2); 88 | if (err != SASL_OK) { 89 | return err; 90 | } 91 | return SASL_OK; 92 | } 93 | 94 | static sasl_interact_t *find_prompt(sasl_interact_t *prompts, unsigned id) 95 | { 96 | sasl_interact_t *p; 97 | for (p = prompts; p->id != SASL_CB_LIST_END; ++p) { 98 | if (p->id == id) { 99 | return p; 100 | } 101 | } 102 | return NULL; 103 | } 104 | 105 | static int get_prompt_value(sasl_interact_t *prompts, unsigned id, const char **result, unsigned *result_len) 106 | { 107 | int err; 108 | sasl_interact_t *prompt; 109 | prompt = find_prompt(prompts, id); 110 | if (!prompt) { 111 | return SASL_FAIL; 112 | } 113 | 114 | *result = (const char *)prompt->result; 115 | *result_len = prompt->len; 116 | 117 | return SASL_OK; 118 | } 119 | 120 | static int get_cb_value(const sasl_utils_t *utils, unsigned id, const char **result, unsigned *result_len) 121 | { 122 | int err; 123 | switch (id) { 124 | case SASL_CB_PASS: 125 | { 126 | sasl_getsecret_t *cb; 127 | void *cb_ctx; 128 | sasl_secret_t *secret; 129 | err = utils->getcallback(utils->conn, id, (sasl_callback_ft *)&cb, &cb_ctx); 130 | if (err != SASL_OK) { 131 | return err; 132 | } 133 | err = cb(utils->conn, cb_ctx, id, &secret); 134 | if (err != SASL_OK) { 135 | return err; 136 | } 137 | if (secret->len >= UINT_MAX) { 138 | return SASL_BADPROT; 139 | } 140 | *result = secret->data; 141 | *result_len = secret->len; 142 | } 143 | break; 144 | case SASL_CB_USER: 145 | case SASL_CB_AUTHNAME: 146 | case SASL_CB_LANGUAGE: 147 | case SASL_CB_CNONCE: 148 | { 149 | sasl_getsimple_t *cb; 150 | void *cb_ctx; 151 | err = utils->getcallback(utils->conn, id, (sasl_callback_ft *)&cb, &cb_ctx); 152 | if (err != SASL_OK) { 153 | return err; 154 | } 155 | err = cb(cb_ctx, id, result, result_len); 156 | } 157 | break; 158 | default: 159 | err = SASL_FAIL; 160 | } 161 | return err; 162 | } 163 | 164 | static int xoauth2_plugin_client_mech_step1( 165 | void *_context, 166 | sasl_client_params_t *params, 167 | const char *serverin, 168 | unsigned serverin_len, 169 | sasl_interact_t **prompt_need, 170 | const char **clientout, 171 | unsigned *clientout_len, 172 | sasl_out_params_t *oparams) 173 | { 174 | const sasl_utils_t *utils = params->utils; 175 | xoauth2_plugin_client_context_t *context = _context; 176 | int err = SASL_OK; 177 | xoauth2_plugin_auth_response_t resp; 178 | int authid_wanted = 1; 179 | int password_wanted = 1; 180 | sasl_interact_t *prompt_returned = NULL; 181 | 182 | *clientout = NULL; 183 | *clientout_len = 0; 184 | 185 | SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: step1")); 186 | 187 | if (!context) { 188 | return SASL_BADPROT; 189 | } 190 | 191 | if (prompt_need && *prompt_need) { 192 | if (SASL_OK == get_prompt_value(*prompt_need, SASL_CB_AUTHNAME, (const char **)&resp.authid, &resp.authid_len)) { 193 | authid_wanted = 0; 194 | } 195 | } 196 | 197 | if (authid_wanted) { 198 | err = get_cb_value(utils, SASL_CB_AUTHNAME, (const char **)&resp.authid, &resp.authid_len); 199 | switch (err) { 200 | case SASL_OK: 201 | authid_wanted = 0; 202 | break; 203 | case SASL_INTERACT: 204 | break; 205 | default: 206 | goto out; 207 | } 208 | } 209 | 210 | if (prompt_need && *prompt_need) { 211 | if (SASL_OK == get_prompt_value(*prompt_need, SASL_CB_PASS, (const char **)&resp.token, &resp.token_len)) { 212 | password_wanted = 0; 213 | } 214 | } 215 | 216 | if (password_wanted) { 217 | err = get_cb_value(utils, SASL_CB_PASS, (const char **)&resp.token, &resp.token_len); 218 | switch (err) { 219 | case SASL_OK: 220 | password_wanted = 0; 221 | break; 222 | case SASL_INTERACT: 223 | break; 224 | default: 225 | goto out; 226 | } 227 | } 228 | 229 | if (!authid_wanted && !password_wanted) { 230 | err = params->canon_user( 231 | utils->conn, resp.authid, resp.authid_len, 232 | SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); 233 | if (err != SASL_OK) { 234 | goto out; 235 | } 236 | resp.token_type = "Bearer"; 237 | resp.token_type_len = 6; 238 | err = build_client_response(utils, &context->outbuf, &resp); 239 | if (err != SASL_OK) { 240 | goto out; 241 | } 242 | *clientout = context->outbuf.buf; 243 | *clientout_len = context->outbuf.len; 244 | context->state = 1; 245 | err = SASL_CONTINUE; 246 | } else { 247 | const size_t prompts = authid_wanted + password_wanted + 1; 248 | sasl_interact_t *p; 249 | prompt_returned = SASL_malloc(sizeof(sasl_interact_t) * prompts); 250 | if (!prompt_returned) { 251 | SASL_log((utils->conn, SASL_LOG_ERR, "failed to allocate buffer")); 252 | err = SASL_NOMEM; 253 | goto out; 254 | } 255 | memset(prompt_returned, 0, sizeof(sasl_interact_t) * prompts); 256 | p = prompt_returned; 257 | if (authid_wanted) { 258 | p->id = SASL_CB_AUTHNAME; 259 | p->challenge = "Authentication Name"; 260 | p->prompt = "Authentication ID"; 261 | p->defresult = NULL; 262 | ++p; 263 | } 264 | 265 | if (password_wanted) { 266 | p->id = SASL_CB_PASS; 267 | p->challenge = "Password"; 268 | p->prompt = "Password"; 269 | p->defresult = NULL; 270 | ++p; 271 | } 272 | p->id = SASL_CB_LIST_END; 273 | p->challenge = NULL; 274 | p->prompt = NULL; 275 | p->defresult = NULL; 276 | err = SASL_INTERACT; 277 | } 278 | out: 279 | if (prompt_need) { 280 | if (*prompt_need) { 281 | SASL_free(*prompt_need); 282 | *prompt_need = NULL; 283 | } 284 | if (prompt_returned) { 285 | *prompt_need = prompt_returned; 286 | } 287 | } 288 | return err; 289 | } 290 | 291 | static int xoauth2_plugin_client_mech_step2( 292 | void *_context, 293 | sasl_client_params_t *params, 294 | const char *serverin, 295 | unsigned serverin_len, 296 | sasl_interact_t **prompt_need, 297 | const char **clientout, 298 | unsigned *clientout_len, 299 | sasl_out_params_t *oparams) 300 | { 301 | const sasl_utils_t *utils = params->utils; 302 | xoauth2_plugin_client_context_t *context = _context; 303 | int err = SASL_OK; 304 | xoauth2_plugin_auth_response_t resp; 305 | 306 | *clientout = NULL; 307 | *clientout_len = 0; 308 | 309 | SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: step2")); 310 | 311 | if (!context) { 312 | return SASL_BADPROT; 313 | } 314 | 315 | *clientout = ""; 316 | *clientout_len = 0; 317 | 318 | context->state = 2; 319 | return SASL_OK; 320 | } 321 | 322 | 323 | static int xoauth2_plugin_client_mech_step( 324 | void *_context, 325 | sasl_client_params_t *params, 326 | const char *serverin, 327 | unsigned serverin_len, 328 | sasl_interact_t **prompt_need, 329 | const char **clientout, 330 | unsigned *clientout_len, 331 | sasl_out_params_t *oparams) 332 | { 333 | xoauth2_plugin_client_context_t *context = _context; 334 | 335 | switch (context->state) { 336 | case 0: 337 | return xoauth2_plugin_client_mech_step1( 338 | context, 339 | params, 340 | serverin, 341 | serverin_len, 342 | prompt_need, 343 | clientout, 344 | clientout_len, 345 | oparams 346 | ); 347 | case 1: 348 | return xoauth2_plugin_client_mech_step2( 349 | context, 350 | params, 351 | serverin, 352 | serverin_len, 353 | prompt_need, 354 | clientout, 355 | clientout_len, 356 | oparams 357 | ); 358 | } 359 | return SASL_BADPROT; 360 | } 361 | 362 | static void xoauth2_plugin_client_mech_dispose( 363 | void *_context, 364 | const sasl_utils_t *utils) 365 | { 366 | xoauth2_plugin_client_context_t *context = _context; 367 | 368 | if (!context) { 369 | return; 370 | } 371 | 372 | xoauth2_plugin_str_free(utils, &context->outbuf); 373 | SASL_free(context); 374 | } 375 | 376 | static sasl_client_plug_t xoauth2_client_plugins[] = 377 | { 378 | { 379 | "XOAUTH2", /* mech_name */ 380 | 0, /* max_ssf */ 381 | SASL_SEC_NOANONYMOUS 382 | | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ 383 | SASL_FEAT_WANT_CLIENT_FIRST 384 | | SASL_FEAT_ALLOWS_PROXY, /* features */ 385 | NULL, /* required_prompts */ 386 | NULL, /* glob_context */ 387 | &xoauth2_plugin_client_mech_new, /* mech_new */ 388 | &xoauth2_plugin_client_mech_step, /* mech_step */ 389 | &xoauth2_plugin_client_mech_dispose,/* mech_dispose */ 390 | NULL, /* mech_free */ 391 | NULL, /* idle */ 392 | NULL, /* spare */ 393 | NULL /* spare */ 394 | } 395 | }; 396 | 397 | int xoauth2_client_plug_init( 398 | sasl_utils_t *utils, 399 | int maxversion, 400 | int *out_version, 401 | sasl_client_plug_t **pluglist, 402 | int *plugcount) 403 | { 404 | if (maxversion < SASL_CLIENT_PLUG_VERSION) { 405 | SASL_seterror((utils->conn, 0, "xoauth2: version mismatch")); 406 | return SASL_BADVERS; 407 | } 408 | 409 | *out_version = SASL_CLIENT_PLUG_VERSION; 410 | *pluglist = xoauth2_client_plugins; 411 | *plugcount = sizeof(xoauth2_client_plugins) / sizeof(*xoauth2_client_plugins); 412 | 413 | return SASL_OK; 414 | } 415 | 416 | -------------------------------------------------------------------------------- /xoauth2_init.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Moriyoshi Koizumi 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifdef HAVE_CONFIG_H 22 | #include "config.h" 23 | #endif 24 | 25 | #include 26 | #include "xoauth2_plugin.h" 27 | 28 | #ifdef WIN32 29 | BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 30 | { 31 | switch (ul_reason_for_call) 32 | { 33 | case DLL_PROCESS_ATTACH: 34 | case DLL_THREAD_ATTACH: 35 | case DLL_THREAD_DETACH: 36 | case DLL_PROCESS_DETACH: 37 | break; 38 | } 39 | return TRUE; 40 | } 41 | #endif 42 | 43 | SASLPLUGINAPI int sasl_client_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, sasl_client_plug_t **pluglist, int *plugcount) 44 | { 45 | return xoauth2_client_plug_init(utils, maxversion, out_version, pluglist, plugcount); 46 | } 47 | 48 | SASLPLUGINAPI int sasl_server_plug_init(const sasl_utils_t *utils, int maxversion, int *out_version, sasl_server_plug_t **pluglist, int *plugcount) 49 | { 50 | return xoauth2_server_plug_init(utils, maxversion, out_version, pluglist, plugcount); 51 | } 52 | -------------------------------------------------------------------------------- /xoauth2_plugin.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Moriyoshi Koizumi 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef XOAUTH2_PLUGIN_H 22 | #define XOAUTH2_PLUGIN_H 23 | 24 | #include 25 | #include 26 | 27 | #define UNUSED(x) x __attribute__((unused)) 28 | 29 | typedef struct { 30 | unsigned size; 31 | unsigned len; 32 | char *buf; 33 | } xoauth2_plugin_str_t; 34 | 35 | typedef struct { 36 | char *buf; 37 | unsigned buf_size; 38 | char *authid; 39 | unsigned authid_len; 40 | char *token_type; 41 | unsigned token_type_len; 42 | char *token; 43 | unsigned token_len; 44 | } xoauth2_plugin_auth_response_t; 45 | 46 | typedef struct { 47 | const char *scope; 48 | unsigned scope_len; 49 | } xoauth2_plugin_server_settings_t; 50 | 51 | typedef struct { 52 | xoauth2_plugin_server_settings_t *settings; 53 | int state; 54 | xoauth2_plugin_auth_response_t resp; 55 | xoauth2_plugin_str_t outbuf; 56 | } xoauth2_plugin_server_context_t; 57 | 58 | typedef struct { 59 | int state; 60 | xoauth2_plugin_auth_response_t resp; 61 | xoauth2_plugin_str_t outbuf; 62 | } xoauth2_plugin_client_context_t; 63 | 64 | int xoauth2_plugin_str_init(const sasl_utils_t *utils, xoauth2_plugin_str_t *s); 65 | int xoauth2_plugin_str_alloc(const sasl_utils_t *utils, xoauth2_plugin_str_t *s, unsigned req_len); 66 | int xoauth2_plugin_str_append(const sasl_utils_t *utils, xoauth2_plugin_str_t *s, const char *v, unsigned vlen); 67 | void xoauth2_plugin_str_free(const sasl_utils_t *utils, xoauth2_plugin_str_t *s); 68 | 69 | int xoauth2_server_plug_init( 70 | sasl_utils_t *utils, 71 | int maxversion, 72 | int *out_version, 73 | sasl_server_plug_t **pluglist, 74 | int *plugcount); 75 | 76 | int xoauth2_client_plug_init( 77 | sasl_utils_t *utils, 78 | int maxversion, 79 | int *out_version, 80 | sasl_client_plug_t **pluglist, 81 | int *plugcount); 82 | 83 | #define SASL_log(args) (utils->log args) 84 | #define SASL_seterror(args) (utils->seterror args) 85 | #define SASL_malloc(size) (utils->malloc(size)) 86 | #define SASL_free(p) (utils->free(p)) 87 | #define SASL_base64_encode(in, in_len, out, out_max_len, out_len) (utils->encode64(in, in_len, out, out_max_len, out_len)) 88 | #define SASL_base64_decode(in, in_len, out, out_max_len, out_len) (utils->decode64(in, in_len, out, out_max_len, out_len)) 89 | 90 | #define SASL_AUX_OAUTH2_BEARER_TOKENS "oauth2BearerTokens" 91 | 92 | #ifdef WIN32 93 | #define SASLPLUGINAPI __declspec(dllexport) 94 | #else 95 | #define SASLPLUGINAPI extern 96 | #endif 97 | 98 | #endif /* XOAUTH2_PLUGIN_H */ 99 | -------------------------------------------------------------------------------- /xoauth2_server.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Moriyoshi Koizumi 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifdef HAVE_CONFIG_H 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "xoauth2_plugin.h" 30 | 31 | static int xoauth2_plugin_server_mech_new( 32 | void *glob_context, 33 | sasl_server_params_t *params, 34 | UNUSED(const char *challenge), 35 | UNUSED(unsigned challenge_len), 36 | void **pcontext) 37 | { 38 | int err; 39 | const sasl_utils_t *utils = params->utils; 40 | xoauth2_plugin_server_context_t *context; 41 | 42 | context = SASL_malloc(sizeof(*context)); 43 | if (!context) { 44 | SASL_seterror((utils->conn, 0, "Failed to allocate memory")); 45 | return SASL_NOMEM; 46 | } 47 | 48 | context->settings = (xoauth2_plugin_server_settings_t *)glob_context; 49 | context->state = 0; 50 | context->resp.buf = NULL; 51 | err = xoauth2_plugin_str_init(utils, &context->outbuf); 52 | if (err != SASL_OK) { 53 | SASL_free(context); 54 | SASL_log((utils->conn, SASL_LOG_ERR, "failed to allocate buffer")); 55 | return err; 56 | } 57 | *pcontext = context; 58 | return SASL_OK; 59 | } 60 | 61 | static int append_string(const sasl_utils_t *utils, xoauth2_plugin_str_t *outbuf, const char *v, unsigned vlen) 62 | { 63 | int err; 64 | const char *p; 65 | const char *e = v + vlen; 66 | err = xoauth2_plugin_str_alloc(utils, outbuf, outbuf->len + 2 + vlen * 2); 67 | if (err != SASL_OK) { 68 | return err; 69 | } 70 | outbuf->buf[outbuf->len++] = '"'; 71 | for (p = v; p < e; ++p) { 72 | switch (*p) { 73 | case 8: 74 | outbuf->buf[outbuf->len++] = '\\'; 75 | outbuf->buf[outbuf->len++] = 'b'; 76 | break; 77 | case 9: 78 | outbuf->buf[outbuf->len++] = '\\'; 79 | outbuf->buf[outbuf->len++] = 't'; 80 | break; 81 | case 10: 82 | outbuf->buf[outbuf->len++] = '\\'; 83 | outbuf->buf[outbuf->len++] = 'n'; 84 | break; 85 | case 12: 86 | outbuf->buf[outbuf->len++] = '\\'; 87 | outbuf->buf[outbuf->len++] = 'f'; 88 | break; 89 | case 13: 90 | outbuf->buf[outbuf->len++] = '\\'; 91 | outbuf->buf[outbuf->len++] = 'r'; 92 | break; 93 | case '"': case '\\': 94 | outbuf->buf[outbuf->len++] = '\\'; 95 | /* fall-through */ 96 | default: 97 | outbuf->buf[outbuf->len++] = *p; 98 | break; 99 | } 100 | } 101 | outbuf->buf[outbuf->len++] = '"'; 102 | return SASL_OK; 103 | } 104 | 105 | static int append_int(const sasl_utils_t *utils, xoauth2_plugin_str_t *outbuf, int n) 106 | { 107 | int err; 108 | char buf[1024]; 109 | int len = snprintf(buf, sizeof(buf) - 1, "%d", n); 110 | if (len < 0) { 111 | return SASL_NOMEM; 112 | } 113 | return xoauth2_plugin_str_append(utils, outbuf, buf, (unsigned)len); 114 | } 115 | 116 | static int build_json_response(const sasl_utils_t *utils, xoauth2_plugin_str_t *outbuf, const char *status, xoauth2_plugin_server_settings_t *settings, xoauth2_plugin_auth_response_t *resp) 117 | { 118 | int err; 119 | err = xoauth2_plugin_str_append(utils, outbuf, "{", 1); 120 | if (err != SASL_OK) { 121 | return err; 122 | } 123 | err = append_string(utils, outbuf, "status", 6); 124 | if (err != SASL_OK) { 125 | return err; 126 | } 127 | err = xoauth2_plugin_str_append(utils, outbuf, ":", 1); 128 | if (err != SASL_OK) { 129 | return err; 130 | } 131 | err = append_string(utils, outbuf, status, strlen(status)); 132 | if (err != SASL_OK) { 133 | return err; 134 | } 135 | err = xoauth2_plugin_str_append(utils, outbuf, ",", 1); 136 | if (err != SASL_OK) { 137 | return err; 138 | } 139 | err = append_string(utils, outbuf, "schemes", 6); 140 | if (err != SASL_OK) { 141 | return err; 142 | } 143 | err = xoauth2_plugin_str_append(utils, outbuf, ":", 1); 144 | if (err != SASL_OK) { 145 | return err; 146 | } 147 | err = append_string(utils, outbuf, resp->token_type, resp->token_type_len); 148 | if (err != SASL_OK) { 149 | return err; 150 | } 151 | err = xoauth2_plugin_str_append(utils, outbuf, ",", 1); 152 | if (err != SASL_OK) { 153 | return err; 154 | } 155 | err = append_string(utils, outbuf, "scope", 5); 156 | if (err != SASL_OK) { 157 | return err; 158 | } 159 | err = xoauth2_plugin_str_append(utils, outbuf, ":", 1); 160 | if (err != SASL_OK) { 161 | return err; 162 | } 163 | err = append_string(utils, outbuf, settings->scope, settings->scope_len); 164 | if (err != SASL_OK) { 165 | return err; 166 | } 167 | err = xoauth2_plugin_str_append(utils, outbuf, "}", 1); 168 | if (err != SASL_OK) { 169 | return err; 170 | } 171 | return SASL_OK; 172 | } 173 | 174 | static int xoauth2_plugin_server_mech_step1( 175 | void *_context, 176 | sasl_server_params_t *params, 177 | const char *clientin, 178 | unsigned clientin_len, 179 | const char **serverout, 180 | unsigned *serverout_len, 181 | sasl_out_params_t *oparams) 182 | { 183 | const sasl_utils_t *utils = params->utils; 184 | xoauth2_plugin_server_context_t *context = _context; 185 | int err = SASL_OK; 186 | xoauth2_plugin_auth_response_t resp; 187 | int token_is_valid = 0; 188 | 189 | *serverout = NULL; 190 | *serverout_len = 0; 191 | 192 | SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: step1")); 193 | 194 | if (!context) { 195 | err = SASL_BADPROT; 196 | goto out; 197 | } 198 | 199 | if (!clientin) { 200 | err = SASL_BADPROT; 201 | goto out; 202 | } 203 | 204 | { 205 | char *p, *e, *token_e; 206 | resp.buf = SASL_malloc(clientin_len + 1); 207 | if (!resp.buf) { 208 | SASL_seterror((utils->conn, 0, "Failed to allocate memory")); 209 | err = SASL_NOMEM; 210 | goto out; 211 | } 212 | memcpy(resp.buf, clientin, clientin_len); 213 | resp.buf[clientin_len] = '\0'; 214 | resp.buf_size = clientin_len; 215 | 216 | p = resp.buf, e = resp.buf + resp.buf_size; 217 | 218 | if (e - p < 5 || strncasecmp(p, "user=", 5) != 0) { 219 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 220 | err = SASL_BADPROT; 221 | goto out; 222 | } 223 | p += 5; 224 | 225 | resp.authid = p; 226 | for (;;) { 227 | if (p >= e) { 228 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 229 | err = SASL_BADPROT; 230 | goto out; 231 | } 232 | if (*p == '\001') { 233 | break; 234 | } 235 | ++p; 236 | } 237 | *p = '\0'; 238 | resp.authid_len = p - resp.authid; 239 | ++p; 240 | 241 | if (e - p < 5 || strncasecmp(p, "auth=", 5) != 0) { 242 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 243 | err = SASL_BADPROT; 244 | goto out; 245 | } 246 | 247 | p += 5; 248 | 249 | resp.token_type = p; 250 | for (;;) { 251 | if (p >= e) { 252 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 253 | err = SASL_BADPROT; 254 | goto out; 255 | } 256 | if (*p == '\001') { 257 | break; 258 | } 259 | ++p; 260 | } 261 | *p = '\0'; 262 | token_e = p; 263 | 264 | if (*++p != '\001') { 265 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 266 | err = SASL_BADPROT; 267 | goto out; 268 | } 269 | if (p + 1 != e) { 270 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 271 | err = SASL_BADPROT; 272 | goto out; 273 | } 274 | 275 | p = resp.token_type; 276 | for (;;) { 277 | if (p >= token_e) { 278 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 279 | err = SASL_BADPROT; 280 | goto out; 281 | } 282 | if (*p == ' ') { 283 | break; 284 | } 285 | ++p; 286 | } 287 | *p = '\0'; 288 | resp.token_type_len = p - resp.token_type; 289 | ++p; 290 | 291 | for (;;) { 292 | if (p >= token_e) { 293 | SASL_seterror((utils->conn, 0, "Failed to parse authentication information")); 294 | err = SASL_BADPROT; 295 | goto out; 296 | } 297 | if (*p != ' ') { 298 | break; 299 | } 300 | ++p; 301 | } 302 | resp.token = p; 303 | resp.token_len = token_e - resp.token; 304 | } 305 | 306 | if (resp.token_type_len != 6 || strncasecmp(resp.token_type, "bearer", 6) != 0) { 307 | /* not sure if we can return a plain error instead of a challange-impersonated error */ 308 | err = SASL_BADPROT; 309 | SASL_seterror((utils->conn, 0, "unsupported token type: %s", resp.token_type)); 310 | goto out; 311 | } 312 | 313 | { 314 | const char *requests[] = { SASL_AUX_OAUTH2_BEARER_TOKENS, NULL }; 315 | struct propval vals[1]; 316 | const char **p; 317 | int nprops; 318 | 319 | err = utils->prop_request(params->propctx, requests); 320 | if (err != SASL_OK) { 321 | /* not sure if we can return a plain error instead of a challange-impersonated error at this point */ 322 | SASL_seterror((utils->conn, 0, "failed to retrieve bearer tokens for the user %s", resp.authid)); 323 | goto out; 324 | } 325 | 326 | err = params->canon_user(utils->conn, resp.authid, 0, SASL_CU_AUTHID | SASL_CU_AUTHZID, oparams); 327 | if (err == SASL_OK) { 328 | nprops = utils->prop_getnames(params->propctx, requests, vals); 329 | if (nprops == sizeof(vals) / sizeof(*vals) && vals[0].name && vals[0].values) { 330 | for (p = vals[0].values; *p; ++p) { 331 | if (strlen(*p) == resp.token_len && strncmp(*p, resp.token, resp.token_len) == 0) { 332 | token_is_valid = 1; 333 | break; 334 | } 335 | } 336 | } else { 337 | SASL_log((utils->conn, SASL_LOG_ERR, "no bearer token found for user %s", resp.authid)); 338 | } 339 | } else { 340 | SASL_log((utils->conn, SASL_LOG_ERR, "failed to canonify user and get auxprops for user %s", resp.authid)); 341 | } 342 | 343 | } 344 | 345 | if (!token_is_valid) { 346 | err = build_json_response(utils, &context->outbuf, "401", context->settings, &resp); 347 | if (err != SASL_OK) { 348 | SASL_log((utils->conn, SASL_LOG_ERR, "failed to allocate buffer")); 349 | goto out; 350 | } 351 | context->state = 1; 352 | context->resp = resp, resp.buf = NULL; 353 | *serverout = context->outbuf.buf; 354 | *serverout_len = context->outbuf.len; 355 | err = SASL_CONTINUE; 356 | goto out; 357 | } 358 | 359 | out: 360 | if (resp.buf != NULL) { 361 | memset(resp.buf, 0, resp.buf_size); 362 | SASL_free(resp.buf); 363 | } 364 | return err; 365 | } 366 | 367 | static int xoauth2_plugin_server_mech_step2( 368 | void *_context, 369 | sasl_server_params_t *params, 370 | const char *clientin, 371 | unsigned clientin_len, 372 | const char **serverout, 373 | unsigned *serverout_len, 374 | sasl_out_params_t *oparams) 375 | { 376 | const sasl_utils_t *utils = params->utils; 377 | xoauth2_plugin_server_context_t *context = _context; 378 | 379 | *serverout = NULL; 380 | *serverout_len = 0; 381 | 382 | SASL_log((utils->conn, SASL_LOG_DEBUG, "xoauth2: step2")); 383 | 384 | if (!context) { 385 | return SASL_BADPROT; 386 | } 387 | 388 | SASL_seterror((utils->conn, 0, "bearer token is not valid: %s", context->resp.token)); 389 | return params->transition ? SASL_TRANS: SASL_NOUSER; 390 | } 391 | 392 | static int xoauth2_plugin_server_mech_step( 393 | void *_context, 394 | sasl_server_params_t *params, 395 | const char *clientin, 396 | unsigned clientin_len, 397 | const char **serverout, 398 | unsigned *serverout_len, 399 | sasl_out_params_t *oparams) 400 | { 401 | const sasl_utils_t *utils = params->utils; 402 | xoauth2_plugin_server_context_t *context = _context; 403 | switch (context->state) { 404 | case 0: 405 | return xoauth2_plugin_server_mech_step1( 406 | context, params, 407 | clientin, clientin_len, 408 | serverout, serverout_len, 409 | oparams); 410 | case 1: 411 | return xoauth2_plugin_server_mech_step2( 412 | context, params, 413 | clientin, clientin_len, 414 | serverout, serverout_len, 415 | oparams); 416 | default: 417 | return SASL_BADPROT; 418 | } 419 | } 420 | 421 | static void xoauth2_plugin_server_mech_dispose(void *_context, const sasl_utils_t *utils) 422 | { 423 | xoauth2_plugin_server_context_t *context = _context; 424 | 425 | if (!context) { 426 | return; 427 | } 428 | 429 | if (context->resp.buf) { 430 | memset(context->resp.buf, 0, context->resp.buf_size); 431 | SASL_free(context->resp.buf); 432 | context->resp.buf = NULL; 433 | } 434 | xoauth2_plugin_str_free(utils, &context->outbuf); 435 | SASL_free(context); 436 | } 437 | 438 | static int xoauth2_server_plug_get_options(sasl_utils_t *utils, xoauth2_plugin_server_settings_t *settings) 439 | { 440 | int err; 441 | err = utils->getopt( 442 | utils->getopt_context, 443 | "XOAUTH2", 444 | "xoauth2_scope", 445 | &settings->scope, &settings->scope_len); 446 | if (err != SASL_OK || !settings->scope) { 447 | SASL_log((utils->conn, SASL_LOG_NOTE, "xoauth2_scope is not set")); 448 | settings->scope = ""; 449 | settings->scope_len = 0; 450 | } 451 | return SASL_OK; 452 | } 453 | 454 | static xoauth2_plugin_server_settings_t xoauth2_server_settings; 455 | 456 | static sasl_server_plug_t xoauth2_server_plugins[] = 457 | { 458 | { 459 | "XOAUTH2", /* mech_name */ 460 | 0, /* max_ssf */ 461 | SASL_SEC_NOANONYMOUS 462 | | SASL_SEC_PASS_CREDENTIALS, /* security_flags */ 463 | SASL_FEAT_WANT_CLIENT_FIRST 464 | | SASL_FEAT_ALLOWS_PROXY, /* features */ 465 | NULL, /* glob_context */ 466 | &xoauth2_plugin_server_mech_new, /* mech_new */ 467 | &xoauth2_plugin_server_mech_step, /* mech_step */ 468 | &xoauth2_plugin_server_mech_dispose, /* mech_dispose */ 469 | NULL, /* mech_free */ 470 | NULL, /* setpass */ 471 | NULL, /* user_query */ 472 | NULL, /* idle */ 473 | NULL, /* mech_avail */ 474 | NULL /* spare */ 475 | } 476 | }; 477 | 478 | int xoauth2_server_plug_init( 479 | sasl_utils_t *utils, 480 | int maxversion, 481 | int *out_version, 482 | sasl_server_plug_t **pluglist, 483 | int *plugcount) 484 | { 485 | int err; 486 | 487 | if (maxversion < SASL_SERVER_PLUG_VERSION) { 488 | SASL_seterror((utils->conn, 0, "xoauth2: version mismatch")); 489 | return SASL_BADVERS; 490 | } 491 | 492 | err = xoauth2_server_plug_get_options(utils, &xoauth2_server_settings); 493 | if (err != SASL_OK) { 494 | return err; 495 | } 496 | 497 | xoauth2_server_plugins[0].glob_context = &xoauth2_server_settings; 498 | 499 | *out_version = SASL_SERVER_PLUG_VERSION; 500 | *pluglist = xoauth2_server_plugins; 501 | *plugcount = sizeof(xoauth2_server_plugins) / sizeof(*xoauth2_server_plugins); 502 | 503 | return SASL_OK; 504 | } 505 | -------------------------------------------------------------------------------- /xoauth2_str.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Moriyoshi Koizumi 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifdef HAVE_CONFIG_H 22 | #include 23 | #endif 24 | 25 | #include 26 | #include 27 | #include "xoauth2_plugin.h" 28 | 29 | const char empty_string[] = ""; 30 | 31 | int xoauth2_plugin_str_init(const sasl_utils_t *utils, xoauth2_plugin_str_t *s) 32 | { 33 | s->size = 0; 34 | s->len = 0; 35 | s->buf = (char *)empty_string; 36 | return SASL_OK; 37 | } 38 | 39 | int xoauth2_plugin_str_alloc(const sasl_utils_t *utils, xoauth2_plugin_str_t *s, unsigned req_len) 40 | { 41 | if (req_len >= s->size) { 42 | char *new_buf = s->buf == empty_string ? NULL: s->buf; 43 | unsigned new_size = s->size + 16; 44 | if (new_size < s->size) { 45 | return SASL_NOMEM; 46 | } 47 | while (new_size < req_len) { 48 | unsigned _new_size = new_size + (new_size >> 1); 49 | if (_new_size < new_size) { 50 | SASL_log((utils->conn, SASL_LOG_ERR, "failed to allocate %u bytes", req_len)); 51 | return SASL_NOMEM; 52 | } 53 | new_size = _new_size; 54 | } 55 | new_buf = utils->realloc(new_buf, new_size); 56 | if (!new_buf) { 57 | return SASL_NOMEM; 58 | } 59 | s->buf = new_buf; 60 | s->size = new_size; 61 | } 62 | return SASL_OK; 63 | } 64 | 65 | int xoauth2_plugin_str_append(const sasl_utils_t *utils, xoauth2_plugin_str_t *s, const char *v, unsigned vlen) 66 | { 67 | int err; 68 | unsigned req_len = s->len + vlen + 1; 69 | if (req_len < s->len) { 70 | return SASL_NOMEM; 71 | } 72 | err = xoauth2_plugin_str_alloc(utils, s, req_len); 73 | if (err != SASL_OK) { 74 | return err; 75 | } 76 | memcpy(s->buf + s->len, v, vlen); 77 | s->len += vlen; 78 | s->buf[s->len] = '\0'; 79 | return SASL_OK; 80 | } 81 | 82 | void xoauth2_plugin_str_free(const sasl_utils_t *utils, xoauth2_plugin_str_t *s) 83 | { 84 | if (s->buf && s->buf != empty_string) { 85 | SASL_free(s->buf); 86 | s->buf = (char *)empty_string; 87 | s->len = s->size = 0; 88 | } 89 | } 90 | 91 | --------------------------------------------------------------------------------