oidc-agent-4.2.6/0000755000175000017500000000000014170031216013076 5ustar marcusmarcusoidc-agent-4.2.6/book.json0000644000175000017500000000003214120404223014713 0ustar marcusmarcus{ "root": "./gitbook" } oidc-agent-4.2.6/logo_wide.png0000644000175000017500000001434414120404223015557 0ustar marcusmarcusPNG  IHDR;57zTXtRaw profile type exifxڝUY$' >BiAH鷺zz&@7P*R*Y~C#hmnBӮ~YO|ܮw^W}/"g?)v/rks֤ 7{1^s>Yjjm̗׍r I!g}GXF[ rc9PDo'As?;ס!)y|Qf߳+r?slgşB2gƽCԞ({;O>@bQY1nt!=a"̧ -j$[xxywLYk۹\ lR(SٜDtQ|&uV:0 ,z;Ʒes蹏0=!ۍ,hBUf[*oQVdR'7hqL$ibGͽ|7i9C&lvO4F]mlv*VYpk:obCF1A"ߣ`Pw> K dkyH,G|}!6f }:%BnuK6r!ְbUʆjF[gu-KfCK$ _[ YK@Oܖ%qas[VTE>dP!3./ e_izDf'!RrsBITOIDATxjb70쳉8e)L8gL& (hCcNZhdi[R-8]t3:Gzsu?=E$2 @( 2 @( 2 @( 2 @( 2 @( 2 @`j_>Ju=_Ic @( 2 @ӗ NNzQsd|+Qwe`2/2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @Gp̔eDNxH9=wzQ~ߵ[/@]i(cڭƇw{?=1=;nٹ.2ߊLz[z @~/D*̙/DrReDQ @eDQ @eDQ ޣOcX8f2 @( = j|x=ң̔eDQ @eDQ @eDQ @eDQ @eD`޺,ã㪪ڭƇw{$)IwQLUU޳+d2@.,2Qfci6e+睞.,&QfcI(D`tBMD Be ]Xp Lt`a2@.,&Qk66׆^eMnձ]$2&.pw(" 8DvX_mC/w{Q]N;6ךxnȃ"1S-`2% ol}d;L L($)2eEXd NQ7Er$60gϞ Hg{gwŚE7:fڭƇw{7W,_ګ9;`D`NNϮY{#|D`N^nϩYn5o`Djsb~NHOsC?=r\׽8տ_Z}͍Ϟ2YF4K뫳@K @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @(.ңX,yv1y[k!,ӳ"T @K @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( = iksVfcd8=( ogN6Wsլ*Q`,_?vl,U#%[<{Ro{gwVj[(`!2qu{UUn5>{,Q.P=e`6ז[rRRUUew޳NzX'rz2n==wz׸gr͙hOCw[?WWU׺?'{sk~ޏr}sm7b\W]ZL5,٭Y+>;u{wCc.~9ûW gb'FC[PN Û|_y|w SD~8<:0\?yӭMNl,24'ˍT0Iy/c; z|'ã՚?.ʏd/dksӳ^ynC|PI| ˗`6c;WߔlY2Azlm"3zN;=՜+1jkVUzLn5lvQsL臶ɣ~|ɗH>;24/..<f>w褪|„n]9%y۲J>˨_~rwvG+qj2i/?fci½('Qf(.1B*n|XUU큼< ʊ&p\hM~[aJeMwqYF)לT.u/dkVY3P<wzEdyN+J[| rkXѽuVs ϬyĬ5k`== * Yl,-U5Tg#SvejܼɣΤ){p n3QfNel3y9O\PeQ jc\&5L 3//S0+gV%^' WgW^~i`Yt/w1Qs#e`6#YmZyWSԿZ4jo?ۛs)sX.n5r,*nc}uri_>y;t2[H6{D>>}.9/윷w?8.ˎ>;Dn56> T'<{OZVv۝焠?-hcH4R@ Z[7ߋjvG\M;SBmtx;|b&c=z4=;Fv.Oj\[6}ۻx͗ϟ_^jc?i5ak;sCƥ i r!ίL1F Vf u4:?yp~TD<=qȣ᠎+Wٔ38N[ho7ac^,f_9:k]TNj~Mg& ~)S$DIʬCτ6c}?pKi)oׯr맇?޿,` 6T3y<Q`r@ҕ!l&`ш2 2cwTBiQRe Ӄ6GV9ӗ.5x:3ϙ$\aksm 1Y{z3A6Q 2 @( 2 @( 2 @( =ڗϟ^="j|x7+) 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2 @( 2xH/p1t|p<{L%Q @eD/) 2 @( 2 @( 2 @( 2 @( 2 @( 2 @+:9IENDB`oidc-agent-4.2.6/PRIVACY0000644000175000017500000000046114120404223014134 0ustar marcusmarcusoidc-agent is an installed application that only runs on the users machine. oidc-agent only communicates with the OpenID Provider the user intends to. No data is sent to third parties. All data is stored locally on the user's machine in an encrypted way. Logs are only written to the user's system log. oidc-agent-4.2.6/gitbook/0000755000175000017500000000000014167074355014555 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/book.json0000644000175000017500000000004414120404223016354 0ustar marcusmarcus{ "description": "oidc-agent" } oidc-agent-4.2.6/gitbook/oidc-gen/0000755000175000017500000000000014167074355016242 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-gen/options.md0000644000175000017500000006161314167074355020266 0ustar marcusmarcus## Detailed Information About All Options General Options: * [`--accounts`](#accounts) * [`--codeExchange`](#codeExchange) * [`--confirm-default`](#confirm-default) * [`--confirm-no`](#confirm-no) * [`--confirm-yes`](#confirm-yes) * [`--cp`](#cp) * [`--delete`](#delete) * [`--file`](#file) * [`--flow`](#flow) * [`--manual`](#manual) * [`--no-scheme`](#no-scheme) * [`--no-url-call`](#no-url-call) * [`--no-webserver`](#no-webserver) * [`--only-at`](#only-at) * [`--print`](#print) * [`--prompt`](#prompt) * [`--pub`](#pub) * [`--pw-cmd`](#pw-cmd) * [`--pw-env`](#pw-env) * [`--pw-file`](#pw-file) * [`--pw-gpg`](#pw-gpg) * [`--pw-prompt`](#pw-prompt) * [`--reauthenticate`](#reauthenticate) * [`--rename`](#rename) * [`--seccomp`](#seccomp) * [`--update`](#update) Options for specifying information on the command line: * [`--at`](#at) * [`--aud`](#aud) * [`--client-id`](#client-id) * [`--client-secret`](#client-secret) * [`--cnid`](#cnid) * [`--dae`](#dae) * [`--issuer`](#issuer) * [`--op-password`](#op-password) * [`--op-username`](#op-username) * [`--port`](#port) * [`--redirect-uri`](#redirect-uri) * [`--rt`](#rt) * [`--rt-env`](#rt-env) * [`--scope`](#scope) * [`--scope-all`](#scope-all-and-scope-max) * [`--scope-max`](#scope-all-and-scope-max) ### `--accounts` Using this option `oidc-gen` will print out a list of all configured account configurations. Configured means that they are saved on the system and can be loaded with `oidc-add`; it does not mean that they are currently loaded. This option is the same as `oidc-add --list`. To show a list of the accounts that are currently loaded use `oidc-add --loaded`. ### `--codeExchange` When using the authorization code flow the user has to authenticate against the OpenID Provider in a web browser and is then redirected back to the application. To be able to catch that redirect `oidc-agent` usually starts a small webserver. If something goes wrong during the redirect (because the web server crashed or no web server was used (`--no-webserver`)) the user can still finish the account configuration generation process. In such a case the suer must copy the url he is redirected to from its browser and pass it to `oidc-gen --codeExchange`. Then oidc-gen should be able to obtain and save the final account configuration. Note that while this option also works for `edu.kit.data.oid-cagent:/` redirect uris, it might not be possible to obtain the uri the user is redirected to from the browser. ### `--confim-default` When specifying this option all confirmation/consent prompts are automatically answered with the default value. This option is useful when you want to use `oidc-gen` non-interactive. Examples for confirmation prompts are: - When creating a new account configuration using dynamic client registration and the process is not finished but the client already registered and the process is started again it is possible to reuse the already registered client. In that case `oidc-gen` asks to use the temporary stored data. (Default: yes) - When deleting an account configuration, `oidc-gen` asks if you are sure. (Default: no) - When deleting an account configuration that used dynamic client registration, `oidc-gen` asks to delete the client at the provider. (Default: yes) - When deleting an account configuration, the associated refresh token will be automatically deleted. In case this is not possible, `oidc-gen` asks if it should continue or not. (Default: no) ### `--confirm-no` When specifying this option all confirmation/consent prompts are automatically declined. This option is useful when you want to use `oidc-gen` non-interactive. For examples for confirmation prompts, see [`--confirm-default`](#confirm-default). ### `--confirm-yes` When specifying this option all confirmation/consent prompts are automatically answered with `yes`. This option is useful when you want to use `oidc-gen` non-interactive. For examples for confirmation prompts, see [`--confirm-default`](#confirm-default). ### `--cp` This option allows the user to change the CA bundle file that is used to verify SSL/TLS certificates. A user must use this option when `oidc-gen` cannot automatically find a proper CA bundle file (e.g. non default location). A user can also use this option to provide a more restricted bundle file that only contains the certificates needed for a specific provider. ### `--delete` This option will delete the account configuration for the specified shortname. It will also revoke the used refresh token and optionally delete the OIDC client (not implemented yet). ### `--file` Can be used if the client was registered manually. Most OpenID Provider allow downloading the client configuration as a json file (or copy the `JSON` config). Such a file can be used to reduce the amount of information passed to `oidc-gen`. The argument should be the absolute path to a file containing the `JSON` formatted client configuration. `oidc-gen` then reads the client configuration from this file, so that the user does not have to enter all information manually. Because this option is only used when a client was registered manually, it implicitly sets the `--manual` option. ### `--flow` Depending on the OpenID Provider a user can use multiple OpenID/OAuth2 flows to obtain a refresh token. `oidc-agent` uses the Refresh Flow to obtain additional access token. Therefore a refresh token is required which can be obtained in multiple ways. The `--flow` flag can be used to enforce usage of a specific flow or to prioritise a flow over another. `oidc-agent` will try all flows in the following order until one succeeds: 1. Refresh Flow 2. Password Flow 3. Authorization Code Flow 4. Device Flow Possible values for the `--flow` option are: 'refresh', 'password', 'code', and 'device'. The flag can also be used if multiple flows should be tried, but in a different order than the default one. To do so provide the option multiple times with one value per option in the desired order. In the following we will describe the different flows: #### Out Of Band If a user obtained a refresh token out of band he can directly provide it to `oidc-gen` using the `--rt` option. The `--flow=refresh` option is then implicitly set. **Note:** Refresh tokens are bound to a specific client id. The provided refresh token must be issued for the provided client id. Obtaining a valid refresh token for the specific client id is out of scope of this documentation. We recommend one of the following flows. #### Password Flow Most OIDPs do not support this flow. One provider that supports the password flow is INDIGO IAM, for additional information on support of the password flow for a specific provider see the documentation for different [providers](../provider/provider.md). The password flow can be performed using only the command line. The credentials for the OpenID Provider have to be provided to `oidc-gen`. The credentials are only used to obtain the refresh token and are not stored. However, there are alternatives flows that do not reveal the user's credentials to `oidc-agent`. #### Authorization Code Flow The authorization code flow is the most widely used and is therefore supported by any OpenID Provider and does not reveal user credentials to `oidc-agent`. However, it requires a browser on the system running `oidc-agent` and `oidc-gen`. If you don't have a browser on that system or don't want to use it you can use the Device Flow, if supported by the provider. If the authorization code flow is the only flow supported by your provider and have to obtain a working account configuration on a machine that does not have a browser (e.g. a server), you can create the account configuration on another machine and copy / move the account configuration file to the server. To use the Authorization Code Flow at least one redirect uri has to be provided ( see [Redirect Uri](../provider/client-configuration-values.md#redirect-uri)). The redirect uri must be of the scheme `http://localhost:`. It is recommend to use a port which is very unlikely to be used by any other application (during the account generation process). Additionally multiple redirect uris can be provided. When starting the account generation process `oidc-agent` will try to open a webserver on the specified ports. If one port fails the next one is tried. After a successful startup `oidc-gen` will receive an authorization URI. When calling this URI the user has to authenticate against the OpenID Provider; afterwards the user is redirected to the previously provided redirect uri where the agent's webserver is waiting for the response. The agent receives an authorization code that is exchanged for the required token. `oidc-gen` is polling `oidc-agent` to get the generated account configuration and finally save it. #### Device Flow The device flow is a flow specifically for devices with limited input possibilities or without a web browser. Unfortunately, it is currently not supported by many OpenID Providers. To use the device flow the user has to call `oidc-gen` with the `--flow=device` option. `oidc-gen` will print a verification url and an user code. If `qrencode` is installed on the system, the verification url is also printed as a QR-Code. The user must open the given url using a second device and enter the given user code. Through polling the agent will get a refresh token and `oidc-gen` the generated account configuration. If the OpenID Provider does not provide the device authorization endpoint in their openid-configuration it has to provided manually using the `--dae` option. For help on a specific provider check the [provider documentation](../provider/provider.md) ### `--manual` This option has to be used if a user wants to use a manually registered client. `oidc-gen` will then not use dynamic client registration. Additional metadata about the already registered client must be passed to `oidc-gen` when beeing prompted or using command line arguments (where they are available). ### `--no-scheme` This option can be used when the authorization code flow is performed. The `--no-scheme` option tells `oidc-agent` that a custom uri scheme should not be used for redirection. Normally a custom uri scheme can be used to redirect direct to (another) oidc-gen instance when performing the authorization code flow instead of using a web server. However, the redirect to oidc-gen requires a graphical desktop environment. If this is not present, redirection with custom uri schemes can be disabled with this option. This option can be used with `oidc-gen` or `oidc-agent`. When using it with `oidc-gen` it will only disable custom uri schemes for that specific call; when using it with `oidc-agent` it will disable custom uri schemes for all calls to that `oidc-agent` instance. ### `--no-url-call` When using the authorization code flow the user must authenticate against the OpenID Provider using a webbrowser. To do this `oidc-gen` prints an authorization url the user has to open. On default this url is automatically opened in the default webbrowser (using `xdg-open`). One can disable this behavior with the `--no-url-call` option. When this option is passed `oidc-gen` will not automatically open the authorization url. The user then has to manually copy it to his webbrowser. ### `--no-webserver` This option can be used when the authorization code flow is performed. On default a small webserver is started by `oidc-agent` to be able to catch the redirect and complete the authorization code flow. The `--no-webserver` option tells `oidc-agent` that no webserver should be started. The authorization code flow can still be completed. Either by using a redirect uri that follows the custom redirect uri scheme `edu.kit.data.oidc-agent:/` - this will directly redirect to oidc-gen, or by copying the url the browser would normally redirect to and pass it to `oidc-gen --codeExchange`. This option can be used with `oidc-gen` or `oidc-agent`. When using it with `oidc-gen` it will only disable the webserver for that specific call; when using it with `oidc-agent` it will disable the webserver for all calls to that `oidc-agent` instance. ### `--only-at` The `--only-at` option of `oidc-gen` can be used to obtain an access token without creating an account configuration. You still have to provide a valid client configuration. There are several ways of doing so. The option can be combined with the different ways of using `oidc-gen`, but it will not work with dynamic client registration. The following is a short overview: - `oidc-gen --only-at -m` Manually provide the needed information (with prompting) - `oidc-gen --only-at -f ` Manually provide the needed information by passing the path to a json file with the client information. - `oidc-gen --only-at` Use a public client defined in the `pubclients.conf` file. Notes: - It's possible to overwrite some or all of the passed values with command line options. - You always have to provide the issuer url and the scopes to be used (but can do so in the passed file). - When using a public client that does not have a client secret you must pass the `--pub` option. Here are three examples how a user can obtain the access token and store it in an environment variable without providing any other information: ``` export AT=`oidc-gen --only-at --iss= --scope-max --prompt=none` # requires that a public client for is listed in pubclients.conf export AT=`oidc-gen --only-at --iss= --client-id= --client-secret= --redirect-url="http://localhost:8080" --scope=profile --prompt=none` export AT=`oidc-gen --only-at -f --prompt=none` ``` In the last call `` points to file with the following content: ``` { "issuer_url": "https://example.com", "client_id": "clientid", "client_secret": "clientsecret", "scope": "openid profile email", "redirect_uris": [ "http://localhost:8080", "http://localhost:34170", "http://localhost:4242" ] } ``` Note that the `--only-at` option can be used with any flow. ### `--print` Using this option `oidc-gen` will read the specified file and print out the decrypted content. You can either pass an account shortname to print out the decrypted account configuration or an absolute filepath (needed if you want to decrypt client configuration files or other files encrypted by `oidc-gen`). ### `--prompt` This option can be used to change how `oidc-gen` prompts the user for information. There are different options available. Allowed values are `cli`, `gui`, and `none`. The default is `cli`. This is the normal mode as it also was before version 4.0.0. The suer is prompted for information on the command line. When selecting `gui`, `oidc-gen` will use graphical pop-up prompts. This requires `oidc-agent-prompt` to be installed. When `none` is selected, `oidc-gen` will not prompt for any information, which requires that the needed information is passed through command line options. Changing the prompt mode to `gui` will also change the password prompt mode to `gui` (see [`--pw-prompt`](#pw-prompt)). Changing it to `none`, will not change the password prompt mode, because this cannot set to `none`. ### `--pub` When this option is provided, `oidc-gen` will use a public client. If `--manual` is not provided, normally a client would be registered dynamically. However, with the `--pub` option, a preregistered public client is used. Preregistered public clients are listed in `/etc/oidc-agent/pubclients.config`. If the `--manual` option is specified this allows usage of a public client that was registered manually (the `client_secret` parameter will be optional). This option is also required to update an account configuration that uses a public client. ### `--pw-cmd` By default `oidc-gen` will prompt the user for an encryption password when it needs to encrypt or decrypt an account configuration. The option `--pw-cmd` can be used to provide a command that will print the needed encryption password to `stdout`. Then `oidc-gen` can obtain the password from that command instead of prompting the user. ### `--pw-env` By default `oidc-gen` will prompt the user for an encryption password when it needs to encrypt or decrypt an account configuration. The option `--pw-env` can be used to provide the encryption password via an environment variable. The name of the environment variable can be passed to `--pw-env`. If this option is used without an argument the encryption password is read from the environment variable `OIDC_ENCRYPTION_PW`. ### `--pw-file` By default `oidc-gen` will prompt the user for an encryption password when it needs to encrypt or decrypt an account configuration. The option `--pw-file` can be used to provide the path to a file that contains the needed encryption password. Then `oidc-gen` can obtain the password from that file. ### `--pw-gpg` The `--pw-gpg`, `--pw-pgp`, `--gpg`, or `--pgp` option can be used to indicate that PGP encryption utilizing `gpg-agent` should be used. The option takes a gpg key id and this key will be used for encryption. By using this approach one does not have to enter a password or pass it with one of the other `--pw-*` options whenever the file must be decrypted/encrypted. With PGP encryption, oidc-agent can utilize the `gpg-agent` for encryption. As long as the key is loaded in the `gpg-agent`, no password must be entered. This approach is very useful with providers where the refresh tokens changes regularly, but it is also very practical for all other providers. ### `--pw-prompt` This option can be used to change how `oidc-gen` prompts the user for the encryption password. Possible values are `cli` and `gui`. The default is `cli`. `gui` requires oidc-agent-prompt to be installed. ### `--reauthenticate` This option can be used to update an existing account configuration file with a new refresh token. Useful if - for some reason - the refresh token is not valid anymore. One could also use `--manual` to update an existing account configuration; however if no other information has to be changed the `--reauthenticate` option is easier. ### `--rename` This option can be used to rename an existing account configuration file. It is not enough to simply rename the file in the file system. One could also use `--manual` to update an existing account configuration; however if no other information has to be changed the `--rename` option is easier. ### `--seccomp` Enables seccomp system call filtering. See [general seccomp notes](../security/seccomp.md) for more details. ### `--update` This option can be used to update the encryption and / or file format for a file generated by oidc-gen. It will decrypt and re-encrypt the file content, therefore updating encryption and file format to the newest version. This option can also be used to encrypt plain text files, e.g. a client configuration that was downloaded from the OpenID Provider - do not use it as a general file encryption tool. The passed parameter can be an absolute path or the name of a file placed in oidc-dir (e.g. an account configuration short name). ### `--at` The `--at` option is used during dynamic client registration. If the registration endpoint is protected and can only accessed with proper authentication, the user has to pass the token used for authentication to the `--at` option. ### `--aud` The `--aud` option can be used to set the audience of obtained access tokens. Protected resources should not accept a token if they are not listed as audience. Therefore, this is a mechanism to restrict the usage of an access token to certain resources. The audience of individual access tokens can also be set with `oidc-token --aud`. See [`oidc-token --aud`](../oidc-token/options.md#aud) for more information. ### `--client-id` The `--client-id` option can be used to set the client id that should be used. ### `--client-secret` The `--client-secrete` option can be used to set the client secret that should be used. ### `--cnid` The `--cnid` option can be used to set an additional client name identifier. This might be useful in the case a user has multiple machines that run `oidc-agent` and he configures new account configurations for each machine. However, they should have the same shortname on all machines. While this is possible, the clientname for all of these clients will be of the form `oidc-agent:`. With the same shortname the clients cannot be distinguished easily in a web interface provided by the OpenID Provider. Most provider allow to access a list with authorized applications. If a user has an account configuration for `example` on two different machines, he will see the `oidc-agent:example` entry twice and cannot identify which entry belongs to which machine. However, this is possible using the `--cnid` option. This option allows the user to specify an additional component of the client name `oidc-agent:-`. A user could use for example the hostname of the machine. Then there are two different applications listed in the provider's web interface and the clients can be matched to the correct machine where that client is indeed used. ### `--dae` The `--dae` option explicitly sets the `device authorization endpoint uri`. When performing the device flow `oidc-agent` has to send information to this endpoint. Usually oidc-agent can obtain this uri from the provider's configuration endpoint. However, if the provider does not publish its device authorization endpoint uri at its configuration endpoint, the user has to tell `oidc-agent` where the device authorization endpoint can be found. Therefore, the uri has to be passed to the `--dae` option. Check the documentation about [providers](../provider/provider.md) for information if you need this option with your provider. ### `--issuer` The `--issuer` option can be used to set the issuer url that should be used. ### `--op-password` The `--op-password` option can be used to set the user's password at the OpenID provider. This option only applies when the password flow is used. Note that it is not recommended to use the password flow in general; even more it is not recommended to set the password from the command line. Please use prompting for this. ### `--op-username` The `--op-username` option can be used to set the user's username at the OpenID provider. This option only applies when the password flow is used. ### `--port` This option can be used to set redirect uris. Only the port must be provided and it will result in a redirect uri of the form `http://localhost:`. This option is a short option for `--redirect-uri` (only the port has to be provided). Passing `--port=1234` is equivalent to passing `--redirect-uri=http://localhost:1234`. For more information see [-`-redirect-uri`](#redirect-uri). ### `--redirect-uri` This option can be used to set the redirect uri to be used. This applies to two cases: - When the client was manually registered, the option can be used to pass the registered redirect uris. - When the client will be registered dynamically, the option can be used to pass the redirect uris that should be registered. On default `oidc-agent` will register multiple redirect uris when using dynamic client registration. One redirect uri that uses the custom uri scheme `edu.kit.data.oidc-agent:/` and three redirect uris to `localhost` using different port numbers. Two of these port numbers are `4242` and `8080`; the third port number will be chosen randomly. When starting the webserver `oidc-agent` will try all of these ports, stopping when the first succeeds. We cannot make any guarantees on the order in which these ports are tried. This might be a problem in environments with restrictions to ports, e.g. containers. In such environments it's useful to use the `--redirect-uri` or `--port` option to manually set the port(s) that should be used (`oidc-agent` will register redirect uris with that port numbers). By using these options one can pass only ports that will be available in the restricted environment. Note that `oidc-agent` still makes no guarantees about the order in which these ports will be tried. ### `--rt` This option can be used to pass a refresh token that should be used. Because this will use the refresh flow this option implicitly sets `--flow=refresh`. **Note:** Refresh tokens are bound to a specific client id. The provided refresh token must be issued for the provided client id. ### `--rt-env` Like `--rt` but reads the refresh token from an environment variable. The name of the environment variable can be passed to `--rt-env`. If this option is used without an argument the refresh token is read from the environment variable `OIDC_REFRESH_TOKEN`. ### `--scope` The `--scope` option can be used to set the scopes that should be used with this account configuration. Multiple scopes can be provided as a space separated list or by using the option multiple times. Pass `max` to use all available scopes for this provider. ### `--scope-all` and `--scope-max` The `--scope-all` and `--scope-max` options can be used to set the scopes that should be used with this account configuration to the maximum. I.e. this means that all scopes supported by the provider will be used. When using a public client all scopes available for that client will be used. oidc-agent-4.2.6/gitbook/oidc-gen/general.md0000644000175000017500000000516214120404223020161 0ustar marcusmarcus## General Usage Usually `oidc-gen` is used in one of two ways: Using dynamic client registration (default) or using an already registered client (`-m`). For providers that support dynamic client registration a simple call to `oidc-gen` is enough. You can also directly provide the shortname of the new account configuration: `oidc-gen ` After a successful account configuration generation oidc-gen will save the encrypted account configuration file in the [oidc-agent directory](../configuration/directory.md) using the shortname as the filename. ``` Usage: oidc-gen [OPTION...] [ACCOUNT_SHORTNAME] ``` Internal options are not considered part of the public API, even if listed for completeness. They can change at any time without backward compatibility considerations. See [Detailed Information About All Options](options.md) for more information. ### Client Registration `oidc-agent` requires a registered client for every OpenID Provider used. Most likely a user does not have an already registered client and does not want to do it through a web interface. If the OpenID Provider supports dynamic client registration, the agent can register a new client dynamically. One big advantage of using dynamic registration is the fact that oidc-agent will register the client with exactly the configuration it needs. Dynamic Registration is the default option and running `oidc-gen` is enough. If a user already has a client registered or the OpenID Provider does not support dynamic client registration `oidc-gen` must be called with the `-m` option. `oidc-gen` will prompt the user for the relevant information. If the user has a file with the client configuration information they can pass it to oidc-gen using the `-f` flag. When registering a client manually be careful with the provided data. Check [Client Configuration Values](../provider/client-configuration-values.md) for the values that are important to oidc-agent. See [Provider Info](../provider/provider.md) on how to generate an account configuration for a specific provider. ### oidc-gen and oidc-add `oidc-gen` will also add the generated configuration to the agent. So you don't have to run `oidc-add` afterwards. However, if you want to load an existing configuration don't use `oidc-gen` for it; [`oidc-add`](../oidc-add/oidc-add.md) is your friend. ### Edit an existing account configuration To edit an existing configuration, call `oidc-gen -m ` where `` is the short name for that configuration. If you only have to update the refresh token and do not want to change any other data for this account configuration, use `oidc-gen --reauthenticate `. oidc-agent-4.2.6/gitbook/oidc-gen/oidc-gen.md0000644000175000017500000000223114120404223020223 0ustar marcusmarcus# oidc-gen `oidc-gen` is used to generate new account configuration. These account configurations are needed and used by oidc-agent. They can be loaded with `oidc-add` into the agent. And then any application can request an access token for that account configuration. Account configurations are identified by a shortname. This shortname can be set to anything, but it is recommended to use a descriptive name of the provider / account used. E.g. a shortname for an account configuration for the DEEP Hybrid Datacloud could be 'deep'; for Google it could be 'google' or if a user has multiple Google accounts it could be something like 'google-work' and 'google-personal'. Usually it is enough the generate such an account configuration only once. For `oidc-gen` there are a lot of options. We will cover all of them in detail under the point [Detailed Information About All Options](options.md). To get help with generating an account configuration for a specific provider refer to [Integrate With different Providers](../provider/provider.md) or if you have to register a client manually refer to [Client Configuration Values](../provider/client-configuration-values.md). oidc-agent-4.2.6/gitbook/oidc-token/0000755000175000017500000000000014167074355016611 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-token/options.md0000644000175000017500000002026614167074355020634 0ustar marcusmarcus## Detailed information about all options * [`--time`](#seccomp) * [Information Available from oidc-token](#information-available-from-oidc-token) * [`--all`](#seccomp) * [`--env`](#seccomp) * [`--expires-at`](#expires-at) * [`--issuer`](#issuer) * [`--token`](#token) * [`--force-new`](#force-new) * [`--aud`](#aud) * [`--id-token`](#id-token) * [`--name`](#name) * [`--scope`](#scope) * [`--seccomp`](#seccomp) ### `--time` Using the `--time` option you can specify the minimum time (given in seconds) the access token should be valid. If this options is not given, it will be zero, therefore no guarantees about the validity of the token can be made, i.e. the access token might not be valid anymore even when used immediately. The agent will check if the cached token is still valid for the specified time and return it if that is the case. Otherwise a new access token is issued and returned. oidc-agent guarantees that the token will be valid the specified time, if it is below the provider's maximum, otherwise it will be the provider's maximum (i.e. if `--time=3600` is used, but for that provider access tokens are only valid for 5 minutes, the returned token will be valid for those 5 minutes). ### Information Available from oidc-token On default `oidc-token` prints the requested access token to `stdout`. But `oidc-token` can provide more information, like the issuer url of the issuer for which the access token is valid. This information might be required by other applications, so that they know where the token has to be used. Additionally the time when the token expires (as the number of seconds since the Epoch, `1970-01-01 00:00:00 +0000 (UTC)`) can also be returned. This enables an application to cache the token for the time it is valid. There are multiple ways to obtain all of this information or only a subset using `oidc-token`: In the following we will describe different command line options that can be used to control the returned information. For additional examples refer to [Tips](../tips.md). - Use the `-a` option to get all information: oidc-token will print all information to `stdout`. One piece of information per line: - Use environment variables: Using the `-c` option oidc-token will print out shell commands that can be evaluated to set environment variables (name of the environment variables are defaults): ``eval `oidc-token -c` `` will automatically set these variables. Using the `-o`, `-i`, and `-e` option the name of the exported variables can be customized. #### `--all` To get all information available and print it to `stdout` use the `--all` option. Each line contains one piece of information: - First line: access token - Second line: issuer url - Third line: expiration time This way it is easy to parse on the command line or by other applications. However, on the command line you might prefer the usage of environment variables (`--env`). #### `--env` Instead of printing all information directly to `stdout` the `--env` option prints out shell commands that will put all information into environment variables. Therefore, it can be used to easily make all information available in the current terminal: ``eval `oidc-token -c ` `` The names of the used environment variables are as followed: - `OIDC_AT`: access token - `OIDC_ISS`: issuer url - `OIDC_EXP`: expiration time The name of the environment variables can be changed with the `--expires-at`, `--issuer`, and `--token` options. #### `--expires-at` The `--expires-at` option can be used to request the time when the access token expires (given in the number of seconds since the Epoch, `1970-01-01 00:00:00 +0000 (UTC)`). It optionally takes the name of an environment variable as an argument. If this argument is not passed and non of the `--issuer` and `--token` options are passed, the expiration time is printed to `stdout`. Otherwise shell commands are printed that will export the value into an environment variable. The name of this variable can be set with the passed argument and defaults to `OIDC_EXP`. Examples: ``` oidc-token -e # prints the expiration time eval `oidc-token -oe` # puts the access token and expiration time into OIDC_AT and OIDC_EXP, resp. eval `oidc-token -e AT_EXP` # puts the expiration time into AT_EXP ``` #### `--issuer` The `--issuer` option can be used to request the issuer url of the issuer that issued the access token. It optionally takes the name of an environment variable as an argument. If this argument is not passed and non of the `--expires-at` and `--token` options are passed, the issuer url is printed to `stdout`. Otherwise shell commands are printed that will export the value into an environment variable. The name of this variable can be set with the passed argument and defaults to `OIDC_ISS`. Examples: ``` oidc-token -i # prints the issuer url eval `oidc-token -oi` # puts the access token and issuer url into OIDC_AT and OIDC_ISS, resp. eval `oidc-token -i ISSUER` # puts the issuer url into ISSUER ``` #### `--token` The `--token` option can be used to request the access token. It optionally takes the name of an environment variable as an argument. If this argument is not passed and non of the `--expires-at` and `--token` options are passed, the access token is printed to `stdout` (same as when no options are provided). Otherwise shell commands are printed that will export the value into an environment variable. The name of this variable can be set with the passed argument and defaults to `OIDC_AT`. Examples: ``` eval `oidc-token -oi` # puts the access token and issuer url into OIDC_AT and OIDC_ISS, resp. eval `oidc-token -o TOKEN` # puts the issuer url into TOKEN ``` ### `--force-new` The `--force-new` option can be used to force oidc-agent to return a new access token. This will return an access token that will be valid as long as possible and it substitutes the cached access token. ### `--aud` The `--aud` option can be used to request an access token with the specified audience. Protected resources should not accept a token if they are not listed as audience. Therefore, this is a mechanism to restrict the usage of an access token to certain resources. Note that the format of providing multiple audiences might be different for different providers, since this parameter is currently not widely supported by providers and a clear standard is not yet established. We currently only know about one provider that supports this parameter (IAM); there multiple audiences can be requested as a space separated string. Example: ``` oidc-token --aud="foo bar" ``` ### `--id-token` The `--id-token` option can be used to request an id token instead of an access token. Note that id tokens should not be passed to other applications as authorization. This option is only mend as a toll for development, it should not be used by other applications. Therefore, this option requires user approval, unless the account configuration was loaded with `oidc-add --always-allow-idtoken` or the `--always-allow-idtoken` option was specific on agent startup. ### `--scope` The `--scope` option can be used to specify the scopes of the requested token. The returned access token will only be valid for these scope values. Multiple scopes can be provided as a space separated list or by using the option multiple times. All passed scope values have to be registered for this client (and refresh token); upscoping is therefore not possible. Example: ``` oidc-token -s openid -s profile ``` If this option is omitted the default scope is used. ### `--name` The `--name` option is intended for other applications and scripts that call `oidc-token` to obtain an access token. The option sets the passed name as the application name that requests the access token. This name might be displayed to the user, e.g. when the account first has to be loaded. Setting the correct application name allows the user to decide on correct information. Example: ``` oidc-token --name="My custom script" ``` ### `--seccomp` Enables seccomp system call filtering. See [general seccomp notes](../security/seccomp.md) for more details. oidc-agent-4.2.6/gitbook/oidc-token/general.md0000644000175000017500000000215114120404223020523 0ustar marcusmarcus## General Usage To obtain an access token for a specific account configuration you have to pass the shortname to oidc-token: `oidc-token ` This will print the access token to `stdout`. This enables serveral use cases: - print the token to `stdout` and copy paste it where you need it. - put the token in an environment variable: ``export OIDC_AT=`oidc-token shortname` ``. - pipe the token to a programm that reads a token from `stdin`: `oidc-token shortname | iReadATokenFromStdIn`. - use the `oidc-token` directly in the needed command: ``curl -H 'Authorization: Bearer `oidc-token shortname`' example.com``. - use the `-c` (or similar) option to put the token into an environment variable: ``eval `oidc-token -c ` `` Instead of using `oidc-token ` you also can do `oidc-token `. While usually using the shortname is shorter there are also use cases for using the issuer url. See also [Tips](../tips.md) for more usage tips. ``` Usage: oidc-token [OPTION...] ACCOUNT_SHORTNAME | ISSUER_URL ``` See [Detailed Information About All Options](options.md) for more information. oidc-agent-4.2.6/gitbook/oidc-token/oidc-token.md0000644000175000017500000000020214120404223021135 0ustar marcusmarcus# oidc-token oidc-token is an example agent client that can be used to easily obtain an OIDC access token from the command line. oidc-agent-4.2.6/gitbook/oidc-agent/0000755000175000017500000000000014167074355016567 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-agent/start.md0000644000175000017500000000141314120404223020221 0ustar marcusmarcus## Starting oidc-agent As described in [Xsession integration](../configuration/integration.md#xsession-integration), by default oidc-agent is integrated with Xsession. Therefore, it is automatically started and available in all terminals through that session. So usually a user does not have to start oidc-agent. After installing oidc-agent the agent will not be automatically available. After a system restart the agent can be used in all terminals. The agent can also be started by using: ``` oidc-agent ``` This will print out shell commands which have to be executed in the shell where you want to run oidc-add, oidc-gen, and any application using oidc-agent. To start oidc-agent and directly set the needed environment variables you can use: ``` eval `oidc-agent` ``` oidc-agent-4.2.6/gitbook/oidc-agent/options.md0000644000175000017500000002511714167074355020612 0ustar marcusmarcus## Short Information About All Options | Option | Effect | | -- | -- | | [`--socket-path`](#socket-path) |Use this path for the UNIX-domain socket.| | [`--always-allow-idtoken`](#always-allow-idtoken) |Always allow id-token requests without manual approval by the user| | [`--confirm`](#confirm) |Requires user confirmation when an application requests an access token for any loaded| | [`--console`](#console) |Runs `oidc-agent` on the console, without daemonizing| | [`--debug`](#debug) | Sets the log level to DEBUG| | [`--json`](#json) |Print agent socket and pid as JSON instead of bash| | [`--kill`](#kill) |Kill the current agent (given by the OIDCD_PID environment variable)| | [`--no-autoload`](#no-autoload) |Disables the autoload feature: A token request cannot load the needed configuration| | [`--no-autoreauthenticate`](#no-autoreauthenticate) |Disables the automatic re-authentication feature| | [`--no-scheme`](#no-scheme) | `oidc-agent` will not use a custom uri scheme redirect [Only applies if authorization code flow is used]| | [`--no-webserver`](#no-webserver) | `oidc-agent` will not start a webserver [Only applies if authorization code flow is used]| | [`--pw-store`](#pw-store) |Keeps the encryption passwords for all loaded account configurations encrypted in memory [..]| | [`--quiet`](#quiet) |Disable informational messages to stdout| | [`--seccomp`](#seccomp) |Enables seccomp system call filtering; allows only predefined system calls| | [`--lifetime`](#lifetime) |Sets a default value in seconds for the maximum lifetime of account configurations [..]| | [`--log-stderr`](#log-stderr) |Additionally prints log messages to stderr| | [`--status`](#status) |Connects to the currently running agent and prints status information| | [`--with-group`](#with-group) |Applications running under another user can access the agent [..]| ## Detailed explanation About All Options ### `--socket-path` By default `oidc-agent` creates the UNIX-domain socket at `$TMPDIR/oidc-XXXXXX/oidc-agent.`, where `` is the parent's process id and `XXXXXX` is a randomly generated. The `-a` or `--socket-path` or `--bin_address` option can be used to change the location where this UNIX-domain socket is created. Please note the following: - If the passed argument has no trailing slash, the last part is the socket's filename. - If the passed argument has a trailing slash, the socket will be created with a filename of `oidc-agent.` in the passed directory. - If the passed argument contains `XXXXXX` as the last part of one of the directories, the `XXXXXX` will be replaced with randomized characters. Example: - The passed directory may not exist (`oidc-agent` can create the directory (including parents)). - If a non-randomized path is passed and a socket already exists there, `oidc-agent` will overwrite it. In the following we present some examples of the passed argument and the resulting full socket path: - `/tmp/oidc-agent` -> `/tmp/oidc-agent` - `/tmp/oidc-agent/` -> `/tmp/oidc-agent/oidc-agent.1234` - `/tmp/oidc-agent-XXXXXX/` -> `/tmp/oidc-agent-s4jdi2/oidc-agent.1234` - `/tmp/oidc-agent-XXXXXX/socket` -> `/tmp/oidc-agent-s4jdi2/socket` - `/tmp/oidc-agent-XXXXXX/socket/` -> `/tmp/oidc-agent-s4jdi2/socket/oidc-agent.1234` - `/tmp/XXXXXX-agent/` -> `tmp/XXXXXX-agent/oidc-agent.1234` ### `--always-allow-idtoken` `oidc-token` can also be used to request an id token from the agent. On default such requests have to be approved by the user, since this is only meant as a development tool and other applications should not request id tokens from the agent as they are not meant for authorization. If the `--always-allow-idtoken` option is specified id token requests do not need confirmation by the user. ### `--confirm` On default every application running as the same user as the agent can obtain an access token for every account configuration from the agent. The `--confirm` option can be used to change this behavior. If that option is used, the user has to confirm each usage of an account configuration, allowing fine grained control from the user. The `--confirm` option can be used when loading an account configuration through `oidc-add`, in that case only that specific account needs confirmation, or when starting the agent. If the option is used with the agent, every usage of every account configuration has to be approved by the user. ### `--console` Usually `oidc-agent` runs in the background as a daemon. This option will skip the daemonizing and run on the console. This might be sued for debugging. ### `--debug` This increases the log level to `DEBUG` and obviously should only be used to debug purposes. If enabled, sensitive information (among others refresh tokens and client credentials) are logged to the system log. ### `--json` Enables json output for values like agent socket and pid. Useful when starting the agent via scripts. ### `--kill` This will kill the currently running agent. The agent to be killed is identified by the `OIDCD_PID` environment variable. When integrated with Xsession this will kill the agent available in all terminals. A restarted agent will not automatically be available in already existing or new terminals. You can use [`oidc-keychain`](../oidc-keychain/oidc-keychain.md) to make a newly started agent available in new terminals or login sessions. ### `--no-autoload` On default account configurations can automatically be loaded if needed. That means that an application can request an access token for every account configuration. If it is not already loaded the user will be prompted to enter the needed encryption password. After the user enters the password the account configuration is loaded and an access token returned to the application. the user can also cancel the autoload. With `--no-autoload` enabled the agent will not load currently not loaded account configuration for which an access token is requested. The user then first has to add them manually by using `oidc-add`, before an application can obtain an access token for those. ### `--no-autoreauthenticate` It might happen that the refresh token used with an account configuration expires. If this happens `oidc-agent` is no longer able to provide access tokens to clients. When `oidc-agent` discovers that a refresh token is expired a helpful message is sent to the client. This message should be displayed to the user and helps solving the problem. However, `oidc-agent` is also capable of triggering the needed re-authentication automatically. `oidc-agent` will also do this by default. With `--no-autoreauthenticate` or `--noauto-reauthenticate` enabled the agent will not load currently not loaded account configuration for which an access token is requested. The user then first has to add them manually by using `oidc-add`, before an application can obtain an access token for those. ### `--no-scheme This option can be used when the authorization code flow is performed. The `--no-scheme` option tells `oidc-agent` that a custom uri scheme should never be used for redirection (for any account configuration). Normally a custom uri scheme can be used to redirect direct to (another) oidc-gen instance when performing the authorization code flow instead of using a web server. However, the redirect to oidc-gen requires a graphical desktop environment. If this is not present, redirection with custom uri schemes can be disabled with this option. ### `--no-webserver` This option can be used when the authorization code flow is performed. On default a small webserver is started by `oidc-agent` to be able to catch the redirect and complete the authorization code flow. The `--no-webserver` option tells `oidc-agent` that no webserver should be started (for any account configuration). The authorization code flow can still be completed. Either by using a redirect uri that follows the custom redirect uri scheme `edu.kit.data.oidc-agent:/` - this will directly redirect to oidc-gen, or by copying the url the browser would normally redirect to and pass it to `oidc-gen --codeExchange`. ### `--pw-store` When this option is provided, the encryption password for all account configurations will be kept in memory by `oidc-agent` (in an encrypted way). This option can also be sued with `oidc-add`. When this option is used with `oidc-agent` it applies to all loaded account configuration; when used with `oidc-add` only for that specific one. See [`oidc-add --pw-store`](../oidc-add/options.md#pw-store) for more information. ### `--quiet` Silences informational messages. Currently only has effect on the generated bash echo when setting agent environments. ### `--seccomp` Enables seccomp system call filtering. See [general seccomp notes](../security/seccomp.md) for more details. ### `--lifetime` The `--lifetime` option can be used to set a default lifetime for all loaded account configurations. This way all account configurations will only be loaded for a limited time after which they are automatically removed from the agent. When loading an account configuration with `oidc-add` this lifetime can be overwritten. So that a specific account configuration can be loaded with another lifetime (lower, higher, and also infinite). Using `--lifetime=0` means that account configuration are not automatically removed and they are kept loaded for an infinite time. This is also the default behavior. ### `--log-stderr` The `--log-stderr` option allows log messages to be printed to `stderr`. Note that the log messages are still logged to `syslog` as usual. This option is intended for debug purposes and is usually combined with `-d`. ### `--status` The `--status` option can be used to obtain information about a currently running agent. Therefore, the `OIDC_SOCK` environment variable must be set. The option prints information such as: - the version of the running agent (this is useful to check what agent version is currently running and it might differ from the version installed) - options that can be set on start up - the loaded accounts ### `--with-group` On default only applications that run under the same user that also started the agent can obtain tokens from it. The `--with-group` option can be used to also allow other applications access to the agent. This can be useful in cases where applications must run under a specific user. The user first has to create a group (e.g. named `oidc-agent`) and add himself and all other users that need access to the agent to this group. It is the user's responsibility to manage this group. Then he can pass the group name to the `--with-group` option to allow all group members access to the agent. If the option is used without providing a group name, the default is `oidc-agent`. oidc-agent-4.2.6/gitbook/oidc-agent/general.md0000644000175000017500000000021214120404223020475 0ustar marcusmarcus## General Usage ``` Usage: oidc-agent [OPTION...] ``` See [Detailed Information About All Options](options.md) for more information. oidc-agent-4.2.6/gitbook/oidc-agent/oidc-agent.md0000644000175000017500000000031614120404223021077 0ustar marcusmarcus# oidc-agent oidc-agent is the central comment of the oidc-agent tools. It manages all OpenID Connect communication with the OpenID Providers. Other applications can request access tokens from the agent. oidc-agent-4.2.6/gitbook/quickstart.md0000644000175000017500000000457614167074355017305 0ustar marcusmarcus# Quickstart After [installation](installation/install.md) the agent has to be started. Usually the agent is started on system startup and is then available on all terminals (see [integration](configuration/integration.md)). Therefore, after installation the options are to restart your X-Session or to start the agent manually. ``` eval `oidc-agent-service use` ``` This starts the agent and sets the required environment variables. ## Create an agent account configuration with oidc-gen For most OpenID Connect providers an agent account configuration can be created with one of the following calls. Make sure that you can run a web-browser on the same host where you run the `oidc-gen` command. ``` oidc-gen oidc-gen --pub ``` For more information on the different providers refer to [integrate with different providers](provider/provider.md). After an account configuration is created it can be used with the shortname to obtain access tokens. One does not need to run `oidc-gen` again unless to update or create a new account configuration. ## Use oidc-add to load an account configuration ``` oidc-add ``` However, usually it is not necessary to load an account configuration with `oidc-add`. One can directly request an access token for a configuration and `oidc-agent` will automatically load it if it is not already loaded. ## Obtaining an access token ``` oidc-token ``` Alternatively, it is also possible to request an access token without specifying the shortname of a configuration but with the issuer url: ``` oidc-token ``` This way is recommended when writing scripts that utilize oidc-agent to obtain access tokens. This allows that the script can be easily used by others without them having to update the shortname. ## List existing configuration ``` oidc-add -l oidc-gen -l ``` These commands both give a list of all existing account configurations. A list of the currently loaded accounts can be retrieved with: ``` oidc-add -a ``` ## Updating an existing account configuration An existing account configuration can be updated with `oidc-gen`: ``` oidc-gen -m ``` ## Reauthenticating If the refresh token stored in the account configuration expired a new one must be created. However, it is not required to create a new account configuration, it is enough to run: ``` oidc-gen --reauthenticate ```oidc-agent-4.2.6/gitbook/oidc-add/0000755000175000017500000000000014167074355016221 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-add/options.md0000644000175000017500000001745314167074355020250 0ustar marcusmarcus## Detailed Information About All Options * [`--loaded`](#loaded) * [`--always-allow-idtoken`](#always-allow-idtoken) * [`--confirm`](#confirm) * [`--list`](#list) * [`--print`](#print) * [`--pw-cmd`](#pw-cmd) * [`--pw-env`](#pw-env) * [`--pw-file`](#pw-file) * [`--pw-gpg`](#pw-gpg) * [`--pw-keyring`](#pw-keyring) * [`--pw-prompt`](#pw-prompt) * [`--pw-store`](#pw-store) * [`--remove`](#remove) * [`--remote`](#remote) * [`--remove-all`](#remove-all) * [`--seccomp`](#seccomp) * [`--lifetime`](#lifetime) * [`--lock`](#lock) * [`--unlock`](#unlock) ### `--loaded` This option is used without a shortname, because it will not load an account configuration. Using this option `oidc-add` will print out a list of all the account configurations currently loaded in the agent. ### `--always-allow-idtoken` `oidc-token` can also be used to request an id token from the agent. On default such requests have to be approved by the user, since this is only meant as a development tool and other applications should not request id tokens from the agent as id tokens are not meant for authorization. If the `--always-allow-idtoken` option is specified to `oidc-add` when an account configuration is loaded, id token requests for that account do not need confirmation by the user. ### `--confirm` On default every application running as the same user as the agent can obtain an access token for every account configuration from the agent. The `--confirm` option can be used to change this behavior. If that option is used, the user has to confirm each usage of an account configuration, allowing fine grained control from the user. When using this option with `oidc-add` only that specific account needs confirmation. ### `--list` This option is used without a shortname, because it will not load an account configuration. Using this option `oidc-add` will print out a list of all configured account configurations. Configured means that they are saved on the system and can be loaded with `oidc-add`; it does not mean that they are currently loaded. To show a list of currently loaded accounts, use `--loaded`. ### `--print` Instead of loading the account configuration with the specified shortname, it will decrypt and print this configuration. ### `--pw-cmd` The argument passed has to be a command that prints the encryption password for that account configuration to `stdout` when executed. E.g. such a command could be `echo "superSecretPassword"`. (Note that this command is not recommended, because the password is logged to your bash history.) The command is used by `oidc-add` to retrieve the encryption password, so the user will not be prompted for it. Additionally, it will also be used by `oidc-agent` to get the encryption password when it needs to update the account configuration ( see [`--pw-store`](#pw-store) for information on why `oidc-agent` might need the encryption password). See [Encryption Passwords](../security/encryption-passwords.md) for security related information about the different `--pw-*` options. ### `--pw-env` By default `oidc-add` will prompt the user for an encryption password when it needs to decrypt an account configuration. The option `--pw-env` can be used to provide the encryption password via an environment variable. The name of the environment variable can be passed to `--pw-env`. If this option is used without an argument the encryption password is read from the environment variable `OIDC_ENCRYPTION_PW`. ### `--pw-file` The argument passed has to be the path to a file that contains the encryption password. The password-file is used by `oidc-add` to retrieve the encryption password, so the user will not be prompted for it. Additionally, it will also be used by `oidc-agent` to get the encryption password when it needs to update the account configuration ( see [`--pw-store`](#pw-store) for information on why `oidc-agent` might need the encryption password). See [Encryption Passwords](../security/encryption-passwords.md) for security related information about the different `--pw-*` options. ### `--pw-gpg` The `--pw-gpg`, `--pw-pgp`, `--gpg`, or `--pgp` option can be used to indicate that PGP encryption utilizing the `gpg-agent` should be used. However, with `oidc-add` this option is usually not needed, because we can detect pgp encryption from the account configuration file. ### `--pw-keyring` When this option is provided, the encryption password will be stored by `oidc-agent` in the system's default keyring. See [`--pw-store`](#pw-store) for information on why `oidc-agent` might need the encryption password. See [Encryption Passwords](../security/encryption-passwords.md) for security related information about the different `--pw-*` options. ### `--pw-prompt` This option can be used to change how `oidc-add` prompts the user for the encryption password. Possible values are `cli` and `gui`. The default is `cli`. `gui` requires oidc-agent-prompt to be installed. ### `--pw-store` When this option is provided, the encryption password will be kept in memory by `oidc-agent` (in an encrypted way). Usually none of the `--pw-*` options is needed, because `oidc-agent` does not have to read or update the account configuration file after loading. However, some OpenID Providers might use rotating refresh tokens. This means that for those providers `oidc-agent` has to update the client configuration file whenever a new access token is retrieved from the OpenID Provider. If non of the `--pw-*` options is provided, this means that the user will always be prompted to enter the encryption password. Because this can get annoying, it is recommended to use any of the `--pw-*` options in such a case. For providers that are effected by this we included notes in the [Help for different providers](../provider/provider.md). See [Encryption Passwords](../security/encryption-passwords.md) for security related information about the different `--pw-*` options. ### `--remove` The `--remove` option is used to unload an account configuration. After unloading an account, it is not longer available for other applications. Therefore, it has to be loaded again before an access token can be obtained (either using oidc-add or through the autoload feature). ### `--remote` This option is used to communicate with a remote `oidc-agent-server` instead of a local `oidc-agent`. It can only be used for loading and unloading configurations. For more information refer to [`oidc-agent-server`](oidc-agent-server/oidc-agent-server.md) ### `--remove-all` With the `--remove-all` option all loaded account configuration can be removed from the agent with just one call. This might be preferred over restarting the agent, because that way the agent will still be available everywhere. ### `--seccomp` Enables seccomp system call filtering. See [general seccomp notes](../security/seccomp.md) for more details. ### `--lifetime` The `--lifetime` option can be used to set a lifetime for the loaded account configuration. This way the account configuration will only be loaded for a limited time after which it is automatically removed from the agent. If a default lifetime was specified when the agent was started, the `oidc-add` option has priority and can overwrite the default lifetime for this account. Using `--lifetime=0` means that the account configuration is not automatically removed. Because that's the default behavior this option is only needed, if another default lifetime was specified with oidc-agent. ### `--lock` The agent can be locked using the `--lock` option. While being locked the agent refuses all requests. This means that no account configuration can be loaded / unloaded and no token can be obtained from the agent. Sensitive information will be encrypted when the agent is locked (see also [Memory Encryption](../security/memory.md)). ### `--unlock` To unlock a locked agent the `--unlock` option is used. After unlocking the agent again accepts requests. oidc-agent-4.2.6/gitbook/oidc-add/general.md0000644000175000017500000000105314120404223020133 0ustar marcusmarcus## General Usage Account configurations are identified by their shortname, so an account configuration can be added by using that shortname: ``` oidc-add ``` The user will be prompted for the encryption password and then the account configuration is loaded into the agent. After loading other applications can request an access token for that account configuration from the agent. ``` Usage: oidc-add [OPTION...] ACCOUNT_SHORTNAME | -a | -l | -x | -X | -R ``` See [Detailed Information About All Options](options.md) for more information. oidc-agent-4.2.6/gitbook/oidc-add/oidc-add.md0000644000175000017500000000046214120404223020165 0ustar marcusmarcus# oidc-add `oidc-add` is used to add existing account configurations to the `oidc-agent`. It also can be used to unload an already loaded configuration from the agent or to give a list of all available account configurations. Furthermore, the agent can be locked to forbid any operation / request on it. oidc-agent-4.2.6/gitbook/oidc-agent-server/0000755000175000017500000000000014120404223020047 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-agent-server/oidc-agent-server.md0000644000175000017500000000060514120404223023710 0ustar marcusmarcus# oidc-agent-server `oidc-agent-server` was a special version of oidc-agent that can run as a central oidc-agent on a server. It was only a temporary component and support for `oidc-agent-server` was removed with version 4.1.0. A better alternative is the `mytoken` service. For more details about mytoken please refer to the [mytoken documentation](https://mytoken-docs.data.kit.edu). oidc-agent-4.2.6/gitbook/images/0000755000175000017500000000000014120404223015776 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/images/architecture.png0000644000175000017500000237072514120404223021206 0ustar marcusmarcusPNG  IHDR }sBIT|d pHYs\F\FCAtEXtSoftwarewww.inkscape.org< IDATx_u37ۜrhMt^&VnbB]Dx]]!v.. .(fWA7."9*Dɋo_r?`Qn{Ft,d߻޻wj_Mw׸g4>^ƮYCw3FQ'81N\j[xG-O pUz厝5ꁩ}Tִ9q:1Fcs摖c };x`8PӃS=0juom>FV6jmUk5Fڢk̩1:d;oOUM_- [Fxu4jǫ?[op9pJX5t:7wG65^_4|gNܘc5{7>WQisc]lySQ.{q žjo84Pj1w׍Su x`p-b?hzx4=R=\9w7OձضOZVMf20Z޿QG>:wSS<GM/iya(aZ[5S=^=wT^\Sґqn(`2[X_XcX7w\acQ/卹8\aOm<:T_{&Z:]v4~Y/~͹y~b'?_=6Ճs7Fժ]y<3wpu[u}p%Z *jpYmj+:R'`"vC<3IEkdȢ RWm;ZKenqV*`(ϾH9$af~u\/͵ 74iL+InJ>IuwҺZ `}2A;S>iFKl-R.IS:y1z$g%$ 'Az0]y9]a~ӓ*yCM0`NrwR}4ܘ{g\o϶7$hY%n$5ɧlܾ>cUn}#Y$*ya&=䮤2#t}.U:xv183qBiWo$9tp@dK'[7eIAS3@8/NrN,K=zInm%lL}ofСA$YP u_jCYp\t `>4kʞ󓜓=@oCɆ ?[:q Ź~l{sfe>n?%lؔc`1-ㆲgeeIW쮒dã9-yM0cs.gۛ4+qo%Y_e䆍Y:uz_;(ےtKr[o/~d<'W+IK}KMJ6M~t t#:=iޝdq`|;dy7l^g4C~3i.LrD`jۛٔ^e4k=nTI>TWMf$M%1(jE&N_׫d^g1$L:1 cPĊԯޓ\V%o{@zR]7#S?Z:}LYM_\`|;;3C{ndYwmҬJrBY= #OJ@71SH*xT`|J>:ݔ˿]:q fܿb$DE{Z|JnJhc0cbIV%9p@7xJkߟLp(83bE_h-]h{H8LJ$)UNsԏ`4kʞ$o{ߚd-y10</cYwT&9/P^V%_mR̚-[``SpUIޑdp@_ڐ[`թ[_Oέk^5Ino2ޛkc`ieMk_`Lrc'#6wxc,Kd]-l[R:1pczឤNra9udVu81HjyUrMK5&hSona`ĉtnLt $W~ |t jeCD\dtI獩ngc`T팟4$1&ٚ Zx:1LPsc/$1ɵuݥcɌc |=I3d~fUiܘ[:8@[k$'n`u* .ِ˶8@Z$$*f';6e'Jq >N}j?Lc[(I6w\o[\1ȲUvA[$Nޘ)1'کOMrSn+Yym`q#5M; 3{I~K08]ԯ؝Z%(@cgw[zWt?$_4@8k$i,?S:U`NvOrvx&]%st2QKْ- ,ݐC==_a :ɗ2!tj~^\$,iOR]>5W'iJ݌c県 IDATc=[X-%+nL!t/s$KrXԤuYCNjg$[cIvGKНyI'YZfIS%ΑJ=c̲;O$9t ́$*@w00R|J^XЃUr_.@ywuO`$i>t 7<ʜp}V(d$[eI5*@9U~"WɎ[t t?ے(30Cf1Cٳ5[ ;/gܔ˿]:e`Sl;hVMߛ޼)+iu팏&gT%/XO/*_47$.JFSOg=̾t@/桃s\XzN]`Ownn^W%hs&S?Va`?+wUɫK@JrdK0c_IIS[Cym`f3qB=I*}웝Mt3U:ۍeU:a m/j%<*1 کI$?\*?og|t3ct@%ٜd~0󒜵(<4/1RI6UpP$(KOgc8p1dy/JrJqQ,ԧKp`<˪4f1n;@@ثj*ɥC$:&yGS}7T: TSP%*<*ygfSjJoUHrQ`6#Jc0&Z%+33 CI.(?Mr *P@N^wj;۩W:g4>!xcqq_:6T:`Uc0ɅCu8,c*S1 h$(̊%ǟ%0]c$fOH3N8_dU`Npcgd]SjJ`s_4WԢmK̒?6P^tlK}~\W{U2{?(itlK&L*Q%<1KF?/0Ȍc}g,wU2t PV検L[`P?U2RoX%Ng!87K&TCK]Jˋ31*0O$9t еv$7Nf_$1,{A+;?[H%K VbeZyW c懒|ǖ12ޕܞdq칧!8Ы]y&9tГ^>t@3v5Mtӎdsz^~f9g'Y]}Mrڿ&7gCG;I;+O̒m_ߖGU}t@1̉uIE%h!80$?}Q0tߖ>|(?yu}s7YQEq`qqwL}iwÈ O5 n1.s4 BnD㉓8DTxlF&ڄ>: KU}zS?N=$wnX& Sϩ:`L%00Y^lJ{@u00,~fOJwYHW0 O${Pu#s.ŗWTjg5KIXPgI5*`lg0@%L©*1Eϖ6+;E.8JSy~s$wn2;c捗T,^u0>rbl28o:ܫ:`LfMr\uӫ;[Si߾'Cu J_X*`4{o c,U sTuR1~f$ϫ#X+^QT`tv+ܵ`\_7|:`q]rZ c,SYx_uRhѰ1[M`\5iunNTubUïMdpa ե{ u8mK^䷪;&zuLt._OOu [ԃ/Qzp2XNGI`UJ ϫX ~[4w.ۯHrHu $GlM=ѫӺlm cTC:`O5٘A$k[&B/7CuUçK01 wUG /g]70`xL].<ɽ[%ߺ6U^u0<%a atui^\;`81ɕIndUS~\+zp蒷00ۿWG쪦:>[epi^u i i:`gyq_)(XKV+lې$ylu;>:`g 6vUv yt.1`K^vكϫMuP㸜$n`l6Uܖ^uPcunlc`wߤ_?,|-ɪ;<: oa qpTkcAtwJ$%f?'Ɋ8MrC\gIO$?N.Q/?wi~0}(*IT?#,'t;XvW!kCcCLIwD%6ݓN[q.I]WN<+/QDŽۜ3WW&9 ܚS^]P@ڴ]$Mj.7䫹.|~Nqqd& ]\Эio&q `xd=2ɣaMwu2VK4_j]"YiblΙ++Z@&͉Ww8^i$n]2$'|^HpASI{B|rIoW8ӮZMtɓ~qNkot4%+9_-sʹ$VPK6`rPX39dI2H${W7A6gWN_<+/QpېMM;M'o[dmy!ͳ{%diuɿ6gϺ[2 o$9i?QL&.9.g27N7IJҫn`;\Yu?^?IwauåK.=/ë;dӮZqNN:_NOQ_&q VO{^rB Ia]䓃4>܅ džOjTw0s[L/ٜ3WUK$ Gw.9{kګcXZ3iGSpɡg^u 0Y|$Iio4IVâK^r9i?bqK .9㼴Tw8LLe$%YSòjҼ}U1,gWw0ܺGe߻}<1`Bϖ#{*7bLUP;I Y{y͵1~#|=Iץ9Ἔ`r >L{CSp%yyYO?aӞ_ȸ|kUG8L &$nah]佽kc9'wώmIT0Rٚ`2![O&/0$r2鴫vdKc]$/n&GS,9]gW05iN97~,IWï;Z$V0rskm!Uksd IDATi21Ct>OwQ1sz즩$'VGtUkW6S$ٚu[^UM~گ%ou#' Yw kC֫܆>u]&yk c4zI6&r&ImAwb=\`1aI$W0풷oK.gëc&ܫ}t'T7㯩v]vŶ4/ҽIab 5N:flJ{_qִUGWi-4eb.ټ2چϨ$ +cE$3 x #b:kmI^WsY?g|/^-޼`< F@?#eWUq U~f^2la gVG㫩nt޹lQI]&'/Vnj6mɕMrg`"C;hV8,~ڣ\#q}-$/n`u1ET8ڐ4>duu 4ͩ\\ioIV0=;֫q3&0,]Τ}}:fy4b!`EO.9+Tu KNh?꘥ebFꀥbɆ~ǥM򹙜~PuR=:;>[Y6=I27GT,V28md`3R$ٚO%Mn$wmCԫQ6$P$W,dc c0|-TG8 %o *ɹ3iݦ6.T v(1ٙ&4-@%/ߖ|l:ܫew쓛pKi 2hCfƀa4./:!oCu{qu܆#9`4]O{tCI[[[~WMyݓ<nӫ dvR?[IT.]OY̟; ?ni`lL{ b;V;Ke[Qum]%m[t>#yO[ҫa7$0RdEϤ}t.5Mc c0 d==16l[2I].y\[rKt/n5H`[Ѧ]1!-{y]Cnncڇ%yxu&y朹-1VlKޝIyH/_ڐ-AEߐFq ̾"ɉs_d!<vU h1]wTwKf]O=2bUNHvG<-Mu iKd`Ym|h-fÖ5FC:E?{ɟ0L%9oo94.yRu0:c@鴫iUs.&݋YzaM ɣ;2%_ژ~ƴ%ݥ~,.y\vEu0c0ikVw\K~߸Qw{hu0lO%YYknxƒs=3|f~1ߘ_db 5o_?_] ? L~O$Y[ {~|UQ q NIZucY q <'1l ,yh;h:,nݥ\,Sޜ$wT7`LҪ;wדW7`eZr:xV`p,gNrcu`IL2{,>gTGc,~:j`Pq XIaAi- ۔ٞq C[OV75c0$۪;׌ch`0&\GKS0`tzq ) FO@  |t0Z1TG3ehɁ`nT5c0$Uѓ @  CyfOm|40Z1rqu0W:#Fȋ@ k8В |ͲX< q Xq=8q _Wz.mn8C`VgAhɞ`c`(c3#dAh1`ҶU7gA1` Vg0+nq %[t{-ǀ2@d`z`cPeX$k0#dh; 0#drkIn8&ɮ`&ɬX<1`zҫ3Qh1`c0;1`c01`Zq ! `z2n8q ޫ3@nFǀ2ɬX< q ޫ3hi1`cP';ɬX<z$_ђ^,q Jmʹ $n.8HϪw IDATf黪9_,q Ju0"\ux1(0-8q Jm2ѓ T7#29W],q bL23#dj1`$f`cPmWul;W,q Ju0gs`cPjbϮWGgR8q q8W0@n8q J50 =yaj1`&+ @ 9IVwדW1('U,q /dFǀ2@q nF8eq fFksu1tR6ePCI>Y,8q Tgi~'N֯g+ @ l?ђóڤ'IZqP\juP\yaμRO>$יnjl`9s[_gz^Yr]uzJuP8?ҳ,9ՒTZ1ѼiGs_Ob#IZxZ1̉Lw'y,yt13#g`I$%y2T20p'r{r,1KcS޳I2' Z1{.={L& "\ERQ,TXRP{bdY`“J"[%\#z(7 @PrI23g>7{콟f=x~NZmIsf;yE UɯJ7e򙉓4%+J H,Olm $hK .IJGe(||;}mV IatPq 43mdi$f KZRc - tPq a0-uf@5&ٶt@ Uc :ukqL ^&YT(*0Z >J>$*z@y{sLkJt``#'oٗ5g$P=fQ3@ud͹I.СcIcl,N[:@g0Ol]ViISUixQ33kIv,=ZJ8zv&Kt5e##*ЁOl''1dQ] :ɌVI(ЭdQsH2-W'uL(c1ywO\䍥[FjasJ4#&;1L8%z䟚-csf>6ɗd-P:KG80f ?/hST{FGU#af)mKr KU:`H=}0~q -0\ϧ[YqQt XJn:KU:`8LOw+F}K-YMkJW*ɕI6/*.Jo_Nt F/@Ǜ|1ɸ-0 6MI.o]cm HO1bU$廧^z} q #M˕=eEUrBk41t/@9%_VH$s1t@GHE^˫ki]Kcc IMoEݒT_XR-C2t9w&oH-y+ oM+{ 1t@Q$عt 0$$&էJPq i~;c`XܑToG3cE > I* ;㻧f]:eq2gmJ#ja\GrIz}Fq `DMO_`Ʈ&y|N.2$;n:½I5{nΞW:cO$ۖn:U򷗦! =9LoNCsUU_N}Oq `X%ٽt I:|KJǰcff6Y&yU,MOHR-3 󉗭Tj?KrהaTOV\-P%Wse[01!5-Wg7WuV'הiF? .a `xLHr3y\:*3S$- M+WrScx~=aF\P3$W.3,W@'F-T%W5IsSt ωc`L}P/iS.3o/JkV[[~ǼS?Y:U:N'Nk@&Nw!JgZςo%-/bf)!cUt}*2VU2#*tZݓ9uv阱8 ~}INX%>7K1d InKsMxjƥ9CƂ@;%_UKO!0)ɻwfKnjv1$ǕB*y2;2uѪ*t}R&w^-2׬ٿOL,0V._/2-${n!M|f#ucFV#U1-U8f cF H}Ft@!G&uzKU dM7VIožhze9f=1zJ*4?SugY:[s@MJ~@N|{8?Aݦ*tH}q9i]nb4OlVw\3)-c7zB[[Kҗsq'KtVc`Cnmw- @9Ri;aV.X+/Ywd1`zo;^-:ҜuKNeƨ1lҾazfQ:SU74WEV.I}}NcƘ9oVޓd-&ye-IZUegc`8lV%WJtt0rgѭ4ה&]:1⽩'KNk1`]Rܜ}uZћ|"1Fʸ>tHiU`̜75i i+R\:V`x 7i)1JجJȜCJbFgI&'kg>tH `zvrO[ȒvrWR?P:d$Jg0\ ctZRP:d$Qj ;=\;z!#* SOZ,HKV$Ktpkocٟ\V=u|٣$[xAM2̿tp0dSI&5NU`L#Zin,lRwYn* i泥;`=U9.2c(1)dlUzJ tNN=ie$;n`ܼ"9r^굥CJtV:-1F7'X:b(6k%_K2t C2;2CBtizl^!YtPJoV=}IJ0[q_>V:dSJ|<1F{itȦ0]jF:ɻJw0l,8tĦ0]9qM1QJ>2;6VU:p3Vڷ5ƊeMz^{Yna D6tcJwc۾G핣N;tHywueH 0ܾ"9l^굥C6Do`ȜV`6>osHOF'%$9tȆ0]9MR6[f=̖;o0/ǥ'`UJO0 IDATduYf}/Y/˒EKi6FX|xV.I}}閗8tY;Jwn'ev+߰k^q˳~ۧoq}keYt#y?U]D d|ƿ m-' C͆k͆3[ܷ_?۾ <@!B}}a{177]x043Sͥ?_.Qe$=;?n6N>$o>L޾tN&m=1G|൙zr'p,1o]>GY0?q׵  &lޗ~usE?X: H[|aןyk}=?.&g]_VOѯ[;>|'ܛve^/`̚]zy멇fѭߚ/w=ؔNqNwߓs)҇N1gL9| zξG2UJ^9h]~$y>1ý7$JwoboGeIorŹ]g¥Yz>'Ev~vػIBBoTv~WWWue]wmEE*"HׄޓpA33gBޯZ3<$3gi5Na<0# _Cn7ѻcEz5Z_?˟oYSIPov(1"g&8TuzN0Uu(O'IkIRq{\%61U,wGhֿŬuF ,Vcjx'Z[_]?4)6 I2+O1XtCQ STBb\KQ2A1!:#[kóBנj73Yd N߮ٯ-wLSLHnuՠC:˥/.Wg9%q);XQѠC=NCkM\6Hu;nn]]|}8U-uj|_,}5JuOr?µ*INk cgoO7Xq:KZ6=a޿e NfVzt8EąZ!L;Oh?7x7şЦ >|]ƃE9%Z&OvֺwO[t{Wx;q;-jѢn1.~p `w+Wc'=_;9PEpikd:ẆyU;ܺ:fDi>#f[J a?jz0fBphS^JM.{o^bCksXg޻Vt7I.Ogxzc;NXה'?ҏ\f&m[pbGZ@\.i=z}T=a6>驪~QᱡvYzor L^@=8Sy )*k?SB/ukv7q:1.{jwԈ^mߪiYOf.},kfVHdNwhcqz):7t}ە$Ƃ`}蟗LVIϡq^@:pʖ{Lwq֮J_?cv/?cl}8p ӄhIPuBc=L^^ϒeqcnE OnW[\#?cko_ʯMlGUBh :9nn.I[!Fj⨯=34qWz+O-j(?lw[$PS5TW\m8bwU5?XK>Zgx}tC,Pby| 9˝j#s*A--lVt)<6ԂTl-QsE˧lT[ꩡv7!";nB"tg(0$[_tYYMcv8dxm,p6?K*L7#s**1ܢm_i3B5ޮu!כ7%ۿ zjvTc=LMc uVߨ8p< ;̗Lm7w#}\u>SiWZmӜ*)(3]fH @5⯊lp 8T:ZR}5UPXt=HEvdgSӞZ` TpxEk嗛M8{rSoM%ڱx}zTWWߢ :p 8R:U[ ܟﭶ#-lm0>n?Ž+/TMTp]:7]utM.pX %kgqzvD_D?㓣g\%vd=ӥ^^jGz)".Ԣ瓗Q0]|g5TׂM7 Ttj}6jc>iP2kѯn`x}yI|Ž7(Zv8S7iݦjڥuo:V]Bw!=;N}.V\hj!ajo*xExWIAOhmoQG#~r隦='łn- ܪp'$=C} c䊷L!'*-QEY }8O{u|OJnۧDazoE(08@9*+.׉ܗC2TZXfwv.;3wݥMM]ǞڶpE]j%n^;=܉BՠC7VhTB"r(D'āRI~-*".T c8F TpxU_ d)pwe)cOK*nPPXƩNjZ!*-,Sq~JKu(W=YrطD%+I*"6T "CYE(s H'l؟R䶉A ㅊ3\3.2#^:\\iWIdDŽ=-S~ܩ3vhrVTiQjwY35(E}RerLZvP;;}8Ϣn}W5kz- ]ONܘvi嗛^/,6nh3oC2, V*+v/((1j@#uZ-5TBh9\N8}khڣڿ9B>~Dz7P^ɪU'TҡMھh/ޯ 3w^\M'fHS1ݨLVѺk:-N}O6jsqMQ^ jYԁǴsXr@gPQNEuBn&ݓL}/--*ס'pC:Atq!.E:p fwhBXtLuPϛU৔uұOk{.IuG,)фow]fTuܼ P.ԨK_;K4yͭꖛ̿>_x01HR}5Mcn ;,^g0!IiW8]nǘ9Ш>4Vy̸Z湁䡞|͛ReNI?.yjձPjIUל7VhkUemxHBhxvYIW/QZz켔:?j &1]޸\|u+Mo{Bݪ+/=Z>UW>W byX(Zlߝ›RVOzy'^ @\rP4TYᲨ#YqHiW^sMuve| UU'B^矮^,T]mG*)Qyi¢CĦjԥtOR`񑖭7_WޡGLվG>Vc=T?Ws9tÕs4_3QmK)뚪8>;.܏ R։kK*t`ݘ6Q}"O`Ҡ?T]|mW6+}wJKUZTFnx5PG/j`3Ǎ԰s=qTrA qx rיH/D ~~UD\Su黲u^ْ9*)$ VTMSIjVW~-Ik덫?Wnh9+\ڽ␶/ڧ#[\r)$2Xu#T'5^M'Y䳆o4\O~5R/ HeΪ>%pw~Rÿ -}ܐn&2!L=nlwS|hC5M'~+P/qc;1/[yMky:q}5ç;-ӂN|DZ_L˴|k3Yƶ7Ei]+},pf\'Usb&IRpDzNCCŚg+hT]UH|{B<9C[sJ ˴uZ:uZ7~"?<(,P|{ydm_ԙg3;>uT.^"uG#Xrv8=++} gBQ05Hݒ M~{5 Ԥ;EͲsAU9Z== ǨF'tݶ  8R1~XsS=뮩**12h؝9+ PxLUnhlw;tۤqc;5gf*c%eZ>eO٨WԨW/ULօDϴq.Sg2?*0ϧvW?^(4@O^myK>Qe:wu9]r}y*+5u^IAF'E($̰wu֖y{|:ϭi1=@Ӟ^ %Rmq~:oM\J-=<&=]>ŌfCkzfBtkjm>-|o}q)t=._1I&3\g 2ᱡJlc.}׹6Ww+mm 1g[C\Qo:t0K+{p_nxj[3tq)tW#:xg&J9}Z{/I:g̩K}]=_[ Ѿ1 {"x0ۣH0)EkwCtϗ#4c=HҲO6(;{|wDą֕>VV\ErG Ϯ1S_ׯ)t0.-xwmQ\(!?ސ7Ec=p0Ƨo&̯R0?zеP4m`ToXwL/Nҏܗ?/\@5M생p sIPnkx):q Ǣ|CQN2 MU6ԛj#zڱxٹ>CgJ thmPXzE% 4{&RRP7#[?2Z%h[C=ڃj~g X/[ -+.{ghS LuVa!ncck궈χjWQrm+u> gg8!ڹߚ*U9Mou0W TG=թ$t';C+>aLoB;4><6TN%Z6ynhk* uP`H@r򏛿ϭ{i+-,ӿ~5Ӷy'Bt`M"51oK{)@c#2~u8qhC-4A QKhwg\Ow}WskMw띛2jo1DjsINjh5 .%MeA'ƥI9ˊtx3mk? 6CǍ<)WmtchmsMoFAVKGZ jlI/Uq]/.-|KxdiO׺^7ٸp۞i& _u#,6|B?r~g:2jة?[?s^Vi#UtQu{WY>eM\\oRņm_أ=y~5G7[%M'i=]Lռ;km[^Pea*X?+2!+GX-3 k`\ ҧ`٬]|J{874|nkބc6pIPS74wЂN|;369ʂNşhCc~M{zk_ ҴGPnzV5G5󥥆eknw\zwڿwO078;ynչj5zv]2j;uȽׅ{!y*-,?O٨9k[Cjhm^F>{{.r4Y_tk inH׎ ?Vk5j7y,ʗ9ڥr8H'CCV~ŲNջclO{G'ڊ2eAs,^g0681WU>׈EUG u8;qGCM\ixmV Jⱳ8W0PV3>7@_i]Z:qGTbF} ][[|9+\vBM{&[ܑq~3>-ܧ/ɣg[sT<ମI4c^vKfw@MNCMp/I.!UQ^Z/MxoWDy۩~D=@.5R}/y}oDfQN>,_kcBT'5޲^P7
p3&G3oT:k';C',}_<:ǒA[Rϡ`x}-Ϛ*iҴ^0AffSFԏT>VWe{#s8mۧ(Ģ_橼+g1 6̩[dqGshcN761so)/nfw-U8/UM}?ps1 M+X<âC,ļj\ H]=mn4><&D}o䑳t_^hc6味*U9 o7#f -}rIR.,՟¥,aFûJKoUCqf\+bAW;-S [mvR}Z+]mzck'~{a_rT tuKE%{lQ~^zz;\['^YE3eђy<#bLɬQY,~26nh3S2o7qweYwz%~^h xk5YPh :=e%kB,ļy.Ks^_nf=]pT.׶VBOZ+皑s4TXI;Z؍yrO\iE%ڻuY .Eψ ?-Y1IQ7S=U̗X(;KYU]^r;Cw9]Z񀊨pc/xg\Nc7A4}>~~{!YY+ViaETn۾n 1=ƛa"lG9G u8sNR2ooS6Xv&PEd'7Ajv7d9TԐp wgPO++./_l+>ߤQ\"?CǨI$\vJ p\y*Uݢ6CZ۴WE+cO]ᇝ>blqUl_O768"})%zߜ^c+4*ح=6ڥ7*gӐΣ0ϡR}xwn6\O)iu -P(Ԗy{zGR8FfJbx>yU݆:uNӟ]haGƭvW9ᵱg RV\n&p v}PX{ԏRfJXW}R:ߨ_n;7}kjۮBFa&lJLgCe y$=uʼ+ c$4VKj9\#4`۫,?tpPtcڣvtvkop+Snx9m=1"&Z=#VL'5Ӗᒾfc^"ɾP+8á[)N4{g H"Uy]G0~ۂnUU黲}8O" 0τcl}نFćY .#2֨>ϭivqgS_>~R|+†wU-MlǢnn Xve5|g1Ĩ3M+ҮH5~eǝXo?֦o[ϢNo"sg'1*ʜ0sm\'csPe. pr0Yԓv2ܠ(iy.kfjVJҾG mع¢C,ƘbeYfΉL\MהԌ Gl5% ƐBwK2:^#[[mvC3ܩ6~eRcWߝz5nU7!jګZӌ{W Z_Tas u(VY*- kHJwجt;ǫ#wipugS뎙r@#ΊSnI׻\.m_ho8;7n9xU?G.S-]`HB󟉰W|}z˻a9Y9ow26oCH18%IuǩvXՆC{[G[!>r4UKLVPk,J txsmKRRlx}\ʲ;d~vX˃iع̾Bpy'![lvt !@k8;)CGٯ-7R`'KjsaL㲏\o~BimVs؎k,.Vl1㶞og!D̽}~xCY}ĩTjpjH8;! x+`nkRnVnLNnƓy1CpQczPGv_qn6ehֿ~]mOobk YGgB~5PT-a6p${7qᵵDӈ=;13=Vwf)mLeQ3iopI4s}_6c>zP%.]WMcawR31X9Gm=_2rVNlvuƞlS뭐w ,B1s̠{??$P9C b$Ԃn`VAV)} \LΥ}8֪>+SYB"Ld˱s5ްg1[vPfY+>V^ɕ>u(Ok|Y6֛}XBC>a~}? ;\xIUekpHiv@0?3<6ԂN|;3n}aفف!o] |sͽߢj+*1t'y5UZd<80;q0ׂn.L+ܬ_X쑽J TVR2f_ׁ[T>O%)s{w빸\W\lh}~)"^GXw>c [-gcg2CuǙoKq}a!I2U f&?WX=C= !M0?TX",<}zRQW䙮e*PLHSgd~|%)a^(+*y0E3w}8WWڻmT+v?ds%)7ЂN3f{œj3zC)G Rѕv;9:J I&cdOt¢ίp 'tp nՄ&I<^N#jJ16 IDAT8FBw~ KSU7 7Us=4S5p.1i^\sp1.1nc} Dž!\z˥B2r#69ʣEM5vWa\ckoW*Q]DVi6ۙ}K؉TQfZY^gp ճ+rk 5Ҩ5ᡷ4%1/#MrI u'4:r'OOrgH{(0?9A1IR:5] y )F1k.eϱ$; ]gTL(ܐ>O\i8#<&D]kw>gO5I-T=l=fj :^t(1J| RD#$}NP \&}DŽ($2Ȃn|6 ]=allp9+'}m5fiA7Ir|}L-u#*rFyb粃ڿ 0i^weiӜU>: 1y/)iݻ_N]7¶+|\7[I8.=8L '4`u|rghv`HAWg5ip1) :1]{A :|TwNAE^exmհS*.g}lĕr9kV;W_z-!04ƊP8͠1z;c,wj#ZЍKߕctS3hl8FMA8`CImM\F8j&RX$|/AaC,^E%gEą˵*}BK>\U`;Z~u49Tq[kZ~t;Չ;3X]K+L 0}Fulw @ܮ[w55_2E=G@ X6yGUh->{ߜv`_ؤBnk&Y,FwjNL`Bh&ɚܲgpv-w$M4]`/+.7zI*e2]g]կgNm45]SZXZ ~>cpK,yWj] T15p8~wt:g5Qy_z-7曾o= :?;0II[)*-.ZV[kj9==_sgv^X(eo#?O;Ku_ɕq/ts״4c@j"_.gߊ|>\.~G0QۀooedӐ^ې|'Z]S^3Q[]AhLmH VVe1M[]J4?ScxRk`mo1مhHaCsZ.=bӚ rQwQD|#͚Ab%t1Zw,(Qa,އtOfu֔e%J.W$+mPrޭ ̷eq5mėҿ;fdv i6++ 9sdY5s`odTu፲aZ.ٽ!esWH®2V|wj>žG(Ma:d()Mwk_VpItc+m"7_n>"e;cS_]&^}`VF۰rSJC *V?C1RS+x=ưl/6-v7iϒkm`r=/&1 sA|Фҗ; >_I o)Sy$Ic4c W>JSv-#/zGFeEUi`װvQEfsA~C=k?CQSUO4%ajhE%۾Fۦѯe떤;KF4\|&~DD4ԝfZ9do`bΚXTk>}9xt*b3JNy_X[޾,6ߺ:6LMV]wz݄^ :,LA~yŸ7""ڦ+7Tk'E.heq;f>E%E=yͤvG떦;KFD xvoHQL$حfX9d1UDt+t`۞T%!4ѦKwTTk&YK{It:iz7ץ^r~5m(ǀ)vt5_[SAu6N=e$νҝ9,꺌ˊkq"Wwfؙv>s_nwSqkl\8SnɆMmm $~xPa(U =w˫aTk;瀌ư/'~k&e(v]+LYtcˌ#"ޚ4|K;]CbĘ="<`ޚ,):ihS9ޚ,$|uqi9÷,ZHH5[/+޵u(y sN* E]BgokeM<lja)=437OiPCVÅuN5httj>@wwGFITk./eZF`Q*VYjM3JN~r3<0+.H@sPA.g3{ckeM}0Q1/Sux/d(n _ڰr].@_C>$;xl zLJ3{e$E]R?qqFi>.&\JGn6SzK쭔gn:EV-2JPFD3G>yͤC?&16}6ڸ o<2L}7?LVrͥcg?8ޙy]ӄx㮈mc77TTi`U^現pH=D6S݅SW7ˈE =sTk^ULoflZSxwjtGԇg7At9cQT:YOwR?MI5?~%ٱ8[jſM0x|^"wa:x86-v7iK?oiQ|z8>W#'oNagX)Gie;Rڻ䮿ظ:Y@(cnH -sݙQRZh~».d nѵ_&wzE^K߹ij~5|>aQTTaȼǚMvaXje ۦEƭ?2L}mJ1j{%"[~{YG 'ǺϷ,/ag+wk %-5۲U1멷>-LȻua匯%/hLv4k v!k[DDB["nsO.3{S5>X|}X2cevA.c m}G^zhWVn}_rKo̰sgzѶkz?{oN$j]mu]\}.L=MeEUM&w{H=RںEu $3;[-[D Hgb^L< /uz/٤;f+Ϗ+_wەƉWQ7VŜ 8o@9dio|< lI]n:ej'uاm| Ͷ/~7'//?TmdQRZx~ދcSwQ??>%Kԯh{5y)que%M\t>պ|\=1%7}96+ |f(LJ<ۺcY|g'mY&֞hŠT%c\.q&z'#z7~IqL\LE^4LMUm[S%oӹ<'cv'k6]Zſ=<ߊk7o-fqѮ[{.qH枱5Ê9k?NL<W=6j$Z:ޙxW5Q"hZj񅿟JS6-t{OŦ5 sqU'g_woLJϿ1 %7EW:bxmܼƊۺ'O%.N8bбV,YP&FsrbL^X:n1ޙuK6sLmD_{c@FrSUѷykw9g'KSvDaͯ_1~u-~ƌ56]ZE=:=[(nJWY?}+>3y?;1z Zk'Ŋ>]rG/ W+߉xgkc~i$RG3R:n}/z8?$˶r8G;que M\Q.=0qmDǞR?c٬񣣯WAB3<Ԙ|tv۫u"pH|S'ʇb&JۦFeEUTJRu1.#QT$agLgB[1gM'ϟcc)3L^sD0o3 ^^h6:͋w*:^?~c,:|nk]()-8ݶG\|1ѹwDk,{S9yYx)Լm8G7^hTpc5? n }Ͽ8yǐSǕw/[EZu(8"~?٠Lwfc9!ynsoj_QںE&Y# 1d[6lnx9!UqØ{#OVvŘj􂌡gn;gzQi ~BtkfJ?};TqΏG;SSU_rO + h1潴8|.S+ Dq3'_g=>J];dbɌk|g{ޖ__}z,/=ZgtvݑM}hv<wzy Ivt(8egG/sA(sS_D } W_{S}PÎVnk>qWŦ[9)6ߝר\_h~1=^J5ǾG_}Qڦe͍FИfRA=2'.sĘ?;\YpCc]-gK͗/o<8`TLr1<.)/}\," WСƳq?%.!6S\s/_/>$J[$[IiqOwa-ک͎{SYXIϟE>x)8\PrGӮjݲ㺋|M#bsמhաr/1ן_/C~9-?]@ Ina?}oGrŧgt >_h/uz|gċވoߟTh1k?⒡ cMd%^ZWΨmG-=s%yݖO\w~4pD>./kϷ>úW/9/_֨\߰M阋Tk|ŃՓ2Jc/>$>w8Wֶ4gdב3Wǭ_ܹ" cRνccĔgW;ۭ|rC=9SWߞȜ={`er$|m\SWDܑv{?~qs=c_ ڷs1 ljWu{ވ?GV'8W璞jb2:y?=1QYQgUo[&Zw,6]ZE]]d% ۲nթ4y1FDDMUmǧxztX5m:qr|wo_kow^;ŀczƑN\<+K㗧(i"$Ù.LFv pf}KպmVxqn{10o&m͍{ǎ9/*tfs}]w.QeUt.o]I5_7ϼWOEQ8GKS3_oqE~đ*ߔgŵ+lؚj]LsF|7F.oٰ5^mZƩ׵_FH:R֮t\_:G/J]ֹO8GUfaElPۿtػM5ssz<=2'ǐ~ޞWt1>G_/}z6n^zشv@EŹ1[ ;?9cP&9#ݍR}.^ܢ(^Fh"?2"c@SEt,t ;KX?>C⼟I>EσEσelhB݋Qm*}񅿝WeJ{GVY7qg/,ėl"8梃# ^mDqhݩ6Nԗ6x֩qwn,tLMwfėx.n͌?sol\]d{_/,O_sF +VRYQcqxyߦԇfGmu]#bŦɷ)_9*{|Iwԫ]tծQ,2cSo6k'%.xIMhϰyݖqҗL\j+ ׽$5bS3: vZ7Jo>{_6n^< 7*":4*+o>uqҗ=:VTVTń^q{!,(t&{\tv0o{U;xͮacoc?3,&[}b6UǛ,͍Ή//|]3{!.1q/N#;0rEMWyݖxg_=5[klƶbxmܼrJz<(Rjj/H6nn|ꏧGbk+O#4}w:FcxDy+ߋZY~B.&\r3v|lXQݘn25>t!1c2ݳzKMio_EVd 6K7Ībe֫b50`q{9"(nQ~7c}!RLa9k&5Q=ϫΎ8[q_<[jمT&/'}h׭u{n\]O]J<c \.Јx=o,Pح]?ٻ$:Px-[?< :m@ P.5ukU/f]֐Ĉ1C?hO?}ޙ8c A#DIg[9]<}+ c ~NrG^0$?أmqPâa{79|̟$ej<}غvZLj1C߶TmK('.C:5s+WŴG;ggʦ>8~;5^7/|eiA3Y}{[ vgEB֝c1={E};F};D~cvQR҆|_!V_+篍UŒ+c cŠ&Vޮ4?#9k}@ҷ6 KIJcbKcóc+6S +3]uνGqm_][]+6ŚcɌU1E1⨩mޯK/e %1Q|S'n>rE9[ :w=lڿct1:hBĭ5Q|cZ.*O_s[ .Znc~r %Ã":iݱ,ZDQ6nF]M]5,1u(m2J۴cˆTUm%%Q틑n-5yݖ']Gq(kEqTm5U~(+6-]iTo[бv):EIiqnUQSU[6l-p2&o()T؝E]yQCMk]\U.XQ-FlWm.teVŦ[To-5΂;_%8~X.oy@&r #wл2PBgBE yc@r  !y:z?c@r1s cC9d Ѳ@Z-?c@r%Rr D p_1 NRV:J.bw[9d"=r R.z~>m2W&PG:RmP@6FD8rBghn.˹;+k[;{o<켢RWMD~m6]Z%i#(*tc))`2W]#c@&rBg+"ƶ))t c|~>^`O9r DV?{MV#Ċ":즶:Zr ̖BB)lc@c@fc)(`WQ/Y9d BgBE+ll)thʔc@"6:41 (P TE(S(1 T9dƈ+t(2|Dl(t(RBSRL9d$c@Fk  Z9dge@) )`OXU P`1 +E[Y P`1 ;y1 ;UT╅W٩erK禈X[P@U1 [  hmI~-"#cã'={A+.*]q-ؕGWu-e{ݵk *MJd~ IL2g& ue2s;IJN I$I$IRD[!I$I$I$I$I$I$I$I$I_p0 hM;j4Ln4vQ9$I$I$IJ(Q1$I$I$I$I$I$I$I$I$I|H&&a!cH$I$Ib"cH$I$I$I$I$I$I$I$I$U70 k^rj23ӝA$I$I.C$I$I$I$I$I$I$I$I$bCX{!Z(v3!I$I$IR:<$I$I$I$I$I$I$I$I$IP.0-=![!Zb8~z;$I-fmh٥ EkJ"F?1?)$I $I$I$I$I$I$I$I$I$Iv-peǧ>fn@BC}~Tuz=+C$I5 =sM5OO䱳_d]iI$IWm2B$I$I$I$I$I$I$I$I$)M2m+ pq%|l\L3tp( $IRנ.\)c\҉ddy`I${%=`?J$I$I$I$I$I$I$I$IMcb?[ lqD`qK?s&#(1$ICnA6=sY9qg aLA*I$)bT$I$I$I$I$I$I$I$I$ik ݀ D8o:;,M`[eW&(ҝA$IR>ckxKb"I$)X!I$I$I$I$I$I$I$I$I @-@M_ fUs̝ dLMwI$I^5aA]BJ#I$ s$I$I$I$I$I ՞l;$I$IRɛ˹Lw I$IRd+=l?`o`-]̬Ab,[Sp%pZ R-$WΨ@tg$IeW)$I(1$I$I$I$I$Ij&Dڦ;$I$IRYC$I>̀sZtL་c}gi"p8sÀ?sjh&F``H$I Oiqi/JI$$I$I$I$I$I$I$I$I$IIn$b @w&=Dř'I W$I$c&/N9$IR]r I$I$I$I$I$I$I$I$IT LF\#=rުaM/ܹ@$Z%b9$IT}줄Y)$I~cH$I$I$I$I$I$I$I$I]Q`tumxQ\[Xg9pjU-QNr I$L~7h43|hy4T$IRDȟcH$I$I$I$I$I$I$I$Io6 /(|.ܨ.kDKh<(KwI$Iw؋̙ʙh4ʋ~LKQ*I$)-?e$I$I$I$I$I$I$I$I$Ií=38 -MR-3-pHH+!I$)\kzo_-;~n4 $Iԉog3$I$I$I$I$I$I$I$I$IR|<|<T0 p#.z͓m3ˁǀ ̍^ )( FosH$I Wqzy[t r IDAT͇l; -6y3sF_EfXjљta4$I,V3C$IJ(L$I$I$I$I$I$I$I$I$ikr?8s9@+`QfGƙ8#̀/sO Ŭf%w(Lp1$I$I$Iu^$I$I$I$I$I$I$I$I$i+x8'^K!帕'Aɕ tzlm|;E|û.Gqm蒒$I$I$)b9$I$I$I$I$I$I$I$I$Iڊdęۇ1>W1  )t$Vvm`[jpmfѐf)I$I$IR 6c9$I$I$I$I$I$I$I$I$IJ&@W ( 9L`} L3_93s>p#PrcmMtcNl %0$I$I$)bTw,ǐ$I$I$I$I$I$I$I$I$Iah B7@7 Rl)0) 9߭/ EL# 7i <RU3{َm%I$I$I ۤM߱C$I$I$I$I$I$I$I$I%VkӜ%QAjY@ ??kw]i8sg.HQ/ o{|g⋞@4 B$C#=%Q$I$I$IJ)oE$I$I$I$I$I$I$I$I$ICSf`10X |BlBͮq;0#d;%7Xg/p@\7ppp:0 (XnԂb `@zb I$I$IR}UNtҦ-$I$I$I$I$I$)^ MZ-,!J$I$I$k|LBe7n\A nv+b!'g$y7Sv^N-Ή3w1nHjF@ m6yЉD`h7va#I$I$IR"0a-ǐ$I$I$I$I$I$jP/;8)k5m4\$I$I$IR"eb-]|'5qi <`6 2kg`4p=*EWpp6bbϑ!Hf@JnݨR׼ Vvn WMҜI$I$Id[fmz$I$I$I$I$I$I$I$I$WsP{1< |L&VD"]Á@{;+H{K7_tKҞ[I2b%燔!l[`l1W*A[ΨV0#I걬LZukFh֡1Mr" JKXՋ YCz~VŘ&1)-I$IRm*O$I$I$I$I$Iiwۑ.1$(+1cnҼL==:]h?xIYiǫ$In%S8!I$I  0%VH>4U-"-^ƺ;&p߈j$N83%@Wbg"O\Rxܶ.T-FWt,Fu,`$mvܝ̮G& ;k//esV%uQm3uN70u|Xg,?v﹩nuiUMkWOJꚫ=JAuۚ>CzÐԅp^-яƱQm9O5M_Cr&+7C.ً!Ik야޾ Jk^F{߮}5KY1o5o3>k8$I?pz{KI$I$I$I$I$IR<̕*-\7~|^i4. ma|^;I[7'?G>gG~Koز7K.+7+QNaV}.?M7Jɞ7㨿Ы&_?b) ;?;8%؝S96=x]pMĈטjӸM'<=~חH=M7bحyx95^S$ ʒ$I$I$I$I$I$)V^Y1Fe-xiH&I$I$ITo7y( Uh ȭL`/h "8O 3Iگ"O̴'VZ׈\,EܚB%@IIŰHh۫}8߿tb`vӁ)~]=~ }cl*3;GuGBCLA<;Wu6}7gʊ16ai;q 8LyT8\I)T^-ӻphE=:rRe+>>gu%IR?WMhcH$I$I$I$I$IF/~T^^_AYyr>I$I$IgU|ܕ4rR`0I{#V(QQiG^389 {U7 &{NZ%!Oxzms l&V@ybkITOD"uy%-238\ uEFf<Q)ٳE&\)}.)O93#svOMYO<q F}sN $)#3O7H$#D8슁Q|؇>8m B`=BC$ RD$I$I$I$I$IP4!?;1ܺmy}9R*5Z4Ҳ*g֯oš;($I$I$IJܷ@>&o,K3> 'VPǨG5P|Mˀprv8s#^bOUmpкysng'͒G]W -ab֔0F9ق}[]hآ*VݚrGp꽬L}}ȌpCGP<7o,kR,]/`77e1VnU1%S,h4v[sN&}Zsgqױ2X[D"p});dCT\ܧ cK'Y/NI~$)'UE[!I$I$I$I$I$I^`_ʣU] f ԟrN{xʙ՗w>-5$I$I$IR5zkUi̔ \Wɱ( x6e8p-PՕ<ެ^Yg8G7+lHKc3\evZHԥZW5.'b$qM5wNA:bLX[|Θ&Q4|$#vwcswcc##XVn&g>r$-:5~8[}E ϐ:pySޛo3VvE s-ȡ6iC+zە۶pH$o>WkHI7Y|8Ƽ0:)ϐ :sWz%9 [7NCfꇳ8nkJpN> ؙs<:e2㫹,_kIt$Za$I$I$I$I$Ia7aY"Mø$I$I$IRM}V~88"lzY̟Y_K(۷}k}O /7졗 Y^ȉkяxbkLzgF_ϞgONNtOgq+øqG=vAq =O;ړîX%LgL[Šk(/+'A6Z6IFtמ'F~'NZ:4/Hv^ҟV2s<M_Fu4˧qwjLkpM8Cg>W$yaBE,ǐ$I$I$I$I$I$ku <f1N4I$I$I$Tq0h`vp-5/+ `G*S'Vp/7_*9M߉>8H uĞ{jc:QJ$TA1O]mEӗhcp^3얃8:`5|]}qR߄Y6gO]:c_~,#䍛?[qov<'?ϕ}IhO%G5jQ!4:~S_0N7Jpq5rĕ{DkÈgnnUqR2ljQ!chBZ5GF?Ui6w^?>cr2]<8-5nU1=-<2yfkdm8O9SL Ŷ{wG'3N"I$I$I$I$I$IR(J'4beb$I$I$I[nU Qg3˓\s&atv 8+8clxwCW"nwGPb_D<$IR-gv1~=Zb֗ԅo>|ΩF]&eϐ~KU?yfZukʑWSx=clT^W_"i8$v]ҸMf͛<G=woM[0gB85Fz7Ջ؅ϟɳ/O/S.a'V$}xc$I$I$I$I$I$I$Io۟wa? $I$IP RS^@#V$6{X9E2\LT";$b9!ٖl 3T+xI-&ܥlv_fv){IU)-)SMiIY{wW|sH L$Kؗq Wcu+gY"ߖ,xO ovsSb}4^VHF=$X&Tvr I$I$I$I$I$IR FH 1$I$I$IR*5rCXw83Cس2Ξ=o 03}}l{Ǚ[<M,<0؎Xɢ& nЏXIX qmIDyr~]*7 5S~xlR2|x4ˑ$I"(FUÐ$I$I$I$I$ImqgOyeI$I$Ihf&y&@83Be𷀳=K83ma5c-ҋ,t`hp9 8x(KksrLIꀣ-4 4[\|?D1c_Vqrcح.3x1v~bEKBG̻w~V[''^?45坣Q9=7Y6<&x.a_nR3EMگ ['5$IὪ[!I$I$I$I$I$I2"Efwz=4$I$I$~h+ < \4F_uO~A< 8{T+ 07⾱ O:+@YR=jEP+p5036n Vޱ&ˁG$LN~?zV lͽz'gw:'1M{߮#^/֮(k? 1/];-͛Y߄_Ohn#{r L礯[+=%o4ٱ VD?`ITweB\cH$I$I$I$I$Iй?┡H$I$I$aO,e'mx{ӫnX<p$*NܟZsre`?`WIb[El*Op/IT C'ˢs!'܌/2yf#]>0Dwȥo)\^R_{).ܲ3O ߴgeYg/rRfQ^:o΄ObD1བvoJIT~Q3C$I$I$I$I$IT8|[񌌵u4*(Ma*I$I$IT7Dd\pVOS`:,G#o3'q8 VMn c |pp0x1qX < (,I$)m 8|S?ɒY+BLT11lcy~iҫuf=Gh4{w 1ѯ./bsR.ŅY; IDATwS>h4@Lx#xiG- "Ij(or I$I$I$I$I$IRѨ\CXN53 $I$I$IR L!VQO4tp`$_`fh$ӗwr]@YC=w$}<mہY@"?Ϩb*$8|g2_3d_SffR㤾D"i)4q)S[)wd u֭_gONu%,n>!F4{X$cLhV8AybbF!%IHc9f4V3/}KXc%)٧hMIB&井KNr愘D$QykA-ǐ$I$I$I$I$I$I$I$I$m>ę%V:Q]`0$]M tXDh*myJ^z7LM B" ;`| M4`u 3E Ly n b_s{_G}ϐ$I@Nl!s]th{Rfώ  $̟KuM^[07E%%k]W+BLٹ!&$IcTScH$I$I$I$I$I$I$I$I9OpfR*4n]@m[иuM6Yyb܆9dfAZukx6'^Fy5)' R ˕Y$Ibفޟ$I$I$I$I$I$I$I$I$IJJn3w WdGb']t7`q1XEX`dԬcW }^RU6P ,2J*3(33 xX[=݈}= #~ u ?w}T;]Ŋ=vuF4V"b9$IiֱϖwU[1 5+QKcR5,Q]boPy9J}Ayu0 F7swVb|uCW/0y=Y$Iښ58SQHϷ44մ}( )MpVY`ףzgHrd;RQ\h- AQ 1$I-/%2o9$I$I$I$I$I$I$I$I$I1ˀǁq.&V8 `2gՏ Lb: 5 z 7U4RuYxW@4p;0Ӊ=<x]'%xNUCf< e-Y$Iښ5$vORl|fc/*,W/ZE9Ypo|4׋bjk Eq-%I(&2o9$I$I$I$I$I$I$I$I$I8XDew'xU|1+X^=8z|*Uq`v5]Ls[ ,:Aw\is+:ZU-@fmK )g(?$)-hѥ -$VQ4GM6Lhv\L~ݪtGV`[QCvHzgc\PxU&I$I$I$I$I$I$I$I$Ifz03@'$S`Lb5q{Y5/vU{kwM?Iw)l({oAP*ˉVŃУqσ p *2Df3i#Sm'~xOsu5I[H_IN`4_b,a5F8|\sT` < 9DDDNgQ!(SA>fE&NI1TMq)9cLXPpz29OѹDDDʙ`IDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDxkG# ?Tn%Vm;{UHL_EÁ8W=\?(n?ΩIثVᡞEDDtcYzB! Zd/6ij2n½\If>+VZ$3#b r()(z 4=_H8}"ch>;`f Uq .p)}p 'ρ~w~3wU\opMPWg ef\ǸYDDDN7QqU(ǭkȩN< ĥD{Q\6aN~_=))? %h:cHH|fx׵Taedcɋᴑo')6FNF1XV$[w's4;p󨝒C\bcJC=)XV$$K=b""JGt_iaj(J7g=ԍp/ ϓ+}!nMq?G| 8fLV?֯3Ωǂep*CDD$ȬaP4Q!`}źHz {M^C!IӉHe2ŗu 9]ʊ{grM)*jU;-Fjtsk(-[[_HVk RT\Ykn;LT6Q&Ln]E%;p4%y(.I /f"*24JٹLs&2 /EuˬZ^Outnvyɵ1l۝fyԬ~=Ѿ O:b7{ ;+NgI5h/khp #_Ax3ӊ`bDS&2]1*8s\GQjw rn[ L7gU \JU+%&p.ǵpkTIcv=~}_H:^]̈́iL (q-rjp\/u&RSdd""""Rcqe1DDDDDDDDDDDDDDDDDDDDDDN7,h#9 EM89v> 5y??M~I` wZ {*Ƅص=ֻ\bo˞WdNu%>wW0>? 9aeQbGmrؽ~] 6&:5п׏kv+Rgֳ9uޭ/Mž= ]ʯk3ZA&Mm1L9}PNq3{7rؕq~YMfsE HI*tM*s~#})uZҘ¢:;FffrƔ<̡רN6kGdeG0a{%ֺF5,[rM؝GE1qac^c O'o{"""""""r .}nBq n cke XGϲD١A:7`"+@;klܡߛ4?q|:xcӅj]19-6$26¤IDBg=iqw?: ?EDDDgo1.ϗ 9])"NrlػhuWVnL6s0e^0Ygdm8yp*uZyly#\0ٲ3[vNBr7/^ Ei0{^Q#`\K4}}ː7l@6nGi{w5ٸW"-vٲn{^eKߒŒ97p$s.W`/lMٰ7m#5}YoMVHIqq6nÃ/\s_}գg'Le0H"0L-= ^""""""""n퀏ܣ#p6q*p`B1 _\3PkWc`ʡ&p0cpM.>p]U`k 5q?^ϸ@P"""r:).pnQV_3YmCXD*[-!՚__` V""""""""""""""""""""""U]=7^Þ#qY6MEyQ:-Uʸ̻ Xm\y@ kRlw0{wN}4;.b{~>_ɱsnP<{'f&+7~=L 1~8Kh홓ۃw>y3:. ؾ2az?VoRgB.vfystЋh =m~p?NW{`7wI<ӉV?y tB  G'p5 ca -}-VrnUI OIsUj}_~3gS3.\sB=$$a ^GF4w"N9Wwb_r`Q>H'>Nw얯㯽WW3csSTf0{qve@1sزN7&L˔q:l9^7?<~_k+Y99`?YRƽROlۓ{5Ǖ:Y_L/{K=_}3%weSOaGq/+YfR7 k=o9^،_AaQZz:5LgzV|y22G,/Lt9)z?<_k%@ ϼ}xY5epy/9y ; cGxsLa1<ֿ9lBpHyMά FsiY-"""""""r ؁;c60h8m8  """̽ޅcW1i1*kwLYO H GJDYX&'*@""""""""""""""""""""""U0k8JSb1aA}*{ݱcgHޛ^7}qp 3o+{wH\PB&Ӿ}kz#xk(,jnV7EM+0I/c. pLt2smJ`ռl`NW Ԍz oNl1kYs@r _daO׀4܏we&3k`e9]ڑU}1B^&L, >׻K6|i4""""RE|6q EDDDDDDDDDDDDDDDDDDDDD$4~\֐>*1멖NJ.bsXs4Dzڒɋ:990-p?~Yo^zR5ؕ$o 9a?qyđS&dv5pc0y"w}fGBn^YuiA=Ng=)LijUZ&NK&U9]iNT{"+;^xQ|4MrqۂK U?BNw{zMJ$mvOk"b7Ȉ,RK#/%ҔregwkϿw/9n]$'y'y Y-?\LƁKp`aW{?zo38Oh`읃oޑ?8+!3""""""""U xxC]@x xCݽ&!݁W5T/jg= {n@ se@E'G[pƩ\&3WGg~_]8F|#).2i"#8K ^SY51q(lVO|-ʿ\,ۖT|6?1DDDDDDDDDDDDDDDDDDDDDDR)3a>"b{oŃiSXvӾ}{~kf[NjT%0~Xz?.w2iXwͥyr[ܘE+r2{2az|g+jX4[.{u ۬..%ҙ6?@6qg<Ub| 7]#Οyl~Ngu31˟¥5a^MrL*4A-+6\Dfvz<2̀^;!27Tkѽs\wbCmfҦ{,Y-=FAask\HhwIņ8P͚6@U$>57~""""""""R)3@ je TzSo n>^h0cZ\S | 4W Xm0{ӏ[_#"""b#;O\uϗ@WY 2RZ$cav2~P(a0kHgb0ɫ5&M#""""Up ʼn#ɳw2T[Gc}QqޜzO~^7_R+5aҗo}4^II EZ\zd@X3f~'լz y5C0Gp: 7Hnշ7S8o )~%/b̨g }fuqUӲ|6YJ<O-9oxC1SOc+{#=+xXhJ`4lpa`":a\Sm(8dͻqsB=HUryy](#6˫pCQm͐!26K>Y"?;kH[@lp `/iʸPmdn4sbz3;I_, iH;(Kפ>Jk.co>]2r Ͻ;YN3ӰV5SԚ3x~lZ?1? BaQUu1ky'){w=)_4ZGiu&~~!coFM'9u2{EѮ_۞p[t0Zl(HN hZؾ7/9VKi &r/ UZ ̏ (.Ìo;rUהAݼ oO`gRӟ"""""""r:HR܁ |;p&WQ@Ea^ < po'0?ˁsqe{XX~NO`$ψC .b}R׼)}V1axR[_K Y Nd w2pLO :":!4^7$}j'+. ?#SDDDD|=Ha &""""""""""""""""""""""BN |8R+X]90C睌,N uO:xvys`ڸ͖ؕm‚3f|{!Ng  ;sfwwCbRoiV`(@5xp/[R1pU\ <12fznm8N-52o/EGpsC֡36 GK`aVi`DkzfÛSޅiJ|\"&""""Aq꼱EDDDDDDDDDDDDDDDDDDDDDD|>ڦ&pV=V/^6T[ͻ/XSOp4\Iy~ s2牊yNjlwry~7jf GugUӶx:0|!goa<֕:l^/(p}ԩg֭ϕCa3ͫpXl6.vw]=+`;9Loͳwb=_.};cac\vX~%<0꩓WXVO^-N@\@y` 1(F&az Bܡ_~~Np`'s)@;`H\߇;DЌS%<c"""⓼zMImYĉk~3£*`K5HOan1kljM!MbQ4p _q~WaD? #f^?}q1DDDDDDDDDDDDDDDDDDDDDD*pGEb5_|/\JbCﻂ-)T3wѵffa/,ZR}vf 1mJmp}T6zjwm{^G-O%=%1s=Q5𹟈Hp>v]|3O]6y#^gKD"5;I3OZ*Cua\{ahxiWf|BlLB%Ɨм Ҙis|:3Et~XG@gƤSuhPmJҏ+3ج.:b6_:3bOukMgyk>[INpm)"""""""bC1V`5#@e5=,󈂡70 <$Xh4[: }'tT6xzlݼڞiύXLȀ1tޢšssj5bכ+0\_#C8Q)=:)F]4I"l(=EDDDkϸPJVPmdnf{ x(ƛ6KZ0+Pmx!nSX-Eis:`okaϩdW -`UC~fXfÙαvpõ q񠵦ѻ˻돏gi{un6UCPtcaf˱werESwV_=x 9Y0X;Ì3p皰q<$78QS& }o\,6kcN;@3SW! x1sTe.2~yEʹdjjD '1,|sUexoOw=F%)5oWkӤiVC- =I [27g\ݞF$Xm8U}i&MR[% -'zOcTVCQq]C ~2ui_s:텦*|y:8O |Nb|)4oMoMW+֎4Tg3'ƗP`8`&Տy+^5P7Af+6Pwo9O; dl: $ہi@kp?CqTUW#Y`wʖ|ßk4N;UXsh1Vd ) KxXv#_/䶏.᥌jVʹ߿+%-6eF 7ßb Z?_%Ԍ康˹2"^u/w ; l]ǫv5j s:ds\31DDDDDDDDDDDDDDDDDDDDDD*.0Tg8vWYFXw|;xSޚ9#CK1k<}>[&9vcUKMMȭGsʦN%As¢4n5=שּ˥2eZ61c^7j3?fعcx> c>c՚ ftOErojWɟ"""""""RYE ˩<]ph?>lo,5`֩,5&ƛ@sL;d Ҍf}+@=kO⾏o;(ԃTUӹ,zY3\ֳ]/mmDsI,5',g=X~ =F-}B-xb-;)33-cr?%,|a n< /iݿ -#y: ZθIӜFdz^.(DDDD7,ύc3*CDDDDDDDDDDDDDDDDDDDDD2Z:9ΆjcVV/ 4cocTF-k i ׮|=Ƒކ RHC Z-E d[~6 Z͏f;.P1EAa =ڛZ-Ŧdxr0TMM-CuyYӮ u]{;1DDDDDDDDĘo@rn^ro'$p ߨ<{oI񊁚q\D7PUZ`!0A,N|^|#\?WDDDLѽsڟg6vL-8!Iu5qЅO1OC a 3Ԃ">wY;)XmnRj4I>RScKV?p5ʨInTOVzU_I20ФiA!M!""""byi:[DDDDDDDDDDDDDDDDDDDDDD*}1xAZ՚ohL(x,lCiiZ~5y".mi5TNͤ"':QTvz&+xQV"#ҷPz.WZՖ<͟4[m?dfkMٻ[=hNuSf12G Jq B&Y ^e0P7ƄZ1G4[MA`0hk`M ,2m:?LbRP!""Rُp 3h~Sys0*T'\GRˤդ|/uÌ&^Ӫ:].jiDn 'b7[ [ìZm9lVnڟbƑ]Y,!0WgωR}C'&]6`tξ͜o: o/ 2eo cѸ5kscT6kVB "]2y?ƔP˷ ۘx)gw[=`E;KTج7K'P_ շDC} ||]#wNfr:/8^SV8HepyWػJ[v0q:i<#)q,!n]PݡcvmzPJ~[Nӷ9$QcYR\pԚ{N6oMdDJVrrX8'Sxj3sCQ!Qe !"""""""p5rnL pbu +CM8p}pTlV IDATfG!)pгU-@u@s9 ˬE*>“CDD[4y =b^״%7Qu`9wt_knU+C? (`VgHjKsݝ{87/FqK"m0acf_E}na6ɿoE߆'ݾe`} ?V'raVFwwq {)%.%Qgñx7_""""b>=n6%)x0u;Ѭ; Y Z_;a)9uNP{;#|ݷ:OYbcJGQq>EDx +6a@4J뻴 kB&|A0|3&*wbcJMDQ{lp`ኺ֤?3 9q!"""""""rA$Vv`I?UPSfBW^jn}?x0 0GW9wo ZDx0b`&ԤD^>1ԳTe{!%C ;շp*f= Gvf iX 4Uc}Q^1ϨF'Dpm[#gpgcY k~S [YZ݆=z[4%iOBͲ9Ȥ[S i\7~.߈]c#?#HZ Ə+n%)ջKI: oAi*漸;]ۺtbNжc}y)׼y܅<ֽvѧ{ҼK#c ͕ 6oٿH~7m\Ռdu=i=UX{$#?o$<ܼ V̔]ěN3ȃfP8HekokA&NR-ws ;#ud?.oem7Q&Wk WEFZRz4Kt^c@DIy 5mQI[89(=۾ϗSIcj7lkp A;LiDGi'jg?<ԍ wEyoy%2>hzTM?2'w$*6ppbpM60xc\">8ǵ Kkʾ}~1By<&~v͝8'+ؿ0.WRI;>άWZ"?W~_v|f?ZڜCf fLKFM-_,V 4Ð渜.b#gRd/&<2Hj4NV"+>5p>_ E?%SU2آ#iݿI@ SXeaswtrPEE"8nsG7o:~gے=w4DEP+A˾ I;~3{sxavN5vN8(nF$j4N&3׭nD#h\np $}K G iYَ(n’iPwsi4õVkZ6qky=pmLW$6f/Y)i]Ā9O:&l<{õ1רzuqyv?c ~pHO3ZhMmDRN2+ܖnr~u=fx>`uw`PoqzcXq+k0}w1RCP!""r:>6?OXBQam[Vt57aTR$zrX0~9Ov{'h+܄u._?]oDKx}MGwAusS]_?Yi;eTwٻ64,X5s33c{Bs9kkCF L24n'sDGm0S7G̑!k7&&9{u%+EC⢂ں :'Qe]tjp- (N c@sg!e1# ~ok0K60@^\ǫ`|9E7 |ݪx`#"`  |M(^{;<9DDDNcXKwv,~YwfF. /zݲOֳk>&؞<]~x7Ur1x;e))ynjD;2y~nNGp^*fڅ"""""`ylp $pmTTb ^Pds:Ub8I"8J ת笜Jg8]Q3-;}lmdj)]c SlCIloDe>hzBCu.IDDDDDDDDr9dgp7{8_z9{;F /Wg߁kMN$&1n2@! mx~6 ?AY}UEL'Xq~ SǓ6Szl[Laʝ)YcY pY-b; ݝW}nD⏒B:gٴy2i)=.?ٚlz{ *pTPb;x<G.HQ"{hJ }7wvgu]{ y&2~$IgoIb5+_J$I$I$I$I$I6%swgdMx8F{::t m*UX WiS{ͪLd%'U?zڂWE SswIkS="j!16Ic$I$).7 3]Q5 @_>G;ln!p;p\uPvRW.BWO dCFZ"KC&EIR/&AC$H^!ܿ[潟ޅ}?3vGdXj*O2+^G6//r9k_yM.=fZ6+esW]&pؗὟٕѕ^ec/i|[:{=W>ҝi.9㪏ߧvY3~ecQfCn/}5\z-ܲh9}O]??hrEK4in=OoS(ly|_^fE&ǚ[%e"LoEz_$IYr~~n$I$I$I$I$I"Ny6Cި3R}eџ`R^#*zXzMrfZ]DhK ΚݯQ"QWKYi|d +N1rxק<0f$I$INVd*ʀ'3{٧f <5Oؾ9=ԕ_yr5=_|lp5w9D] A$.`^; r7OEh G3ǏfR3mڂmƔ1~ьj4eLfW6⵵xm-o^/qmǩiC+}}-)Ȇ?:-|62~]_?s7[?]> [9;c܌$K:'ݙf ,{y5_]k/blO-.%G KOW>?;6SZ+㱫'gOfm\ĝ?GϚXv 5iOb̴XM"{&aòV/XϊW2"6,kr޺_gZQzpx{~d#Hh=r ǐ$I$I$I$I$IIWU40vt~A ޚ:`1o#l;u1fƌjpE]}9ڎE+gCִM}"t5LLIhƉyoUI$Iq"p 0m}l87*@ l QJa/]Ij7-9~ 0pgy&$Pfps~ Aݜ:p,ٿʁ&<_DZKfpl"4#IUT1rڵ"F$I`!+8m ǐ$I$I$I$I$IeT^1c$3=m^ّf$3Nӽ86&#cZj } Q"ՙG8F2dIkCS)tUpYWwxFg*?["I$Il a``Mg #@"e'@{>Cd:B>oo.C׉&cc%Tm5ieF L <0/~xl`$Ib4q۱$]}sM 3&I|AC/Mʿ-(2ڒDl2p߃sIkA$I$ [}:AN&.nzlgȆS\Fs 1x%|Е p;zc9pS5 ;ldM 1r05,*9)O M' "WQ{Iﵒ$I*dB:a"F$I k7Jl.I$I$I$I$I$IC:;@MuɒvHզذI7ۏC^A%mN,[Yōw)qR4&ɡs1dJij.*#I$I4mX7lp n&Е׀RU.jvcݞ2 \_^ 3wU跹ud?OF86s9iNMR2IO"p%IAkwf;Zژuܘ&Ҹv'J$I%ߊ{$I$I$I$I$I43 M&;џdx^xb/`h] .ͱ+@ Gys:ǯ-@-1  .;}.vlaCddCJB,~xCkAjj>R$iH$ir 8it*S74$I*1$I$I$I$I$I$i IW&*Ge7{x^=4=wo,|^T/^G?jDSKyMf2=' -q I$IQ 8tn@a1i _ lK1 N3>@x g+#v)M;YX IDAT_CU1y8pp+HZCKܳH$Gm[8FR9}ϠϺO$I$iHIygv=!I$I$I$I$I$IG2\ɔљ40L"6?d+dK٤*E6"ltz\ɤÂ*_˟.LTőHtɔdEy~ë^zؒ)aq I$IZ`._fs\ͱW"y3ـӁdZ q9.@͵* up$_v<;K!jj[O''H?T=$IR|z6 8k {/Y$ItᵜdC$I$I$I$I$I4P$LSMz' Drc16ۇ } ksvQM0@~zҝM#I$INdfo6|ͱ >;xE ks9cWpnOv!{]n2p&s֐ i-4$uZ{4 =$IR Y!}7oCj_ ^{xQI$I,Uqvɸ$I$I$I$I$I$J&ۼ)ްTa%yL7|6#0d0+8$3t%) M$RqM0@"~—SM"I$I w8``O, ƘD6+-5n$Q`䲪67?]<p30PC r{גn:p0cH/{IbgԚK$|6Hwa*I$IAqvq I$I$I$I$I$I T/@9ލ3y$D%q@tfEO8F>a*Y!*Jy74rF1w-pXl{=8$I$Ip:Pͱn@MsCׇ>B6>\Q$p4p#8q6ERTWZ`LF$IBa|QבIG&|x(K_ZL$I޸؜$I$I$I$I$I4P5Uw8FxҒ)6kRhEYZD*5*6=H1ʥqЭ34GHCiI#j3l::{N{p*U$$I$Iz$ǟ* U@͖X.&uSs6{ qx._'yln~̍h.I};jϠS)EM$INW|V:R(-/s;gP/?,$I o=GWq I$I$I$I$I$I T5>6v "$Zek32RDtٗ?q  /vDGIICpm*d*UFLw~"I$I44qb R@jv _ڵq1$I$I$I$I$I$ig8Fs[|wt&dJ+p HOhIٗ?ta.C}kXħ$01ҕEYj$I$IRq|㫊2Eaa KZo70*3rc&Iv_\$IŴá+_?=QSjޤrϞ}O9+凮g㪦>$I4$\w5 E#I$I$I$I$I$IRF/@*IzbU~++ __rF81lXh*ڧOYYڙ$=.~Diij;S?$I$Ixg1(55 Q(*.k)74 p1pk)i*v3Y$I| >?-*o^βWRߖ܊L{28݁N!H^9 Y߆$IaQ|-!r1C$I$I$I$I$IUaTDlz֟^W}cb~XE:' .,C]SSx(KM'טQ],[Qpk{'$I$InA6\D`R|6Uz`V@],#MxcW?>.`c EX0Ƴ)@-cH֕>{I޺[8oxgh\8dE%w$I2! ǐ$I$I$I$I$IbypmK넞"R޻t#k:"&Zo]G2\RPHm~,^OP~mpxM_ܯD1̄1t!I$IRǁO%T_)`15;_1]Xw}U xk~e6\@T >ɻ_KwG{}${IZ*ڝ=CeòW{_>$I) 3ϔ=$I$I$I$I$I$)ëPp@Pms۔^cʊNRim*-ڋnۭ#l"_v20/ڡe%a/nۯvɄNM]Cצ:R͖o؜Tj$u97$I$IC(׀i_.Z ;PsLӍO6 7{B`A/z ȆL < 5($wLj;+v{IJw{ޓ©L`ǵ-==[ۘ{^u*- C$IА!q??9B!I$I$I$I$I$IɤqX*-<К&׎1`~[cxOoհ46T[Q~2ʊ'Ț*ʗ6# w>⺿m$Pro\*I$I4 m  I 0CH`'+a-63iSx:ϐ xkV_%{&p4@\'JM%Io,.ØX&n; c؈ ʫ(*rֆv/gYEs,yi5t1-I$Iӭp"cH$I$I$I$I$I@vUgj,+V cĐ =S&$1jTڶEӼ ; 5pS#fJe1**љ*T{#8eĔ$3TƦ}ZI$I$ &^q6 YBB dc8 lso;9@c `:piפ]IWmzHGos?k/I\8 h"P)Iq] 떲੥9FUR^UFg['t EP$I8M"_cH$I$I$I$I$I@+zR f͝_|[jm!{4/ic,x+35lv7*IctEXmcVù7-ӻ=~?}.)@. .-MoS|l[\! Ȇلc4=TjTǿ~lU}'$I$I[q{b M!GW%ΞKe?XM6a/ ںn^!ڴM'.~G*`C!nWkUHdGpmkGUS{{1fL Leނ.i|۸]l朩91KW!I$I!wD p9pmZ`6`y@a++.eJ` `+zƘ ^$l@5cԓ cH p-|59$I$I$iU>a8$I$I$I$I$I$ 4=\~öNҵK{tibAp!OΪ;:*+զ3<6{ZtHu{| cFqK&ڃkۊXQp#|D"|ޯu ݯGVG8׮+RfcG?s$]{mr_ISR\^qE/#k:z%~ݯ M?_&jŠ0߿\sDŽ?꽲TP]c$I$ eYd`:c`ݦǗNl@_=XwFzd1&<Ȑ'P'Zu$Q>/ P\4E5o:|#=$I$I$ B72X$I$I$I$I$IZNEڶi[0*ީqנD#x6ig);QP4ƒƷ>v3f׮X[@5y|ؗ4It*6pwjhLq^]_{]3w&(ZLf{G8In=?sEdhh +LD;$I$Iu p(7pEͅ5\tf`ݮd m?`. c ?70OxlPA>5 '2p;pD9d_7aaC-FF|x*Y$I$I$iYFK܃EiH$I$I$I$I$IzaXꄀ?7o@3t:SaT=IOTSG*sPK} J"Lnw~cTTlmh*N`E]}9m[v)-;5u[y<{&ڡy@ڥ+|=9_;%%u|lP+1.cnljwׇojmI$IzW.'s {?#/H$I$I$I$I$Iz7#_'y9/vNR|'0r3*p!EKMu'<T9.m֋hmҒҢQ5+k[!9{vL6𢡊:奡$#smrX8k;ﶒU/7K}CYۓg3U dJ{oe٪Zv\֕$I$0~P=큵'Ы7rm5ہˁ=3K;NT#V0b|‚1Ӂ`0]M$=$I$I$]2;9 p I$I$I$I$I$Io1*o܏uOrmXC2G>47}a}m< =<E8;=\ЃxϢt'04?GD8ImO[{7/ڌiak:=[yoӼ۪u<͖33Um8FcKU#7j]I$I 6`a5 }앯029u kGÁ#col*dcls@Od| hpFIz+]X X,$I$I4@:~b8$I$I$I$I$I$ TӧTTr]xim&v3*lCC$Rn}s}''< *+]::;WG~.! BXMo V>p"=w|8u'{|fHv"N4g^4 y|¸^+I$I [m )ϣk_>:y|cg Fg c nv%(ATtv.p4,$I$I4IQ}r-)1$I$I$I$I$I$i:{))]OO<L`eHgI͌@pEǑJ'"3¥daAuE< 646F>g#Flb~pҕGE8 ~δwNGO>t>*Ϝs|d[0룽<T[p 3g'?ؑjdMGPJ:ջMJ<>}$I4t 2O`5,U,'! \MpXzdk+oWoݔVO8x9$)WST t$I$I$IzeiJ?~TÆcH$I$I$I$I$I@5qL+&TԺݻsd[05 =r&wId{\Һ=H>5{뤏=Dyʠ^?1QS2z m[rCG6˜W>zZvÑ̜=u'ɔGvo}I$B6Jp'D>mƆsǰPNykҙ 74NX2P$I$iK$+#.j''w+Ҍu_lg: 70*3@-I*k8g$I$I~!Ik9wY܃$I$I$I$I$I4}'l}#{O$T{О7F6GC^_< eaŪa}=)hިJ mlڋsfy?4g;Y88}V^O <%=Z#Qο!_{I$I$IRWr q1$I$I$I$I$I$i = l9%,ly0sTXPs >C7׶ootJA_ЙFv٘gcF1m6n>`Jo_㎺dǺL{;T:acӾ9kvOW*W[4-w؈-I$I*I6䢷?,9}.V־8zR]LFg i${ |?$)P{1dH$I$IR7_;"*cH$I$I$I$I$I@w 7SQ,vsX`S[d2=&m| ֻ?;ا|%~o%7|5?ǒfvغrDMu'zIPmK۶|A66`ln*x߸$3L@g$x| ?~7IFII}ps~޲ } 0قWo-N45/xR$s<[5oqqD_ X6p珥}jǍy#$I$I81 \s\;Ԝ Cw0zI`L4GwXNsV^l:I^!͸$I$I~Wso"JcH$I$I$I$I$I@7=w]Pmgj,Whj.)Htw bʍ<<¹v.}7'aM}Z>ѧYdP';)XG˯%gMu+|c ֳ?9࿓H;'qٍQP-;ILtz8{Oз/TV9]o_nh\sہ`i {<ҙʜu{J0&, [Wm^>rvZO$Iߍ`DfP3\~v9lPEi{ve8u`r0`pPp` XpTLp/ɆH$I$I&vrCDp I$I$I$I$I$I N9)Ǝ/vcJ'k>Β V:_:1|eGәJ{\#J'?ǜ%ygI"·wKkF]DIrcuL)?{>w5_oai! a.C$ˀ]lީ8xl1lY黥;Ȇi\K6|DMC$I$Ib6 NOַ_d$I$I$I$I$IRa\r$K'=n*~~ew!ջyN[J'O''-xtWsttشWklf=֝ncWư58ȃK2{meʹg[ٕysҒ Ǻcv]c].Wv^>!ߐLN?|WϽJY7v@1I\sd2eyWQCJ9Wrbl<'Lzo.=uKWP]=?g3c msݻ >q-ın`]V第o(;9˚w?>ɇ52μ&y{刺?EN N3}u|)+M_ٍG::'K&939`/?4NYS߰U8d9okO ;rz!I&9"}1rB V.σuOc/<o}E^3JrW=$Ir2ہ{ǁ@3%ЀO64p ouh}kp~{B_tql!'s(18 8 xFa1}%i; =$I$I$8Ԯ{b]I$I$I$I$I$IR7uR3G#|B2_^;_>8%L<8,ZZwk.,H0 ;SpPALfC{|Y{:v2ͼذ?޷?|0ekQOeE լ0Ug톃:%8?ջ35.oF8o-mSpBnLs^>K+&rǃfQzPZ/~⒠uw[O̒뼶i3bf=8n>zTw qㇳxtuYմ=bo.w{dsѕ㎚KΊqVU᏷|3yBFSVq8+NcX_w0tM+UWiѻb7_I-;o`x3ȦT]IJOU;$I$>hs~ lF0D{ pD.1[euPr^"a\ .jVf  y cl'p0\u XQ.I H Rߓ$I$IT,7b C$I$I$I$I$IWW禀d ]xuxޜ:?Ne?w,o,eGPX[>?}ӞઉXrpFXfgt뺾f/G.=YP`~Uz}wǮ}K xϾAw[;Kܟ5B"o\=i$Xv /ȬӢ"Z6"**r)Ў=))ݖ Z|QbڣkȶQgvVP ɚ}YS/TDN ] dQ޼4:RvGһ*Z,`gykA>X;K507{}JYI[^}sYp\ƿ7 z;%I$I; l l= .ߙ/~OPb6K J٨n.3 r_@?<'=a@u πk ^*|~v5lQ&Da*K3 HGUd%I$I$IrrMן.Y!I$I$I$I$I$Igp^zMIiw>l}O PA"QN:W];d9j1 jPDPsݶzN~Ϋ .?w\; *zxP.995ʱS;rmڴ;|V**QߖUT/E}.?s9r+jtnڵ)Sswq\\gTny/硉nz]i6,YvK7%멨hN~[6rYjVٷsUl9ZyTt:Ͼ }}j}u$I$if =7M }g*:,zGfm U54 sc}9߀cB9xpe-+跅"'̐7F́!X lx)w&rIRT3 2$I$I$5r$)!ϲd$I$I$I$I$I$I$5!YbnWOƦcFt%?Z6ϘwYб} c]IVxt:=0Gk|vCuK88 uvfNr-q}vX]ggV׀_r+I&y;>Ʌ=Q'ge;` Ҽهڧ5-hƞ5V-kwp*:_oj#NzgH$IRgG]A)F}78clTNPƱ$V58#@bY8rXH\' B~ \ wKt1&S WHPa1$mFn.,J$I$IIw/Wύ;H,ǐ$I$I$I$I$IdOMӧ\}mfWүrpK#=E+ڴƒluW_+:w|OMӻ==:7k\~rgD$rsVr‘ctϪ^A2Y9ڽgQkl(ڶ]gw:oGUsvض sk9 '$I$~pKヿܯY5pn6e8'Le9%P FW>p[/C}JJZoa]c^"7hBYHtFDNRCb$ $I$I$!)Nյhf9$I$I$I$I$I$5f?=5ZHlcs{2yCi;t_k' g?i)OmDb۾ȡ%opo9-T9賻uCەtTuV_#ycHJKi:νWe5%Y:.;$IR#rp'P^ 6~v-M 3V_s757 9 >j0C5_SI҈rIR5L-I$I$IjHJ qJf>C$I$I$I$I$>CR]+,ڛ>YvEwEUh:栽}D1K;ܣXz%ۅXyz8`{cXfU9HӼBvzc{=vZu6.Yƹ>naڳ.~ /vKH>BYIc9ЗcmI<,\r$kEEy5y_U8tc-)+,ǐ$Iʶs2,'?Z!݂m'2}_$(4>2}7 +LPQ| |APP&(LQ`3 7䚷ۀ?35'x6,$I$IzHPR_X!I$I$I$I$ITX!)n>m;))mEIik䐟B:;}^wFo[ƻzM'KR^O~Z4[K +mX7(-mEqIreӺ )v (AG]=_NiikJZuhm:¾>EqGS/PTܞ4ok }v,v!˾qƹwNץ If- ?EaQ;JZQZ}m:|Nm_㎫XywVLYYK+-!?~.?} ),ǐ$I¿C79$`>+~KYImw#3̼] 9\rMx>cIR8n_Ά']"I$I$I%{I=wr I$I$I$I$Ir I$mtKjűY'P$Ij,ǐ$I 7neC&w}00;c̠k . xx7\ N;$I$I$mAa~8ԿR$ I$I$I$I$I$I$inhzuI$I$I6g0 ). _E!Dt>}1gз&K +ta1$~~2afY$I$I$i3֤Ie1Y!I$I$I$I$I$I$'1: {II$IlFoaf 05~ݲj &9 3 `$ Hˣ 'IɤGi5,$I$IQSzVA+1$I$I$I$I$I$Icm8$I$IjrIEDe 3yEL-3C'(yxqN5L`=`2PaFIR =Đ.NEFeq$I$I-2|IC$I$I$I$I$I$IOJ:gI&Je1$I$IRuF/">o!0#>9HU=̰5[< Xc7x2Hꉩ&CXH$I$I˼pq,ǐ$I$I$I$I$I$Iꋲ I+:d_JN2]$I$IR XLz9upvR"7`q3< ̯Fk}H&\!,\Q$EqOW~%`$I$ITNOX!I$I$I$I$I$I$F:qEO $I$IjcylM^1wxʁC̍"{ J297\\@`p 2Ě9)`eT$IucwI$I$IMƣAUqi(,ǐ$I$I$I$I$I$I?.\VDD$I$5LlbE#4* IDAT ӄ3[G`]c"0Xa7#'O Հ5o/ k*A`!Œ:6+``jY$I$I$5ni';KCb9$I$I$I$I$I$IT_|bPs];-8$I$IjX% C D_O3C#̰ bntj~-0 ؃EYW E f 'p%%a$PwI$I$IN?)Rqihr I$I$I$I$I$I$?fa\frWĒgB c~I$I$IRð#p1pЪ7a@kwVGKTOM%5 ![y$I$I$5 igOe}qiq$I$I$I$I$I$Ib}ڢX[KuE1rsfϝ4$I$;cl4&KYrPap`3B̍0 E$`'f1F8u{8p!b Ijr{&๸H$I$Ij&Hf1FX!I$I$I$I$I$I$m*cμu~ņڵ}H$I`f8P50=bTkk<(cKʀ}^,eiHZa< G xX)^;IwLʯI$I$ITm xկƝC$I$I$I$I$I$IPg}wPs=y%$$I$@aSE}B3:˹6&Ž 0kf.X`pkQ$`G4`v-o)]!lv!7$$ HŝG$I$IR2= N#$ $I$I$I$I$I$Iw-Yֿ\3~pp8J$I~ \l W~~ΰǐuQZ3L/3kWr|M'n u8c5lvFNhLaTwފ;$I$IzWV@80$I$I$I$I$I$IwX}eyz/%\ֳuQ$$I$I + <L=򁋲ܜyfxXa pZ x>̳qn@S~O8x BHӼvZ( _&ŝE$I$IR6 ?.` qil,ǐ$I$I$I$I$I$I=xo7B'$I$Iȩǁ*SJq.в92y*f(n 17HT|K.{G!r46mQ""`:0 (Ij HO!5*AT`my$I$I$+%g*1C$I$I$I$I$I$Iڜ9+ݕ/Wq.7g' ~I$I5T>ʓ 3af`B1Ծ0*wfM__la?*MM/z6b͗nfbIe ^;$I$I4^~~Y+1$I$I$I$I$I$IYPOysH2mڴyI$IT"( l401( aJ 8. +&c6y< ׬kr kSvg5 @k\۞niT$I* Q:FR:8$I$IQ&qTRLqi,ǐ$I$I$I$I$I$I6//  d3bDE$I$E- <N6-<hӁfv~absc2J` \ ʷ_N-}#﷩N^&xr3~ LGQs &1$<$I$I{pT1 M$I$I$I$I$I$IҖZ{weP${3Bݺz3@I$IfH%M$I$I0[qi*$I$I$I$I$I$IT޿^V|qv~:+dm1HVϗ$I$Iua{X3!*fa/?87rR`*, َD3`m50Ib7+VO!52H$I$IzH. 5l:)C$I$I$I$I$I$IJ:?~؇ ]ÜΡn]-I$IB`9Юk"Tf3lt0S< Uuo#@I6C5  OBy8Hˣ 'IRmL!r%H$I$Iʞ4wr I$I$I$I$I$I$)t:/d賻ht WҜ)hjMqwjt$I$I `60ȭcgn1s*%0CW{Ho0`."p aM071J?eSHJVǝG$I$IRO>SHw?D$I$I$I$I$I$I5k/Ksbγm:r+g"w2z~@"Ym[a9wg1$I$IqU<_BP.PvbM<ZLsReh 3#EY:ppmu`"(K.b{5k?7 "%IRSIM>ȥसI$I$I0b*ԅ%I$I$I$I$IۮڕdncH$597O=/D 99kYCn|[RZ5-jgx(zX[l$5uvY1$IR\bS_wGgNrx$̗@q9<` sw%Y@ˀ$`E-n^bk22\$b8㏇/$I$It@KsyaaɸH$I$I$I$I$I$I Z:݌ :S\қu{SX;%jQ3Ob I$I/lg<Q040g[Y Ln> yV!׬ %Ē$;Te.ǝG$I$Ij'{IEMՐ$I$I$I$I$I$IÛ:33[4w;'~NΓ$)C$_ :g;/'sU0@w`1WLl{({V"`A1m,&(Fy/n!օ$9NP>86,$I$IRT;Zn'.021$I$I$I$I$I1$IkxɃXV@:,gh>;| 1_ q0S>0\_+"A jYF?rCYMP"I`WA00,$I$IRJ qqQ̈́$I$I$I$I$I$I$I$I< "}6Zc 4WsbB /f% ii"aIgC W1 ! 01$Iʪ)\TR'IŝG$I$Ij>IȞp $I$I$I$I$I$I$I$IjL/]QB=F*bL]J2tDP'2(b}_L4k'IR3t; #/#I$I$5+ qg*)RqR$ I$I$I$I$I$ණv%-$I$IJt>#]w ITriYܳ8x:{n* d9pSDN03B |Ab\l v+uD$IRr}2/Mh+I$I$I(N4\W@jUa=ɸH$I$I$I$I$I$I$I$IYHv1eyߍ*!.r#L!YC$I$),ǐ$)#pf>_^kK @oT?bF= sFp>˄+ Yn<$I$IR$ɫpuT?T=;$I$I$I$I$I$I$I$IT C7'h\A!ľ!nn> 17&>97haf O*6x6pp=Ma 0xps3MIhL'eTAb44L$I$IRTafJjM$I$I$I$I$I$I$I$Ij~FPZccSo=8,ljb$`ۈ"\|>8x xP†mD/ k* .k~+fY8F7gYMP2\X Jv^Zp _ ^ٵNp5cB)n"P$ s+e'&B`Ϝ$I$I@%O e wΣ-yD$I$I$I$I$I$I$I$I<Ǩy1@=T~u#;Mǖ$J,p/&L;7Y<߽}N7y>կTw\A}N,j@[;M_{]؍X WP=x;zv[tuNu؈VoZUέQ7[=z6콡?;A Os>[ݥAծVGWh{ ߑWUߜ\^vuqjꙪO{j]o?Yݣa?1.gwݫUwކ9چqT'U';Ʉc|!uS6t}}UYuu5oi1Yݻ:n9 a"k:?tGM5zLuU \U]7j@[;M_{tP^U=oݫTm- w IDATC#ǜcCƬTp|Μ@/c//nu_~vVVoפ;0V?7 *{<4f=U6LgSM֟p p '&a)1Oϻ~YuꢹXݼ ~{U9Fզm1?~wDC&ՇY! UUcӪ7n|;}ߪ_<暫ӫWT_\7kzt65v{=&;+O8[WϩQH7HT:j޽T/Ug/w6iCT뗨cNn8Iyk Vpq|yU C6}x<`jxxC;W?[g3V{ (;+O8_RKCT],pmsjfs'ZqDC(uct C{n'vi2]{U\ٺlTSGOmeرXy1`{k8!b[jGܵG][G}:|sF\QݺpB3ܺ^=V=]3FmL fmlvMym꠩:pn^ݤouX>U߬^?S___u}ic]`=;Wϩn{[}1~TP;VZ[ǹ_[fO&п<헹E1թj iԆAcz{u|C {ZLOu{ft::`!@sjs^ 09UW4[ՅulSnhݺme:5T}2_y?X]=!&QC0ߎyvgϽU?SXE ٕtC zq#gmMlvdGT/h81|:z{u`;TL3z~5;{Wxsjvf֭}gj}M3T3[]mv!c}5wmߘ3sצ XQ _]% !uSuyÿ]L v\˩xvnGfkf}3O~󕭻d}c/Ya#7T/_3/t~^g<^K]=]Cw^U鈺l8u՝;Wߩ7<;ݪ_~˘kfVΚ\‹^hU~oj=u {iݺfwuxM7U5p4a5[l !cnߦn~Ǽk7zl{Ǽ>&oK8DUy'%O&f~%Ssu3uDT]=s_[?jjߡg/ +f2U뚹rnlutZw2v` Xh 5/:\1v*ӛ/jkձ ^0GߓtߨNQv=pg_]V]OL?Ky;ճs%՛h7lDܳmyMvnvKxGS5=@녀9n[O׍fS J;=wlyGCfC:d[9[9oLE]ŕU5?6]^ߗ6MBf Q+Hqaߩg?U^9oLכe]SkͶv,o. W{v'Voj?+1VN^YD!jWE՛獨;.7{4b]y) +pzss,E={_n7l*ck^XFݓ&8 c\{T '7Q^}oDaM,U~4flTwP]5vt'VkfX[ݳ:r3c#4 _WY=K#jP׍Qw ܧ::zWuWV'WwY}!(cW_ù[^5WUUwVѮyvkp uxj޳j luu5g]!Da)>V5̇n]ub͆skpv5|ab0q1VK\^}z]?Ӫ/oCCoc:ሚW0߫z- T7Qo 7i`}ײ#ӫ/5^c)]wvW1VoTZ޿VOn^=zVЅ;]7$9Ճ5ҮN pB5;_13SսC6mgF TXfz;f3p:Uo5 L VW5:`9^0TwPVQs#j6,r}su/k8;TwV7^PWk.NT=:sbcT~kPݳ:!c'T8F흪_ٖѷGLUNpƨ9skZ"kX]<[WϫM>fw&:ac2?s8kӫk5ks;H膠l_=OujF27׼rةLobG۫=;;VϬ~?lk8|=K./g|; ]$&Q6Uge+ !M]Ԙk.\yud`];zc ƨIߪ/Ww]o_Qs+o!QsIEu28ڼuh84^0ƹձ gI0 gߪm>T[ͻ=WmVBfƨ; itHB>^=}h혮S}tj1֝UYݡ:|RXӫW?{! cyϮ6[|Vy{nME#jZ=dwQ]6f}Ui}CXzGsW NB8~:Z}gy￿:}DTu c=oQ#XIuէVpk ;+ê#pcêS̳{z?FoT7@-^]];ư{Toί^KTWX}kת{W6:`54ܦzaI ejj%Xu^uiu`oGqB տTXf@Uﮎ\f:!b{B1f6|hhzT۰ue^V^խA_#0~OV[mm cخoCy/ !+gk+@89zEuAu`k_jյgn<oCxO5h=GMkjcuHm!,1|ziMl:v1]}WU/ ŁՃZϥ gYP [;n;~gW7Z ! 9r a4<|o&yQ^]5Rr`Lh6vA1ٺꗪOT\Zaϛ ]y ~l^U=?%#j^=xB]]0ȹ[>OvgvEa~櫗7t?4|ߟpKά[ݫ: +J83ڷ:r%jZ7_Z=Z~u̘ 1ꎝPkWQ긮cE־!aW_|:k.k¸KC_T_{?>^go.sw5jeZ̫#jU~BȰ{6|v}fwCqN]k]::Ħyc;Tמּ\W[m{?[ 8z WP7-sZYֺ#WΩW9ƚOUOiX]8`!1/UsTOϽ|eO6R1ꞺBr5On<NfqV[ֈݪV]MX3SzXu ug^,7:ա+wQW ?1/mӸ}C'&4׎f TwcͥIac&N8ֽ׿R[p}{+3ڏ}z+wǨyna{rqAjߪ^ +[uru~ùd cA8m^3zH WTVo݆='վ]#jnU zHuFuNjzbC&s; 3=X) WVMj@^1WwKN\bnrWQo zu-ϫ3G- {-ڭ:\Cˣ2rM}SvH14 ,ՇeOƨ9Z?ToCXzT'U~eZnQmYk.n8ê#pcǬ;dBqB0qcL-r)rV  _թK+ձ]wv_t01]GώQwxlMkǨ;v8Qա;:EV'{VudÙlIwcV=0ƺ-gwdžI $՜4Ft }u刚T?3_>0f]yQ}D]:jBsO8luou\V/=LoU\L>^Wmw`Cn4f[Vg۷kꅝUϬ^^]}x7]槫GUg,3?3cݨz\_BE1S}::sBs2k˪7p05êGTT_`oUﬞTZV͎Fթon܋`#j~z ʆ3eioqy7^P[x5|\=&c1qyE}O;cꨆf]WƬe:qc깵VW۹z)ߨ^XݺzvuvZ~p~1|ziuHutC 0p `V_޻Tν_ݿ:wm=N.Qs+okߩzcEKՏc-;:y՞cTjcC_\fT~[uyuuǭ@Ŝ0FͯVX׫jXunUg9نYwUGUgW]MX3rDu`k'8#cKٯzS ? +TYXJ59Vym3FV=k?Z!y\:ꗫO.c޵gMj8 E rC̘O}QcDTu Zc}CLWh8gT{+&5 cjE^]3/..WVQ{;+k!3Չ+0ZwC窏TFTuv1Q=ig/ Xh?FͳB2&c%I_Ɂ֘[TU'W↳;:I *B_o>Tc[WO^QsWWB TϪ.PkS~Tձ]wvߘt,Kܛ xI53fq+os1]^[L5GVwcGTW._y֊3Hp c;zluI  c yfo9X>f흫@7V{eٳzfCG_]\?8dgOuLunF[ gwZC !/ @8-qC .ieMuA#j_:3軖ݶzEꄆ3{+C'58n<됆.sϻ?];Ǭ}\azTmQ۹7F|%wZw Q>S[M8 {T?_^U_.u~unu^u2zzplƬqٻp˺Oi"dZ_!$68`244Q3MPstٟZ{q]:}묽֟g7ί>̚EQZm1ϞV{U'4|>e6Nk_^T}A꯫߭XݱD`}o:b8!>Z}~샧tBëv _n;޷TZe ewiކf^#zSuVk-;sl(\8q<ڥn;bN 8w^Wlܽtǫ{n`fs EUOd={/jEڭzFƽj(9zeurSfmV=HuNWQyCB\>bfo1W|ňVNέϊ1\m3W5db^/6_cP Sut1`׃OWRkg?dpb|:uծSmՅX}:b=7R`갆 Y?wbu 3SmVVuW?WO=Op]F=zN'Gdg߼:zZuT_\ nU^ԆpVoh(`6r α!SjUg$b>6h|n #fs:fyqJIV7bV`/7}ur5V[Tk+գN1Ƶ~:z}q겑GNxb}cp'C#ݫc[kS^#WqFuHgkV 0[Vc%SKw=|K3g/5\ߕ՛}Uo.Mݍ0>WV8bկTY6N[,wX3ڇ f]W,✿9bny.IoFA~:ۆrMCӫFnUf D(j9^?㻧QHF>zau Y"~oOq֦n걍i(ymcrjAXRYիFnU \׭Y"ξzDVgÚա+8zhgulffs]P}k ,kF> \.XzdXin\Y?b՛VU4-;+fՎխ\u~~ 8#yKb.^^pjfܐqvY )j|q )`Zv~c<{\R]=ˀKhXRcv1{y feQs]QlF4{WGVmO|zuubV ,.}{V{,ݪVoBYJ@.:iWAVcy3sزzxjߑ{VNM4`%SXUR\T>jc<-I:a#fn][}yn\k߬?ŻV[==?:lbr c/ijwu X*{̳~%I1}Oj(ջ{UߟkV;g)ܱܳzzjۑ{YPW}oFUfr`wϚm1Ƶw,ͳ~j2e߭~r{[.-Ṯթc%ٯ::zF1Ϊo(9:)U}zd}Hu%oVn2v %TO`~# e+ q=ՓXSCjUuXm(9sbOFbѸu'sWoWW7n[Yݩ5l(^ l֎ƘV7^8r~ꌆ׋س]udՍ9zJu+-Uh܍WT&(`Bgvm< l_=:tݟuY~2bvcuوt1^P^۫WW]gf~ë6Vk1ƞ O+?Q.E9v3f6ToYsCA١:zll7bfRMy ,o=m[aCNvج!+%::~`IY,ObUT{V/lb|E/}e-:ȄnQm>wT>vvmb+Vwjcl]V]V+{W4(r ͱ~NuusR60gSw)}ycgb;W'T_mĆ|o;SUC?4bAuߘݺ:zƆ|.v>:pc)X}W\m?o(x 3|6\?k9wX,lfՃTgWOVWZau"}j/Y R=و=VG5Y]8tb~z]C1]\=:m\Z=zWuzu3KgάgVAտ<ϻM.Yyg_ZMGU뺲\>PֲZR=}gT6VlBchb7TTe=[=eIMOV1Ė.ݔX[}pݣ;U_fRgU{sEK4cUF9{CVYVk-}1Ƶ.Q}XSL+x`C1Wffu% %(wnn[Q=W6l,wfnMMֳveup%M}zBk,gEzGu͈ͫ8\8/igҗVo\1Ɨ(X9fِTT.si8ϑ7,+x%M:Ψ>QPZ!4<f+۞՟̱eKe5:~Vsgd=4vսG칼zSu׆S26Y1VU[αUKe5z{n?,w9fXv^\]PSPڰzf^T7+њj{Ϩ^'`eٽvC0 .9z^uNj n_=xNY[qG#_nPV]V D^~~zV-;Su37y 8oj{v^YתUw~ 7ߩ>ԛ[WP2r{.XpFV,+mGl7GNoˆbNx;#No%GuTosWU~2\+ޚT|N3O$.UR}sFwPVW1ƚcQ=ڣ:6`eň]g8ڽzNu[P կV™#WMݪSkf`5b0U[})XK^Y=:z@uV]ZGܨR=#|3)ߞ՝J%vvvnquus=>_}b kjga.޲Mu/` /Ujx] yՑc] zS (o IDATRPݷگr/m7pƔcܤڣ⌳0UXg6~z^CyT﯎NMA P0اzǞSV1VVTNL1Fa0cUO^:TgV...[Ww_?VW= ]ݳ̄R=kGT[/ o^kSO6n0b!՞ձ E/lc`CX|6&݆€N xku4ƛ '\d)5a]V8bվ~~zVXz1-?Zθ{)fk 2Ο`V AuPm>r[Sm*PrtN#|zI[uHY`y)6FjnȳZ|N ܩ-` 2/.UUnĆFV_U861эm9bYV/:}z{q3L0jˉ-[̳%I:Nkxm՞ձOfr `c9_[_OgRi{K oTwhݛcuk߭>TAV>KUM Skfr `c=GVZ1#;MӫO0n̟\}q?k(A=`kI4Z3=߮^PXV=tlcKi҅cVPݾ:f_1UO 淨7Ջ&uCnC'-YmĆrT7Փ]fMr `T>P}h}Tw^Z]WXe_`~T21ձ׫c>VjfA90-:s=cw{VW5#go[Ƅ~^k=wfk䯚߷WTS=vĞ Jn(#y)cǪ4wqNoOߏ}OV`Nx7'N03+O<'W.aMVag>7|7dmuju@wubu 3)bYչս癿zS×߿`^Y]5bm(mX[VϩΫXuĞUUUUY:V<$v.^%W/vi(/8k.:bn3̱Xgկ-g6 2]]Ӱ_.쥋أ::zqu{R=ڹ:r^XV} g+ L1#rM \UiUWnTo\8^Ssg4wuYP ˖oW]}zdȽ߭o(aXJ>2bn3β'VWV ܿj9W^hWWY,êV5l6Ϟ+{W4ﮚaFV)P=J4R3Pxȹg4c}=x9 Ec]}G//Z]3[WGW_XeĞK;V4031:zQuEYW珘۵zl,ʿ[S{>Pdr{nS}";U'Wh(XM.ث:FruTgꂙP\-3g=eщ&wuʑG2"sٻ/ 2>85  k?U]m6]iTUUgUGT7jYQ\+x~mujK[GyCyrx]sq:`S; ?`߭U_^msA˫sWixϬ5{gLmQCJSM ~ 1XFG{c/]g#{ButEՑ#fYl,v)wuW e'Tw`M'{\]PqQueMC .՚].jsۆR7vc|ʆXv1{ESg}94qӪ;6"jI՟Wp/{,~CCjwCc_zmYc56YpA(T DUSȢ@ )5WfHҊMSi#4Q0jkRHL5THc *.E֙qs̜̜~>uoμyouj/7b U۾Dcstȹ&GUY4suGc>\]Vbc)`3awWws16i#玭v2ȹ) $QN# 㪷63%ؽZ+syڈ۫WV?X41 uwuUS%e#PBf]Sh(xJCQƯUW:8 Cm=%VT7TWJurwut"nST'UϘ;Wo1aLRYS}mܭK>_mu@CCWUTݲ7WWmxvcSufC9m(6ƷVF̞P`8u~Gf=='Z4%1ֽX~;kV'KbŦ,{g4$gY;7=a h͈weL1wS@9>Wk+&2[G gYF\;y ƕT\SW]=Y:Xb1pȹc] 2U猜]5eZQ:cfmwK.vsyuL꤆*lQc?oCpZf Oe}{៪n^,ߎGKêmf쬩]ZT[5aFؤVTT_YCu7WV5cYsY]s+?h\QbLճgX:i@#7boz=zau|;ߪz}u4`6ݪCg7U]@K_:}TV{Ϙ{TCAE'Nkv9FKSۦ3G)'V/vs]uvuVrfkŦ[Vom(za1fٳ귫l{n;7]5ey#XY7T\ [! .WV6ꘆBSSVJ9LoۆV6b2]{W\⌥tvs?U=q,s9}ܪj)p)3fΫ,AսUkgΆ⛧TUVwM6{1`:+cOUowRZuMc)|:g S;ۿ:d,y^<߫޴DY6Ny~܈oVgTh}dpl'l6UOڀUWGU/>P4Չ՗'Onl(?}gYݴA"?VjlWLrO~eM9,{U6l;c_Wn19 p cX}mOW1cLg}l(׌[}jD?TO 36ފ! ػ-ƊM n(81TVLok 1nT]۩:bmFν!RzLjmga+ߝ16nձ' WqGu^ R3)s]گo26 }I?F?F= sƕU,ICCv6ëSJmf:u&K[ ^\iX=dEo'Tg/p9_T;-r{[పfsXu񈹝&}^? &իعƩL9l7Wۮ쮆)f^Cy˪;?b#(,c <^^0e^ lyf>>q-ɶեe 5YPrDjuß1`) wLjP?(ϙ;sXit{C1N(,Y]=b! v*UR8s&̰%ٽZY]W]X=uy v/j(6r 0?Wc|zFK:s;W/&NgVqeSxh,<3^5[GT~jꓓr X}wTۯk3O-ita:`,_爹m?y-#~z"߽S/3saE{Krpu~uMC.#v^ԸclPp^P]--`~dž&rzvQ?˭Y#gW-{U~awUGVw,[Wz;kwW6]6aF PQ=mS^,X}pN ǫ۩zPQ=b'GښWsmubbe36C1`Uq0x}̿7Uw;z"=#g_MuhqQ]= Ji($:׈KL>:Tšf;r+ VTY 7Vk܊E{U1 og5e{m(XHQɖd׆"Wk(BP(rA3?xlcP=}O,UE7AFru [F3fVR= mY۳::z숝oVgTWT(=n-UErg ?z"g]_]zCuF5˪VsJ1^SogUC'KLN93ο$)[Fή^ IDAT}wB-#+ëUUG5xiuDjuuT3ο$)m0Kd숹;\}iWoX\]P=An;WUoxvUk ,=0]glA&R1Pk3FΞwgF{surOE۬=:|uz;7WVVGW*i)[m;cf2tOF=zӓV'V1jUC)IW ;:yψ 4Nn*yPs{Ι }z&ECձս_[ecխm_Yj;k#Hή7aF`36aSХ5#gq _5bZg{Gտ1wkuVY26VVVWOs[u^uhuQ[r #f>yiȹ'Lp?1mE9KG՗ӫ䆂OM)}GObZXAe8}܋35?+O~e\.o(9e1 "'>;cܶ1>Sg+G0QT7bՋk  2zFu`Ek9ء::vΚա 3ˌr 3T?>uwf8m_p?6#fX=tDNc|a;VUϯv1uuzu^uyտkzSg,:,.`eެ„-=+C+c]qijꬶrՇ˪WqiuDju[6!0ۻ0Sج?E jynR=:{2,'W+Z6b꼆2HN`>1`W׎ݡ:a,S}۫+[s9:zD+fj;_Nn(h9dFR޶cOe*VsŲGUNn\OW3b矪㪇U'UqՓXD1`0գ'ʲ؞=ٛ,Ŗ5#v.i(8T0}ufDY~8%̲%ڱ:huIuX?kwWVUVwN`21`a.Y5QyNZ [VΩ?bվœX"1`~_W9Q.︮:s l)XU]_g:zhapKM9,=s<8Ke9[QR]T]Y[4bթ7 lyWW-`'ʲU=޹Y{UGWWVm3c)Aչ]fؤ`:uk T"V/\8΃T?r /L `bSe3 ߶z{i=9[PqӒ&Z>T[}zM1>[V(-`~oNf=Tq +G]Z=zLT6g1`[SyjuYss:ulvk~:dy (wguduvT]Z={D?h c-%Ȳ<::z􈝛E>6U8F9,TV,`Յv"TwUZ=Q3իtPKSXcz՟/`gE[S1ӫWc{Ջݿv^NVTWVUGUY[]\Q=Z,Xn^[Y  U=fޣZY}~zr gK{ó\uaInkYZ][*0v:lSuqunvf%՗3W_5]l;NNn]@-#WruVꖉrlct_PRZ݇VUu[C1Nnծ '%8.+?h(8m\[9`C>laVGT?UCudu_*XS:cl6uJ\Y=:gm&Ά7TUi*)u㪗Vs{mO՟TT_Y].P+#wnήVW_(VM9l^YZV=:kk+ 7[}iѓn^U=f;&@1`S[S}xݫ~Tj^UwV7W7U7VY7';V_Gi(9t\y҃V/ꏪW7L (J15r`jES6#.VW&c)67V/VUsGuO \YWȝoVT[}q\lcժ#w>SzsuDX1MaE]ZZZ;A.r `)VZU=l>>M,X [ȝUgT7N )t`w櫪7UUߛ(KD9ضSP=eΚջD`)˽_~;UTT&r `ch.#w\Ue\lcն#wVW\9Q.6#1wQVWi J@D*v%!1jzLnzx_Sn1r& vXEOy0 Z9wά *|%ɞ\Sqg36J8P=|4IIi j34g|"Iz$H`;!x$'Bv}LKrA˓T4Okloc+O2)ד$$a/c1^I>3Isͪ$&Y1`ǵw^ 8+IzWI.N]D8XIh{$R1`>IdzٔI~f w9;I>k=,NrI_'L}N|!R?7I.J`#=I5`ݽI~$;0#Idz٘$?I2INҹk&}_'YL}cg|'9%II=Is͢$J$4O[a]9M]r7@]7yx&wN**9Y-RN$Hl&Cm;p tjU^y]4s'l [a}wquk2eܭPx_uhz_N7*$%f빦:$$oI8b6TVk޺ []ʉ8~9˷bG@TV' LR$)*QMnƚc$r\1ɕI~df3@fUA.k_[Yz[ꪩ'|%iW5&0ɯ,i`$h1/8gynz*tϟ=77 }A@GM5>bkM$Ip ,۸><;ksTUWoŮ&IEew,ɤnU|%zJrsL}p EwCRRT􎱵7Ǐ]P:4g}=[֔VmG&l~\&I~f u[`6}|qm~UU]ȼ5Z/(?C65c$9tm[5I^L/cAs)m~=^<_ 9OXM7Smx݂mreʊj׾9`UCO$%)iI.Hryf cۄ[>[>m%?=ܬ0ιCܼ: [%9+ג窪$7'Q{3ءfMʮu-.^'WORw߯Y$?ORs;"lW鳾z켱SGO̢eGֻ$Hra츄cVUEEgeonjVYUo'>}jV+Mr~kT6[p d6oeq Zצ5f,j/ IDATl88OO/Ac*g5Ξ\^ʫmsmGŗOͦ굦hsvWy$I4p тmreʊTU9u'vWʪZSR";u5'~M }%`Gf]\N7I5n)YrBK굦u }8tIJ8@ڔMIY6VUd-v8w[QS6UyIIږ~^KҾUWnnY--Kl͛̚ZfվUZd\U]ۤ*6.-.NV)JQ6WUfرUyJbs6T{'.߸a׾UʊzTl׵eRV\iV%%iWc|.\{9RjUxZ886O>}zn^UiьqM .յMN{3 ήUq㛫ª噶xA.y,Z;vtlUw^}ӻm76TV+srǼ9ǜgj=x}r2gխWzi jEY4/}ۮn.sT9pٷg ?MUryf-_翔^~!ϮXBO6r=2߫oycl]U]vvɾ=dhn)/kbeuu^Z".y5.|%׼tfZނ7͐N]!#r.C3k--{clޚUyl⳹jSYq} vʖ礁儝f\ءs$UUYnM}sYY miY70{Ș}kӮC4:sV-+fꂹW[ݶ};xx}{ɀRRnceeY$-Y) 9Og5XRTICF>;񞸩2 ֮_ʵs-s/HC]G攁gDi_*IRdeyjܼ Wҡs;xX/tϏ|q/^ɯ^|6*୮m2igӳO~cl yr>ovx~V_;]xń6l|e3S_q}n|'W^f.g}rX]RZ\r.x\5{VTmsrn{%$6ϮX>zo5>]{%P!nޜkښi!}PgڕսMZ4?M9|Tk6oʯ|8N'+*ӛu(knqldמŸ5}x|5%Z(.*'=ޯ՛7Ys)5dXO=F\EE9=|t0$mJK\ɥۙO=`@ӨsΘCr̀!o ;KEUUn|鹜;mJ/^PОw쒁;7jmeuuʋ5Ы_~}գ?a^X<_\7FҶ,5>_z_\8_1F0$5>zolޔ˟7mjYYО2.^e5TV\_3z[BjRU]fW3֬jtOۜUkoup~LCI*gؽ=>yj?O,\rl[׽ Iiɲyc{ҿOMO {L8@8Fxct.o3vIgS̔[hڂ7haWΞIOzْq;G;5j ;U1-coP~ȱԱKAY"x\ԣ;X[Я}|j|l٩AoW{nΣK^-H8in'[ޒ^z. YkĥṄv۫QuZ$F}|sԸ|{-mX(=h~>3s6v~o=L~{zŒxynFQ=zgiorG)l} e@Nr5e9k?r1R]G5*iK]4zkny>\:?*g ݳI_ԪNr3)d>QkUlN~ű '?F'Рp;O.=WuMgS -z|=Iu6UU?_XYYKJn/w{~︿ky\}i9+6nșˍ/=辶)Z8Ƃmreʊ ZdE~IЮ?}dyJ<(I. mʟ]Oۄ d`tPy}glR0F%9m<1L?0 nmL>Fc$S?ʊqs ,X0F 9?:/|vP*獝NL3z|1dl#Ffc|sq6:#yNHB>:dg?;I2gۛl3o9uаq߾cAj.79tς|p=NLyIIkuo6wpFֳNҲ\{=6l.:<3a>Ի&%EEQyͷFKP%%14K|sq$:v?O<+ |i;g{>C\кKJ냎lҩk=}P֪Iuzi돝NSn=զ]kߓ+@gsʠ3<2][7EI5j\.pRdp&xfvйu{m[;=jaMN[~rM;`H~{бu\:7&ca4Fc~CO4wԓ?1=n!)-?\: >ֵ]6l,};{fv?k~oؽ:ҫۍqɏL:nzs ;}בb vؒ8Txk8i‰ x]mrҾJr9n]~útϝ'|lߚ>=b\xq9>>n^yO(h́;#Nnn3==;ϸ#~C.)LJm Rop.ŸEv)z:*/X\zIٽsլ  uw=C*{LJ$WL<5',eHSL}Z+kG;*w*H帣㛭~m33ͶGcc3@=.)_OP.KovFo ^!\:g@s_g?_V*vH3.pCX|jL~eNTVRklޔ_~!w2'֮ ҹUj>C:u~2G:7vB_0<).=ۃIsDX{@nסU?fRP$)7lf._7c 5̱;Iڟ_<|I.y>֥!gg.zjz.p6us*-._89=!]YYvKkRZTkkY~m:gvرsK_gLJw35zѵG8`g2yޜ2>\=6G6Н͙'MN[ h11`;Կ}\{O蘌շ:rW\XNs4dDgѡU9j\zosyp +IN4,útS˗&pds眇ɲ1cL>ft-a[=۴Lʘ MKJ򻃏m/.x}o,l .vV.7+.^|̆sUu:ux0c<'Oٜ)Jra'kyT'yӧY5ۯgGJqіԪ7}>֬؂uk\%g3 ڽ9WΞMGvu>x絹{KxMlU\K;!_sI*5Yk?fo߱:*~gHҲs׼3k⬫؜e֥{77PwYk-YǷS9kV喹7x$-FPC:uw/>O5/^űOȮ=srX]2+DbsNzgZ3'__x*/kyKJs!e]v:LOZ,^49|bҡs~[/ 3:Jr~nq앵WdútlU;tvJ}eFz,|SHʷGbxYUuu[jf,[ݮCt;w^EEžcfzR:vbkIOn|:=h~\t{8odמ9o|seuuE{uUkx>^ٵSwܿ2w)-y56O 99x[69ciO+nU4^uuIf>z-.^^nͱ\}G.jmp |z9Z|cٯgC@ZΏ?<[Yo>0yo7wk ǘͻ/MLڿS;UՌer 弱ѵMἦdrMssQ75wpf %G[fGTkE= x檪}Mi]Z3YO~:o7eLYP;3|0}|ɹkޜ=.}Y=|TshڕZMii&  |(kAlgg\U] gMϏw1útyc'=wu$4xx8:CGo5WTU[/PUU?O}ar휧k]T79p|mA?r'ݗmqQGemɇvK8F}]{L~TVv*UWo9Z]^O9n ]۰lswXs1癜7aՓgu=8ڴkTmGgjSY]3u7oʩ]ώӨ>s{.=j{o[nMf9\z[{nnT0|gFׯ>us7W׭7]'.z㾢$c?t)o]ukrƝ5(u9- (}@J/r剥 3ꪋj|mngN~,X=N<`/ǿ)H&<|w1^WU]o>09?{5{^rss_0(-./O1(I7'?x׮NoekS\TNhpBۂ~;㑌LB0Fټ)?{p%Y~mё IDAT?ЇO͛r˿s1KrWν:P/Gsп$ lQsrR\T3y6h[Җoܐ7\gkUc^ׄ{M:H;V ƨK32zӯ(_51D8lWAr۔[xXq}}j=̶CY`CCi]R}@Λ>575 u++;yˡG5Vcuo6}Ps~=M繕GkRslrsV-q|ҐֺMyme b㆜vYy}`t)/Ȯ=uW#K6|M˗=6q{Ҟc]1ZCFqU3f-vϬXSn2u %C:umRwvp|c{il+T_~kK5Ы_SywS++{nNUu>؜Oss&%ب5Gǘ.zQ7^UUd Wrq=~Ҙz:__QO]{PBm*ݐMUow coVsSnGPK\뜋z4ϯ\֤},٫8! Wiqq>{}~Cf}H9ij4%Ʌ3e 5wo6\_]NuyhF9g}U-}siyq&ep7no喬ظ!=DX(w{ K͑9 ]GU73.^?~Z'[)-ךF}>+\(I/^ϮXZgPۖ,X&sɐN]ӵMk{_ _ɝd7}jwڪE # s;ٳ ۯg$;]\uSsM-=>W\vlU`#C++G:fd妍o3dJokث[V-ϕl^I\>>ltjU^xUuu.5{^ sW>Yc 0$g&ﵱb7/E5|Z z9{D;2}$}FyfX9{nt}ުMii>ΩO Heu_oBCZ^;mQ|~0S˗d˓Kk~M*l.Ir9qaQ/ jsp9uo>wڔTTU5jEIYsHk󻙅ؒWs߷ (ߋ;UTUW9:gH쮼udڟ[Suui:(>v^ ?!x 8uа:(.yu}Y|`בݳ91ɓsnyeԪۧ^y1sV(sVȔ;0{[w ^﵄~ O}?hAJ^l(X.)YY뜿<ٻ;)Y{cP%eM6uM$'!xH!$B1c1W{-zєͽW3Fz9HsK}3cۜ08g̶m>+WmWGLcQ;nr\1jr{,/=97Vw.IX8gdG(c׳fGOv5EnY*?CAv̖NZq:&;7~^,gv+[ؒٞvqS kV+5I^,gۮ@(j+LQoZO_49dÚmonְ+0 }`8lׯ39Tui Mw5՛H~Kru 5=7d#bnZPH[}ó$=.>=~a͆JcTj2gϳVxT;b1ndz%#Ʃ05ݰehwujCmaMr>w$ݻ-1jm2ʚt?WeMBmXW?!C7iA08$Gmv7?F.IKF7NkZ M?jS]*X{:28~Nitv0cAC3 <%#ƙּvƙs裒]6{|#&+ 3tR=^͛U cEh3m?daKI5P[y{!v |,]S}T=sl;*RAjZLRҌ,Ú+4GoBޮ2-6 Mʊ1aD@:%[18Ӭ<:|nVgSfRa64sYhJǣô#j]Mmcn<f6$P34đ?jUe=v_\2:K%i|N+07ǐ1"^?sHҼ^u OuUc kޫ%LIܘ0{>ot1yzc~񈱎<~ԺZE =bVn7m" rV{Zₕܥ: 0`{"sɼn{_;2E{iO $wjK¡Mɝ yǤ.>ִƩ'9t^gehq,Ϛ`8;sj7hApG>NӠ ;IXgOGMkaZ<.W|Ú-զhYylhE+ߜ&껿||ux{Y^h4.'_C3 k?^`ةͺ5رc5.'_Ei5-~ wPkOg# KF8t-4 Z~YQ}Rӆ{+z#! 1m~RG:v46^}Wtoms Vg fZ3Z'6Rm>qIAg2 pl-$lM6tQ[Oᒔù]m:m|n/(.ox݋Ҽ$''vAMKsޮ~&?0 $9CMk=la9E%!3lG:Vs ·c8.r:7t/L5s1bi;Ԁ_r~d~.6twBh5?=ա&B!ƫ7i>h;dXF R k{ݟ )y~Wc}9ޢ85vubv\K.Ӽ^M+tt {{w^JBsUY{|/]]Z*Hh7BJDYe-捊9J]M5>ؒM2m~IXzn\܇9IKLN;f)7%հɆ҉3qQȈGd(7%U;npMt\!!Ja[eHbJn5k{f% )V65&a&tݾ{Hgx=͓s-=;U$0SX uG.ƫ2~VMF k^|YGt<0]E5gګf^:H`3 Mk:]CV_T96K=~e&%;2"ynÚN羿;Li^gaw8ؼ73 Ϛ]f.xsl Gm!i4 H;;?҉=scɊ,eYs8cr^nckh/ ^t$/&c%xϴ3sfAO!_1]+7L`n/%BoBB7k84 @gZc%"V7e21txFOp0Kx~l '`b!M&ͻ%kC,>SLȟ `x85(ʽϚdG2]11!sUaf|hC SӕjX UlxgW+EkPJ y=Hߩ*)Z:W5u<.FeU;|JFQZS9h6q2,@:\zj^#c[ v8J{ǫ :όVMuG A>ʳuv {QGGWT]m/%SrJ2RQ*ZS+U ;~#8} V۸qoXRuzj++)ٰn\vqt-}8=Ӵ鰀cc[i\,\fչCGhVadixFҼI4|suZ߈l,9V`Y 6E?cIyFZNNއ19P 3jlvFfe+͓cxL6fgKgM'\ q,1?njﳒ3clv\:'t {QeM}ji[K.W@Iz%ToFrURT3gѐK\ HP% ݝڡMr kJ3͐ 7hEݨ}ui4D4^hoesS 2qa}N.}`fY9pCV 5%QNJҽI5PH ]J8Lg1m>k9WȡHK]I֞Ҽ^妤~ͽ(Jկ_>^\%y}D5Zs*kNKc %ʹlL 5=+雼TӚfdXa(g'ΰ}ܪv={Lc .&Tmʜce; TN6/H5?kǰҰ柱?x7$?T mUiUeaM"|6Tto~`8$)1qa?Dq^(GΚl`*c;>JrpCJşҘ~?V>*k#;9Ŵɳ&3N=WVϝ 1h+#,RZ*TJ3'UjxdC84g+ `2Ng%^ORR*R*fUtHLN`?~HPV3ǯyB"6I?I/oJ&ikw׿gvaU}ce?^yN$4$kpwbfd5M])aeY&T/LYyލW(@}>?Yzj>۔\#.WnVJ5kr wo@ * X аFсzJ6c<,5A1[UYk?V/`VDDI0#P9=5HwLmZ^-{\qXUYy t%1v0X<[z>x B!W7U\JeV*+ZE99JV++#>p ACAӚTo|~%8c>/hx[XS-QB!a].ú=Ï.2X_}J=8(:Ό|tsVmWë鱸oOs?kz?H` ڳCy[&>~VDce|>uvI>--;^%fK|6'k~o<pP;J6 a%}LkR<: ?)q'¡:xaMXW׼3 =|ǫ)9`Eϥ4E)i~TӺ#1$k@=+AMq{>pK;KgJW^ Kٙ'K߿c$y385g|bxa%#VI6I1=FMںqX3Lkݿ]oWuI~IR'ɑ1mtYSm|6XݷNߋn4ai` =o밃sf||D0L%_8\۴&^Vv7Ⱂi'0@&_rSR kc6U'ܷ8$v-~iM'߰pkޭӊbc{`#,kiCh'2Ku^}(Ѣ5dysfJ.e%%fx/1e9sͮOaa afRfRp鹕{@xiKZ`eg%8g`'4",g3fCd_YqX~ck/ۭ$ 8X^g+%v^T]Wi͐ y\.24=ӴzJ3]acqZcdLkoJQeTioe;9?n}[b> '?3 lkul~5 9}"1Yy޵ Ϯ=PB`"HPGZ k<.P;?5ʹPkF[_Mn:;qt@59дfgC]Vb-5.I%x[8g>:jZ9rRniz[( VΚ}hXJ-쿣p ACAUv7voiF\&5m=~uu8hY p)Ph[iL8F\4JBLi֏vl~ Csie{Y*Y 6 Mϔe+0e-kp YitAiyCm+6+Mɝk.9@oax 'N&%>J&0-ݱ4G4pCV7|]kgMNVDSѦ`&VQsB"?`p"H`;Lk>(M7o|ewq'FҼ^0bgc&5% TIɆ{qZ},w h̖˱ѭ54G!I"#)ɴfV A KXoZxx{} @{ڴfTVkm>/VO+tl94uɰ(5]i^R=?{*w'Kzu5#hXb%eRnk`hY*9s:yZyA8ƠeevvK}kns:م'r]+MXtXtb΋xr錂bmtl ?P׎oZؼ0 - B̛ȸRw'KRO( u?4bͨ\y\.aGRۿ>ڱ2GQڤ@ҽIk S5*+GGZl?'9E){('dF8ެ,7OIӘd%!ˁP 'RIz)LMG  XpƎZt֘Y1{%oc ά9\CE%2S>WSr Pj&NI1HjUICG$睔[`ZS?='9E{I#򇂆5 J B8 .s8Y*Iy)iYjr̈_pFXdC24"3ۑ a@1F-Sؤ&RXYN+MKaclwJ^ RoQaIMއiCdЃ$-*z|ܑ9jB=1O~>y:\nYN{zaOJFzƺL1nqũs>'͌u{|APy.lHSkY:ukk'>k> ~5}c Pk64[bxGbZ^}#si]149{pۃ,.1iV,6FII5f/cw;0ܤ3l։36h Ⱦ餧v>ym(LE 5׏4y}=e u5׌d9x{_>rJ3lL۴)g'!'bHu?<`0 MI+j:$4(3)Y_:ǖ>; [)"9ڤvÚdG;Ŗ<.n?ݖaݻca-(-}9~C+}>݊ceXoXsMgI1v? p `{F/'ʹeOyE5}-sK0C{7ckJ^>7髣-zÚ}.-ӕ&zC{mI-~1Wߟ}Ҽ^[2陲݆5In~4wQs->F 8}Bk+ k~8w'd'zms}v68髼9⵼4]$npӟl5<$؆ϷU+Mk2m-'ϲ-NJ?٪3l[\P5vJמ8Su]1/Om !I.LIxnO*P k.Y|MޅiJ^CXX~LW2K5,#+.1NwLf=U+9ȍ%ibnq"l;pXwZӺq:}xAM~1*Iόzy_m]ZZǣ-CXѼE!郻t)9`_0l\eXu1s/UVRr]OcFݿcawfuCγ.4ytvmjDש+0OI+WެQY9}kC_{`{ /'Vx}HZڨxO-Ͽk=@͙4jű2Ӻ3 EryϽDfy1o+dFg+nR~Jq]~`0evONκ7 E^g/AM͜QPG_H7V` MU3czM+}]:bߞu4;}[t)x؈5_6WpX>|~L"0ݿcu58u' hWcǾMҔɸ<õʎ6t3UkrS 盔WpkIaI_xspҼ)iZ]Qn~ets5޳` v9 &s/$a}NN.m!I]/G4koU>ayIWTyE%ߧtoܳU|7vڳUߚ@"˥>Gߛ}].xwX#:t>ӧ{$` IۯK[/貑tYzx6QPRz!}WO,wqd-5A?rSR _w/Lj+c]6 , o.ՈW:ҰґB+7%O}skZv@_mH8\~}z˥>5~tݾ%õ3~z==5˵qqI*[-&S0cv7fy 叻8vk:Gƶ־:f Wv[Ǎ֖j]15>cݽmcN#8 j::dS3v]q}oipXR`êw_omqu9֢YwohK@U}eP8/L^z̡0pX?޴ZYB)t'b׭W<'5Oaˮz~TuO7Ls˞ΨBܾvk?c%ݿs> g][tvo%KS cj3ښcZg/+ ~|n}yݶ%TgGe< ޼XWSO^_6W?PEi}l۠{oIݻM+2:IMֵZYyXwkk鋫iwS}ߩ>_m]պzB^_0o[m=c%=S[al cI4aImި8ϽD1wֽ !:ם* FeGEkBN׆%-;r@_yUZI2oOi o.՟voͿ@ 8Mnn&ݽmZl<ܽUEiwRw0}@^V}Pj}tT^{IzIkƴD|=9R]7vr^[ڤyUˏ~)+҆ uEaTM+V=fúaT490qZt{}l 7 81'FzJҪtҮsxx6z~0\}y\z|m5zdv^;Zf_cJל7A<·kl]O;q۴\͖Ddlc D'Z!H\B\xi\D]Lhee#mM4q|6/:v&9ϱ<76/^= \]rϧ>ܲ}|,[A)ݹ3:o5URqej"oth+Q/N'v-ʮ\k|r+Zٙ8|;rx[ܒ׿>2\:_;s"7Vнr65Gu/nlT3fDĥx=EbLF#~o~O~ӥefOƯW"P1Jc1~Жuͱ#k"WS#3143=3:TV6=k>&dD*gqoK[l_f21>7G@ΔzIJpX!G.gchf*hǻ)55wqkúhٙѡ뺷gx(޻ޯMF1Fa 긷-65D{ssqej#C183U)5UU3[5G.fc?SW&J=" Q>1,dK=8?>R1VB9^_Q㿯zwiblՆfW.> %K0\Q4 P6_QB@t z6=/ LOų}24'ɡkMG3c1ikX[pmf~>fqʁ8Plc\]i(@QE#C6 C(%׻G1*8P?*`2M@9ɖzkjM}ȳ1=Ov潭mK9/q Xkjw^_Wcm\_\c |eJ=<:[6&ۻ&޾ւ/^\(_P!%;L)3c@x[KcT.q 66}ߦw\#W~.+8TEѐ.XSX)1|j}QYSruWp}va>;uhc@c]S<ҹhٻ~:\Wh` ?zMw->0oV6q 0uk?-oxv퉿>ppkw  9^O/\m]ՁO=L%|1F`˖z4jjK?'^x6^:wml툇;FSMN\X11ݝDDl\h-Qɼ#瞊/'DV|L![}]k\ k<~ 'S럚_B9,MO^=,rc*;6o;m}< dRuj_|cW5'n_{Mg/,aRV#q ]>·'76oc[&-1:;/ġqd̉b#c@_\g.g.l&ri}l^>[s 1:;Fе85|-\q p81'K= Pzc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8d{ IDATɈc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉc8Ɉcq@7bD\t"Bh AtRWD܈JBҫô,Bt65Ib\/$ϳs^F4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF4 8i1H#@q ҈cF78+z-q f{EaK`.c0yei%01m냟*v)q fۇQ9%:{qV;b`v=|Fx-0=ƣ8u_ ^0U1CAģw^0U^zO<;1&6m+`c—_{_/_NxeXf׃@a,vc=`-,.|KhOGX\ ڃ^")z{pW|I^߽Uwқʛո{c0[:gz# kV[}woqp8cpo/DYKD8}V]bs`6t.*q[7Z\!݈roi%-BYqҪ?wGL1 s}wD^s}{_1/C?ܺ hƶx8>V>q )1(E%z:{YҘ!@mnpTr4ƴq U9G9x8n8$VrM5Wr_O[F6gj38eչUo}gv/kq[01(ݍ*c=s0\X9'@9) sJrt7S=ccP>{jo)gWx%lCV h"@ڙ +KW;-sBr݈ᨒz`;;`NcP.'D9:=01(Nws"w1` 1(f5Չu_㕉3L?ܚm h&x$q ʣ}V}$c01(E%z{YD#@9lnpT蝃r4&z'q U/ޣb!@9t{{`5ѿrM5W f8opѬx?8ӯ}V/V88ӭsQz-JJ鶽Q r4 %%t;iՋGӱJFn=!"";1^{joEψj=FUXFV(q W^ǴR`:u.*q[/zc-=A;z|oXZ%<ۻ1Up9vk=Bg]F7?}go@V OttZq gu?3-L2)$@HH( iJPX`YQw.@WX)`) B @L2} Ks&SI'/~sbr@7%KxP#䵶n,TY8L,eM1zE6vaz1~lSwߋLMRaE6g޸hkWyMiS֕: l cfİG"M{GGzt 5 )%{VDqC1WZ'E&S[uP#@]0G͈iSf:l c;_$ XWN[D4ϿOc]n>ysTK Ukf:BV1c۟L6;jmc[YVu,P:u<1aF45zpQ`k4⡧#lXqapé'>xR:1^ZD=~rQsdx!tXlj~kzw-SWw\-ZvC}Wg#"\ppYYj]9^nh9VdA&[;>|RGr -d.&oICE=doxc#"\:32n}i:ϭ^vgs_zȂTcv>1خ,]1eᲙ Yg7ąy6H$: בLFƃJ[m%Pb1~zӱ˻g{P"mJ[Z&sF:lc}9cPբ. 7@i`V#@"7!`k|cAdEzb՚#+VmKeP1""Wp˕ţs@@WFcR葍 39U5Sgx{VsG6WYh IDAT=G.u ؚ:>༵;/A.;Xl6v3ͯZW4{K^Up̈XPR)U弽r ?e1YVŬi?,u 賺:Bה:lcc>V:*ҙ\*R1diԫYM%ɷhx|~zn2:ҙALd"j!Ukci/I%WHSs*^0>61mՑNWG{zHT5FYYcT7Ɛ!o>^%Ԝ89^{cRlhC#(KFeņ^FGz%j:6,<$@w̙7.Ǖ:FMiS֕: lcl.j=:sWľw{?}Jwq7uUxm=?nqiwovmCCy\w1'DKH=:"xl yG'#(COe->Mm1確s>Ճ3/T~tXvܲWr #nƨ^0>91E{o-#"xQqC"ݟe̊6ܖK^XqLLƈڶ^gu7 P;.U}G͈iSf:lclު7Gg~x#{6׿?(8Hdll:(|x~q?S!BWno<2hm؃Shj']O,\vJ{/skLmdCxCb+kcݯ<}*"YWHgFvtfd_u޽xE[n=>˕Ʀb+EW{mۣ:ݫ,;ߖl|%c՚'+V͊G.K{X}3~1ٟ}b͵Q1Y~ߺWʵb1F*e+le Q"),J|ߋEK9u-YQ|_/#hQV6e2k.2~jBDޮdhiݣ{v2:FllMͩRQY@Q]Oƒglv-la5scpժ\.""GGs8#rrX+_ ueS~C]ޗLưju 6D4]bC*/ ^jWiw9_!gDGPqb7WO^; V:>ҙQv46Wħ?ߵE_ ?5!n}IWU. G8ij7ֶm{~~3>~%=!]ܑwt_~Ve51ѽG/(u(z/b}c2;tus1iiyǏ)֮Uo {\QuKV|//[Ӧ^=֞7-_LdƏksO_tYJ>+N>35=k+滎LmDD䜸mk}U/Ƈ8Q _ MnH,} Iŵw|/;G9l6ٵT?8Sv=[reΌ4K$ұǮ?{+DGɯ?-D$N$RǎߣeGt9RL;yd銙7L璧T.2yfFr 1GS'GD}i*ѵm1ve>P7Ĥw=(EDD:=$>HDD>%L~k<9oEkq%)zko;,<|hG.ѵmqĒ*_UR cDm[T㫟)nB"l>!~vq9(VwT3dk<^X<7dvDd\}7"\𞑵ǎ?̙7.uWǿ.bܖܢE lrTDD/}ߣ,ӦcfD;7㽿dkΒɖ8#cmqG2 coQG>c19_tϜybmӧգo7uR}L^c({7Rg %+jbk*lylҿKVe|<~_D}á﫭y,ƈ>ߪ53' cŪ}vhVgN.\l]z];f-~#"iA2?^?9Ə?luw3je-6e]Q{ij˕ov=+ُgCEy@鮓#_RYz~q‘/O.ֶfm}tNSW'tL"ۇDKиWO\.UԺsq)wE*+F`H+yZQvc κ..*bO9ƆԜ>r G>Xqmrk-xC̋Izj9>W35S=sٛkTo@)Z!;=\#b,yk6 ~>Əy8CĈڶ"sG6W=&\_}QL&f?w|}l?1屢=X29F[q!˺qCG&[Sp>~'&Xcy1[ƕ M3ҙ{LўؗguU1fDkў\G̉Ļ-K ؂%s1&*XlfD(H:I.ΥE۶Ĺ]Οyw,y.MS2ueWk %y; p{w%qaCx9"ryg\y=k[61m8 ZJ膺Wǿ" κ.&O;,R.:Eeś1fDkx[lqƽ Β֘Dk}Fꖆdk١m R Ze٢=kئ(KtfD?y%H^\&m<.66#罯1;}0N;ߵ(9E[^bM.8뺸+?ի-mE#cڔuEN$l*Wu]7F*!2ay-{ty>MDKĂʊ#)ߪ(_-m_ ;łW& D{ǠnmkLΗښhmPp>͢?p9ƆƮߗ[Lsڝ=:;{V45wԺO\}qz8+gY>۟ #ΛӦ.z.)`J5}ge\cld9$wWCiBo[K/66l?r濸KD:dpeږڲ[џ:/űG|9gu'')~?ߍS0 -Yff D1&})XjVD(P,; rXlXLTwvݐ-.~EWy~CE\u7?l_ߎb]_{+3AgQ]lo˲Q|㇣bCyikqN\r޷8ЕhlsYIo 266̈TTS2f$K"j- 7V[_x?ed[wnr]7zuⳊ1ަs6WlMQre˕eÊenR\Ժ0%w?ds=:SbM.8뺘0dkb{S"ئGQm}cr mgM]H#(k_/.97rTg'c'vk19**ڷ'mw@ӅqPDԗ:ƀ4!'1w7 <#g0P3{t)16ҫ"T,\63"_(6(Bl\$ E~(Wxayd9&vur=1fD ?| K+.eltיW#phlw>!O;ɀ|6{bMzZnfD\ӿ` Igu9h/8ZَtU2 _zP1FY8_YV lHE{ "bҮ7ƹ1@i8ݏ\޳//;- lfμq>[Npe|7sIAFKxߞu ZR`;ɥJl;31j5 ml6[٫L],/=5꼳D"|;t1FDDUUSR<^p޾K\{gYݺobM.8뺘0nܙ9s<Su3]YF&דɻ/8=#Ȣ7VU%GGhV4|m~>ӵ̺>"r狖 Med՚[g_dXս2؎)Z*? +mc EL H [[L^/Ohږ5ye=8}=s\[WFC.4ʭcdllMͩJ[%;d"[pVr}[>{AN*8lNy{zm`"`%*88mc-d,\6sVG9p9ƺ{u]Uҧ}U mKr>e?mqǸ1]ϾPڂThf+TvdY:32^^R_Olgmu}D zJ44 \ ""6WǿNS\]dSM9p9FC>gFDO;ꙗfmכ-8K^t$K_W}elnpўNOq@:?F2Zp~s捋Ή%Caw/o;ns捋qo*hi-YDUP#3rEY}ڽ#tXY // Gx0j[3i|C/~P>p`7wN׶bMd$y-mr {--8khlW{mij;{l>y"KYw{Vp/o;:=={ޮ͂l*zx>?'fDLs*d%#Sݭ_ޏ߾ٯޣ1)O/2V<;d"{f#k,8Hko&۪537XI:B9d(Kwow|ß\/ъ$;O\<Wt{ǎՃ+8Ӣ~CE2]Ss*~>_{قg_ʊF}Wgۿ\ey2=zC~>?).#MxoD7|*Z&lWN/;.|^o>-{|w |=LzgWtzfגɦkgQQ2מּᐸö-ɱOw>a1i|Co=sy}<)L; #ُ]DP1v4}_YKԸu!nu]U\ӳc""rY=h9{jH3HtDDeWoh*yz{N2wms0?* k>?l z+s}Y.W xw/.9/ѺfәX.،1H%sόGH&Οzq]t ?5!|7>jQ{wGp7D?*.쀖;V1&o/Xlf@I(ќcP‚5q?>syk>ձrG"""H,R]||,տ;"nΌz*Fq1tfT0x6Ccl cmr vHR "~zޑ,;`ϻ=’H6DD"ҙ˕wgğy⓱dyTV,d]ΎN[[42~ƌ# xq7D{ e1f1bXnXS7>Xhj7ڡODž_0j;,ZZvqW6T?}$Fծ#ފ\$bmXn\hhNg~7.7b]{n\D"}&gC,YQ?6˯mhs?Ǵ)6=17N{=:.;k$\zyy]|+2&l"G{.feuqчm%+j◿bUN{WO̿(ʼAĔQ=7佧~CEr߬XCNT>=q‘nML'Ǽt6:ң ŠW iqY)ۜyw2rTy2+Φu}~Rᢘ:DDttTpI"U 4.>?~pȂUVo>\?q@gڡ_G{sqҠE}znμq3.I1ƹܐ !?mlwg-~pƦ#"" dž^>8#"bO5.tPt@n{(oֶy74ܤ |-~yFDll:0p`{!"lTrc"̰*:?v[コ~ \.-S:]OtR\s+/'32nʘ1H&>:E6;oA︢GNz2ƌ>;z袼[K۞1bl Z뢢>LDk6`MeŊ83v2$m]ki}י69|qߣGg fhj4kU/NX(~2H$zWHt;,.81#Z/|#tkDz'l'(}Ű}0;}=&E$ޓHcqƷNQ;2}[/c?ѝ41Nԩ#3N\tO,Oϧ?XL{CϝwںbccìND&xEvғ=~Q,o}V>H&Z ܕ)]qBhi+o1F2e|3bmqD1`3*^v@;N=Sqלunas#~SJ6(CyY? :ӸgO GD[g3rv|ob닞-",_=1w?{ZoRBL}$8e͒֞tmo}qēO [PHY.v}Wzb¸ƿ^O{9j\pֵqmij/m}xRI8bw-+mxOee}qƴ)zu=TryoƽoOΥ/Izw&Xɲ%+#y7ͭ#tMD䢬!U;x1{1}[%ܢܔw6f/^^ܜgںeBtGE6SHFY)WGWc]㎜k_ѕ7V {=0^_45ёTG"ґJFyڨz8rܘdž͗ϢƉўL&J6E2Uv蒘lI3geXXqjLudC6<|1}K=^\<-ު-DGzTd2U˕G*(O5D1j+n::d[+O7{zkڸq3Pί=s냪$ڶ{ ޾KUUD@Kg]1`GWVl%۳.xQnڔu1m"׸s}$")undžcXt[*;?"4y5ȫ%0K9F9F9F9F9F9F9F9^wRNs?wǗ}NS|~QݦOKhiң3Lmdj;]H+s1`k&7DDĄ,[D9ܐ1fDk?$PmV1~lS?%_d{ ٕ6_Pomљ!1֯~CEk/u `uScn^4%١n{D{TQ=3gυq7Jxz|nd/] "cl|pz<Leϼ!"n(um5ݣ3cG}~Jƪ=edE6['mLDyX*ފaC.{-KUۓL6Wxt,~hishk5wMʵ.7bİ'P r /;-/<.\*#L6Z&ƈX.bѲ([Æ>F?<;`3wy>jk+מKA9l{|bԼ_QwuںbsG/(~`S{~мT!<5DUUCD䢵uh7L "4mֈ1|yg-G[n"B9a?tCADlmFXk:Gpp 򿏊^>/rʂ Q=haTWQQLYd22:[w斩Y{x^X<ɿ1#ZcuoP`p!C.;cO p"ۆ>Kֶ6[SO|*"t}ˣ8`]_Di.}{|sJܵ!qOtfdv,7ў9l?sN}d;?"@K:m S+H%%k|[,ytT,WB5l ( ]\^g_۟UVj]QTP}Uvٗu2I&̽}>}2s̝s#I=rj[7|GBIҊAc ;t 3C+%Ѫ8hhܵ-Y#f1ͫ FR\2s+u˕nL`KNt2/=^UIh^`< =zv}EW\5);sJ*oʡgl;_Ff '{" iO%AuѣFcނ{rl6;~QҋQ@9[AAci)(˒uъ?>g%y|(4W(%{r |2t>+ce WAް,o4ØQ+ۺVVmyIsդl6I6Gs @SWfP}+Y)1n!}+c?0J8IJbm_*>MNjh ?xUBQ&O3t}mXq+4oE*WdNmq)YrdАj뒿Vh:|]И,7A@$bn95VVA Sy}*ں-[MWYU/{:K$9nƸWշ~sVbܸ-KTyew:~;Sk0z:{^h4@tJd*>Cz%no֨2)MuP{M/LmtH/M|#ۀMNgbk[h̰\&uiӴtuכ(/QNGbb]JO:94y.K6mU;#ӭbcjR>=mԂOݝiH'r[b_F97!wxYSm]IWؘjKGoWZJc[ڮ&kځ*=K5ue+1:fA?K6n[s42Y>P뜝pk E,%c@ҼxVr)K-yT[%yO|V#4;Z*UU?bIWINQsP5`"y~} G69K?uIҙW<~ZeVk!ݛ/}Qr:KQZ뿵Ʋ -{^FN7/,Nԫ3/#iRu[7>jPjGD%:T|=MJKK5(%y리k^@4@ 5}D*wH3滔By5mʧZνZa򋆫,5x;4o~iڀ(3}F hZj,\zJ^(/p\{˫1}.8-Vx mCӦTV9F^_&WA}{%(%*5N8/a b[uzQnEQ7S kM>ݫ۹7]oι^G&K37_Zڣ50eM/z5p?GN'u[bw{4o|K$ -Vo ()3nf?ߘaз xHJUQ5.y~xT,{ Iڲ;CܬҲ!5Sk6]՝WOWFzUCUW/yUJ9ƚ-ٚ>Uώ\5iOu H I7mڹ&54tmr5B%3Qe#YJZ{͖l-];^%'uNfrVlu ui/kFԡ,3!ov>MO6E;%?9|X{5k{I>JUFiQtvMWM'@+9BWR} Jߟ|?EiqEd nte{hߡ U*T’ޚ>j-XJg7p+7P5 ϗzKhr{Z.k"8U’*,!u>SW]9ꍘ74ǫҾu {?9_+7i=7}q T8'BtogmO蚋~#->M<=, 6VLt'_RmzMW=-YX8Qr2Sݲj>[|~Նm穸|5x4ⵏk_k]T8^x<7Ov7twhS9!QfaNG;Ӏ~ui6f, =4Nc5?Bv2Nr w:ʔqXC(-xkս}@8|i OCw=0MRd1^xg6le[,=x׮O?ҚMõ`5M4"?1Ɨjгoݫ|OT3Qa416Gi_ĄRIRm}GuVupFjKv=pV$|H)I78R/h,.6_My0uzv iνz6r:5|U9&٣c~YϽuߩ@ +#m/Q-[88ѭ?R4TiѲZgM*8ꐺF9)DՊkTiYL=6}g/N%eS=$h,J^hxxPmٝ}C9m[*-s~!&ɘnC^rW.u#jH^ڼ+SUF7u;sPb½1$슮Nc2, IDAT_4W@-s}~JcSjdVJKqс9_0BeM[>XG悱b}YMXfplϡT͘wxwDT`cUE7քlOIj܈|/]=u_lD۴DޞWF'Wuo6kqh#iՆ>rh6z=Jw,nL31SVhȗ5y1Kм7I!GϾn{Kl+Fo рwy]L{E鞠c~M}hC|qOYgYw\̰K4b,7'i7khɺߪW!+P `kv%7byϿ=^EGpTc2Z]JU-OA*,oxiEzsBM!%+\t*2^ütTWߒ\_76p]wjBM+ں;2%Iߣf0]9@)I^\4uqp^ ۿ3lۮ_ 4)I^wZAUN#O7|SK6I2RӢUo᰻tZWt׵3 $}&}=-hzWԇ 7hh*WMB玒ϟbr Qn7r5}/s(U}{Ͷ h`?=sP2 Cӿ}t1aua5_>?wS9yKTQ5Ԥ8YM:w/iwfA aZPs3m\k>ݫ]򌇾NcӧU`!c__pԧ{hЧ$yӻ^oj_󭏿냅SVWm_hXca:ga yG*-Y|O]h&]2vwTչkzyCjfl^yR8\7!^@6=$ ^5C4keQؖvK93h!/M3y+4n4oz(SM;"\}R.={||cX ;C9rPqsW 甖_]f讫.uAFH& ==~Wݦn9ݳoݷGoJw4Z^l/0E7_hg{tuUPJOSUFK듍?Rj6yV>Gڞo?^;UҟDWNޢںGbc=$_'_׏o+Zz5xHb uåKZ_[1!uݚ< =ײy&Wk*()|h|fݭ+'*1]ދ1Ɖ~|[z4\,VW_x2=p{QF_v^O|-ca͘ON<ifOMZyĴ_0GL~wrϟKX_4cyྠ1ݥ.Eȍ1Nt5+祠1r:LnqLӯo8pU&x:k%'=!lo_X|mT5ߦR7nqG f o`6nҊ @9ky5vدCnqKL~/),ihoAJ$%d'K h@'}O4o>1O6ugoAVm6Ύ=amb߭[&$5zU^5ΊrO68qڿBj"aW++dߡ;4kqVt+Iqk4zaiajFY{ RT⸺~ZGHRIMg/UF'd^:${ˊuo/:oY];@>X= 11Qjǿ4c*co*tɮ]WJ#=! *bxOn/I/8xEq{jSM816G]JT9}<)If1w@7׸EKlV~ፆYƏ#}_I2| OuT4{HBT'ާދ*$яh"\kڔO#}K0hsĹ[R:GԽ憐š{ߍ+!~AԦk'DY][lv/խ;)I^}pTJj?ڗ]4=ϱd/͒}=X'gIy}*žy'4 h+X*w cf-@ &+> ]Fc͋8߉2ӗ([ϧtw0hsGZN7[ߌhnC۴L | Z?Rg3h枟~< ݂b um4|qsoЖQJN*$M<6}ˆ~zqAc6GW_RĹ%kVlr)}6~VR/u lވ2cguvk>fؗ k,@.<#h 13-3?0UT۲T\60޹:ghnn7][sϟb 5_3&ԋd-#Җjڔ u:-9SF(9qa|۞țVf|oSRz~MG$˹1#BLRbwcEDy 9ؾ1ՒcuӷǶ&XF^|w= mW-δr:K-DS^7OR"o(p̍ѺT9YO{i`gBSoB̵qmvjTDvKӑɆgiPr֠^9iޚu&qe?RkU:'vbΘvax2UTMi[t?z`zM鞈^[wFQU7s .Uors?!/=\NmE% **0nytIҠ*g5O4߇Qw^9u࠱1:Ru rՆr593%(=og?o%u|~K65?w\Tڎ\s:1ZIRVM<@Nة~]sZ&Wy:Td=he.7>/? Q+,[Srؑu2.\_y\?׊%IY 2nk}lX̅fYdq:|xUm,I]#0|UW0޿ui݌jp-[cZl^]2.ͯ, kvVIy|Ĺ۳F]%G/0$nR^IJSQp{Ɇ5ΨtK'|f/I<-]5q:Ku[%_*-x ?QvΥ/Љ bސayİy5x;796ya9YRrk< ]UYe|l^]adϡTU90X1wwiP2g@OαlhVv cG5q{9Skr,,hl>]8fcװw4¸ϔ䵪DtUBxiIZrMas ҝuhׁa`Z3|~݃MtxUNM>h/*fh-{aU;L[vgEgӐs‘&yay)zgΨשUY&Ǻw1TU'v{Ii RT:45So@m]N!K)0C{Huƫku*ܵia_xZ M+3}J-M٪\ƯylLM'$))ѧ;Y֜5[l :O%[euɱ1.X+)Qy1ͫay%T^oe9ZV&߼Y}8&;kNo$uH5nԚVkTKo rRMl89i}~6e.[yPA98QҲyg9MͧK, in\n>C z$Oc| %O1y*PlLbV_ƝkO;1qE߽VFs |i<'Mn{?2u\nV~K zU5ŷ8Ѹ1M {3ӭm@1m6đ8{o-I q֟ؾ:IO5Ԕ*0ڴrS]eee:1547:?0_bh|+a<ԯUSOL>v$mۓa 5 UJW2CE%vh&zuqNjFjoAr7:fOM$O6cieh/ZrpXlL}cWxZu Nlk1pw7xn"LI,R"wZ|ޭYo k|MF;fk-ך6m*nO?5}0N2Ӭ3=+>]u17&rK7Y{K~᫨6zR/e[-k9_oQWOKи捼)?cͣ/XdwnԼD Y74]gtU oroys!d]?Ҽmaks*V>q=Ik:ΓR]C'xFzU|#ْs^U+m!4lFi)gܒ#_KR[k^gb+lfo6o.+CgA%_׾nyt@[x_qXvL(UDŽ+6򜡚:a:$OCNҸ/u`bV(s͏6vp'J2o4kuUsBq[ޘUα$ߩnosБd%>u_vh/y,gv~VgLic^Ӊ\1TqM6>4&?JIh?s^$믓6H4Zyl|+MV8l |qqcrOj뷕.Yu𶠱 [{ h|oA*68#w^бri1ܵzsgTlX硢M82ӗ(#=τ K7tCjeQ率T?ZŶ8{?:5m#0dfkTR5|?SX'_ܵLWO1c2S*)-|ނڸhIc RҬ lySkQF*'KKdru&4uS^i~; vDB=Ƭڣ#woUћsڋ6;k81;A^vRm={s@#t5=TW߿ɱ7Ǽiފ?O4[|F7L]?u^ڡ9Nd7EmK69wCyI[5g4uŽpKGp!t|o{A洛@ ږk_S^8Ӹ?jkIPK1Nڣm[wM4lxRY uVz*UT/:2QR+>mN󈮺psбլ%2m!m~rZ -k? '/lƛ7A|fGL+?41wzWDjN4^VzYb)7NMmv:]53?/_7)׾9q5q)V7I8&9ѼGyqmv|r596|qI8i衙7;T\:ɱYLjӯ!0U?c4QvGwc[+U!9pԚDm*>b& ^q-HSSV}:McA~ uc< i^g]ImK1Z m/+|xo+lb@RkQBs]KZm4elAc~ޙ{NWOednuNGa|͎M;XNM>˼ GK|~tQJ7uI3尛'oDccRB@s |)64^nqIjh4߸93ռLoyl@)Cbc7o B\gܬ毵z|#!i_'I^.`|֟7>o{BӸߟ=R[e킒lӸYg|=?r07yӊҦr[nXUE r:ۺkR ;5nD~kxIyY:aGTk|+ʺ^ vh/%'o:=T|da,!~&{ ZM|\i|pݧFCPx*1r4^VmuRQ4fܡegn57X{KװqCn7i{VYhOxbn8_ڵ/!OCF4q䂐z:^_X0#q*<ɘ]jo~x QM4*zNi|#_m/(a:Kg,6_1ޜ9#mSu.YMG+U^ii֔ͥX]W$Vc̯j믓*y1LpjH=`kh.fzpjIJ)1q’^v_vGMX:GΗQc5 ^/-3$ַq}ላ1n&I-W}c7 +URv6-Z55mei> L4@S]\UXhZK֝kټ?j:Ӹ!!ʝnIc:e4yrdᨬmm6e' kczNm5ewh:oņ17KsV7׬5n0='kap[ IzpK\ и_)O\LabH5WZq%y;@Ik  6ߵJ9¤ofkhޖdMkwIsc~a|.HUQ'Iʼn:R60~iENQ5Iz=MC5cXkav^|ϚM/Θ&/ 3RJR1Í5z;)^(h)6jhȀ~5 LyzhAQsoJ lOԿ˦c>| `+F+7 h]_Mpݵ7>kf|M'&XR]qᶐvGzwmzk%ԝ,z \S'0mz$Di"}W0joAJTk[[Ƴ;~._aJ,Y hn. il󈮺pSDUfJ17ܧ(6O8G{>RRVx{&>MOx< }',@s 4?R>e_Gy훳j\ИQ[z:\mk˾#f2XeUm7-mL_hwV\tzD頵[ae~A΢J5{J'7;>WBΑwFْiZ Juy Nm{Z67hK_I[Ox#{~%%tV$hE|7Yf.|T@ɉ-De~HF:zNLl6aG|ranSEhJlwrث ǔMգτ]#OJ.$l^^ 4@p\?WY{zoBuɚZaI7wټ{cxsد:aRƫ\n NwC'P|r:ʂ8YV]T$)5iMu.u4كnlOҼ#ʽl}zvH[ +lWn|T>9s+?sI;/N FWK3 =oL$I6Gɉ@2Q]ut -ޜ5T_S?v_S_dQabծQzoWNڐXp|X~rh@=k/vFS;K@ߏ/Y5[wO{HnܖWg\58~#ϭj'+i2ԆϞ74߳o\ח!Ir:jݹ#,d:aVVk6_O ]9yK9ߚ=X\^_fb{ݓԯY%G,V'^|\7_k + )'͏~%OCNxNM3#|2OծQAEWOe< Msmޕ7fݫǏ%U>5,[M}И?o8K?x6Wyְ7"_IӫVM=}i'󔗻PS'lRRbfG*Qړ?IUs KKYn{TahۂXy.~L7uӬjVJK^Jh197HIc$^+~*-pLޛZ*v^d̦v@+=v@gvvcVkhv<+s~ϛ/wU 36HMAM+`p h|eg.UFZ:UR|jɪtuPI*MfunC?m*5O~p1ڇk 4t z~gƮhKoRxA7?wzLđ 7B+X ͫlǵq{u}'9s$3!U1#1F %bEeKkV[Vmkaۭ^U[Vw^7+Q* L !!H d!3s\2yygn^|"Rc9r1uk~ .ǣu޿\Xc~xl8US~sCo>s)׼w]p|:wT;|j?;;wFG[_tu<;0OǁCƑ"ׯT*_Xh`ܽSWr<܇+qOR|4J1|xlGԥK(S;^*7KF2u}>+rΏ~I.z_}ʹg_X~-S=.ްodIӽog+/YG?w:㣣ǽߧ9cuS~{;=hǸ{xSjƥ>c7|bqM%RS_oW}zV#cVyX?/|\~ѓ拈K_d?lz͘kN&ֿoܹ#Vv{s/g?wpSտDJ3^J_q:_s8^߿㹣o -2q[6 xg"_Y˭w{_ć꘿{$7-wWDHn҃;WQKpgYy9w?c˲Xҥ~!~r{xsGcǎ#KQT*bۘevPgqO'/o4#7zjFDhg g)N.d'5o2Xߋ|qesb_Ǿމ\tk{߫bscɵNl3r jprh^ 3kJq6Lx{g/-S~%􍧢p46|)"]qg2GldL8X;K=?cLDDݱqH.5Rs /Khlc/,EC{;3hy!mGGyyH\Wn;ź.,;lCK&c${\;xetʱcɮɡS#"Nf3nkOLhFgH~;WlgU*΋7 ZLe@X/?~k\W(R/nK𳷕/ Lr ά.]zyR1펑lwgR:w}9w %燮zr-/T*E0 1m}uG{eY_%^Ɔ}q٦O6=Qtɻ'(|"""_1vËRXk~#}g)uR5kH>^N޾5޺xꙷI]JƂ=5k.ҩܤ5??"*ylYeį.D-K/-f[bkj^Sr j.]gSmmxhU(&5#99|uGtѸ'Ş}D ADD)<Wmtaҟ}^' _W_}S<51x)G&s$^w}=.׽SҤRZ_)Kf#e9|'9ᇳO-y)ҥ!]qwG]b.E{=S?7qśvTWeh@9lZ 6cѕ+7?/m{emīoxwԥKcμڛ# Xy!Hg&cҞ=w<8ܺ|Y亢XhBq^Duo苦Cѱ8;Or+Sq۽DWHW䋭/FD*NF}X4%Ɔ}?6\EM;vmcϿ*GVF>RcÑNGKӾhtǕ)TG:7Ce1B%`ԥh,l+\3:f+󃯈lWQ(Ώ`eNDSco,7^3޺:/_ќjo߳>:/N>朏|1}?%NG7xX綞;OL֘}1v>U}D.MO?r]JK>ݛt 2I-k?"O:ʘҥxۦ'm%g|x{1]~ѓqEO&5W<&*k({q-cO'ZN:0cp8PkNf"[5湖Nj_Rfs vU8 s 0s0Z6?1^S}`c@<&=R>wǛƥ>]}`I9oo,۬ɞ=+V}`c@Ҟ{k>j?x=>GKU(|Z*#"ࢾIdz"4 2^XJ%w$ >1lnx-d#_W1p0\fu47펈3/,ƈhmy(v' B1wbLq{TqgֺR1^1FDD- $jr 2{=VLδnH<+4Y+t:/KEgZ;2SLk,^qbUG9T|P֏~"k[Hg:d:ܾ}]KSYpQ֙4a7DDisRcs+jrq#eo^T@ QUh93ѵG-|OR&>_W1ppsMؚܴ{bsܹ +\(]p,Y`*(*tHO -ƈ6&\R1OXl;l*hjnoO[ѵdK5J9TBdI c* ;rgT,5]=o;&+\>(θ'_PSnIdL#Rut Ś+.ug\W,6ǝ|6nyH5q7z~ܷQ*՟qmCPH0P L#S.ƈ\=4\R)F[ --57z~'Tj[f80(*wYctTDAt1""[FTQ&mdL#"{}@PU**G2mS~& 2[qpQrf*z"4IdL#K6T@sg:DAF91u˙֬ݓ&cDD<K;g(0(*ѭR3cH]}]9<ұK0bѭ3OS-ƈZ2QU¡LΙ?~tf:ܾ}]KS?ΘqkVQcDD_( P冚re)1?xT 2Vn_K?.M;H*{vŻ.}bJtAt12beNQ1k(K9F 2ZLa{i4[Y3>SmS~ 2[ѾpKsr r{=VJOkd 2]12RjJ$ƸSqm%Wn }*ƈ^Q=c@˧ őL~[9gN [#"ࢾ765"JӚQ,6ǝ|6nyӚs};>RRqӚ1 4g{=sHՍWwD1u9sFYM=Xj|z7z~'Tjv֖bi Pt)TB+;*:ʽ_!Wf(6_H:K7T kg=_75C9Ը얤3H*{L:ƌ]Q>t15dŦ 1m;ٓte ۳M٤s̸L1Z%cL gWa PӔc@{f]tD9~dedVųU]^W\@MR5.*G2mI&b6o艈R1^$K6H:0w(9`9ۓt|]qosTܴ̚;/P,N:0w(9@瑞R)JI6fEIGE-n]ܵdvjr چ I8>oh3]~<0G 5d-EHEvOѝfHcDDDCPlZ ܢ戣m'-e ۳M$3$>SmILj[=1`xf]tDR$ZΑU]޻W̎|P%E}[;q7DD) h\qG9I9!ٞ$v LbYahnڝh֖bip9I9!:JQن\"d?%LD5 ="IJk/wg5! k{b_{:HD&"t,Ij+W잮;+luŻ{ODV2b "I*hɞJgFsVu["{/ܒȾgUJOTjqj,Wx@bcO#JwpQJ5mJ%wTtOPs@s{;Vbfu47螭- 1`:yTLmU,ngҵ3y"JF(vDU(dfjE}[gjvۼ'"J3G*5l1{Lr =317_W1pp&fׄ5iP,=&@9a:JQ*lCnFJ7jxI:B}qw&^Sι )f8==36:j'"'>_N:091`jW잮;6V]qQ(}vCPlZsfiD*(䥓$hɞre ۳MrάIbn \)Ps3ˏ*'5oee5mU|V+<`Ps\>U(d5ࢾU6o艈RYgRqe 0 1hcNc`9f kVGslmy(vu&4d$@瑞T*RәmȕdcNYzהm^E?$(IJ9>?9gΔݥ7PQQPQJ#3QccL|Ҍc%(XP1EEP齷]NĄefwʖ뺼paf\WlԐ*3Do7#j3B3!ILt4T9<`ȳm(i=&]/Y^kF^Qp I*Llp yհ[AJ̥QE  ׇ`YCW$5\U6:]|a8$@c7t234ԸBa>M2:?ʒo ڗsx_O*Qkcz%vQJ5V@QӻwV<՞LQj@AQٔX9=zڏWNKjO@?T[2 8jfh ^64I bز)y Qi|p 0iQmj1vQ<[?f8E%{p7#PR`ۣ@;ha @d&:hZȆ,lm[:FqZIcf5hڧvG9@TX :LW\e̔<WQ(4#UI3­Z=.'ZTz xmVfdM@ 7U>-31' qTz՜P2#hS_f0Ctn[Р=:&Lt:-k_vxdjzT$FU{&-˯a*Fa8Ќ #bRڸ_ɱ @ߨY20haUEaK ǐ/{f8Re!ÐQW-WV#7@;?mhD@!g "Z<ВWT1!=֟4i )3)[+35-Shi.nm[:NqZIcfUk1N( `ABAh +=.'^yZ Tzڊj28 @1h+F&R|Ohivt(\2BHf0Ctn[`1:b8ЌU{+z{b@K7A_m/gVe܈I:k ç V'@Y*>}PL6w|/ @KR\]ܵ/|o"2 O*QkݽkIKF # IDAT@rvuJ;UqPTzD1f,PK_+ޒaj/ UL_8-e!Ðq QZܬWpyHu՜9Q7jOG@4q |ڿzKJ 4a ZPH=)Ht-LazeAzUҌZ=.'ZTz ;XV$j6>'t-Ǔs4͜`]Wϗa$ǣ޿y**4EfmG!3XDiU:ёϠEYSJOsi5V\4w}Kg0$YNNfGr$0!h7AX{s%2O2nD1OFLLa_N%8aYM@f97ֶm5]FO<T4}e.}ܵ/|oO*QkݽZZ7j_TMގJCQ(䬭ڰJ ڬzMήM Nj6k߯_Xu>ZT`8ZJKϼ:519qzo5%ziӪl|m>=A=Lӭ@kvr7G@%i4-W^o58ݐi]vgNp p cXڲDJ;Ucv/;P{vLtV4M]Q=Wm-;% Irmp ,<8ڡCc4G J W=+UV5ёs2.1 ,mzԙ?Qu^|Q;,C"Dδk]۫ygu8$ԆV_v=u([ůRI`:ve$W3 -];z10Jo>Z =n3c@.ˑrkafw2MZ$9j?^X$ `8@Х4i\S@cu'7MW?P+jTKni.,n gn-^^pfBϾ"N/\Ro]Ws!hNi-n2HK=ȱO!o )PM!'y[93)[V2tHK r(P)Vp.[FS+S2#[]ƚ%u:y}5nT\\B!} ]1n\5KyF?`h16Jx^!GǑa=\F![Fg%etVR$IÛU^En(eD-w4w"4,IJf#Ô-+ߥR0 T,6GQJt_0㙮L9:i5-%nޒB$NI:ԡvp4s2={:5n@|>^B=̛b8=[FgvO=`=VvToȷwyL:-ϊj`UQT5)J]ό^S&WIr$OT-6zÐJ9:VKn6Ni@.׽5ɻݬRky̤oiKkQ+enDeO|hetVYW6|Q F({Z˳壨N4+'AGam˻@"ߞeh1̠![>+4K^Bŀ-\3!?(皏d2bٰ_㘤u1B!Mȿޯʪ8j&꺪aՖ=kΞ3/6e Cߥ J-Aw|{p{ŴLn#+˖1잦+Ch3K!_uDZ {!ʙ1_fj۸urH_PAwIo [ze]FE_A_A evLIYpY9=ϰJz^CO ojNð![+r*<$epeɖV#deT9Q_JP2Fnho_)7K9|([f׸2F93h Vm-$Hu[gBi6 TM[@~_67c:}~-]:i@+p 4̩͡/)ea |ry]U_Y5AJ|OLWfX$ !+o^:i/ӕѠ}X,5 U}$Þ,35_VN/9:׿ Pִ7T4ܰ{ʹa Pe^%Zc&Jt\}.a7,u@W-B_X|9:AW>PڙRl3s>.wǧ\#vG~X#C{nKfIc쪜гDTεLk`!yw|*ߡ RC {̔#YᲷ,fQU1 } >p^"NV(J_Sf0kki',ǿ>_EGר5c8r*kڛrwT:6E*>KJigޯԑ U(ўfٳ\:Ù?ʞdRD<#z˪\|Vy~=li4 Q'wvl8R3>} KTT쩈 ^ߖ-JInHlԠz+*+G6Xq@oʽM2*m/׾ʥOȻK)wAW+yM{p~2/|VQ@cf K y4ɽ6N0Mӊ:Ż+5%5dI|mϯi=?PEfztЊ1Mi)k?y~X塀Wݢ5jې\e)=ͰVnUu U+6jILޝ[gcm3c䜈ֹ7~@qd7hboUQMiaIy<;>ozǓ\ gx7Q}HnId1C߾̩e2ݳ Uƕr;*%Uֵ^=GYS_*m}Y&!"ioʞ?0eߨݛݵ$uAwV YcxetRs/>?:*1 ?#*^ZB_:?j1 ;U2/~Qfj~s'>!5U_FF<WCO wcY?2&=0PH'D<6*>CϜ"oO8~dKv1#*:m,M]`TPkˎZRԳvihRߨ7W +qso(. e\ts4)P^ RUܔT*|~yݖ^i~=p(y2SۆUۻ\LQW>ήg*m1=S!'*T4{=͓=`_C}5ķ'u7nARPW KJ=Reމ5/W40!81dY~#aW|׿ILhi׾U/|@V#a Cgʞ?(Ɖ0,2Y2k xk^WG=KWï^*Ǽ2ZfRVk(32<5_Q_D`E^@]ǼϙqL:\WA:m$Mߏ+ ^_:=k{j}wG0MiSE/ȰaQ~P=~N?.35?ɰ']*>]8!6^)~v}هOm1L_UO_!.{5{ܛW7/4mJuGIJ9>)@6 _!orBʘ􏆴 hXwOc8&Xq@ů].Yp=Pm~3$-_NR4hm"E9Qugn냒ʏ{6_=wDJt&)#naח~pB_ Wق3Y#Z32}Do\d2FI. >wPlolꕳ4xFLir*ssaUS_m>}t=+?%{V,z@I}4pܥ`塘e1S*mtdCJ߿UAwi(ު\>}ž?P)'_`@f;/Uq=oT 9MvUr9Cq֍DMSnBCWnH.-$]>>\>^}V?flGN>YO9Ey}(CfYJʒ\A_%%*ݳG%;w ڽ|v0ѿF1LS]FRn l9dKK)+Sɮ]ڻr6~*kMEf9a }ӭ\rr䭨\%%*ܼY6iʕڲhKtA=:KGV!Cl,KjUhXV[/֖ U]R؈á.#G+woԼ<%geɞ$|UU{<*߿_%wdN[XVW"ѿ 4w)Ot.WzzC6hf]R.Ts;Ygj?xRss#f c1JӰ;>g˂&:Vl*=V}Ҁi&/m̽W.}\1Jse2,W\ WYeu_|_AVqݷ/qw`{qdu咫%ZL̲-AlߍYUk+ j_۷Ҋٳ5uW+-?:{rԮr{VϳΒ$~m? ~rgD%s:~}Tz&Cz2}6)+KIYYY :؁m IDATuWU𗿨dXFVf+*vX:k3nULi]RRV'jwvkͼy΂IZt߯c*?p}Ӳw$s„zϯ)IJkV 9Xמ֖O>mb|eOnݔsIQ鵫@juk]U<]o^}U^|1Ϭ.]ԦgϨ yر:[衇kٲI}}Viۿ?OH0cIu̔+x$ijV1M^a*uI@<霰C*<D VW/(eMq۳>#Ui(?<[>aUzYiHB($O{zcH 0iJT|dI(S(sq۳>̮Jpy B!U|:NXKS؟mO@|s9Ivg:Jj<0LI#v IQomsuG"nitZޢǐ.ѡm'!_]T#aqq{A}5g6-?_,hL'-Dsk :ցWξ>}]}iQHz^ Ǩzzxm^cyyz m\|teZ7ĭф{Րg]wi]w5KW\o^y}2i7ݤ3cke4|я.+Ӛsblm׿ O"2c&QةtL]~k :ցOK_h;h~~'|eySg-|R㱴|Mӟ4dڴ>77d Y?_s@OR]k[nZvuOrwNSbON֔Gя׬ѐiӢ2hi|/Ǝ]k6x0 РK/՝V˨QQHD4uްA~8j1jJO)3f膏>Ooׄ{UjnnC))5|Qq44ݹr2&gSrҙgk6x0 .ӝWȑQHh&9aWy=a^dSnQ3,\.hMcvۻ<&eXk7 x|c3ɵ9xq?<Ûê3,LxeDVʽY+TҀȦV%(!&4e |ہsFxgTi5i Vɒe үt2ϯϽʨd3-MgII:?͋+k׸]qw߭y]o|n^X9?2ۗ/o045kuʕ1n\LA^a?۴|Zٽ8•y eu׽3:vy>ܩO=Ν?C9vlv{L2LSgԝ+W3ό^6>PrvvTk7? @|#w{+FI{ |a'Z#%|'ٺ jNB= *Y@&o҄ 4a0,۲1&fЋ{ü{Ƚ~n ծ3aj=GgHUKer=; DpGKa*JT~p.xuA~@dAڰA6lPeaBa>hGR jPh1 C?&2>"Ȉ>0hTݺdh]šC*ܴIVV֭{<I7|FD.Q~YNgL;RRto*WGldw 4k"Z Uwmؠ׫!\.Mto~>̠K/,Q^>;yx6ވgvY h̙K&٦geZ+m26-%1LTr,atz58Y%8zE &J˄}"l[$Y^(S]"3)Z+'"0®쐿hcsó 2l|\Hϙ=K(Mܛ= Vc$z&5+k=bҩoWm߭*U$G#^pKb՞u]YSy=:oت3~O/>'j f\m XV`]}X?^}?_)m;-?_7.\vO܈2>%b% (w˰\ =mb$=yJ0c;E}oG 'rKW%U =[ aoU̔e$_->ԢJ yj#GǑ `-{Dk|Q2X򃞲81BlaeLDk/I=:Lm++Ŀ$KSx"h~[Ǽ6c4 jN]m1##XK^Z]1B6.^-@'nt)/k_֠~[_>yU]?kХYݵ=[ϝ{Ba>׬?vL} ه'kܹa(O^kK/iyڹlsf+5?_GРK/ՠSes8s½jv&/]q^Zw3F7}I}^:-_%AclS4}xoe>ya-}y]4ujC6 x@ xΚW\̉S8Q>LXwc}#Oa:5 C=wЖ0~k衇衇pE9{ۻ Y{=QYc&:GQ2XQ'+_$-;ml„(̻b5@(MB!kGAnnlLgݓ#ٻ8{}̙ 3Лt(1Fz&zM~ɽ4c1Ɔ] MD^3~.Q9{Ϝϙ0{}|(5'ns̶598MI9.{& wm5]k R4y|6iIj )pamswd s /=`KkNJ~c|QA̘11QfsZQPR{ڻVү'Lfc/3MS+g֯ƏךW_=hkw-ÓDzV o<~qikmWcw4g?5G-5pKըQ:Y2л~>hfpּAK_wgiٚ5s~9v͛g9oY~|eYy .߯+}V= z7}zc|сu}uht\СdRO]}uc41hY |{z,HniRy4sfV^kȏ"OQ9=ƺDf[s&;TMn{ 3ۇ~sw *Awc{LS+Z]u9o=K(Ѵáj ȝ@ܗv,KXIt]´cԇ[gՖkj]5%jF[^1;PAIIrs!C,\R:$m|=pZ9{A??i&ao=zd}g?YZڏ%<1_?}JlWPnhg-Sa2}>|as*;%g4ujƸKꑋ/V<J^3owzSayyعnzo˫?Κ1}3Ԙ*T_y^&[uW%>jy'?nic3M%W})Sp>lP%%s|м[y {[>HjyJzd;Q$_CyYzd^ -׿=K7Xrjdv>=\tt5"BIcCW!:RbLmpl 12Z*c}7踗{֑@7嗵'i} 4uz6JnQoygk8`):ߵۼg8kW>@Bs  fY֎"m5ǰL"{V*Ub]2ܘG*IJA#)thysZaxߐ$׿$ R70 )NseAڱ'Sq1R xsJ{˙23{꟒tbHVVa9$M}Gk'ۻg4M8~,mƝ_Zxܑ:v&oTݖD4kLݴ|Œ²= i?aƸD,g^Dv韛~W׾rsS͓Lf;Nfy$q=sue2 X04jt}pʉwg$X568RGӮ]? ˑZumeKbz볾wE7]KcϹNuQ2M35kmwc%K_ h-d+>ѼۡJK6-P%3ۇ'v8Tuf)g=2|$[l>QI{O~w$3sdmOB#.WptI@hW)o+>/H й9y:6)XiIT=5޺CEj-d#%`J,jFH0I4u#ϩojڻto~[{׮u4ڹs4hԴ1e}jg }55jtKk^yE=Lkط~޼NMe[ڵj#5ymz] =6O#f'p+FgDz+髯jYgeq4=vlƸ-[pEݧSoUe9;?Ci'8R9sy 96L ]O=HV|q\Y˷/[nNs $c Ox؊7c-UbWldRebmv5Ds[f.=*xmweOY]W1L%'P3e3㸫ŋɃ?q@<Ş [7ϣ˕t-7v앂1쭰֦Gaz>:]57[>WC+So1a/|a{#Q}Ϗk֭),]?+ym6ǐI3g:[ng^񏳞~+﨤ڱV7NG̘14MͿGk睺&Cs I:is^欮G-5xY͋n;P<qx8w~;_:?^.(c[/1?1Bz1Gp'vR8冯P^U.oi_sC2#8\g߭VkUc W OR~\f<"3/c̃?ÆB})%\Yi\RƷֶ?j/BZ?:U'| #,,оW_UM/yQnޜ5ϟo)#akQIUUƸHSΚBEGV|U-a*fY[؎%dUӞ?˹:inN K;Å|ThҌвg{͸ߧm+5Mt/_԰>5؈ѱ\ySc.vF=suM1<29"KqPHY͝J;IgҥdKbZ.T$yeKq_pF'NߺUۗ#6/\h)bh{N\ᆱD4iGIUp6+u}'s8+/s`j@fgδkmպy`[-r5_wdglqs%bM;J5r֜i+4µ $O $I+Lt0liW-6_ofwD4ut&zݕjꞹT=@_*}qVc䌇my|L 9F̟p:٬[?L;dF^?aLOCX-#l׺Nߣ6z^;1$}=K)=@K FkmmƘcd-ϷvܬC6LLshT{֬{4ۜ<ܽavKqՀy^BK[/V,rX|MdK]?''c ?ö Cg6_ojyv}=CNVwؚ 7K'( K(\hW3%?? 7 bcQJoK9^8khaR56m^)EfT?=qR$5l!I鬇fK:JsoI'YuΕ+F]w3ΰe"+9ՃpÕ[,8V6`UTXݲpqN5zsGYig'_ $ɌdZ7Vc'`/UfՁJr3fkg =Uֽivg\\)yӎ}4ǰ*Ѭo.N;~WIo+O7}6|p϶i|I#{sdAF@)^( z+V?`IJ3S֯_Vr{D4˖e%x\֯Wc])IO=RiWs08Q%%47;\vRo(挸\~ic?[Jқӟk޳''t5v-.;V.$miq5ܩHSc7l9݆tz{=AB>2;ks  ͌Mp-p=i&m)v43nc IfgYp%2l|g{>Vҿ:XQH&hckJ>| Pן]iښ E|]GA4us7a~솖Tl> `\ONZuǤ" :O$Y -6cHR͈P=.p?գFYEs Ϸ,~V+Irl1Wrߑx$Mٸ6`_q^*RlΝJbW7`l4MK ?ie{ R*n-vd^)<ַ|8d=u)?΂fڊotl!oi_lLso&JZf)Og)S!CTһJTPZ*#EQe9wjhp5]Q~{p8F;oDBع~ow䂭?B]x 1'm{JH'/ZTq=[-`Axrx{2\d^xK9Xbp9u/;TMI4nn؞m5وuQۃc`ɖ;R/C/׮W G_`9[=v,_ŞYBǼ2c@b:&}w M9$XJ5}OzLk]ϛJC;cHRi"I'=jUkteԩ:?Γ亜)w8,(.ր)S&51JzL$\j?7>x$x'`%ȅΰFZZ,5([#3t?h۹T2Mɰ7l7.XP1߽RyxoEu؎cX-h([-9TI~VQP*OIo%w;TQfrݣ`=/fNs\'p{q|*RntctYs: ѩ%CJo5{CX)5'k5LjV${n[(3aE\g3+ӎEhʧ o)_Ęc#h/:=|V4tp~qv^pfqяt*,wYg+(kmusZQϞ.݈?er]p@d*>:Sƚ@mG־P%hڡU " Pq9}@yb;(sTxKV\FNɦ2]'Pl+>s6Td|'̴<%0xi:XT6;'fŶ- :^r6Mn@Di"t>biztM;JNWHfúct R^><8GKRD~ t<mQ3"+e4+s-;9Jgs:[mϋG"lmYH7*T_P}⑈u&\xz# jst%=;' ']17"Ɍee_=N*%[:\YzN^;ǡJr+ {1,S jQ29c =91JOCGKLԮ_ucfV 8TI[I=dhZ7l68Qx]%SC^*D6a9Ɛ+&_Xy {,? 7 ﷍ܿ3iB|aЁj./UIC!cTŮSX~iL FGʤjxOv}U{g#8ܱ]g1ƶŋo~{v#T5 $m8nshuxr]pmQ' l6&oi_?hئ7(wB8M`Oi[ȳsB=*h?kvF=+lR50-=EMHd&==8+>=O'oUӶgUM}))⋹[L'2=FF7t`w LЧS`0fGa\hjLݷĖ_#O"JKrĉ_ur智裵'Mfy{0}O򝿨(%staZ>fZV>`EP2?ԺR2`E [-d)T8.ÕpW]ϙJv"el8qD*)^RU bX1uT{[hz/J(U:3UV8s߳J]3PԼWBx*SekJ+*&iztZk+pUb!"rw%6ي ;_Zซe &5O"=~ώ‘P5ZǺ bc.Wy/רxL{pr^6}}YԘuJqo3 &x%0eؚuYguնݻC;OwO']1_LSMorhDg9uJ4f|?LD-$w }2 b?_<_%0<*:6C>UlR rO|[((u jTBS~xOqf:WP Ev!G[Z/3bJ*z\o=Ô1"tfT77O;|8}bE9jBڱ1lL٪Hfulj:W28|rMXDqan7{HDɄ{98WPjhkVt>і˱4Gzbt4@ }bVX/9{'WN)vX3RUqBͲ5P5ซ]gTӼY9XPjþ"_6cZR5\*3n;`iVt0_0Zq5/)%*wb*nPˇٚS-믢)Z>@|2cQ6Z t=iyG})bW,q/Z}0RZ<67u\tcsBC귢]goKa;cM>Y]?64Mg9 :XMjW5e9݁ٹ~|9O,Oc I5~po!*>t0#?r|ˢ)ѸGwLD-P'8Xˎ/(/KOݵ$PfiTh23Qt[sJw Cqu_muʑ*:+\ )Oq+hy72͖ 8XN _+%Kj8sapN׵,L;PhmUX*spc ^!+yE7w4}i[-DBI4483,Ǿ8X/*r>@q˱n7]Νc9OO c /E6eZ/='|nxT~][nwY5W*#?&^g;#ꟿVfRj>W4 L*r_'mNs;YEqHb*.~V7z o@3謯a箒m硕O(gêJ)<`tAɓU Wyq8'Mc5}T+J-TRGcbM^:1֐j\l!I@.u7)^#FZݼpոoT0^\ͷ-8JgϚ5JƭW1d .w%t- y-u^~+u 9Y̖[xD 7uts?ؚS| @v:*z+o#̤f_خ}#Ty\yJd#P^6ŸG?5K%[ٚrz^2FaⲗT0|z̶x:5][s ^W#oyv~;Y] #y2\Wݺ@Mo[o$I 3Uy} ;Myf"jzY ?7%E48?4:;OKRܰvV5!ؘ_cbYQn4eyR0ˋfpړ/ϖ&R1tؚ $7~4Ǩ5|Vib{9F_o)WX~nt4@k]7[ssڃ2EY_|5bKooe%wg-n}֜±ƕ*u~zR%rhd&<3ڬWnxnf~o3.QI].ٺOl{^sTu%*vFu/uK}MQ $Oj31 JUv׵ ~x(ټ]k;靟+v9þW)80ڕS\s+^?LS/ 3ڮS@\I ɐnj~+4`{encWoPQc*-c3l皆,gz:gWd/yyBwv+rlJrcecsjhWEgKU.7:m~h96{ ijp־ӧ5)P驷[/x W~K;}zϾKCO<'U>u|8[IWeڸC~OeK6T+߳jz]|Uc-+kZߣȧ/*Ѵ9?Ut7T8B”qޗ~V/%b{2]a$U^5?_-^,PˇەlӮ*>*926&HjUJ/OҺ!Wښ9Tuk_RPtNV(x &^)oO喾ճZ:~ U,P9Te= 2[&.Q V k]ZK_D6[$PxK<ғ~Rx J4lms+P`4'\ซ튬{EFr=߳WdK@FS7Qc.Wnm֔c1ërEm0aF1O2K>SG>l8'*.Uv`663vm"˱z+u%$5ܩ-R_һ*+ZSBeU1z i[5|6MWF=Vg.t 4FOUkps[%+YRTt*8쌔ѧI?wxLF*xEY[O!X>0c\fԁ5QxLU^1W^~3Q=t׬U|*%)m "yKW9Bd \ujnۯe#RcHRaj8I3_ߔ<^}&_O(ɳl/34oY1*8떮dA.ocCN70T8<:Oun~[-o+QN%[L)Z gW}xe[>[MorWhiOؐŠdBuO_Kagؚ-DVRnHxHH>U eml`%{p[?:WxMlUQw]UJom3K[9Be?Jkޮ)m=ն8˟ȗH}~4>S>L;עc[bE֬>jB4~1Nfiq舚,jts  87 s=]s ,50 C9Fq* o;`d;R}Yvśo=bEn|˱ 7ݤU?bEcSiYtV')4kʦJedR3%#MR2.T-o!aq5νU cum lak_Z_s!ѴCz&}WWh21%j+^Vɖ2͒?(OQ<=.o{K4nWg);Cq݁ f9% 9]k*GW9BESP-Moݮ~ҡ5fZU;\C ]kx[>HmI/}:'l٫};ySk _p*/K%lΩ0Ap5)zK\ʚc+TR`s!cvV-۵kp6טL4[848fLj8xS-}ꘉ'ixJ>WTYJֽ|bk1s '`ݼyG"d>aNYs?ͦ&ۗ.u`[k*9C=օ6tE_K.V?s#lWAoi=^mCZCOQoT|̿h7U8ly{f1F|*{$cdl٫}e]{J)w=wG~ꞽ\{OK^|UcT8*rNN#awTnqmQպ/kUsWS4/0Q={0U<|:WviTg僻][?'.ws8 F}ih!]ݺHcҎ 5y ]Ⱥ׫O>"m?^ 3.5[;¡-8{2X-pϕVWOQ2n1gp5qpCּp5:̴c֯Wݖ-.V}dK{{R\URUp5;.U5W,t>=޵kUutIO4hN)ѴCO|]SvHަNRtBwFfY]g/S|}{j^~h ЖGjϮ7Hj0F<2]&:LDU·TŶtDt{Ĝ43}x:#j{jf+3j'fj7mҺ73;_ׇ=BUR#oĴ㟼+utu,WG̘w˥c'O_a.}e|As t~ɸBRhc K7C,oÊ֕+v̸{OĶt-%[9vNݬRkTt-Ա5w+j~x7 Y_ͤ;%74Qp*|dd!&TtxTˌ4v|n UԼUt*|uV"^Sh ~Nj;Ym4br{ZZܯT|I KkF5/b;eoYtW֛L8gf5ZݥITёW1Ck&[k,5ND()l9FZNEhiS6ǐ cblgälo,Ut.F@Pc;b{'ɣ&FE5@, ,;l/ŝ3;3 u].~k͎hUldvՀ!^g‹ HnUUr0rdty#|&NT|zzށz1{߰;ڡVUisiwjs07`YۺU,,,<=!=]Ҳ>mZPy≐1-Ugp 2 /Èx++5ǃ6E\+^kūŧ+0ŶH({jkV%ʗFy{wv|-(ӏߕ%Ϩl31i5P1-ʑEFL|>zٷ\U;˽k.G8BT2VLk$9255U.@͘kiɤIQ{|c㒒jvMWoe\=du\YB phmi_yl\we ڳbEDghL~׬[7u7NfΌ\oƙ~FVB~z**TQPP51#'/ϡuZe/?lϐL͙&'2=.U%23My*h([b$ 2rV6'E*s?Se7ْZȈM [\LOLGfe|,1PW?fϐ- SFLV_Yt3?NGނ]ʗ(I2챲-.EFl.gU7<ϡZ/-zRd˖B6gџJ~Oc?3B|AE'ʓ<,־"R.P8Td䉋Ֆ~=xUz%41J>QߩssMJ"!I"~HLScߞfMײiCO~bˬY!cH߯2"st3F{ڡ7q~NgQ|<.Wc!it?wci_FtM5WkoDtƮTK1 5%5wڴ=|owks_~9b{7FEEc'{qmK}ΔܬFRQT'UYR"OEbk ~@C5@4+ +.ρUrY"_)oVJq1?v-grylx7!0}w{d=ޞ^=z}ө |2F}Z+!fk}43t:< [W {NGl ";[&L<Mtz /0s4f߯Û7ݛufY߶5miA#Ù5ʔ9o>#AZꥡwy.z)ŧYs^E6cNc9b$3p2aNDɧ2[:.SuU$2Fo^~"$ {a06Ȗa5dD* IDAT@[J{W f>PL||XghD53k ޑܣ^Z5#Xn us5;eO.Ԫo߈k-륇iSOElƍA{2;vɩz'R;Pdzώ ?󌒲,\ٻ1'P޽#6ˀnPֲ^z?tǏ"uls^?!@#Uĺ8޲yoY":`KDqHccQajR=;Y:ߢY]W>-dJ2":K[4yg yϞWNqAܢLJe*;rD3/^f5CNeMiR͙͏3MS{:w=d>өMv>CQtտ%0,{> 1wSnWc-;. yIў=rA MMú{Ye=a?W7qb>ө۶ G֕Vcϧݧh|!]?es8B^p[ַ/XZKUy8I9 7iDݠ>qRȅx +fVDl-ŭ%_\ZBi\/WJWSprXܐ7yX{ڿvm^Oz{CIj =rg761Q7͜7T -SV.'_ڳ|y /kɒ}i[V=¶w/ }Tc†O>w ۞ŋ8N6,5{_}23ּڵtikl}rо,6g2;v ˾oUlcr-#=ƾa&hۼyAK>jY0}<0~wLnmnؗٱZX4sf%?A7\cS h߾ƞmCZ+M{UzPZJJiٲzLӮ]^1GrsUUZz?uΝks[oyOh؄ 5ءA>'ӧo٤sgUYKR޽uG\Y\?))A?u,H?\?Vc߰ }%ddh#X7|<;CR _]tE0z`jrMAn9Z4ơw4,*N^0lP ࡲ2ݞN:GO%ʼny(_XR2nm7m/[$Kf{wF`'~*+Ң#ʡ/D7(d\)uiavڝiGNkvWmQjy[f*hD=xa[PK<*6ܢp5B6&zMR'/O1ňtX̚o9scQ#}exMR=oD߯_=tgon>,?_1!c,_ӧk߶M£߭OKSfjշ:}߼m>]m!|L^-|ِ笫m5!'GgRe4'OzC:Ǚawީw]ct4uxֶH^23F(>se=w8L5J[_ A/I=Oh6mt݇k4bvy֓ElbnO:U_=YCnMg?77wl~!-MCǏ׈{QRVVyyzqpٲ%go޾=sH+:K/i_4,mɓf@5^6L;/I5\TfֱyQH P4`l/_dY.Oʼn"kW*Pfa]`u1-K>vBf`Mȯ?ϰJ»NR\g- 9Cz`X>L2i;[~-QH5?YnʾZeu3F~ON{a 6bzKyb Xリ7q6N^#p.8|ns5=hu)*SڹdoڤCqdUBf4sQAd vy^^;^АoQ/?WwѾU*(PLB2ڵSױcdyYN` IrQtԩ2fL^W\^W\i̙ڷz v쐫@ͦ&M={ӹIHKUW:Ss^6L7u͠A~ծIYYjշo~1P7 ^o5'⋫_?۷Wױc5hy/ p 1CR|U*{0"٢0}^t_%(OA CUf_/C_u֋#{]'/cʖY/'Z2if>`cONeH$6i\\o]C}P{GrsW4l„WkGUYyg];iU鍋.e/A9-zR^²8,v wҋÆ5s۶M²jȖ-W5t~~ Y#8=4 ひk>LZ2EGA٧~kX8HÛܭk!I׺+_Ӗ<4~#X?^[|w?5_[t08>@}pMC~{ZZ7\^ uZ+5G޵F|4s^>\EwGt>i4͈zuh͸~y+&RNg#$CuYoTmY!<MV}rGeO4襗Qc4UUQ{)z1N=n>޿zo1 F,*ֲ1xv6bzG 8"EG;_Slz: /)rdgzuKǶ*<ى8LcKS3;L(ŽUKB2rߐf{z}jWGշVyjr7LSHiG j7?׫C\}J;wq4oj=ӿVn׮*-;W_+?fX|ǣ0~)=ݷ6Noכ_w$<.^=Z׬ o~u}uO6NՓ={jkE4`>v>0A>'b tǮDƯ~$./פQڰq u!F,*ƲVwzߨԋESe7î{.p5TͪZÑՠSIЖyU,W3iZ-A|%_;L/dJڙr*óܻ*M;u9k:_EIݧOvuhsNW.bܧ 'Ν5UQT5ˎї?z{wm5+,kFCy^޿:M5J;/z>[^zIOt颵'쉉yrxfy%zq06-߿v>FkWmӰ+=tH/ < Z;eJ;<)ުZrm;]w鉮]OkmcrhӦ^?׬7ި'tw3fum_zaP-|ٰ/Xg кS04n~ K4F̐[mYND.K&ECh*p-W{?>޹EO?<jlM =&H,$je_v:{NgVoɓnTy_T~p կԴiYBk'O֚>Pў=5>,ݽ,h>B!5SF]FV#X>\9sn_6B"2ڵӐoW﫮RF!WQTsI;gN'<9SRmXu3rPҷ4λU JcCԪ\tulVvW~ќ^ fj%_u`=hiw0wVw}8Uiٿe챱jէZmܼ$Weq\:eoڤ}WԲ#'G;rrE^jfݻ+!3ϢD*ٷO׮վ5ky>챱j&:)sgl؄*'WAJRў=:ql uQsf>f>2;v+C%dd(>=]sWAJEt`zB~PYR&ݮfݻ+Keu3PlBb$I~W|ݫ۵g չ:wVr\?Oc;vhCiCəgU~Q \?@#P;'7_̴W1+ y {B=U<=h_U~zTZUI|ԧn{Ƙ>ZdY#qT<`c4bj{cr%$rI K@F{dZ6z.}?{z~FKǞoO\ uꕳBݗ#/F~2Gy-jgNae˴{ٲ}>[ZVq!&p#|n\+W6(m۔m[CqJ|:~_Уpl^yc@V7˔c⪢y(OUmx F&^!qz4jULk ˺G' ˰B-{ȕPZ߮ kޛVia2W:>7'*%Wf]G@F{duRizj>ǫyh}}mN_Ϗ~ˠ=߯: @@8aJ UqkΪ(Om}z#?r%ȌTqȖ/Q|Vbe}~8ӫʬn%)ѳWEsIܑr$C+OIQC8J2b%$ycZzH^9˕_T NA-Z(C]ǎ ڳw*EahJaQtGw3mc IDATTRJ˞%֧ΞQ԰vNI+T,ϽZ܆C/'$TmLl=Z}aRSjnc@F;&^^XNm~~EAVnƃp T$xg9C&Dʄx߲>pvN M(xx7~ڏP44"gv\m,͍yy!cHRizj2<mC^7cn׷²UJ+ ymEC^s⒒jyٲ*_r#V'$˔=ڙrn8,$)6!cH*KN3WQ\ܻe7qp4kiBZtbUk^11:ᇃ> F(2βV$dzO(Բ;R{Z:hlYsrNxW]K-8\GIVIf ڕr` IZNcz$Bꓤ1֎8K>{^C>% 1m6};l*`^HN~챱g]w)C{L__o1h+HUDyh/iZ?1\-ũN]Fik^8Ჯosϳ%!_ݻ)qk?Y&Đ$4=%hn׆a%IlڙryHU'|VZ6 kżf:w=v1<oЦM#1hlL)2.`Y%C8ř^QZK,{vԋ#8թymٲ>`N$.\g71\َ~ R}-1$3gviA{馲㯏d\5*\pedCfӟԪ_ϘĚSgƊp 'Fv[ Q ĒO׳ײg=UӮVX>ze˪J;R?Αn?> ѳWvr|goJ2C|Mo& :{-!h1%A{֎81SvmOƀX']g4oF~<+<x6{Zuڴ 3N0AeGuQ@#XgY+wVEq|AX#F&"ֲVtGq;j,kY7 C\u1-8enl51gn'Ku(pfkg!cHR-N$UcOzJUSnuhgI5ӌ8z]!Ϊ(NR?G)~˞F^Q-[4U']j:d$P"_j} 8_ukbL_Z27S CG o|. G;7v8X{4!Ilɚkm ^$#p8w{*2 ޡn'=Ugpa~-{,@c.cƨ˘1a_;5x¾6DsҒ*wŹeQGo6B˞\GIL~oy@//CO$Wx{ qˉdyZUjαg Sr/tv{ZRq|V>ȓi :+?_}rc@@#+AΪ(OS{6z# )%ƴߪ9ZvHIRb4pu>h~1C~%` Ih1Ϸ%z@:-jVV)C 5gQbqig8ΞWG?vSEQQT{ZzkW~ 4"IցNw'~qUYֽ]]](N8_%iqg14bTҹՎ'zᯐ`c&% vXuJ2c1N3I2b% ػd9wT5t7udt$7WGj_~~%%ysgMEl/@`DOMeQn/_ʿ$}&j\| /ns,{^}!)U[%?CǞ=xX~1JO")A[vl2$)rRݹp4WecFh'sxV=Hy-kiӴ~4IRlu9|0@MuS.]dGlюEO;{UUPKcЈ$W8-ke 'VnKgz7(MdwvH6˪Rݹ 8ġzxZpuS[o~ު :gz/Xj5ȏgkڝ{فu`m2;tP=ܴiir(.)IqɊMLTEQUUrpNܩ˕߀scЈ$NpA]z#dZ̈奔%TUgY?GꚻRce7X0$}C_Do$z0$ӔiZ?;ѶcJ!5r~uƽ557nUMkg36Hn6(zcHJP~I*iDwPE)Z|NHL~o4ͱ#n|Z}gj1$kV 9*O>zUR.a-{6Cm[YGN%|NVcH$T Q_ik/ѻo+_aٳՑߧFnW6G5ӔR2x{r%{lI 8߉UjImǐTIڑ6OkKn0 ͻ|e9k!,YɧC!΂B(&iY+$eA[j+9lOֵשƵҲ~tΚo~Tj郄~| rvv?3Ӆn}n:sa3*_&Gy2p N>-%]bHp @%UYJOpdSYt]ڵkOd%3d^Y!-^9+bpDx0D+_|#N痤@3dF'R |jU9m> 0uck@t$1Npxӣ la]kCL(N8ZŗKU~j"74}Ke6zMfha 5sXֻ-_6[Gq"p  go.4г˲4 =rfudS3(ڲ<^i7ʲx2h5tnn`o8M-dYϝJ>S2`+;BmGM3~U$G h:P"3.ԏ'6F/cY||¾K1pՇiH3m1a}s(P`"I$I$I$I$I$I$I$I$I$I$I$ kyLuh*%T7WP(Rǯ+g'Q埗5&M0E-ziʌ[˪o`H$I$I$I$I$Is8$I$I$I$I$I$I=@QmnZU~ ENYl߽y(~^*/.Be+?OO5 ]N*#(e[CKX&$I$I$I$I$I$!I$I$I$I$I$IRPX8F(չHv09;qADg[ֽh[_hm`^7nS#dv}{%|l2L$I$I$I$I$I$IR8C$I$I$I$I$I. u9qKyIv098C r Oy\R|x?fP!&׮gKnyYY<}”uL$I$I$I$I$I$IRI$I$I$I$I$I,2*wsYDc=K}(3d盛ٮiCE<(a}yg]}tYؓyVs)]1D$I$I$I$I$I$%$I$I$I$I$I$usy k E\V'Լl߂m8IԆJ=> /gZ\#)3@Jx6g<{4dgǭe640YJ$I$I$I$I$I9C$I$I$I$I$In6p@2dD#b',l!dC( \]Hz]( K& VER0&a}̓sɯjs֞w 5oQ$I$I$I$I$I$IJ1$I$I$I$I$I$I披sBPY&R~׾l߂m8׉8#PT>ɶ`VgNz`s^ Ψ{ԯO7~.+_| |,#<J$I$IBsBP9 iȝofPSY}O  A*LX eiDkh8̳3poh[y?vӓ#ɰEoƭ]_}w%d$I$I$I$I$I$} $I$I$uEioݥ_vכǁ>j[ٽ #HrUabO&{`H&ߨַ7lc۩k2'kԧxUER`y k~fYNA_z"@$I$Iz(-dӐٔvm[6FHs|ŏ1ʗ0QTvs/-ꌖ~;{doĭ ,3ޥ)ٯ'zf/CxnCeiyܒ$I$I$I$I$I$$I$Id,p!p=/LGpGSepTYJjc}RWaUC Ӵ_'{WJ|a$_oղ7)=={|7o7n=Cx91d$I$I$I$I$I$[H$I$I!}KO`x[Ć, `. Bl§M 7㛲c+d+vb'3H`x2 8"%I$I$u5 k)焚׸em-s8HctQ pa 2 Q4?,4.a}oSgOlQ}Dvo,gKH$I$I$I$I$Is8$I$I$;a:8Ǟ5'$|_+p>_XKB6 XClb4$I$I$uSE5_(kh pU,2$싆Bh"Mhjh=הKV)n= qaQecSijg6H!PSo( 0$I$I$I$I$I$I!P$I$I* (nUkK`OrrX;0[|Tq<*^ =9I_wp}9|`99؎\Ć= Ўu$I$I$ur9 !ܮM~ίl_S(M/k?ϖ IDAT}aiۿ)3GOLX3F[{<:^H$I$I$I$I$I:DfG$I$I$)p=0׼o5[ -Rd p;AOݒp[+9xHl be/ˀ v$0%I$I$uWQ(ɍ[ɮ1ċ"u\_v'ׯj.ŹxNmKwRB.MX_-wށ!oCOb=KwrpN ĭoC<9p2I$I$I$I$I$IROp I$I$IRgk.!v| IxHl0G:U`,b^S $s7}CյzB(<\<(Ño'$I$I$I$I$I$dǐ$I$I$u&Yu p +u\ $>^f'?A * lܗ'\ J$I$IbJj(/kz'.➍77RlߒLVfmJU<-UKS0n-2nj|uy('a8TU$I$I$I$I$IZ:G$I$I  M4eXOҴGON1I q0*/)C90x%M{H$I$IJr~=JV ȈFέb 9sf Ts^_qRB]AF/.(9*DVü#aƭd-{> & 8Y2 ^[e"V<8$I$I$I$It(rKJ:%k}[4%^CIŀQKвMذjUJ֒$)!I$I$ILMi0Xdyo0ƿLBoppj3Il8xN^$I$I:H1*h GZ$Rf\{?->F­ʪۭa V>?K2g$o;#^yV~>Xʶp1+Gw']72LAHy^$I$I$I$IjtG^yeJֺb0֭X$}[~WS֒3fd-IOH$I$I:=I49 uS. hJ DI鍣f`l}Ӝ`puH$I$Ids [+/Kjƿ8)ҢI(ct}#\N {.-ȢAh(SߝJ$#̆둀Su?cч&q wį`%I$I$I$I$I$I=:$I$I$u웛_k\T<`V}a?ҜE=Kg|. $I$IԉT%ֶxضqC}աl,=k kuF^F4Ÿe@"a9ø!oS%a@?gg17L=6~V1a}>fܬ'L$I$I$I$I$/3phv8PI(H$IRp I$I$IRg16ɾҚ>%6dA^dߩ@V:GeeS'I$I$(OX++IXˈF)n,H]{|ы#9ۜSuqSyea4Cݯ~ e[NX?ȭiy IGSS\e90t;$I$I$I$I'ի9b~).| .Z?I$I=1$I$I$IŰ$*ҚxmEbNZ 8 Ys\&I$I$S(΋FjsD}?8z.hdm>g<_*5&-욗729t?1+'O<  ȯb,g@Wuq!~pEL7L$I$I$I$IJ߽6J ?̘Od$IRp I$I$IRgu}}ҚxhnOwJ:GL$I$IRۜEvCFZya]ܯnƸ-{4և ڕS3q-(١&?.>72*y-%a}ԼE Z~vܖ%2{hl 0$I$I$I$I>;O„_"a=3e=0$IsevtI$I$I sOwl|}닃OHotQQd߰6$I$I:] jEdE?0,M{;RW Z熂}/otZoα3ȫ((04eQxi \[G19$I$I$I$IB_~9Pپlr6aB@gޟ̂olgsWHR"U޽[[oQ<`@$I $I$I$uV5]A{ںst=>Oo41zdHgoX\K|C$I$IRJ jf./hqÅx^Jh/m7&Y=K 'mj {D&6+nyE^>||h(cN XV9vI$I$I$I$IHTCx*>4ͩҫƺf{joHR6%'IRwp I$I$IRgHlBi;Қ6gR/&04fQϰ(Ho0q h/I$I$I:7ףD(}uT'n4%/ sv ֆRS[~B*AC(#Tmp)[gs#k 8YRUZcN瘿F(sȝn4 t$I$I$I$IRj =:P(ѣp I$s8$I$I$XOr12?&Rwq8o=?ぺt$I$IԳdDQ_G$5pIdEZ\=.3\'9:R5g0728teB!Α?r8am'gӔd{?bNܟ1O̍[lhdwqEgSp:I$I$I$I$rZ_܅k%ɓy-=Q5G6H$$I$Ib904ރ 5ۧ&%6(m)ԓ,vOwovD!I$I$)Es [ۜ_-es-SS;oTGT i,o璢#XM0RhS>,?cg?Śx鈃 8P})C~/ntF&|9h($I$IRJG2kʀh3&v$I$IR'TaC׭KSsj笳Zsǐ$I)$I$I$}V vLCL};:>M!$I$I:/a?#~fs1:pQ2>,XTl/a}_G{B>m:Jl抄J$I$I:.S$o+@$I$Ip{#Y(i$I$1$I$I$Imf/ /V([xQkT$I$IԳV%{3|w2Le,H1sW_ٞK')-23}t"G8{lh 8YS[XCgΠ)33a1lћ$I$IJ(5#iXS$I$I>gIwwSWQD$I!I$I$I,Ϸl=V`TRzVԊ&%}6\ wI8%I$I$uc! n8\N œ9t4%T{dG( lf0̾|xE>4OX:y-^7}6dk79qC4a>~\(I$I—1$I$I#jk-+cH$Iٺ$I$I$Ie68GlE \< |4vޞmE/,~옊P$I$Iš\L`Xβ׽ٗ#c0(3dzvЀKkL}-o{zfCS~'$I$It̫n$I$I>{Mn=X略NeL$I!I$I$ILL:Ro'K@9G\`0)Xg8k`9:)XW$I$IR7ի*?am1>銥8= 6So$Y'"s2!'CSfFz8apG4;xcG^w'M$I$It85%I$Iԉ,=+GduQf M }!/\9r$ϝ%IÃ$I$I$IYO1+ )8X@>}Ց4;Xv%y x8$I$Iie?Wsϒc9d:5tGS;Llfj_O慜 u[g{ 0Q՘ŬDUIQž |(T$I$I_-&I$I`sY\:hegm?h$IR_$I$IΦ80T x a/?}y3WGVG4=x 8(H^$I$I>jt7EUqy?/ڊI}QAETM[ƙf{){c2@nU^OV+?0QUURgΠ)33aψW^gg_0$I$IҗBI>IrHcJ$I$I$Iq8$I$I$3Z=$v}VIv;pg:8`NKlĞ%6(#'{J$I$IDԽŜװG&#%q{MBPG>N[>.~<7+  ,SGn0O8ٞ{/ ($I$IR${1$I$I$I$) ǐ$I$I$uVlN^w 30*{vg3؎"uD@M)pG$I$IA5Urݦ;[]T| {e/Ì^g&pB ޸7AN1al~X<uu[m+'|XpޕEF9{G$I$)eJp; 8+];(/΢iM!I$I$I$IRfG$I$I$=0n}ˁط)[07m}q[T|qvkTi$6j` m$p===%I$I$ 2n |H__p9Tvڦq#wlh]žO_e>Գ>`8c9W[e">KɺMq IN'I$IRd4fQ실5$I$I$I$K"W$I$I0 xg8 < `Ϯh߁닁 xhv}T[Lv'6'SN <6О$I$I`MܹP~c,)EF~oct}#^ET CY}$$)hJ<;xcYh8Dz߄=yU}?ɩM !I$I$IG#+5{0О]V^@l@lT:07ݗr /=%I$I$@fs_װo?oܒM}^p3֎V(qƛٶqCž* ؿ//N=4a=7G(♆9/=„=}>'M&$I$]Zsֆ/2t]@$I$I$I8C$I$I- y @4nZs: g@U:BI]{ρ'Fp U$I$Ic> rIE)e5銦ʍ6p٩qmžPlHXҚQY('Cuqa–}U{tA`L=z/P"I$Iڤ588J~>$IRo I$I$@$I$I4V  Dl!y۹NWk~ 괲ޯ(TVL%64c"nl:$I$Iڨ4R˅rrB2u%{?{($IR5C%ȑl?~Y5ޜ94uttlw_{KVn. lDM[u˗zꫫ;:~ώ&0d/%%_>Nkc+||97oiW

0ӎ5\ ;$I$Ieᔚ\P5$42k~@BY,Hp)2vI}#y%뚚23yt汜tlh8=B$3gp73`=&.i|ݠJ$I"=L$I >) Gfb1P<`@9Kc}M |,6uu-lBFLo#W/Ũ+/Yxݧx@ydtR><%k)9ڋ1#:e=M 7Wo%wȠ{ߧVxYS,=5m;n99 }>uۮyޟ;7[2ss5}:{z*ۍGfn-.h-;t(C>Zպu,{pKk" 1CҺG4gMpa'236q"9EE {Qߺ7WV'yy>(c g2>zf{rKJ3{9I%(0{n.󯿞XR$I$I<o[gL].c|+#:c$~B1+biҺ5RE$I$IԜW޸6k^%SX˯N0#RpN'G9f݉*FP@ɺ rףhsOÅngd70d4$I$)`y]W$IRkH97aoZYYlwlwu:GO5rKJSSxӟnŊ&Mbƍ7yet޸捘:%[[b~xj^Z\q57 i|w}{/5헂DopӟCۼF^i)cN?fd=0ٰre Sȑ_ԩ32RfQ:XF{,~;q~=:j:߹Kޚ?NZߔ[\́^_Gi-`3kL>{-կXzDw](짞J7 ,iP8̞ʡ\pCk1jtFMN'WT_vWږ!_%<]s i~ÇsW2 yw֨n¬$I$I$IQs`;61=uq]Zp5oK[_gRs-m\>I$I$Iڶq+6ޜ`uW2c{`r3^SVPKS~+ E\Z5Sk4T0_r<~U71zڂ|T.IؓS[?JO=D$Iԩ_d뺒=X$IҚk1J brsϥd07e0ʕtE);<;Bpҥ5w6_fLɚXwݕ7>ߔ[\A? ?]?PD}U(П/n` 19bv?Ĕ>}qM\d #9&[;G^u|3gbCI^JP(~OWdNn~`njIjmߟ/fƍ7d07-_G i_UԿ?'qpYY)H(_$I$I$Ŭ~@XPD7-)V-E|#ޯ34Q$I$I$)4R|9aJ[I]!-c87oW+^]yZ1z~}/l٥3h yPC['uG\qYyߞgx#ӯa]T(栟gȾ}R*쳔"N~oI|8 Zhq.\N;-pF#:~ .e3|~Ċkoߴ7h=yL~j^83I<v%2z^KvA9Z&={rJsz}>$%I$I$I8dcӐ~OZ[ߖ~?3hđ$I$Izh?~_Ȋ6%uݒ,..BE(,ޕ?`MyzhV>`ك^wbmU[N ׆[p'Ӑ㯾 e&$I$)֜I[ [/yK$)UZJ?TZ(.{%WV][Ǭ[u+VPf H.??\A{٪뺓"ӌ>5쳙?w̜5#fOƍlXoM545cǰr3dZu]wWZ9s0⨣Ҿ׸c}R᠋.'xV]P[իYlkP[ۮvم .ȑZK7xuv8UE)c.[Ɔ+?{EqB&Q@T@ATzW \˵ AQiJT:ҥ^C$!m<2֯=S`>1 3u*-_|de};֬-?xrߍٳ/Sc-"*&QeKݱ#?б+obB)RJ)RJ):ק@{Tph,,b`co!Kжc8E@\+FSeJ)RJ)RJ9"c3d8>EF_ ݄cdEdny~MDe-%zfm:Z3]s2c+ʛU+2[h³JHJ'#9QOJ)RJ)4c(RJJ!o+k,[VO IOI98_qڶ>}HR6˔a ܙMQPE-=SPqc]8~h(AbժO700} _(LML嫯6j1Vê عh9#vm.iݚ={r{f\ɒ9Qzz,&!{MW#GH޵'8ML$JiG؁Qatc>g+pe޴M˖xNȧ]ޕ+-QXX}c߲_9m9GPꫩӱ# hs'\x6͞?q*bөP׶9YYy}f6mٳ|96oOߧtU;n4KAFQr5?Yw6-b쑱TiԈڹi\ɒi+l3m<~X wd<4cT'[J i ؑ0`Yhт;Ǎ#":kۤ'Y5~<.7s@fOә3\3^P>͘g͚vEYj̴gW_~ B#6uӦ1駹g'e.{N&M v  z&;3d2҈/[02x0f$3-m(  PJ)RJ)RJ) W \l"T/LHB^e_apw*jZo.2CsRJ)RJ)Riͤq6KSWa[WtRHW3 XU^}Q9WXSv#?m+&5 +64ï90W|J9mẹ}9Q,>#TJ)CF@nYN2b97+b/&R  'Zi!$HdXKA88| $@8r؏s_w8̄^ +WRL+RJ`VCy/{KKJ=0sdexpMJ1cY3c{d>nԈ{-U5{q.͸7p c'Ahcx6a[޽r߻B\ "sxɻw3kWv,\hſ'il~Wq1EEgXFv,ZdOh^ʍՏnz9jwh~ڵ8kLVz:a9L4O>ɍ>{FHz{@hF8ECŋ޹3;v#(v W+VxM=G^7.[5,UhЀƏ7 XOnˡL4U&wDT.I|պ5Y 59k{ؾի֩GnGzJ S{U&pqTcrugXn׎lK}_s#QaCŕ*E>ϨQl5ןRD J]|15Z~(SXk??1J)J)RJ)RJ)B]E6ke[v;,5UP*LXki|(D$>UcL1<)RJ)RJ)Uh\GA-My9-԰gc1k8FPE,y,mzl$2wͱ0+YTz"rww.>6׏}qozgz$psN, ?ƘGrxBGRJ)Z-Hx~ | PpTɽ%"XtX,f73v(hDCZN?*p$\:!P d-u2o, 9FcV#2]i.@ΕH0oN 6-X{kRJ)BNNz%Ҏ勛nbe>ul~jՊ~?LV<-Q݇ chv,)h*4h@74nbFKfj~2Ҙ묙4?L.WHgߚ5{Q㦛;u+_x#Gm󹯌'ݧiII\CF-]'~z?p> *\C_6nLpƉxeVODI(QܨUrO{m:vǎ~UNdxά>>>z=Rޞr^\R8Or{|zu 5k@E͚壏 um_{m0E Tڽ&nݞ+I=Ԃ3iS;g~jjE7?uxm˔9mdÌnv(.eҥ;\stz}'O QJ.V~UJ)RJ)RJ)bgm6~n}U'E MAy}=hmFqZkds(+2RJ)RJ){u1<{fk1LTA1"r)~HGe9c,BD pھI/~a^xv6퇍qkwmnm<)?N^W)*T.v ?:`O0;='X?X Jǯ>uơa .ڔfe۸d8"_zXs436yl0 xc"OHJ7? m O%É׀ R*Df,D ? x3Óx  DH.`3?USFƷi@c$\f'0)|0@+ mF##4{~*a)0UH(@AB B`@e\ TF*ynVKrHH8X/{lҐt >+;U)@{ȹ)P QJ)Tס1|8qǻZ*MƉ|ۣޗе-˔PᇩPQ-s2{m)~l>isFȘ>V/SCO8(pƔ+'ݻն-h[ߡORNfd %zk_mߢ޶?¸}HJv_lә꫶oW{NN^۷Qzm[߇6n'p[bz=m|KOIaH6]1ILaxҏ}ߘڷz5??vtxgaZ=a#u3 ϮK;׿6QJNRJ)RJ)R*Եbߦ<@ IDATV~}^gc (,Drw}-ʇ1%.U-ǶZh){hsY9 m<)RJ)RJ)u޹$%e֡O閶pZYa|׈e /TJQIp`(}GqeNGWwbo 1UDI{횳M3mJ?ĭ}CQ PJ)BQA)qHPc1o- Ƙ^.8#Eև,<|ן!żv$k}E3KT$ܰ pR9py.zWӐСǁjQ|lGG|7ٯvG>gߎMwge? SsQlZ4̅_~R~S+2)2;AU1td_逕#@C40.$)+rZ\,D^nCBTR(\c^)oqTսfjlݑ[˶ojvŊl?b3j{q~(L͛If@n45ӓGieM\ɒSFmӎe]we[֭cY5>Nv7o]޽86|={۾ODڽQۉ?LZRcX1v,k&M.xqڿK{ޗqC۷gbFk5h xi{Ř1:¶y󼶫Ҹ1ut+W@דF#ϝ넚 A軲a2'RJ)RJ)TAU;sSUDէ-^JhRp08j[?L$F'J%n'=,*@#SV4Gʕau`=?թF_mSrA8c%HRJ)Bbro#Œ!sȜ@)vG\ 7O#}-|HF#sJnD<ۗbc@#o*`*V&}%2GM$hƾ*x\$x;s=0O%" wl幷gH@w8@k-[9Ǟ\</"3;HO}"E!'ȵ€->9 tH"iIǁLm#$(ě*@o买j# GvThHD u0Cs6$@ 4aHpHEg(s[! l ؒ{[$Į\D#sSroJcB'26kvK/̼oŵOlqϗ `}#>J\)yI3_yőQht=FU`UJn\99LyƱf$ծc"}u{cc O>t_t$aa4n4nl;#=9ٱdoC/Stiv,ZĊcgo;`_)[7 +=;^ʵzmTà0\.Ӟgڶ믳f$\9WZ9|߻7id3kW튕/ko[UJ4C)RJ)RJ)T Ce~ 3w2d b/{ OXlœa5pS> mz8ș5=yYlg6oIŁ,~4/TJ)RJ)Rк&sK,?x#ˣ*:cxI2lOyvI~82 rroomc#ч&!Q$8R` ks eppesՁ~FضU"D`r>=_:x*x#u&*!s;G!Ovթ`ٜÑ0d/ɽC "5ϳ)\)w2W5떟͉*)7 10 O tGcZ`S.5Ͽkt'%"!5kkk_m:I(Ci \x{ME!3@#| TD>GG!tHXq$?`\*T ﺋR]]#Q%7<QDDG]AKӇ6j{l><رLyi^б+ߴ4p_Y8+W::}3fxm𮻘z~|䍉7*ptN|#p\LyΙWUy],kdz`Z d4x 4ߨW^qt,׮eرԻ6h|ٲqR=>th֍R_$"ש+ƌf粄7;.rFەUZmڰvTbjǾl흋s`:֪mūp ]URJ)RJ)R x gឭ#/"ξ =>" [xU%/ܾ6XhO88Ζ,^gJpp?<[Wmp}m sOlbzeJ)RJ)RJXWgnc.c.̻7W(Db'SqV%@1WvTB'ÜEF3(w浿HNM#G`V,)R +O|kcs& 1~#T\c1ˀ_9]ד=HI:2n$*'06-<c=D :D6νq|vW]zǐbqHe@e@v2+8BK󽯺 q0CC`zs}J+d&?kFyl,r&oX'5^&*b7\{q`h4A+a4؁{[ tfW .B K#Wn?X|]Eh:T '0jϨQd9h0WAL Ӈoun\}g\׎E@߳'ŋO:ܥݖs ;)":f=fvСdgd8<"JOg/Kk#DD{ #ivMx}p -ZP+ٵ }="t~KxĹy9YYٓSd,%Un݌.9y=#';xn5,AвJ)4C)RJ)RJ)TAPY`p{^5lp_w+1 ǨCzZm9: C^àœp4N_Gn춨USq)0?p{.򵾫RJ)lbZO32/F د 8/PE5 <̩MH0Nai~NG|lDɼizCeA!C>Sˑyy?ͽ~ VD@ހrR7$rxogzE-<?.#B&}OGrMFqLT@ ^zw'`(9!>T ç܀썄XC0K@h vj,A!E9"" 9.vS HaL E:)h@8 Ýmܑ{,pO\|nb6uO!O= ϷS?d0澞C7+r onb'r}(NӜ ]Lv=JY8>BNΔ^ݨwx4ppzv.YB.T).ڕ}vMFrY4|ãCh8FicY:cgr|<.EÆΠ`1I=rq9qx_0dHP1~;+Tr8SVO!$&>=nɈre\n@ٯ FtIG"yѕÏ}r\>j7} oۿ? a+ǎ}~զ %*Wɝ-[li^ۙh(J)RJ)RJ))/wI&™I ZOC{b D66o odU7# Y ayMZh_dՏ>K wp?IEہ (ijqJ)RJ)RJs{R,y`eQ+Kx0F J_$'ERB+qO|H5cpѦN1BTNzSը`m<-jՔ7&&-nZOuÔRJ)&E qJ&6T40`$F?y#V81 $(4c%2s>:X/T՜0p!s<l"!&J#f }S?M~|kyAB!Eb󸨇w3#!i0HQֆs /c>&Bw s1? \ Z_ $bvs_*~ksedSȾ,r3onERp}:- QHQv'1ꃼPNVl0{\"Ƕgk 3 %{j + \ y'/N/vv/ԮoF'_1)M!55 aj##_!#A*coZd{}ma{ !:gV!ann)勫1jg Yh)S]՟eDJTiبֿ"y.Gk&M"3U/Xݨ횟HayU̾*RxtTh~_XjxNtw;-#ivGsچ3BvBlbFmWOhu7[<Ծq͹k"cbιrӀ aȊ$'ˉ3=ʶ 粛ov|<4ɑn7Ϩ]j_)u)l?j)RJ)RJ)*^F&i].s'jm'mt^BVZQXocO%d16mio`E4̓qdŀ)!ߪ#F>,,(@&:0 ia}1RJ)RJ)R^d^86%iT̶)<w&CsC|R*$`%^oa{-Ϻ!gqg) d4v5FCw,.Vq ~]n=55*V#s">w"'c< EHTG*::Џ'# ';Z!/t?}q=/ШpjQpȺWW qMA x +D/ Pːk@\/@q|ϛ)Na@}d<``ǏH`ݩDZ[]$*EYmlR$1Kڴ1jڰ66[fxID#'xtL_8y8 H_ʳ:wdGs9aQz9<b4-)ɸ݊W͛]̴4\>{ɻvgrGsqehQqc QE{Ϗ=!C:-Z_Q[%;lwv1rnTG}dVSR+Q#+?^)RJ)RJ)ӣDG6n> 6n?dmocPq3mFYL5U,`Y|aU`@| t0V%d"7 @:f^FX2 ?Y|\8^,v;A^2@-df}/XE\|~t -O.3,wغUVty &o>n:IeKNpX:NbdܼYtj4ĪkRJ)6")2lT_dS\H,fLBa؄qqjJ =9O &E@/Ci8:#ǰ7ñY17w@y3 X+6lAP#Vv%)mE d~yY1's?Ji;s6w"!@"rH܇ 2@73#v:B4kOi4ϛx@y7 SYWy$ġ:QdA d̓ɼ;q}2~BQd,#j :̟甑dZ4j芼s!   o=RA*6lr$H|WkҡXNV#͊ qut!2y]Y54"Hjl42ɸv.aloQmۀwEFrL[̡L͚^Um҄+kWF +o0-`˻u#,j\;Hٳ|9U&3ILf˖Fm'.7Μc"2IUGQB3u*1MsG;<0˸rĕ*Ea {YݦegK޵ڵ=!26tơ:?h8RJ)RJ)RJ>0X F$"@[d-OPݏ,MO+>lk")_K^` Y-fR,Id9O s}NSK#sL~ J:`VrbAcHu'} TA {bǾ̩fch5u'D!(I@K`Mg/"}c6IغS!8g kx?OBuYr$SG^ n?!*}c"l=M =uM{HauFNG.ȽN{9N>Bl CqU3y8@pNx|p1zv(!{%~6ւCGt4 _y9.D֧"!e>h oa@΁k]H_:.E> ]a1ȹ>_p0OQx~wgY?$ՑyvF֮Jhnʝt ~CΫ+>BBsZ||H]y̴h+' >Sѣݱ*?nTkڴcGFRkڦoMv-嫰pjtQ۽VqqGtM{.,,7Ē#0*ERjs8~ 7yZZo`5Z0jE͹L +g]ܼ9f岷͛hΔEz5mJdL Y'O`d֕[OH~7<*azw*2Nnޕ+څGDPI#:?ttɻwsp x PJyJ)RJ)RJ) zȢNh~22 6\YVYm3mB ~޻omk#;& f6#Ad}$L[Ůl|P YdU8SߏL>,A }B>%P@BALtAFT oA3v*TQXdsvwJp hQ^=uZWN82]9|zĕX^go,˝r<iDTQ ߘ[k>4`M˖QqqTiܘ,*s%9d _Z(^l ͙o'=9?V R8 ΖImωCRp RJ)RJ)R0lFùKsoT>z#@`&f Zd³L~~ f Y(}f%ǚ Ƚ"9P`}9]cBmqFx&p:`J)RJ)RJՕ;零kre[~|FX$b/狢ױ)jgN.CG>c_$++,K 6Wx8JwPjo?{._|O!;R8aKݚ{776t5,XF|r ?{fRJ)w#Ek1:!h98[0+^/=<ˎhHgӢۀs+ 9k\{hcWuNXbSߞ|=mж2r׆~ 9%y'zhcGm\O6e#xn 7bӬ ]ŦA :3@ޓFu+'Ykz5ݡ1'x+V"_DB /@ i{1`Z5} z`l~ ؎̝y&  csZq?fh%@V <<ʹǞA̰});ȄאmO(׷őal|־/، ܝRK&!\ s̃1~F>VMBB Sȵ#n~ jPQ :(;Uoܸ-[7or㚕%Tp޴Νd>+4h@LBQ}k8<ڴ)Ϙt ]Λ_}ȅcVZn_et\[Mxm[͠/_>5jg⦔*tFRJ)RJ)RJPph\ nG&c~ldQGVlߊ/IY5dL@&v>6;2i^[@ =v 8za$PRwL)RJ)RJJ|IW.6אEq0S+~<H ;RZd"44/w'NJXlFä/I? m9)}|eݮUh_~-EXef Ej:W+R!w$86kڞ s8;4H7 ţ,;v"yЯ9TرOG"sL_HnF%ٯi~cdުDTv}32y0P>2HSܛdp펎z݊"v,F 9 ie@7 rڂL| %l*+}gLdγUvmrk Xg K?apb& nA戛p>NCx|ǷҊ!~+>@Բ(\5@u+@̤b - <ڰAV,duCѷ'{ E819>yx/? EN . P8μ>l{UL\ ŰhoDTekb?V2BC:޾&?-֭5_%.+CA(,q7 )Q2EIKOz.pZ)oWN7lt>Wy 偵FaKKr2SS9/P?f%~+VdYX1q~\g2\t8#YvtTJyJ)RJ)RJ)B*~$ 2a%N T+u(O v;,rzm"`' /B@s! A,$0uD߿ŋvr!$ǣu'EEiqAI`~c6B)RJ)RJ=J[i+)i#V/r%a`a.(R4r]$ś-6REKǦBm]9K3IeK12;rSk*\PMj*_\яM#)vvl}zTTGRJ:i|}B o(  O_y ǰ2ɝX7ޱ_SW9S1d>؛~Y۰~?" ~_|Kw$d>j~ރW(xmO",RmygF׸RxSЙHA@'/+r}Ϡm r.t}c++u)'Z ycnF1Ng B|u)3ݿ"AS@M J#occ}g -ӑ{~&_ ژ[G7RR@fÇ^p2l- @8qcхu Fx ~ipee\GdP?Nnprˤ wPV-#c޲%(!VB]v#&\9̚Ejرb\יŊQ|ywvxDegd,tUVC)U8Q`I)RJ)RJ)W:0YR| PˁǑGo YT{1NIA>ؼ$3d#Λ? Z,iK% Ty}W,f 1_o*e.V?ijdr%` RJ)RJ)# ܑ?g/#uO+H[hZk%N}N^گDNi15 HmVgn\;j/\NGʕJzlWzPRJ)UH \ͩb&L\޴P&((WPh2aˋ^~?R4v>] C"Lя/pi@JX XP +6B˷ˁ^}+, 1v%`.1F|0F82pn8g/jض9y"oT2wخ`S>ZI@Aaa嵬Ϲd.6z(YxAoUp7gMp9'T7HOYnse լRw8c 8% YlؾNm?HH7bo0F^aN'*e++Qܴh1@K/up$)syFo0'WBxD6,pJqy PRq@,+9]].+:%mf Qꢋ KLԬɓɶu,R:bc;d8x`La8av堔:i8RJ)RJ)RJPw?;Jsf,KG@@Q"DŮDԨIo1cbKbFc]Q b] ]R]`]XLqןmg~fsg8gQsޙ{ܙz8Ywb dº  1bؾ́vJT_`7`]sIw!iioی5)ue:.[on """""""≮S5wͣ_BA?X|l+?ȏU3v7bXә 0?zYW^(5 ʯ:㊚@,Ż>w;$6 Vd". 2܉ztXpܚZNq *.# T3t;n)-0+"""ƤI8p[ϟ9ܗJ1kv9!~Zj]&_jՐco{"Vdz2 *tVK[`qx?)g `=V#2/2aiϓTX-VϹ6F7/_cg8G8O*N2 V^Z |ls%L_qw11 ;a`10tX'D۰ߜ8w{̶͝or 2H~ 3:/<:݀o:wˎ0gƂ)ZO8.+';_M;ͽǢn_,&h IDAT/|Li:1׿ukr }us/)Q壏z2LB.Vڇpދ/} PWϬ4WHӥp nJ,f\bQǹ{&kHlVj0k cɇ>\-<%{m-W"Zn>cj}?SKN2fI(/oΉw=ӗG(7 @IUq#E ,{|q/_tZ{XxSƗ`58V3,~Hyg kn s9eV0ECRiz,fZ_G|pAXB5 Y📘'Y` [n0j![$ֶ0&n{`k|&p?)[$0aǘ0 TF!?zOʵdd1cce-ɇct.4`x'< ܂yk0>v{MxBc$~κXN)?1VN! mxC >cm&??& 8vǎ+Pئl,on h;mOW`_>,?4ò8wLzq'q+y'-兆 83\ΟED5oDDDDDDDDDDDDDDDDDDDDDwJLSy?r6֭Q0ƒ쎜4&=*/;^aGP=?`9x<_5c3>}E˸m)Nigx /oV0cpf hZdEgPwLǵ}Y63?/>ڂƷǶ6s}J*cijh+qYX ~r+K1C _s9)t4p{8g8rbG0ZNJa.l04s԰v `bX :߳W` ƨz}`RV!䙎u65`3w rkە6=b\]v/7 Uj%/$?cn']lHwv2(9(qj-V@PY 0 TJ?`l'ת,WN8VF::Oi]<+""Z{b5Hlź9hql>^PkP_~b=6,u jb5Z+8+$$\/*22XW!ˊ2am`eCc V]ck Șoϱ8QM`.pcm'j1]>p3ֵ*2i7|7 qIqO|c+)̗~AH&Yn cb&2>y%#4yEETT ۲rsm 4կe% icylffez))0`/;x OaB6.\ɓ>N d=pAD.3.}E&P8H@""""""[ido!(Q1y^""""""""""""""$^q58v EZVr8tVfsBI>2,f|”1JZ|~/̚lӾCE>"evxXIf۟eHhu± r:SYe?IE dK9נw>egI1_>ʚ<{ٌ ѱ,a Ncn=PDDi jV F6mL1V؇7A!S2sQ Q`'`7؝ƛ2 +(ހS/`V`:+ɔb:׍B -4?ʅx ѱռ4-1 p v1JA cwKI8Fkת,&%T ' F&!p ǿ qYk {yp nL1\fVLI~ah A\ |}ZaW&}bѦ#&A hz8,.S֏xLyC2ZW\q&xumY{>*zvvMXݗW_^tq5 ;1DDDDDDueT`qQ'̍>/(~Xc rA)| J:1 -ckblbE;y/yN7b'{W m+Q jeC!z`W:Ae>X U-v^V( qh7UѵKSZ? 0' 2͎DQ^\E8K}2M_|_|`y/ͭeO1#M{≈H@4ג'LI0M· mlJrS^I.VSV?< n>fiC<)Ұִj:er'_@c_<nާL9R:ХR 30ӐTA@'Uj+cÁIh'I 6{c$?w39h %1O؎_$1O0َp.b̤iqVNY99DBtRDMB`2 10Ev^^$=Lp2evUn=F}fd30.˿'ukLyg(CDDDDDDQz ݣA`Li@x/u~#ۼODDDDDDDDDDDDDD 1f1=ϻ7A,'ޯb7.{]c1~0뼉,)H.ڧ-eꇷ@2MupOm`˕`74їyM;)XOc5P~Xc;/Xh8.3HݜeJ~WQD6bLp h> \1|p-MLK\!Za䵌*$E_/ND` f8>6 ^P9g8]70p Ǩ~>532q%#e4>E ow5)(pss2il,"Q[UUX;Bfywdl8֕+yvTOZI:saGf>e79j;z4[V+ߙ]F#)l&8b>p&t"" cHRbCbQb0(`dG+CDDDDDDDDDDDDD8f!W/G~`hq)b o匬[ $#3 13?Jy̌q1>ϨڥV$VGdC4afX~x2MpL/%N5>(cCs+3Cx }!ؗ) kJ&u.ӿ5'h "6 <5 ǯt 3`a0OxP7LZ9ٰ}rt2y}<q*\ 7kķ.abÁ[],w>4 "rµd@),T8ê67|BCt?I=̚Wnb%ةGvlH,## c;vlȧoXڇ9ƌa3wlKW\)>hx#_ϚE*wsM!"!""""""MS]ƕF9(NaBz[ODDDDDDDDDDDDDD%RگWֺ2y{c\ylqGh5׭dD2[As=)VdcVߏ%{ܦ:O(6_/;᚝o]5&pky@GJSS]T3i/έeS(L/e1'OOF>=`$~?ʂ8IG@DD$A$C1ۀ[ӵ$@Gs:`0ev88%LS}.ڤx(!(?B#pa.V|99f/}L k1 e'3?k| ,7m|pCavuޠ?l8>g1`!~>FJw6Xwe0כcuW>.!3"bظ}ՑW@2\rrB.VsjX>Ǧu2ijo,/Ld~Jr ˑ>a$m/Ǩ1cX7gN<0'Ofѣ>fnx?nSe6(SDD _J^a[cCQ R:p6dtT2v1js@h]f4L"0!^& ]őuZOV̹!K;0+_f@oo?󇞑ܱY}plE[yxP4u;Ky39)ع1;w1ߏSQRqׂCS;o:Χt^?+RDD$30e!]`n͉Ud3dk4pz.]G y8x8l%c5.Y`Ks?Nj> xk?T:-vƼ'ՆH5>k?c84c s=. OUao_SW?61יQcs1,&ekU&i (|k8~x7Hy-;10Ku0 N̾4nwcmNb~?I* lc*qU[ fڠeNl_g͕:5N%%.V԰yM~X,F ;MQai 1 4\r d?455D ܰS""O""""""#eT`q18(=7hcOIQK9f1G.'ۃfw֧QЩ~=+^jWROɞey9?^ߏe}즬C#KLz4}q}˴%6? LbMߋs;g/"1[3x*-ZgT4izN^;DVQ"""i #&"}M4G0o ) X~-ļxrB`5Of27W/ .SpZؓT+nV8g&ʄɤl)$uƳ/7?In< }'́W Ul:P~)]>~1cb#p־k2E@

۟bQٍ|Vn|q5 ƈ|Ӄ@ ?l*۴+Q!x?Â]py9rj3ג1oR}?Vq'ܴF{w2W*!?x0־bfiN>^uptNYl?!.˰Bz0gȄpL#2 pzj2BjG{Jay'5&tR8G IDATP 7nmNJjz-;(VD8Dݝ '/M^61ɶFm jHiHM*eo7e"hNoo?KVpMܻh[_:2i{&mM벭LAy*־+Ѭ,>8a4=wG'}?Ok}L9&OeB-?FI&ߦ3A2M595TUXMjiÁݴ"5 ì)n.j,ﶣQx yp.p-~6o#Xe¿ͿbҰˆ?^~htcYX ' q־ۏ1)4Wmk)$sa򣟗۲{p(`z#GJUN͵9wLNgƴ߶xLe"몈'LWn ;6/] f=\:򽍋{H[cr7p(dOʖ.QƦ{Ӎk UZuJv^d~LI8F&D#:.7켼8,>K_{͕z6.Z>c[ҭ֬qq1DDDDDD47Xq G+CDDDDDDDDDDDDD}Y(V1z.(>?T֓Y>m54W#8n-CV1n"d.w>|uۊ޷n)/暝sޮ$ӕ K&.ăD`ŀ}x󔱌~cl;_Cuԟ0VߛG~1cx.+6:mLGǍ Q""" 27M0yM41y.j8F2_=@)pʁðOty K`1>,S2a24 ?vUGSCgE/KO1-u_-Ӥ8x÷X:|~?oG"Y7gX@ כoε=Ao'Ac̱=֤ɻS:t C x)&i1rs=XǦcb#  ɶc?N%Kx禛'{?aN~QW|n:C>ko $&l" 048{sc!Fww'4AbZ˸ŜPQ/21 dtOw)M^v,B& _!uk\ay}  rR! Ъ~u9*Z7pgޜpl$߅g# I݂WSaϿwLpl;X޺zOhkԦG:^rXĢQ_Q8SD$e3""""""Gl`H,8`T(ʠ@@ξV0JX3!3ȟJ5]"Pu [~ d"ͷ>wzv^o>ɮ@ks㶖q;eH[tʎEcoX߿jůZM'%p[&(^zuyV'`]8Tjt|9ygQ"""iSi ē&ťI,T%LSm 6he﷯xVTh/򀧁_8&V {Oz4ߟ^T9ɄɄEipN4~8Xg9pfñwd$؅l.ƸPbs{{2'y?aBCtbn3[k] }Ndga}qKV`GY8!3"Z۶1:CnU9ϞguV:0*ÊQz5wNkԩ_?򊊨aPdM_M}~ATc>yRvmJa۶ v8|5䓛kt[oq) Ǖ;-;ubƍT^Ȟ#FĽS#3뮳5v⫗_vfsVae_r5 2}.C[^vzhU[P:>]L8Amj+оOX?DD2N`#)2{'pȶt""""""""""""""Ҕ 1f1j=\rtKё<_6Q6rPh BTnwl'="tgAN⋧YǽoSJ+}›u 7rx<V2ɜQȫe+۪uLQLd7#\1C W?DD21DDDDDD2Hl`H,8`T4ʠ@<͍8Z""""""""""""""+jq|BzF=^022R^,>M~ mox#0@0Ӿww?@>ܞ|דYŹHGܗʡUUýr"l+r7e"L1mrbwX+hTth(B߬?sI(/^%<{YKxv69u_GC)""M(fN6:8T`,c26| 5ƭBmX:`ecӁ# )pV d-1C 2am2렟7OnvO6#\ !@7< zR2Շ{~ORʯA^kdnonA.ckaڑp<ҪΠ8|x+(j^' *30mKM!IĖo͎Mhٱc±GHK8Fӱo8o / c= =r'H=$_~Gw"K0;^Gak^*ϙ[ 1N Hv+PSQA~ęG4{&p4g8q!ڵ˃ʬm'O,5ɩ 1|\9ĞusyRp{ǡR{qq׳f#@+W_zMmz>OC55,1=7}ƍy1NcFCgxfD0 1r kh{8{Ww\;w܅2fĨ?=Gْ%հq"Jϧ?G;Ĵc4v z,L 47XqPAQWO"""""""""""""")hű5_1fp"T'E%'6y(հwxC ?Tʞ-=t ٟrS)~q[q6ȎEco" LJYap"ޝp4h~wL0G3:o/`ȫ3d{#r g.>{_F EDD2DH!'']Mk1 hs񝅆;R^-VHƕ@$@3x|ɇ#j{#_&46 iR-0 pZp6~\YιlúRmiJ7?'֦?e[5 -C_1~ tL@ DomN>X\&?̈8c%4k//2ː .{_Ν,{uq^]U g`'۶gOzz(>՚LhsygǸ=ḽF]ĉm[TT?aB±ed_ZpOV8F=<}0iR0Kb8Νew0kyc6}M8׀O$#;?}mc|D#σ%ʗ>3L86;?_:b η3}qw$of\r d"JNQw q3jEDO"""""">P6NYbzi(W1;"!"""""""""""""Vj&Vm3fn|fT0ƪ출tv/|rC"ܫ?wuټXӫdZѹ9x?Gp>NMsMZE*eb<~ 8ośodf}\1׭SұyX2n[M%LڸeXxepwv*;yP}hѮa W.ZA> e $ݥpa8d$9}L^t_-u+93n|g`aٍ|Uv+f˪Umb-$鸭8}Z7͠ѵKyo˿edXݑ^-GSP/Pov_4aodmPXidqGmA>u"]rm7g ʻsx8δI+U$ftW1k7M73`)5\)@rGp~ԑW [2aFI0 Lj`&bcp2Krdtu ܯFkk.a7X4 ~ 0ywY::`2&_Ȑ4: IDAT-y p+8CfD\k"sM}?wtISĽ?w;y_m fgsu9>?@Vn.p+ w!]avv~>Sz\wٹ9L>}8ykud1m}krb1lhcsᶯgb&_\W\j-_a_xe&{:t0~B^cDB!f_sG\qM>cDz&WSػ0'ʮd ۗm&]:JW. ,رWQŊ`(HQ@@@A#RvY% /L2d\gdf2I/[q,XWl']c#3I%oAv\@k1unwzC| ї>~E!: B!B:ŃcYU$fc ]I B!B!B!BB\N:ͬY%>͜ƠD]ϊm!ܚ0MU] ͓*tۮnT s9WNܜsIw!*Z.w%?&x )K4ϔ'fM48S|z&'~΀B!H񥿑 f7LX @;kg^ǓX֏ދD.Oy]r$ԮoW޲Sԛp OԌ]/q:=9.*)^*u]0~wz*gƹmisb6r;._ŒT[C+sym<ۋb[ ?._];xx8N">=O w"*hp?~p$t-Sw6nzm[MɅB P`p| Ռ9y0Rߒ*ʦ`л}kЃ@$-FnXQDr:̞ݑzzpkpc ,nAlqɨ@QP&! K߹/9u_~iY}zF޿c3fرLJEFLt2C]駦6oܧ= _H߱ô{SOAZwqkވ*͚'59+0q:l6}0^y%<п-ЯӴY£2i6 {r&)СrrJQ^{$3gr>jw:S6l`[o>e8OzD6 jX&?дyV!& ܎Y4aҖO!p !B!kX^<8UN'l|MY!B!B!B!Bqũ<3G_ᇌܜdg^˲q}4erPٙCC @s|dFTMs.j: L_{brtHi\i W$ʘ|u!C+`4V{ec橜+&Xu/9̌I=SW`P v&FwOl 瞸q$lj;Y۵.vO":8^{B!'G w~NCc)`+e`D{Ƙ_\@o/ոۋ0\Tc`H& !PgTo>28ޛp;T0^ +1:O SV֖ .ƊB jkp%= \s?:-kI"(9#@$- 7qlpn`l6g8H00ފײנ0& uu>0úXX%I<-\>7~='pmn/),d]w:gfͨܤeZ DHϛ?56&%[!F ۥ ÿ_M}q^9~0Թi *ƺORcf[Bڼ9uz+VZ_` awߙr|;t1wq~>S QTdd{R]ccTW^1mnŗÇ9!?޴O o?;};-BL>B!BQ'InK7N2\N~!ƾPH0B!B!B!BԨ.o07}n[A%G[1]幁b/IClּ3fVƇ=2<Ú#/mᑺVB+?͉! ̯Јcrs0ڧE_6\xL?u2.g~N ZfMXM$#DdxP_VqXܱ,1IiFU +pH?~p$USt-Sy׾}|O|$\;Jo'`ٶ  Դ ~rW:y9jgz2u; .r. 1ۦ;d!7=\c,śnx/q{ uP+P(K kZQ 1v~#c36M`a8Y7@&wU9";QY DZ߃:#AO]c`ǨkA \k-QeFWY'IÜ%%LZ3[4ٓ3fYќ.}i.}MZ^Bl,65>HúVjܘ-~Fny}<Eyy^,x\aQQ5ʒ̽{[p |].zNN_~!~}ͱ.e ̓͏ޫk? *|e˨qw}l\t2g>%'Nx^39:l'/_C7ΔycTٳ=n/fF>)}f۶D^g;t{7GqUrTdz_vN`w~^]A#e[oսh3b zq`|5b.[On8)),4eN!8W!B!vqA@O KO&B!B!B!Bs^-G&!k_bWd6=JI3dWU$9&rRC3J*;st^=2I ѕ)#ξTbGh ;BSV!LO?U3ݶY:\1Wy#)zV5}m[T'!p1_܁Ʋ|zB aOÁE>gp`Ƙm^vj@j$,`Fw6y.i_Z+K?Q=EF[+:F) 25 {gƺ[-Ũk|oz`+X}'M4bQ!Dc,E/#%cYx+18=Ƈo@kwXOy e+grܾ`ְwo[Gd2/Wΰ>~m5%F~sԋ;ź̢ciYc#LVF9>` ߒ*Ӕ\GH@?cݹX>"nChjoY;8+Q0z؁ VdP}wLe&Q1ЉcXp?3FΑjXV578ފ~TxTcCfFr62\W(/LBxm|6h7͘AHxDZ 0j6|-K^PSΣ#8vUL6 êj9_Co\9=4G;`yl;k֐uEl6SRHiؐZ˩ڢܪL^>'f.d/[2g_%*)TmтW8&u:^km4K>>snjᲗ^5>$,Kzmֹs9v-YשNihrTiڴu=W#F>]s2{umE|q\W!z] /rd tQ =v,M8KJl%֑~=Z8 /qǰ;2OdefTk-eeqҥ4|_:/XSYݾ]s6F^JqվSD8Q cA[kO TS7O~:δ X У'8gQ=zŝœXcЋ Ǹ1ȾL7g#ov~昁m4êc!1j)afKGQE]-9Y%Ilg&ǍOD*F!Ch9d;w9v-Gn%/#GQQQD%%W*;v~nTi\Y1kܯF.藝rޛQzdz=eB+T˽YR—7Zl],~œz=Jf ;طr%Gn%?#AkԠZV4Rvͮpxx&}xXeʢ NN#^&4"W^I+jNGQ_pii^-o }ǔ/ 86Z56-s{R7GP|QQD''_u.ݻk) zHv,X`*:C?㘰Hn?|ÆmO&Mh>`W)8*Oreb.0< Wbڸcw$".jբn.֍jeg1}(3B$C!BqJGRԗ%i!B!B!B!8gG@Z\9;ߟ}'@#'GnB-_E~;C$7$}! M&ߦoz)v\ ,XS9?Q2>RrqZߥz~1m@ҩ?ϪBɮ<.֋V`wjSVsaLbSѿ;9嫌B!j pb`PMdXsAm(Ny c}11]̩g pcO8jv 19&̵Gk@kᨦƩZjU:E'Z)W_M@\}l6>s9KJvlŦ3yGn>U5W5k|Gd_fԩt>nqnС:6mbф ޖ/G^n\7mFݡ);KJc,~S'[+߿!'9fS];jkg.^y٣GK8BB! CZ0}N'l|R__c!B!B!B!ʽP mѳpc1k?XmQX:<w;3+)ªI0+P%U7K{5]w0ЪOqWH0nlB~>~\.. \ˠR\!Wa@:\4c #z.?#""BdB5ңFY}qTcI=ꐒ $Ր,!HA݀V׀&OU561{cO :fgzm0Or/. cIvw@4c`]Oyn4*Ÿ"1Ly Uݔ^H0 T0t͢: Fk9ha z@\ z* 1[0@#j1 pƘo4U| x8?3 mYvTg\Jxc~xm3юF1OS(>K۸ZOe&gI ƍ>}, 8g,Ybh?'MK#域x/ (nfYcCˬ<5'Ѧ mNGQs{z<1N#|s-8,07Æ-L{W-Zoց׭[puYv?v)RRXh:tda&}޹b Zb~71\}5}T1G.@!B!T"t}C:w \UAo :6Х!B!B!Bqؐ1jFhL|(Bj󒝹M#a҆@rIjH<H=cFcTu'4 YBv4J:n r#+ p36?ʲGZLW>l&;_!lnߊQ aEnǵy 1dzwU4QI35 -.e4}55:PjBy xZ\  ac{9}=*@]߮9.Xje!A>TӸuD5׻? |{OG ?=V)qp-@ټ'*$CN@OX'Q hyc klJr6:bnBsnc>q90܀O@ee`l]ԱErXZvzf٨pw\:+o!|v";Gd_sK/QeKKg~n6nt3}q\{)Y~JYmI٦N߱)a+'^׮C٩,z<ЦMپ=w=FTlaΝˬG[6GYrdR׭cȤITkenK[٠A޼Z-/= ŠA{yh]&O>aCYoG}2rLmuL2n7#b}=<-f3rYX\&Lk,}N&vLoO[ٗsQc7x^1*j; DD!vx)fvB 4k:  ϧС\|kt}|M΅ M[^-csً/zw";χ ӟ~=ow@oۣPOKOg /RrGxTuT'Y>q\an._s EE,~<.Oo&zuSt8:w.*/6eсիym[:EG!F ֗{ {|@Iӓ4cmFࢻ2-`YR3f0._^wޡ5ns9:<@1k3vk+ivU+,QVMoBæSYR'j:unuM[q~>kE&pt6+ÅMA~F!B!B!B!B!BQ9lv٣ȴErMfH$2Gs($t{4qPh yڱ<׾}k'&,u釙g (x}Jnh蹿Zo>d}(+X؝6.АP{ewp"<ҒB]etm}XuǚVW5K 6FƖ;PQ, *8ЪlI1o1qn/v: s\gdy]y̸} !&+?Χ *j ˮ&}(sEŧd6^ߨtm]FS̹˾j kՉF>aěo>~,1D`ƸL l`}>TXVSo!&}J(*$ª;*okM#Ak -Bi5dAF軌(*bۼy6NNQ^;ծ- AϞTm8X~bԩ]jݡ3&Xig`Y>q" ݋IIհa4Ӈ]mhTv+fd̙8, r֍ҠGR52tv+[e=?Ҁ/0ڟK !c!B!B!B!B!B!<:fPhYld#ȴGn=Za#ۢ8nY; ({dw,6 "zn霹.o;G1ӝ/.ĉ4qcpk ThjTj$T&:碞 3Sjk |bҼ/ 1i5@buۊ 3K.4tr5qVG]"c Pa|\ Q E5ȇ9Fʯy1 ogF_ #YYV@{_;jHcklzCȁ7.AylsvQ/޷FxΞBʊըk clwFwcw206ŇyhܿLJu !uc䥧(.C5B!B!Mh B!Bl=u!B!B!B!B!)O8+~06@^dawޓh\.%!?WtZuGư5³YPA ;{%[K amҮI_R@_?|פW~]޺ ،B!t@N柎jx*#1Nl9PO2673e_bz(FmfN^9@, T"P 0θx|t&=BQ ֣e9l3-@P@mT@zyzmj 0*؏5[ D 70op%*WFw`|*E+ U|ڧhyAh>@Cx8208弧g ,*5}G13$THi\~!T&:LJ_>̝l?*L5QL#=>v?Q&έERyZ6.@GI:qհe^Do~jr-Pk>72s|KԱ^x{bRGz [PR=AʢP`2 ?gjss5DT 10V+“aBʕ5d!B!H~&B!(& 7B:з1hUb]UpÁ.D!B!B!BsFlpvTs^־ލ+Ci'09#+#n*n;>+%9=n_NDrQs Da37Ghoĉ(Z.пEDhP_\vypVNao\!%BSUHƑUwg]TB!̶eTR1fjդp,ٳ`3n`3 ,C7l5_|oFͨW׌HTCk:F~6yJA*᪳= դz+Gz_&Pk|XǙGƟmqԶkC=( hj|Q c{ IDATYoy xBDԾuԱiYG¤@xjlS3з A_Gj~9:υz ,*o/ijmh76>{ƸRGX` 0넢uߎ:& rP}%&٠?+ BUP= uѫ=X{}z6Tp]UOCT>isCKx*WoE@` wp` i*j 3NͲؚ^@TpBLb]wJB!B# B!Bk'J`Nuσ}1< Em<kgbB!B!B!8gL|>Ъ."ޠ>Kn[ڶ]p8ұX>t*͌c.q8i@#DUN48Ik=[Du/W` Ӆ[ +BxY0?*'=^' G2k(m+f~ޑ~ZH6>7t}-\ч[!D9 ܎jT ڍL&ov8M3 P⟐h[?̥@5?νx >栚^\&`J&3,_5q7c€V'oVX XE7?Sj8 8j:\ {#+u0p *e-';uC&4Bm!F\NK 11^{7jhsnPU}ƸmfQ;c{ z䢶sc-OC?B7{HI睧9!B!H1B!甽6u #v0.IO'y<尳!B!B!B!(N}2tX Z]>m>" 4?W4*9.a8;C_s[n[NiO(W7ݹKsнs"0>RTB!ku$'!~OX:w2Ip T` +)9#i7o)Xͩww|z&=MًXwI{vmOAt !8MPM`N:`*oݦ-QMQǡjY0 -T(Fx Y8~M=TJ09 ^C\jk50x(iԾc64,PM{XQfG1֔J9/@C5' (=ٶtI'oF5 QP&0` +2C"n0>"9ksm *ltTs\L|10'sKŽٵZjj$pMG8 u|G2QDP+7 P=^@!aԹ;nw~@D.߁+ A=7nJ Bڎ@"6ѝTPk \JE?,?iE5E"ܬCNmGr{*BgD3P!m0|(꺀ֹq*Թ^KQۅbQ:?r!j2B'Jiؐ(ϧSׯC5B!B!c!B!Yp=]sd!B!B!B!I9ci&ŇhWũt(K Wj83GHa]Aˆ{&YOv1<%4tix=aU,Nwo,W<̰?;Hͪ~P"+9^ޝ dDq"m6eE߮dVwBQ@+T# Ǟ{M2Tt;Ӎ1˚=Pagû`!*c)UZX>y@ ֝ j쉷+Q I[jZ0A6}GQwJ{Di{*Y΋yN'E@z ! m?&dK|?y(3~Mv?oo]`眅Q$}q>}^+{~@F ƹ]`Ž ꊥ~a'FC.sq8Kesq.pн6\c4W3Dg&`*SqpFX${ٵ {kg0sOq)Fۿcy m\?^U[fZ8Fp&`TƯ:"1i+ka6""""""R)CDDDDDBt2C`l$a|8_.N ߼rlkVf[m˧OY }-GSBK nLD؊*hps:tDF!X*~a`!ŕn{7oe4f;t8 {iv")|Lz3N]ة\nx=tVԕ!FOK~!>tZ붱' GD+OԞ⍛*3`0B <6'Xـ Q{ FQ&aZ8,Q$t/?;FJh[QupH Zf'FxJ"C0C# b psE3 o Ufga?uaMFh~9xiR`15uX0[< pFp'Fq@zM^>Xu@A9Ub'p+uS[}>'.? lد],=K>F\]{iTmpܹqfX1ϼu7z;r #1:>y܊k6p~nqFȦ's`ԁk mncnǙaws%8v6?\KD.2f{N.k\ڎM֭ddlDDDDDD>R8H%Z'S[$a"cޗ .T 9G9فhA\ffǒB{{9|ÓaE|ܧ#R4;٠m OyeP[)4pzϷ1dN@'P[JD\0Nf/[G|-;f$즍{լl}/ʍ] 0lt\붒׌{_w]#UDD~ +qRX>>gF1U F 9*6~ 9Fx (i){ژsc: !)-0Bcc ;a't`_Ų VѦ>Zq|wQ:Ž6=ş`{lֿ )b5I!UT f(D$p=pq)%O,a;F`ԙL x]œŝOw(^}+UUyb%ʛsE]w\uHsZ}}aOQϕ=hM,԰ځDF(_mJ=g;ע/7{$wWلX0B2`<0`"]1B] 8FZ#w10xW`͵܏ Rr)w&*"筎ۗ 4fڏݞy!*""""""1DDDDDD\A#O=L? FƇkvvB""""""""""""""Yג~-y;|6ZOՒJK }ˎЮBI>H5]R7}hZ6;vVVۢNb_퐞c> x3d(Ӄza5ՇR"⮒ fӀ_1+"1}.DEO,Ԧ2>^%\..{߃1[4NMX/0B<|~Zw^E`}B6`G:. Yq=q0z?_ "rt,xZߝw:ms|FҶnوH}oPt21>@7P20I@A} j0 = kҩ<#)Ka@a l~> Zv4~M{`^V4[q;o/[E\WH[16+5z.~uDW4 !2 ʩKr2Xpsz˨ݜQMوC^Y)%rRL6d6#;7j*&+m sݯdgjܐ9"ɘ#1a۱`/+sXNl/Xҷ)s0o ~Ĺ?9cЧI37hH`"8UZBNY G XG E喺G0ѥE?[ ]nҝDCT&[ҙEy=i̸ĵKTc80|K2d-+yGP?oy#iɥ_OyA.y(ֿç+_׺rE1슺3&A!Ui\u=rS9䖕UR6Q%!Nẘ X{\`rҋ (Tx]z3c/Z~NgMi%JKj4ʄӤm6R 1ϏߵV"/YsY`zwWv6-ݞ{@9-;xcZx.dtP!ߚ8^A@ QAhA^bC NUQEn@8B 7(;W|_M7\=!""""""R@[ FMvPQ`FPN`;GDu0 :b<d`<k \/L'm'Ѷcߟ?)}1Y46\bp)_bL8KJ'JsƱm{1ΙPsu>`c_שUنnB=//U7~w99{6Ǐw&aٮV9W/r(ښx1DDDDDU1t(@$ b2dx1jW= 8Wc-K%~#j^OȠG 栨Ofx knY6[9Q7k+n,<6E=A@ b6U g*p-xe% l£ݛy&)MͬHlpҫq a3Yvػg-H~~%#bqs#\E]Wv~ P?z&ߌXZ~}GpWǮԼLE;;X<0_nIwԸ_2:2ªYdA/x7<1I\߶s׷Vs&4'N'qm׍De倨\=[fU+[w!chR b\wwnv}7>yE8VGgW}PX>5ѭP<_eJdwn:'׏@>UfmtN䝡<23ל+c=c{L@ a!)5bۻj01V@C _ ;F`=WC@\J5eyK2S a<O>_.F/= #`PLa16o ̑U6~n?vog'xm""""rqR8ŧ6RtEDDDDDhL b!=x$L$a Ƈ|.$qV3KN< o7̿ kҥ-)OY Myn7÷ }rƓy*B(37Gn&ۤ_]zj^x^{-?/ϟΉ5 J y({7.fU¤6t>xp7kɩĭqe|Y<\&ӉC1?b|\S5 [?C>Cxkۙ{~ܔۛLkFP5Qh ~o.|V]@ 3;dt^aۮ+6$%゙܆f7.kWT@S'E,)K=,[+?Dy ?&PN}w+2h-YY+c3s̵yP?~{=w-OlXɬ%A(Aݩw·6(*j\ܐ i˳}3y+/la~L6~Mqҹv#v]=fq,x;g|I&o}GT`ΏuKؽ6g{hZ`&3]2kn#}^Ht$2bc>c&z4nz]*wJYôKx}WHY%Ӟ*rmA;nژzO7"x*qVU7wD˽KRjWD䴀"wr` >l8QT=޿'nq(0V7pODnHB_xk: w8yfDRTxoLɩ;;IPWKYV| {9o}KC{!C:F6KJ0i>&_^rf|lS=57 ХG Xt3OUR ;iL\O[sAr/R B*f3Iʭt[Lnu{üqZFMh?^U2 `\E(*ja`m#4~q%=(y57 nAra^l+`ןS\^1Ý3qD8YuC ~5%!A7*9;F%W1n4taZB( EMl?$q.l,I=̪H)#`_?bCݜ u`pL,s_#=9Wtٗe-mS򻹅 pOVw|5ka~ڤ naAE]Mv{ߕMmn~6("""""""1DDDΘL&z\w=9r%7nĎQKi^ Bx;uA{%/G#"""""""c\`9L} X g5sShGx.""""""""""""""yL/:mw0"VҐ>4Vn)hȉ*}O;޳?X l<~&}d́\"ޥpoxy@m#\jk`&^ܴylǭLm`ѱ'Ty{B/ߎAvYl\02yG0l݁G\FTfYVDۏ56~R&l㮢U$g5$oodّ2Ql`ټu-:lҀ$?Sͭ:0e8\-Z_.aھX˫lS]7/G*9z iUnw*rO9|cj]%-Yn/ȴW9tXVn?r^8Z`w| ew ,"""^Ѽ9ݯ\~-̸G1DDDDDD.`Q Ty2q$/SunlLc0ɝzŽ{Wmn~ȼ20e/ HMx6fKXylCi 4ܙyiGVb꥗RZP@'Ӛ-sK{{uP|*)fܜi|:j"7&tq8ƓQ~J9זՕcߍ,-7rk\Y0О7 c [/g/\:[ߏ+ZwoNiǯI dn w~Չcum:kz4"ҳC a}ش->]ӜuWyһ٭Vf >;vӹؾiBu:%sl76{k4%? <8%'u-+"""""""""""""" Gd@63`Øؙg1=tj7GkYOvr>H.E"ƽ9y뇖Se+rf3o㴰6>r)L9%\=xA9ѭe:R냬=?`F'.AEtZ|=O̥_̢}ֳǵ۹},?p{.;Q] ~qq{%ЙZ-gux6= ĵ|~8_{Nѓ|/arc0о[%܆|^\\;[Kt<8#Ul2RQ.]p eNy7g:gD`׾oۙKZ;lcx|8l rvH˰U!ٱYj!be6k{oʎ . Q%yQ|;:ySԉLn|]z-^y ١w/ܔyuj: Ӊ `RABk3R,I=R %Ņ5˩& Z,|qMe; *( \gKx7l'0}angjp89]d@^+;E6掎=[#;w׼x̜# x,W]QX?!"s_|g;ӾjmcrXmMim?_?|Gcrm("""9vW|˖|sl9|IIac|\K^y⵱DDDDDDp ߈d6S=ǵ|Mfb `ӝ/jԧh1DDDDDDDDDDDDDD A:R>߻U{&|ܛ{.+ϭMU^<3ާkyZT||H3̥^3[k4n疟Dѯ򛅄1>.FV=M.wի[V>#9p+Gʫ1rUtdָ%E.qwт<$pd@t xtӮiӉ^.URm?rZۙٙ<|YymsZږ1tW7$t&?~g^\wvl8/T2>&cJ֔+m9lk)g7e/ ,r^P瞫g{ָ'V/r5:ϟ'z xuOD|LJy*Ki`ĹN8h** tn[oqt{YEt\>"78sSkOYtsTcƬJ @> kP1_?i^s_2 Y~i x,W͌k{9'ٓU+sy֦Wew?3=Avk@qI;+ Y{Jo+CDDDDDDD.ym"""rQKMe{I<Ӱ!SFd?s>tzV׳Wxs^hՊ%/LI^\|z""""""Rb_by(=x$L$a Z}Xzi7}WUS"1۔X˝w׌X~~ ,Yt[[d}]7?ޣ8}ee6 CWTi \I.qro sխI-Vl\Όx_O*/\'T/ ņ=M[ɶ׷%RVIVk4 ͝{ $ߥv~!`hfnН!1q@ݜCwwSZ‹V:;ԚE<ni׍]rVOmSޜ,^1AkX- jYp IDAT4hgl̈́$)m6$:%9w\Mj|rzV-He6+._i;;$y/䖕zdlWrgwQ+b65BqFMe /2&TSR0g1ukY>>&ugTks}hӎ28&a7~_5kڝ#v!U?ˠ->YKC{:Ԯ 9xOxv^2Oڜ~c׿}Կ')CDDDjba_iҡ;h(AFDLJ|,EEgdpa$m6,EEuhDDDDDDBp !:T`da06$ CY,Q@wXρNM߷\m^ڼihA@L~G~OY ],ihrO}W23GICp~;)]Tnk2C{H)%.4+%sM,#A=<+{W5hlV>޽cb<~aoNV+XwWwt{Ȁ@еWMٻnhq; vsx23Y \3uvWwsTcvdgV{3=j!+&gG m5lF xi@y{՝oaL\[ҽiZtqzKLǵ+e۫:T0\@@'"""rKQ7nƍu=q'AZDDDDDD~Z'S<͟͟Lv`5FqϩOSsݐЅPض+:#C73a:E?Qj䇟ʛy3pWYS{hd1>m^ܴ£}#Ǘv *m%+5vߵP5dD9ѥvon_fRXvԣ}Ƅ.4 sL`(ș}w^ 6pźTH_ wvڦCF}<2Ũ4(&_kbuG_3#`7W+m?lǯf] 1sŪce)ar*>鏍+~ݝ{a& /m:4{yu? FxusSk73r{ДcfA`{> xYrHrվlA`fqP __&]voȘzoFzogrZ>#Cy9^֡=L:aE0F4o#>޺c};EqG~K+3[DDDDDDD1DDDDDDDDDD䢢p qYdJ [$a"c+$*jf2DDDDDDDDDDDDDDW[%1Xx+|| ]$gzEn3Yp^84z_ Hزw0>N1BBUV.~q4ZqUs_ε''wmΉN(=ŀ{:'&Jg먐p8N\֛s 1p] 1Nt X({&n{^okm (ԯ牝_hi\ qo`'/VU,>|uMUd4|KBTܫY C,B~?2wڑ^k8s*mL>?rQz姪-U##$Z#85Eѓ?meh++BKy ܉ ap"$ wZX_]:vd_\q۸ۇ|^[#qi uo/,c@{[5ُ"g{ϝ{f]JZ3&Jݒ< cьI8YDDDDDDDDDDDSDDDDDD߅ځ` a /Q 8ElݓDDDDDDDDDDDDDD7;TthkmP5 ӲPHAÐS~iS~Pݫm(F ̲A}뾫yQV;ZaQîSXףּ193`*N܌DtUx*꽣&%'!f }ޘܙ_"T{9y" Lo,"^i/,5dM͎%spZ^)kvaw}ٮzRta/!>i=oǶǑ-ʑjkv3u=6ԑ2G* hDzg{{憭irtm74 MAzG7ՠ\ r[ *~?-[BUr뭰=pIVd7#_? H}',}9Yr?OU~_YO]e7OPSo O.CûA&hza;ΙΞNJݒ~RDDDDDDDDL@_Bcp %"""""""""" """"""Md@=yXP &+eg@QlX1'l-ho [s5'z:&;O}kkqPŁޘאby.Y)V!ڳ]P^ 8Wszqz=ak*Rq=}g#+yP7^٪Ot`HW+>SWtBUP8g5E4^Lmy'h`2`Ke1^ovjX*mxxKpZp=- Ljl&&;o_Nrp`FZ7 ǐFt= Or{Py `8Ł4C)?n^.._[u cl+Ϫ4mN\80(b`Z^k&'-+ppc17 c\/9Cw5w7|݂_\<>{e ;~\=GjOxX ^\pq)ʣgrn:~nEf{׺咪'AJ#Nu#DDDDDDDD!(H+Zh(.ADDDDDDށz?fHzE """"""""""""""|:1uça !dA̙ұ8= FY[04 4(!(- vSz=BDž kP}Ey.3(6`'R}ws5Xo:o"4$Ͳס;^W(=K`Lo xq ~˅(j_F1qڥ^R-C֚=^PzL?^'~`_ Guly;[=wvVyhڸiF?g]Ȭ^)+6|=>0- X2 =եq9Q6ޢy!+'mdenG+2XjWu#5{n1ߓ6s(uKycQpo"""""""""""a """"")U]ADDDDDDDDDDDDDDQX'TW-@O aȉu%nPb%:$|Ɲr|]{n]sw?/8-j<:E53akr~}`/һYZw' }A &|cmpe9(Hted(.{pz;k>Y/M[0Z,}$.XCS cь0s`4aA`7 Y3&cYf!edQ_w[Qp\j,ƼG?ږe3fsIHz LbthԌy,2c7{=.P;'T,c:4fLFC LjIBuh(Ö_Y!ˍ~%tz{h2% K2qE\{Ia ݣ:t4yx0l8$IȫkF^]#4#!Ϣ[d4e_}9Yɟ6¹j7ŦQk~ :&Z\?=Ox;1b_[[K[;'9TI Ԭ^]}TBDDDDDDD$H½K%""""""""""1ftB }۰߇AWƈ$hLzm'ixAUJ4РIzEl^} XokIF4iRބusl,~#ߒ9uC̵d 1_*x+daXռdsuϡK<%t *x“XXpLf=!ZFp412HL!(1Fed3sOM+LOE%NjV]iFf5RU;o )̹}#`~ѣmjcɄ~僽HELtFpkBm/K8I' 241#1=\_ٻ:Z e$.^?S Q ,k$)zjsh&TW`fc8F#YmtXN GQ8yHC  ~߉{ոs偞 J2m1vYn l; 2$⟉ނPHsz=bKTÐS}D՝;y+8-KNոwvD `~(`2Q[~ { 3|I4ԡsC/7*D0>M4 `1uMilŜ6FFCt(.<<:I_OK~+W9H??֫Ve)@L dÙ %C1V^;kÆcl+lPA{!bCCmL(J]N[ɕ͝k+dEIꞴJRz/oNu/DDDDDDDDT7@DDDDDDDDDD41h+!fـ(nf%婁Ccݐ7E7f4⾢rpy,2L~#}s+N\쏡wOWK(qYSoVP][[Y6P]Ǩ*%z=:>M֝)e Dcî: R^;"O _kX^q(P9-VE7edNkM2tSe}9Y…@5e|{M¾~5.M;ށu$;FIMƙ2>!) ,Ҍ9EjRjWu#5{n1STQ%%;^G"""""""""""""""""""1fL_O0q'r% tF4(6 #Ct0;{nSUf I48Rօ8i*fzņ?`\g؃=6\{'>` xhl{?|r5[!a`|&xQd HXc ׎HTUqt%kQjv䷏Gt+Ɏ>0`^¦'_@3Лma> <5FI|HXa ) 8b?[q[1D$ "axm]@p;3Nc  ۊ'\wgcm\ | m?; ŒTd{_US iI""""""""""""""""""""Lt rδ̈́p ٯ(0@m.xT+-]K`6C2v[o‹8i*; P}t":H~6c/q)ݚ" ȴ>:^vpeQN/Y].@n}3>8}fs##'zJ&@ƽkMu8uAlb Ö9;ģ˅Ӎ?>$f3L>fh8|~$}: KO~ XA1472WCs6+ >Ɉ>x=^n70}\Y]kla`!x[=c͜/>#q#U|-%?I}i)*dPq457Z֜;&t/"""""""""""""""""""p """"""""""""""""""""n4/;X9ɢ_TJQzg7kRFpwzk[v6zC:^..k%ΚrTZpL <1Gfy. P #` YQ(;W}lEcܸ#`ed|J0_OЭ؍b$n}װݑK梋.X^#*>E| x(85"=d)QgYqTus_aLu+q%X1dFgst5=hvJ? j/W=.FRGɽHw7^' ޹3l8FZq1r,A3]u+$Iw;#Zh^?3qmz]OcՏH$!`iD KxLqzML41qQsDDDDDDDDDDDDDDDDMJtؼ'fw03!cփhx'DiF$JsR]Jtmc׽(@{HW+:]0(= ghZf2Lϐ=+剢U^u5 8q~R`!$k?1H gW|kx$:0/94] ?o s"o>XFaGvFcc,ܾ}m"ZfiLhӫ7`ML41fD𘫢 gvޟ^1M }DDDDDDDDDDDDDD4"""""""""""""""""""8k"ʌ|RUq$լh 8,_T2 Up ߕF~\JF7a/YCvu !Ϟ$T Sos'xDK^e>fy.4-du~ TᵇCO^vkEr4ӟDl5hWrǺڰ&;/dQwiQG"F()x$Mߔ~$ AƕQq!\m~+=Zm0Sli^4dޟ^18c ,%Phz+<.@ f? DDDDDDDDDDDDDDDDDDDDqVҾB~ֶ/$r*I9F4UQfU"/(vAUGѯ*!.I@]rZ+RCRxg(UbkqPm@4j}w?|3xȷrf 5C ߼o۞^^E_#g""}8#ak/-s Fv HHUa}h {|91*-[`0~aN㉈&Lp~EUnA0ex q1eD ex1DDDDDDDDDDD4} DDDDDDDDDDDDDDDDDDDD݁;EU2[ܢJ@ ׋=eJ37@i=~`@N5̮_6 NZ%EU! ZӫctE ͛p˟kj% D:_A|J_}5XG]`uaxa`qyf'\F PH:"bʒW$TCDDDDDY2Y{@twx $zPA&1" Ex `H2E\#E DR P%@#-c|F#A` !""""""""lch. #s$>l %“/]c@ OlTyD=l~z,_k2ӇT׈ XoœH1< ͥk>K9: *䴰u ƆdL!|_;QT9|U?\uٔ͛ {z$E4]hGlb^|͸wꑬ\{Q^$- 8d)!s^èdYр? Yc:23mj(yrPݷNN'>Q$c%:ʀS(C s5 &?Lp$Gx $)7"E>WǂG"*_$ ^1~cR }D4- ,c1DDDDDD4wb_4?^T`3%Gyƍh{7&p0Mp """"""""""""""""""""[U2B *RokioAvΘqdqY q~tXq/b^`7E6?ע߆Uf}\q='rKq<-six.^=*pReۃTړp{nQ:O0˞SAt8,֘FuD`j$N"QႀfWU=ެ~TΠe޾n^Uxhנ%̾A=ab~ךXsGS!k̲+(Tb۟o٦z풎K^iY5{n5YSݟچ{V/7l5U虜GNKk^gu-JE "]H1XnDJS8KMt  """""""""""""lH"""""""""""""""""""i#wVYK"9֬ ͅA歞jpΘ~|W㿰thDɬq'ײ~~]$f H.۞SA4da8D zch *œ7lxF‡dtϾ_h_?pr2,vaE ~)!&Ilw/fy|-tt|6 FŦEBl) ,ï(~~Iz6lnO5lVvIG%Ԏ.=G4u9O#RѥxQy/wNu3DDDDt(dC7cd gz_41oMQmL29㈈HgT7@DDDDDDD1tTwnUYCjJWFF5__:e;[O^0MY="Tk܄$XS[p04':"M#àك#BuV+f!;RFm~N,sPݱ.U~CNe3Ns1&J,E {u,;p$C߫Mp'&f5LDrb~ vmǵnZqo6=v\?9rZ\l j"0緗TLvm݈r/#V>8buQ 8/?~0`x fix ]@^14/c=.GDDDDDDDDD$DDDDDDDDDDDDDDDDDDDD:݁;%2 ssaйy:8(.37V=nr k?=egP]AbƝ\0Il?ڀ׃KTtu`nXhb;ۑ/E\^l1o*~GAb(̘s]Cp[qb0<9=l]z=; t ǰFqyP\`׸&\$pS/!91r0ŇO|+ zHhaPLW'x=صhѲLرq pu4 ̜ZGU7cEn[qn+ ^nK7AV޻j>JʒCUKr% ETDmHE>~>؇^f(z !\41@21<&u:DsL?t/ tp """"""""""""""""""")[v`% 8.$ŵpyJ_3}]N>^e?CUMّivOPbXf} cUf$+㟾Ѓ#'qʔB21$A'e= с}]qIo#6OrZ9b9QyvdI [wf߯0$6hNB"L hđq'D3SNBd޻ rkT?z ãaqs{tWhzsLW+xfa~n_3">O}xmۇîKpq7}F§rs dݶr|+n/Ht`a :gEtZQl(;oy W%.=SJfbݧg?=S QE0@f3!Ld ~qIRsU"G{}* @?D0ExEzOU""""""""1h[U2& *RokioAvΘqd3 IDATqYb_OSl`qo0qGcD1XO CN,pTŅ6,a KP2 YbD-'ℹLxRC& j1'=ӔudLa;9O>qLLwCb \{5()B\]p :R#?HPf)U^C'  2xc̞V+HxQL b{D#f?/]7v׃x;~pOܐe\{'T{ڰI)X=.kkq9&Xi"Q9]cg,8vvezgr*YQ̩ J|vO`@l`#R3痍5W13cM8ܾ[ UATCZ׼pwIo2Q'/rfP{ma˒<C r.|SN\7_:?l]`le(t<Ѝv BR#ϩ1E|L!'̅8a* s!j' øޙ.My$e|J053Vua>IPZwVD39 ^޽.aԵXR^SyK߃DDDDDDDDDDDDDDDDDDD3tE"""""""""""""""""""rˆ}W#Ǿaj{ !Eq-\k^׌wjWO~seSv'Sj X5 сtk3}]p/o c,OMÕoicH-q­8mPv( 9Y1\{~} ?7h=ۂL䃶rm /"ŬKOUX8z;&$#[pLǻ4>}} { N|}'s&kMv*Wko <3 ΅Y6XIK:.?v[>,eϹfLu睾?:QtECDDDDDDDDDDDDDDDDDDDDy"ig<ªK}[r?}B׷|/rcP =twYnY;lMw_]#Nt $eӴdh:Mx/׃sanJ̈́0~ daS~Ʉkvc@ W0¡qxEJY4t խ׸ ӖDM\*+WN%K6 bCAD"Vfm-UPf)~OȽ0X!tgz!fPW,۵rv#vxNvⷵ&=ud;{Lh7B5E^n+.ngcM" dy^G~ MWmB[d乧/}~Ϟc!""""""""""""""""""""""""""""""""""""""zW:>MTTCZ׼pwIo2Q'/rFvtGec<#ϋMNlenTߎ@=H\ ,hŷu6ntpTB51u㶾/cIzv,cYjӼY~%l^&Ihz'kj%`{v Zz4emv(Z8Ն ukthIQ>\!Ƽb鷘Qz nvJ׋6zߏEo7V.* `4ZS<յnG^!֩7eȰ~-G+WOil8o{I~r-danתMM77ts#'/9ҜƶuIΡrPIe/Vw^^(B """""""""""""""""""fc@{`Zaй0ܼSmGJ~9`COST/5]__n^r <2i?s8hִpd:17Ћ@*a '<[ =è6$1y8miskKNC]߹/4_W}12s'ܛUڑiA}2z=.4+nJt }8݆[+"rbWe|bIȺ `1 u붜ɿڛ 3B c CژW40gⰵQ49H=G}o޷K+YۊĆ֭lϫi?w<q+{o-.n և=~WOo;lVގԞ egcmpyp-X;aҋMWtQaC .֯Is1{%SԖ<[~>@DDDDDDDDDDDDDDDDDDDQ`8442&A5(+} WP)Sޑ%z)Gtj†ciHX"3'l݋MWȃ3( t߇`/J}_{e/9b[J7C2Z-QpmaC9/׫kڋ`s' H6 s&rz#m/"陚Ry//7ׅ -s5 ǸhfkO湆v rJv_{_2ѩ:#a, #k ,JϚs{[~황|W<@Ek!6  Y8rF.}!5Ԅ 026(NJ䴰u6\I71vEU# oūQH YiKgތ/=^egC mm!k̲+(í9CC > ,'.ԥ.=tbUvsZ*YQtkFU~ WADDDDDDDDDDDDDDDDDD4s0h)eViA s6OupP]3fo*z~AVjWs:\#IH Y`Mcl/QW{??jlh7 R9t?$Xf;up֘Zc "O׊ե3-V'rTY,KsKi)tGxi/?}v:o<3a3S )0fJK?tNoQ}OU?vZá0qrcCs+q{[v kAKnoةkW l@j࿠Tgkk9]=(mSg8{͝%"f~9u9[[஍^{#vzZ޾])i#n'sS+8&{&&2Ͽ燷/CKu _ ;tGePBH9z M'v|Q#&htt{* Yy}zOeY?TAT NydZC_^7zK@4E:Liɿ);JGNt|=њJsmݷLŃ1Se2m(kk%G5/j״)eF%{߫V觵覆rL2vaƗےz;+=&AL9ޑ įlq[35.A3&eAʊ^^QЦ@ow=i=6:Q ɣΡdյG'^P/8Lc}LQtէ=WSW='i 5zGTs_'aj; - ?ettn?~ {tR/qXk~2a9˚:wS d03.>qZtzxAW߷;m}W{O-09А2Z}鴌ٶK_zw/0w#JߛS iNW-JW-)^m5ٱ2d!rZMKEa~gn6Vf"tIzno.`ۯ?l__L:fsaIsr~͇l%29]:P^^ɧb=6I%^Nqtb蛣'l_O[-:[گzJAҟg-+_IXI0КeX_aѺ{Dm/VYp0Z!7S7v<~S+揶w9zƃSLwqqkHuGC]Uwʟ nطW>IJcv[u~HwEݢY}~!}Ԃ>#oԣ .0E'LQgcHg>ϯyp`Ћ_@3/[SJ ;ݷ#K]/uTt>S*>8͵Q@VPv3^N)4$5-.A_5':/驪]0P; =sѕgpV|};HUGa.q|f_6NOX.tՕnk hK5D"\ׄKPN0P3ţGvo95ёZwzWƜ)r&؂"]ۺ7mWIt[+= 2k7I:XQ=|/੡sr+C{:PEQg.ʷ'gYuH܃ Oarb\鹇w.4JgN^&oH⃒6&9K7${բ${բ2F\s-C>2/dZ68E EWڏV(Ѩ~Z9j^OJplf5sU=9[;qu}W)Rh0u>\;7jyH]_LODw_鮟.k+g֢q q{KOov㦹Ǽ%QT usNtvH(hAkp隐&w~^o'+]  6[e「GTKhcZ=&q[Ǚ uD OtxNX' =z;ܳ3~NQ_kOQAu/\Vgown6F_wґ eUd/9@ߝQQ[P_KO{ܧǡ=JQħ6"WdoPAaze2ן:5'\Һ[֞gj3C7RӧDſ`]: G[Gًu:GOeI.kNE@6B9OЩ/zh&2lutz݃6_{DZ%_D8j1;,[|h2 vN_4#vt߼On\> tGB|&l}v{dh3%o|:7o(y*Wm;Î&l IDAT QRf﴾ozm_NwקʦCڒv1, Syͳnj:VzxzCۺhK@O1$hMv`G?/ipew%h_Uþ^_ܠu5.kbZuM W^+S;4z)܄{wH'j=]^_|]Of KnQJdL?߮F9A&޽F]Aȗ a%Yg][zZkOT67[׸4K-}^&{^4T̿fݟsGU~-e\םvfYv[W/V~o*oÇtCO[i =6{ۺg';חM_˓Gs=>z,ݳ1m0?T47j[߬2*DHIZ?տ~k/7[>3h|W <ߌ@x$tAI[{;8IEIEW7eګù>;6Z}2d^N}%iS_G:jhRQF\t4)٨8{5Ч^v7|/dT!ݧ+ io~G@K۫U^zQk@ÇS36E(^@"%^uf{pF]38E76jFF76}RA&~0n~9m9 s,Kiŧ1SI W֬}νE7{KeHÔx}t孺7< AIp͊f¸3ȷ*A 6CڽY.ʣStT&Gj%PsxW67Y3szF%{=)1zjsKu{H ]]>VwPMSe{Dc2~?m k;?>8TV _a׵զv+rnHBc^!$jitj<2lL5b5"\˓Dc{?ߡGNTLo{&ol@kNatK3Ҵs&ӻ߸E}O7iruL nn_tJv/uwka`- 2 eya}4 Kҏ&ms5ԩճDZy<[zT̀ W}XH7.+iGe1%tRhu4MpMڔ;t.>S/3_r]< OVKkw9RRuMn#e4ǻ/ioQsR沮|SNo SRʀqZmn[>~Gy Ipm2:>\6CKGh[I75~d2k\L;FW6O4굧_W/F_>뙿kqnN[譣Kj f(š+2uڷ<YgA;\h0h;5o冀W7.o7aB,g^P[>~Gjg_ެvmн//\2zUV;mp6)Ѭ`(٢0GB UMp4)٨ǙG"M w4)●=N[*^J},S˃]\7՘ZNnu"sq JZ)<޾Mͧvc M:mP9BB0 l # @n[F]xv*5CNn_RߣP O33.іWZ66(l֠HMMм$ ;s*55771}5: ӃS)ds8TܨVE)<͍mk}1O4[uk5iHeYZ}h[%j~phԀp]6 YW .LwnX` a~[[e5k<;uz?uLv癯FT`n:Zzbm[gkћt>O~Y/QyjSesN 6[Ωw֢׼թ` Irx[״/8eɣtE0wHTZ)>o`4iRl?-##]IZb P5D?rj]˪lnpQ/d'Wݪ&{J`4 2TqkS7}Kߣx`C_竟%q8RUE_knsb9^dw:UԠ:[fbm8֚yUǏ xDg?@wȜ1toKz;"hG'&gllzG=J{F.Tc IKOkw'cmE=%$MKPwWʣ*{vo^)Kd0+>- 6ݱ~VH(ZZA&2/{++V|{7z7iX, xxyIc~YU$%iwjquӺ|~r:_m6ө+Z[(/wb/TXCPۋ)7GΓv es8|tꁴ >Yʊ`Ջ:Q[ӾSӧ}}d]^:r{wXs|Fۊt> 8ii k:_e{Q.}eU57oCM7{K=Ӿ2MY}}UjMv6 xXr<*eu\zCN"ϒOku2[[F,mQ_Ѣ5h^-utVCڿCc_i]V ʝگK{Y>>_^9Q=Dz}t;5[]}FAɅ}]/QM}>k*Yj-ZHYO_;TQI+cAׯ}S;5U83pզ~򮖼Va}nd_σڛyuЛ]+_Vz-5o :TᛀÕesz3Ou_x[ Gm&-yU}wUXvڵIVEЈh-hj\M&6CK {HG;xnҜɲJe%uwwͺvH8t% TD@&{6[GF6C1zѨ+Z<`B-ֶ:(/֛Gt5Թ=4"Z9|e'rtuhv_SM4_SѬak@P܄8N,zX6:._!Ɣ?@G xf7Lt`Uhx\KRicVֿ2j$MwY|͛za5fO}}H+9fgT#!U:Ԣ>ٮ #4 俙l9zD$):Ъq1q S%@AE%:TYkC :YRH0\"!P%vPo"8:Ӧh;5~M諹z#U`̓v z0tjュv-zdwk4[:ʬ,Ufe_ 5$*b9Fwt*LYU=+ࣳNV`EOZEySO򥆖ZګÍӺ{89(/VFyqwCkI(Ը-5nn5xdەRTE>J7UfM-0SQ Oto]< z1\*MyyZ// #e4w*Wm+` Rchs'sw '?OЛ$l׉.K`p:&J[.Re4v^MܘJzDi5{z1QMUEX?Mham^_yHOp n| g9_8%G1(1bkYj5Tux,)P e/Sх|g q[SZڻ`<R50+ϫM͚1U78)Asq …+ ϹWSn= =LU~OXoMtaq2ZcM]<WQnk k``PMQߓ5iv9(Sݫ>} UZeN'_?MlQcGa4cPz0SrrO#n&uVs^ѵmw=7DO8Óp.:x@>:m2ٱWRxyW=4qc&nLUqR2Ld i4+gw$.` TUj M?`u RNmwڤ.SyՕ]4 t^CXv.] g+@&lީYG%wP} U:NgMR~=`ICϹ^ܨmE'a"Z IDAT4c0R0SrrRx2d^Nɾ.#/:#\M.|i4*wH ݶGc푵>Meo٥~}=yΜ?Mx ='rptDiA.` TUj M?`u l7*0҈ZwD)h%d]>풽!J sYKf֖z|u@7)>EeMp3&ͽ&=J@S33qZ-+kG|z'1zƠcƨu); j2:{.=> #N9ӿ''XQApeYc̉ Ec=lʘ=Y'o~mݭ2dinАr [ eOu*y&'ZeinxTfMҾ3T:W ~εf]u6z>1za3JRݱߚX3蹬%D~2J.5<2FfZm~ka5OK5bWmݭ'O{'IָUW+k8Faj1w5mM^]ٓhע*KT!aЧ97Wͻp\8` 53F LdȼVs0f39t4'BG ~ A]/+ұ*ݭ%(Ps*cTŜ.Ѩt۶[A^SXywשp4ZWch&G{|RtQFkTZN%'i3\2ɕ W[w:~A8y'c Rchs'sw&vLz:V~4T6 'Bg]9h1.?HC\ּs,ۯ3<>N[.R5bAݺ[ǽ>68ꟗy:بa{_kz[`Vsnÿ 4LE9ŪlѪPUqɣι穞Ơcƨu); j2:{.SJ)wt64Oe΄N[>dH==fuө:l:0k̚ӥOv+;U}bՑIc5y `p^]wYk1W^qEij v+ Ϲ j|:7z71C=%f0UC>ac5s+"Qb9qGhvVt/ղ#\|p"G'j?$ڼl,]_ebWP}m٥q[v14X9F)sx$sssGz|۪YiWG|>3z713s0SrrRx2d^Nt=ݨa]Wid]N_ZE5HˬE29/>z2#ubD߰D){jT~P?k]mݭq[w62\9GȤefs-~b-4nn Ѿ376{FNl빇|;,z=1J qϽ*| "$Mq*I}UR>:s^^כ$5Jj,VR$G7:1lSU14_ֹ;[k{&r:V$))'kO]0PKc>qtI`5kNic I5Hs(c),}Bj:/F6j҆TUR1ʞ4F%}UgBn|g4CLIu91Yg'KR}8/^U+.ZҔywc8eWL;?TЬVٝ34*$> n‰/\|f2+}traE.kZt'>.e qtڼlkC3C(Դ5[4mDG*w4ZC4|p*_tzn ưLU~OXoM9)q2c397a ^`ͺv -=ݩ^w["+S{8[T` _o X\iqUoy=|i0T@J-Kk`VFWrFL Ҥ !U!:6zLca<`0h/~eݒ;ȡϹj5z11\(&K(iا,Z :ΝW(xP׫9W53F LdȼVѷ"\15/Qcp30,BۗݩܠVCw'YtC(ˊݛ;:*v#YӸ׸\_־թ=;Ʀ(ol JsP#veѓckTZF!,DG*g(>HvVpwܡtmyvחms*9z)~@o(鷒݃|*ZucD< +` TUj M?`uZ dr5T\u&VuD &4s8[ޫ󴫤ݠ@]Oɣ<:tC 8V_j yӴ4UhؾCP2k5nnۺ[N Q05=˖i+a9vIުa ynש{[jYʙ8JNA8ے$݃@[N=%$?LS{њyc눝Ok5;v-(AP,xbol7qlTI͍E"!}t)?$Iڻ`.j M<ព֐r *cS76E%7!iȼyz9gf+}Caʘ3E{PS0w\#ZRuVIA~Z%UzXA$=/o')WRμ7{\u}flvdsnBA-KmW֪B[ETAZZҊ JMP@!$$יgI&ΙM^c9ΞIfw$ASY@ƕ$Itynqܼe$VYFPFs Ӗ,g3~SHӸ19hɺYhY**WpiokןA m9=G+ʋo=Q#s4$I$I$I$I$I$I$II!8/! ΘgL(~ \qO'}9p)wI$I: qEn'r!+Yzm♇OxC WfK28c)^5)twy|t%Om^]Y+I:Xۨeڴ/-ceTo<œ'_`Γ/UTĺYu}gwİ M|Hkä;8g8gY;{^|k'eyJI$I$I$I$I$;1$I$I$I$KD X \O(mw?v`yy$I$ MG <ήdAds<}qɐ37$aTtRs>>#ܴi>:t>2tJb//z,=ݩT$I4׌<η0iK3cѫ_,dL_O5'd3hp)׳??j]"vJjdˤq,lV1Tҏ$I$I$I$I$Id8$I$I$IcwF&/ ΰ g [$I :iHJ^x_.HeXID RINX?t89)[/-zK'O3p:Jd]]`NjOyRR,ڶ!1/%+7Fds鋗1eo$1 Q1j6| vUn4f8}C iʦm^K5u/x?ƍ\8Xt9,=45$I$I$I$I$I cH$I$I$I:V\6 |z|pp8PcI$IĆ"ͮDA˶䰗K{zq]1(Iync5}rf҉,XupךܵfeC y\0~ 猙c)JDΎv߸{֭eirI}9[s=*_(kM+}e%SS9}OOH:`qYGÉulHOwWhB-O=OeꈑGݮcnVfZ֯ wr3o᣼rΩyUh$I$I$I$I$I$I$I$I࿁do0~|6 $I$屦x#pf:M΂ek *{ts*9 ޔ+aҖ>ZF=YHٴ{֭uLR;#G1qKRY\JqAZ:ٱZw;ӓT%{G sOsO#JQv=KW1}2*6h߉t1 MihGwa!MuhYK:LJNq57`ҰrfWV32* x=صu+{?OO;8g8ϱ<4ubF{H$I$I$I$I$)!I$I$I$X.`Nڧ1ǀWq$ItLpWw0ƶ^?n+/,I֪ ]p.8={r-uKWPd%]{Uf,z~ 3hYǺY,)p\>n4]N.w|}#i䥷ys** $I$I$I$I$I1$I$I$I$ v'x)YcH$Iǝ1v&T{{X8vz6IwE]Cw~tK'$I;e:$VvL|mKWQt%͙gYy<=Cyx6L†iiDW zX:o.V70Y=r.ν1^=Mpk䈬!I$I$I$I$Ix!I$I$I$i{w6őQ_I$I9z%RL!͙s=$ &32ఏ6*IB2u'Nc݉ͻIƭ.]W_c${zܣ o, Lu86L̆ShD{Y=Dm9_{Sx93y~NZD$X}hsGag42i&gMxW2b ]y ֪ fT73|}2~Pٕ9 {Uf,zI<yϞDf$I$I$I$I$I!I$I$I$i0+愬=Uw7樷$I$IҠQJ2qlwvS?nk'$IǺBf0'LXԾa-mU}'s|9OU4Mn2 ':М>n4]N.r= s(L;\%I$I$I$I$I~0C$I$I$I`v*vEq"I$IXMigoT'$IǛ#괓XuIN3zf&gzƯn`HWwzoIzeZGU4mNdԉl?T2~a-7 Kf.~w,=4^lvX$I$I$CО9$I$I ǐ$I$I$I4E]$I$I=%LZwbW'$IǽDk:/#tu1I+0yE=o"Ng]yNʛHm&e86LBۨw$2461jӶ t| ;/egBwaaǒ$I$I$u SpI* u? &aa.eU$I$Ie8$I$I$I,BmKlSH$I$߆+aʖQ}>V;8$IR)ʹY9w6EԬk_@ͺ&==YMԬ]< @Gi ǍfˤqߚVC"yK= cֳ3:wp0o᣼|7,lj$I$I$)Sn%޻$`^7̇5# tl`I$I$e$I$I$I(ٴ6$I$I%AjHPs*)HG_Y$):KiYG: ;:v=WȄ twgov72YsP`ƎդCf<ĨM2>kv`ܓxiX$I$I$w(=LI-pU*]WC) 6gy\I$I$e$I$I$I(yf~e!I$I$ ͕SklgwiG'$IOWqa]Ԭ]τ Lxmc(茥wQ{ftub [& ;q;nj"~=P|q(fֳ/ssKh<.83 D $I$I$Iʌ[8|@~+̇eiXG{]$I$I1p I$I$I$I$I$IR֙y IDATuQwvnls'$IʮBOZU2nzj֮f*4C:K}L\־1SqǍa}Ksh{\_2K4W3yE=['dYo+%I$I$Iʆ|3@If%`V[iZcW$I$I1MI$I$I$I$I$IRV%YjH}\NFW&npo.3?$I$Ib8$I$I$ IΒ/Jy$ȝ$ )5dz(vp\8p?=9EPh,$I$ie}>0fme'IYRLSd&HܼuYȸyT*9bhжݜscS,?c/\vX$I$I$?KM07 s·uCixԼ$I$I<1$I$I$)g@-0MpޡZF` xxؘac{o|?*/' 8PXlKM:oNN're* g >wo%8w%RRt,E-puqq cLci-C^~ tfmJR|לYoA`כ% O$I$Iv1it-Ʒ$I'H\SMsM5s*EmhfƬȘƍh6<ŽN<'?"O9vA$$I$I$) ޓ; r pU*7}C$I$IG ǐ$I$IܫCFr=s'!Y,=o`qy0K\ΈT?=Ӄ*Z} b!x ,{Ƭ aP"8.ޖ}n$]:A;'{_ p7d5o {05>O=Wx;%a M./<| h\pH$IJ' \G1# េugMI7fxqxǸpb}:kΑB2NIp I$IrnQT. ;9,޶$I:JKhLS+Θƍ^m;YK~pEys,)X$I$I$Sn{ }kM|, ;GH,l$I$Ip I$I$IʾY7yOHN'Xd}@En ,H bX ?Sǂ7eo9𗽷=ρ['\x2{q)A`Hf | k4co'A WM#x+gޕc#I$Iа}%m.Vߚʼn$I/%4PK WeȘMTmBmtp0bG oů9X:o..8ʑK$I$Ick~pO]4\\Qߞ/ܻغL$I$)o!I$I$ISJN)0kہGy}f'*|011`GnwRi+c72q#6m:E}Y;{^|k'z,I$I$I WCQn 6 M\ܗGnu$I$I1$I$I$);w'sm@#,Z= J.e&d2ףI$I$I4 _ idh,ct6C1G)ʏ|>> xx /S  LߘTo8ƅ_ר%~"/ rmOfo~J?<"sT{G X{k%J;> H>L;$I$IR||OI-?I$ڈ\;Y>ףouTK;;Ҿ$I$I$Iґ we2Küa>I7#I$I$ zcH$I$IRK=o-!;gzo#bp50 ApB>\ ' v- BaI*x Jq3* c"xC ӁG?Kcz7~D?K#͆ yn4$y!#0x?rMp$BN&9a9{{ `h?z8 !D1MI-Jx+qy_o#z3P x998@$I$I$S NZ[C"}Ӥyu&R> $ISӖdN?8~7_>Ys*/mmK$I$IAijH{6 @Ui· SpW)I$I$ 6cH$I$IR|OpPm/EkO3^Id| a CwQDܦ @`m + &/EI/ǀL bnJLy0ds,q8i#( sRM1rCqF4p'Ikz;T903c3u'Ä%7'!?> mmn \` =뽝L:wan[ϤWGf9+yPL >Ep))B&'ɕy q'AxO԰&yshk  dhy}c}KI$)ߔ7؆\nTڥ+]֪ 6L̶]vw]v趩DOgq0}:J G]`hu=CzwKaZ?joI$I$Iu :?.ѮqʶY \ _ 5hp$I$Ip I$I$Iʼg SEo#8 h>B +A1K)D~ckC XDv|. }n&\+qK,>"6\0~xw&߄,6… "PCC/'`13DŽf`?ʉA ,TG_\L1{ p .K$I$k(;{~6vfwI$Lwϣ6LH=颜Xߖ(3[Q>!܉v]<:#BmX;lKzh3?%I$I{7\ i# c`n뀇H;<:0I$I$I$IYNY_L3ΡKΆ&X(u_ߏ}ϼ+xqkĿ~}/a|v g9ЏoR?O1}ǀBnN%8nZ>f` 0d `,3(xgvxE*"7$I$)soɘ}gA G,N$I#)oe߻ MEudw !,xK["ltJ ; ZH$I4( _ijg)U ͇{7Bcg$I$I*1$I$I$)s\`EL Gi][z p-8:EY | F"pa `u 迀?scD8;Y{ fP-f9PqqP~&p=1_L<31Ys%X.$I$Iʂ"fl=NH:*$IEݒ㶻)vnI"%Wa(q$>wA,!CX #2na$I$)nKgɀRۻkMM6wk%I$IYcH$I$IR|( Y |9Y,3!DfVG;8 L .P4~R8q`ap#DѶ/HNuD*]H :Pd*UbtI$H bb\tK}KKsFiؓ&+w:םnja*d=G 7-IHM%&i"ߞ/%4,\$I$IYe8$I$I$e"Ԧo5< Ybom{q…c1fx/0>B}pwL?1#2_"¯f`T='ڵx(}Z< \ձM]{<\v,Ploe ?i|Ѯ_$I$Iybwc{a&mD$I:%+xdϾ\rh/+֪ Z*_)tgqQ\M#v۹hue{wԺ{;:ZPYTuduU4ڴЅWKt[[ Yi(%d]a ([$Iǟ!*b[8I ?;a3aCW҄HGJX@7K2]G!.wdȺ;ړ!J`!d$Is I_;%i |v>lL.xЖ$I$Ip I$I$IY 44E r_GrȺ0`w 3\@w ss`N6*#_'V'pANGNUD A=1x9jA`:c䣨7?%b ,$I$I4kǑ4Mt ,I$%J1G9'!TA*F|;TUrdG[Ȏ\Ys9MC}JCn+dG CeX†D(I$I ^Q )d0`6p'w:%tG!!CX A,QK2aCX |A1~]gɢ_'ோlv=_N$I$),1$I$I$i..G,Ss'uŸ! dn_fh?%d]p*Dqex~Gwsy,x1>D {q7??a DŽ=CO7a$Έp I$I4PCIgkNΎ{8$I4bG+z֬(ygv~PFsh׌fWe9d% W!, 3Yt D .pʠ=HXz $I oP@fCICL6e8d b9Z]=ݙIc>\ϒC0/ f-t c%I$Iq˓-$I$I$i` #q V`1pZ ہc(EC1 W2o+k&$Iʤ%+xdOȎ]e_RUIۨQ $ fBX tV°0PrCPtDP34S!{'aXNō; 4 ;|(N@YބMbB&8W<ý%IĒžG$ yZ0vG+ u ﱪv*0X!N$I$)P\$I$Il*Bz`uL(1c7cI1(b1bW+.X8?9@0"=h/]~!|8|fxACئPE} .h IDATh稿$I$I`ؾ7ړLk K$e]Aw!> stv vUJZzZ]IKU9YN$G ]S1tE (-r JՖ&C%/\>Cׅi(%dt!7AK%It(1*Üri)Br2ƭ$I$I/W!I$I$Isigb"3\ cnrQq'2{g|8?ߨA!ϑE{:b%zlD ֈŸxI$It$s֌'{63SI$ `ĎV.ƭYQ2.UPTUZUAkU10H$IǞ>[Da, %QH\Z$)+\!I$It1$I$I$i`.Xr,SDu) `UGT 0"n83D1g(0/6p,+}^>Qg ! hP6e @Mmr^$I$当jÛ+8%I$ `˹do5qOgI1-ՕTUR]Iku%-UTWt3%I$)W"¢1%{ u$I$I$) ǐ$I$I+fGfIDM`30,{Sn`YgjuSˆC Dɇf`+0:6glcPʅ7c7$I$ *cD-dMYH$ITy<ʙ? t9RZ*h? `H$It\3ECK|J aX"@X1$I$I$d8$I$I$D_|MD&cNmN~82r+j q8QU պWEov1H8h;EzcDgZА~a.,/jXf qI$Its†1}>JYZT$I:ʥɸ5 e4T\3GYzq$I$I=X-oA2ײJ$I$Iyp I$I$I7cl.d~$ D 85A{ &@Ub'GχNKSQ?g~\<'B}.Mv-Q1_|$I$ISJ0gxsWMB[o'I$e˴%+)ٛuy=[kU%m#R$I$I$)ĢLP6 ;C9( d"do`x*DҨP&`ry=F`pH$Ia8$I$I$_mVScM.`,ͅI+䩨b"?D=.v[ؗlg2I$I1'60ǷVu:$I!J1G9'!ѴTUZUz F[e9d2ޒ$I$I$Tɓ9nx`7 :z?3gI$I$!1$I$I$&Dϧ"֏'LfZ (p!GI5ic>o#ӱ s\oh\ |r׸H$IÚm$5}>^ŲI8$IkV.[Әzq%I$I$IIo0Ưs=K.ao~G幞I$I$h ǐ$I$I X蟨cbMoE&?rC%?˸ c%TH_!Pq|F$IM[oBo*(<waT9/ X2,9HETĜ1TT0 EPAWEAI](]؜w'Ys{fu2Tg皩:Q,7%ưd(Z˱cI$I$I|F1w/u2kkR׮$I$I"!I$I$IXMwqd1!݌bF8icx]W8ƚ:eДN#/[z7"I$I_Kv5RŚ'gə%InR{^oۗ#X:vpQBI$I$IԽ}F{׻-,0\-S$I$Ij$I$IT a:K{|pm X\$I$Ib8$I$I$Uo@b\N5_ q pm-pf>6bhG/f摞|,SG#z\y$I$IUt([>7mM<8u.}[ J$wZ:vT[$I$I$Is{jLoYWI$I!I$I$IkIKթƛ P_0v#fF:czh}ѿuj.JE%I$I2p]?==R'5Lϲ I$I$I$I$IK9&`Z{,akz7$I$ITcH$I$IRRg|KZ:p N3a*ltkXEO>XM/F$I$IJVbיҧb͜qK7fI]I$I$I$I$74lRة޽Tip pm_7:#I$I ǐ$I$IIWU9ۀwr{1Ey#I$If _5ˆ H$I$I$I$I)i[ر޽$hӛ`2bhwS$I$Ip I$I$IFlzh,b|uT MM 4±̋Et?CH$IMyazsV:R{]I$I$I$I$8&6% %qͷa~$I$IjtcH$I$IR&7dP]//YUځR:p w x4a9.Jeޗoْ$I$nl;{35%I$I$Iz |p#__ XX$IN`K-pw\υ H$I$)H$I$IRf'7d&/VU1UݬcI=fy^1Zt]\M6J9F$I^h~ k{sAy7s'I$I!I$I$Ip +5?skYb}=1&a0::ٝ!D~[G];V틉Ǡ\[pLfԋG#KQuT7$I$d`disY=$I$I$IR6td0J h˭ IN]sYϑ&>n9ZP$I$IEI$I$I$iO?rPHg뷬ajc M:ʣ. !\%]\M7 H5M.Jtyb}=>oø$I$xceytZؔY4leAI$I$I^ЄޡPq$:v+c~Gq^߅ӿ4C$I$3C$I$Ij P? Ns L?:քCmsꥒw ;z.oڱ.jawꇐv} OWԋK {K%o6-xLI$IzkS)W=~1-.)I$I$I&t 5<Β$2'^mp3?G.㹰ٹ•oh$I$Ia ǐ$I$Il\O?)F:`TBƻ'~;`J ط*IlbϟiݝAf{'x/5cلH{. &\ b}hɩ)oJ\y4RA |I$I+oOLok_c/+I$I$IKkS˳ /$IڨS`&?: ^ ;)MI$I$p I$I$IJ NIpuc>,H\5_u@{BseC)ho? ϩJuQs!Z8koq J; = K$I^O[͜urZ:_QK$I$I$E14w(%z%I%N=V8:>]G5I$I$cH$I$IRv.N.Ņ3|Ypaځ9]|qbuB}_{̀oH6I$I%`SۚVsh㯽$I$I$IR.RBLRk8K O7K0\xpL{$I$IR!I$I$Ii>8+vkWs gf4eÁd4v%%`h: XP*.F8FO!%։9Ӂ6$ڀ"؝ٞ=9"K S/f B $I$Iȶ7aa_o/Sj$I$I$ILʼ6yw_gI_ %[ {}NLݛ$I$I$I$I&ԟS/##N-|$65_9]'^DĻ"}:.v8ϱ'g0jS;!PaN:} a"8+{oոf!X!x lqG&:s6׭jT#@Bϱ pA)G;9x" 8=Z|p#]BJ$ID#W b:exr $I$I$IzymrBy=cI$I$I$ѷ H$I$IRG"vHk1u%lwckE~,rn\`|:KÀ3ǁ} 딀FؗI{F^80WFЈ =@dz8ޱEWN,|)/k._.Zk{Z?g0VBל $ XGE֯ \a @67.jvv&0nP5iCb*;XQw'0/nK`g+Tϛ; 1$//$3=bdB H$IDcיijCW1cyɿh$I$I$IC<$I$I$Iz 1$I$I$5o3vrS=dG&>4|03c "L:&0I Tc>Г \M0-=A&L\1w&psBIW>Mm׷&(` 3 &ߖpD%󀷕M{~zj8LB8t`O&LbkwB1N%#j¹[͑뼯ńφY D."%;։q&<ʎĽǦOuQspVCtoI{(2Euo".@0 IDAT٨;IؘkOa#|?Dx(6Mٓ6dI߁$I$I_Kxr [*so5&$I$I$I M} @$I$I$I$I$IUI>Kϕ[2B!^GzЃ׫]&Wi` 0}0mI8>V`:9;3A:4+uv.. < %\ J@+ ||B_7|/% AuqaS t]\SG.NHbo3ʽp#u'[n |(R6f!cfۑ$I$OLakm偭gܷ|\I$I$I M$I$I$I ǐ$I$IwBbR^\ x]+p;BE۔T"L^ ض#ۓ-"| Qy՗0qk3:Мvc_ImN%K/o[ \\/B+x\I$I̚U+ִ6ֳY5`]I$I$I$iug$I$I$I ǐ$I$ITf~XˁK 3 ._И.hޤ *! B 5pcoZ`lc=ǀせ0Hf>A1?  B8E瀷} w!ga IJ\`*=R&AI$I$JޔM yq:$I$IR;ͪV30F&2/+J`o`+` ¾tJ30 %I$I$Id8$I$I\0}ۀG2c-K?x?.#G wRs#0x7 TX@M!33s}!vr !̈́K$!"`Q҃/&gUqܿf:ἹƜ} !,½PƎ-寭"|߿n$I4>[;S۹Bx xs3x3/aZDy`?49Ron9)[q~F½' ,I5$I$I$I ǐ$I$IR^ L0&<1bƒSK !-/g~#0b`802`>{[xuy9pv'Z  jBV:ZLx?'@87.΢{InFy9v' F >o D1N$I$ eg6+xx H$I$Ix^3pOյ8++&qy#?S/O' R^N"¾2p|N:%ku,/s {V^-aO; ?>گK _m:]*Bh֖kaB+ןG~V^CbCP{hd# lI\KЯ}_YN%I$I$I"!I$I$I0nZˋL#ԡ+8ҩvw#SS[ !  `M˃%)>,Dx&017|b>|0zv*/_Bc+h{<&K3p;p5p0e#KVQL@P;W@;%xCl8FV}xM-,[^nl KVo$*;"I확YwK@9=0 #\Oww/߮$I$I$I$I$I$IF26vNn]H$I FȮ3'V9cŠ5<5!I$IMN*߁ !_[ FIɕ_E8PR¤gAC%g}Xa ;XBA-P^]b >8v!U-`ܭ&Z=L#L[ƇpKy+'8Bh[s~$\M8SUEc(3kw4(<18BzS_9jjrl!]Jy$-d%קKywy@?VB/9bLI$I$Ip I$I$I$IR#<BN#|eBXչϺ<B'y8$u=vᥠi~IcI$I$Im!I$I$I$Ij$&>[$I$)kSo+o;5{z+p&c6:9_ 8^EI󭉰?{RSsH/]oIe0!h&p"ש{ A a !(Q}1!, `| 6b \J{~XBFY %|cB.m4Fzs p!`0$uC칐aO ؟TQF<:" |UeO>E؏$W@frµo::a;I=B5z8Fz||f+ׄkJQ W']!0vd9q}⿗C Mǔ's#kƎE?[!y,I$I$Iz41$I$I$I$I$vG J$IԣoOLaw5lp|]$I$Ij`]M| p]O^_;clEm_BNF,#LeG3p&p?paryLо=!(dTd# Yx82!"vr"HZ0ل[j*Gsy vfp)qs|<B&.>B}hY [&.*~p!݄>QIceG"QZ  :8`L8~VX_VbC:&Edy.M~G33 aikass[`5ڛBXP8N>Ϡrkc~ڟ%I$I$I`8$I$I$I$QkH$IT}[ YӿbM{ fl9Q$I$Iu wO0F#JD(yᑵ>c}&q?Lb,p?,θvBX" d1ځか2{%`.p!aG8?Y܌ 7VY %WM5µ+[7{`zy堈C DDןS*%hF`vBPic=Aa2p"qG8&Pf};p A_CJ?uD1JicQ"|asc"I$I$I6`8$I$I$I$.[&2π eCo$aklD$Immb'0|ՠ5-5&I$I"tv'q'?NH$ct:ňڬ&q%qB%`OSY }ї.c}gc"j {؛ ¯%`Ov&tQչ.`߈K{Cw%@kI]ʫ G֯qW¨"1mή^_G EAv!lkC GVѽїBwJoev!q~.c8̈́Q |m^_͕cVrgy{|\z:!+[Uyù$I$I$Iz ǐ$I$I$Iޥ0j#_?c< =F$I$I*R&v9++cPG'ge5&I$ITQ8o&Sr]o 0JE~&tr2th^pJNv.}1phO{B*%M(+됐r]MW^{o:H`.jh&r1BRɰ#w%]9P1^Q|=\Cַ p06n̈́ZB\)>eBFdLj8ϡK BBΑTB*֕$I$I$WK#$I$I$I$_hαI$I Vb''3jN래s-.+I$I$D ϧω/F~@d+reCEeu w rĺ0z|ឣe>$I$I$Ip I$I$I$IRPDԵ܋$I$Ihj+I"cf xzu%I$IUFʦs#6#LLW7srꥒkGj2󀁑327t.jO BFcwB(I&BN^KܹqSF8XY oCcx'm)4ھ9lB[r$xL$zPq+/3Nj^`7lו׿5qëO$I$I^p I$I$I$IR 6bx&^$I$I*DS[]fMbҡ^,+I$I$AgHYpCdy6|#v pA^t,&|?8$"`QIg57/cpaF8XYp`cY|J$Ⱥ'3+x=pF]1Nbv~oL1<`*}Kz:;sphd:] <Yj#9X v3!$jV},@aYxCBiCd JN^u>u-@ Ryvz_8F)(.T:ήo2pNt=Y_ D!z!㱻iϩ j ayKN ̨{I;Wߜؒ$I$I$cH$I$I$I=BHFQ!~"ssE$Iܕa`ycӦ$I$IRw | x7pzˏ3u;sr"LN^u2}}oꕺ׳ 뀟e0f_$B|E%G֍qTZ6D5Qo\Pym D ɩy#<,ۀWvRs!8cyƺY{Yߕğ3rCubz^ Xm;>@`P=H$I$Iԣ!I$I$I$IZpoc  [o׊$I$I+QbStNl\%K$I$I aad]`xKB"Hqg'ԠkV7tZPg, ,I?EmQ8ZυёuMJ5%H1KMtn^t|BV~笎ݝ:.`mFcx pxBdPI;u&x}/%f GN}H$I$I#!I$I$I$IО?4ï$I$%+ô&΃1^xRAI$I$I/ v?P7%>Rk2 xGB}+!ZB>Xǫ:B@FcZ~+/ƨ _e6'Pg*|7r^7BfX} 3[s3 ̩?Y[>x8;viA YY;8!^Ͽ9L7F$I$I$I ǐ$I$I$I$m)$F IDAT< ?r%a I$I<5Mu1r9n9I$I$IXds7C!jP`PBk&jugBT`Ɗ={0N5Zoq)ש8VoAAmRiv/㊄>j\:SVxǩ~,ϩJZH;>BsTUknd=뀻Ո$I$I$I=Qz7 I$I$I$IjX#o'{l v - Lv01%I$IjMm%v~j" n<4u.$I$I*K-Մ6q{2}ׯZRBnak+j95W&_X"JyXsc5B87 1EX L/hFyRpuʣHW?!>Xktb{suc&)<.ډ94F6p+ppdy6"I$I$IROc8$I$<ήr'L d! ! .bQVպ!RZ[ݗ֥mnֺa]J тTQ낶k@B6BYgd{#!d=sϹxGKsϽY>oI$I$Uxk Erm0|9pXk?P$I$&mcN]{X~nYBNI$I$I )xbD0pG=1| 5 ߟ6\!kOaͧGߔC1VF֟|0^ ˓x,Eh]׫6\`[MbOCӁہ[r%Nbu"!(i1^|>g_xCR3E?ɩj~ sܥwƄ@Zۈeu!I$I$Id8$I$I$I$)ƂqA{\ ؑ$I$mk1L?c5z$I$I1CdY{hˀ#Ϲ9Fē"K1<h']`ŏG_,/~`p;pZ#n>b1'dLp Kyc$}cV m 2EѸ0#<~:pc=ʆ$I$I$i1C$I$I$I&{^^x!I$IRM8mQcH$I$)OsQI?c;gqvdLbBƀy4:H_3<XNO|طn"80@ `Mab–ÒG7CClOޘCI0߁VY \nbV~Gn&y8ɄPņc4ecDm'0|/$I$I$ ǐ$I$I$Ie|8x\ p%0>$I$IYhYĬݕnۖmbYO$I$Ij? vrszd\h3q$1E֟BpM7gF7J)o<g{Cb'dmpf97HؠyH8'}P9l6B`s5A'My4aed3 >f$$I$I$%-I$I$I$I8$q 7a!Fd |$IIc3W/m1$I$ILx0"0?3ܿ٬oPXE6H'װj8w.V` plHS,ߡSZ% =`` G|h?8$I$)su"zV>n_R̈IMaz-VnJ;mf[$I x h ?آLpő|J0'zIG/6daad^ <8u ܇ ʏo`a#2X{ <$։i߇̏?%zKk{Kl8ƚ\J5:㽫u$I$I$I MuI$I$I$IRRJ$IT6uGݳCŊu[fr1)թ/Iq$˷} 12f dsΫ݊$I$MD /ot#f>;95b 0+z_@|8|y45?\L0v`:!!8 I QW <x/peVV Lj y0.y >MU%1xϝsosq}!6`!ַno3#w8SL8 $I$I$%d8$I$I$I$I$I$=X{0e__) = \ F읷khM.]vu4هc\C}]ML8$I$I$%d8$I$I$I$I$I$E>03W/8^n;Q[ԕ6_" =Z][Dm#YL aY?X$孯s9%(F'U@Ɓct-I$IDlNQc4C> ؓyͣ 4j$ 8;5+V>.KgBMfm 0󾭤QۨpyN+c 3Zs&3ǁ"Y:@Gl1K $I$I$$I$I$I$I$I$M:pKc` ii@B?dط]` ťE$I$"arO'W_0DA׵NqN1[D<4(8Zuz?~ N{\ ^F(fM'1?1 jy:/kX#+*l~k|w(Z+I$I$Ir:݀$I$I$I$I$I$;fpҺ*6)G=#vԭ/I[\AFࡀ53.x\;]+ؑ$I$M8(͡Psf3LqD/E֯g%\%v,Ԣ̬RW2 _"  x0'axsgac+nOg3kI4σ=w^+c&ea1/3Z/_Of3N4so$I$I$,1$I$I$I$I$I$E⸍(T)ś4wWT_}]+X?D 8T0@_p I$IL; 1A"F?n^G#+u!$}"6 ||D0?}'WosޯYU5X><饘1+|goDy9m+dm",Sh0I$I$IZN?$I$I$I$I$IVU1Jܱt߱ᶞV5l1> J$I&s"7ϩCLp)Ι`d}ansa7q6mv0ESg~qfCk`4aicW|q#'_FI`I9YLhVx $I$I$}I$I$I$I$I$I:B N0fUm+q~؞:u&q 1gևk9 c͌K-נj7^0@_׊t$I$IK# 8bg9xSsi@yz cnD/ >3.&M1% <9o^xp)Н~~BؐͬQ'1Z=g_^ H~fWWgLELu=U? } b4sE+I$I$Ir ǐ$I$I$I$I$I |B5bp,Ȯz&H}]>"Z? R0@op I$Iȉ@pS/19lGO"ƴ\|Dwpʹ4B@%$>q`6$kd8F^5-jooyxuJ4ͺ̯ܛ$I$I$I-+uI$I$I$I$I$I0:GyKc u!M2}esBieaƾ{Umf Q1cɞΣ۔$I$M\O?`oT3ۈGd 8,.C}1;.&;ec Ce;3Z++Wg73fVkI1`{9F f-C?Ǯcj'47wѺʖD+I$I$Ir ǐ$I$I$I$I$ICj 3Tkk>n{w#1йpܯaٮ/L@Fڸ;2VpV$I$eEeR\ j{ˀVs̚u{$I$I$e!I$I$I$I$I$iқW-apgź3GDS-Peٮk:to,j0@_:t$I$IFl\cΟ#iYk,~<Xrά "dSj̵i`{#E:GE֯xx4ڈs~cTGp99ZO+s55D#$I$I$I ǐ$I$I$I$I$I4>3V/cWo+62L@۪Ϛ,0²/1cߪ:tܬ,Z` ϪCW$I4iLߖK5rHMˀi,n# 9h{#YG#:ogץ8{!$4S)/dQjF/ʥxE:by |?!$$^iK_''I$I$I҄e8$I$I$I$I$IɩGo6VyɆ#vp1+4r6Qlb4ʲޯ4M@FƸ6Q0@_׊;$IIgjd\lFDm0[E܀vԌ{h{E3Q ~F.]h<G8.vb/rU)mP4Ze͑Ӏyy4i)~qxGˈL49bCGEkj׉f$I$I$e!I$I$I$I$I$ii+8yVl:ܻ`=DIulf Ȉ -!I$IY?=.*;26!7F֟C^ |q0;źߎl=6iq {]bSgZHT:FӨ4K3$I$iYD.]TvJDmC?ɡUnʵ?_ ,OWVRORzD<aկot6nb,| 4>u)B?r3צуY\zcj`uD}`z{W;~pBNfhI$I$IZ$I$I$I$I$I&9}I$IRU"ͣ D53Ss# >9&`1sjثVEBXI%S{_48г|#ڕ)׬4O^zke[ V,=YFg#cy>q2c]͍gE:'w34so$I$I$,1$I$I$I$I$I$Mx Ջ+{ֱgjl& Ȩ%/E$I$[#8.w0/>p#_SI~X#/aZ]D_a~Q9^|}3!4$JnT8})v76I\ 򫁟D S/I<芨1q%Iżfk\NY_-L|qQN{7J*FI$I$I2 ǐ$I$I$I$I$I4aJb~>*Rï[`q>Ijy n6Ȩ5c}.C3J$I<<`FEy )/I9Rɣs\԰S'԰_-^S} 5_"XFfT*5_$5Rinq)B_DW hO^] kGZވڎܺH3ΥdYe.].2<M|Xa.gӳ|+u %[;$I$I$IjYI$I$I$I$I$IMsQ>i.-sn8b,H{gHjmҟS@F}]+2H$It> xIN(#jWwxP#3?5/=c_s@|XQw5WWzH[ IDAT.aZQ{WǻSsRBy$P!|%c 3s-=bQ9)Go˩FA5tsIIz(PzZE{ofLjy~OtI$I$I2a8$I$I$I$I$IGhkcFj]j XWoaբ2#i: QɤYCw[4C$Irv9o>L㈑ H>L9k|4}ƀD? }x:կɯjأܐ?& o kS_v_b,!}G4=5$S'\g@ľQz?/^eǁ> EԿ0F"<V5T6sp$I$I$I-p I$I$I$I$I$IS(ikԢqpr@Tc l<|g:4u.TgaYoUMZޯ1F'{KjZC$IT'"gPj^<;yyU k ԕs1w*t\7WG#<J{3#?A|K>]f5SYig&W3R"j[%&*__ǀ-oBƈ 'sp8; ~K7oo>j5TILouI$I$Ip I$I$I$I$I$I)3]{M#v٫0e_Gź]u>P$MT#mS\X:(z: #c\[s0Rr*I$I_F$#9g 41u] k%9K0zE7=֞ |(eկ?dk k! :W5UZ; 9ItpM=* \rZ$ g#Md{D5쳨E5@]<9VE\C4GxI9"0=v'uDqa'Y{$I$IY%!!֥/Ը|Kyd0@:R|7\`0a+m J CJ-a b# ?JW?7EԿx[N@.'_% O3+Q=`w<`楜@شO9F}$6Zr)>o%ॄp$N&\y,ɯ.3-Ŀ_N =%Mx+kQg?$߅ \JmēQ^)Hv FL6?HXwpN_Ua /:xVQ9q(P"| |"{ο!\Yx<ڮvH옷fޡ$I$I$ZH/I$I$I$I$I$60WEhPG |U wr˲-!u˜[3]P_ݙ^ ǐ$I:<0kL#ZI } <8uƓ0NHxSBj^BXqXDE^|6a}V$ Oxr o,~1~,!l}m.$PC x|m/p_\EPd,/!= a}b#/| } aH2HYrbn3';>G >Bh n5'czBxMRI!!^MI8M!n4O^%$'` "I:Gۀ̡,$ <93T Yv (5T$I$I$IjicH$I$I$I$I$IԴ"gsc92>FuUYh;] GЙɮs9%( 1:S\6$I$i2z0Je 1CJ;|< Ftxq ! 1~OwH^<|@˙\D+IBo/lvUN:#s9IyN%d\BN(FN$<0=F?!)z>X ]r sbρc#&>qD?9As ?S}|S!`dńس@0,#eUT~,Xo9!hZh-t]zuϊ^"έu$/mǀW;2x/y<zjֺ?$I$I$ ǐ$I$I$I$I$I&μg1őGJJIGQ`,{Xv^Z6gH t.{xc[Imwq)cQs%I$IKy0yA|0${u ?OV["6x3! Fap0Lujq4s\>r"{yRT"Fπ3Z/vBxE&I?B9Xr<x71>@V>EL$\O^ [e!B(MJ$BAn&O79'O#f?{{ aJY9WST)3!$ y'ᾎ1p_OOx.tBқHd̲;~:|V;&bK _+rUInJv;ve"I$I$IRK2C$I$I$I$I$Id:F8EL:]=7霼vmJXp+mSg%/ޮnA$IZF|7`!^pdMɄo>!Kx +F> NUs7aQ{Ggdo$ ѿLr!!w??!kX0h??'U>qyǕ"[kAVJ.a]]Џ)%\gHqn5z9X .{ڰQ?]0v y6U9o1X|$VWBlKX_9{2Zk&y-_wmhU'o>Nxߒ$I$I$iR3C$I$I$I$I$ID fN{UQMGT31mǎ!irZ_}E1$I$I p°|Cbw*$! ޗUO.|?js$M1U>K ya:*/D6C/B_mOsXT8+µ~D T>>b$n ܆Ohb-P4jϛFv@m_ޟ~J\si{ /;;sL`!վS^gsӬ*и|O$I$I$ 1$I$I$I$I$IIPS.di֔ ۽]c.UkwOe1XCgth icѭDlPF!I$Izak 8xpX" o' ]ca0/ 䓸|1PǗSAI{? Ȳٜ$x,0u/?yCBTp!8i @kǸA`9LB`cb\IxWguPw+\ c1!0²A` {vhpB3?"4< GgZR!4;4y3@(X g;+[p۳.?$<Nv m V/8>!3(3sSYc&\-k6BJyx5ppXBˑUX"nо4c 05A];P퇔%X'q:!`Y1?J9uY|JM|$՛f՝p4"G$I$Ic8$I$I$I$I$I4K\۽7n=X{ҳwJھi{u&uL⌴Mes!JbKK4CL$I$Ip$ |Lw?ߦzA,XF@c~H׺4R> <8XAX9AF 1V~EZٝgRhF&<Idf!hi<"$I$I$i2C$I$I$I$I$IfƩkhV tcs$ia>c<;lfT$)%QޮnA$I$I&!%qqi@70~B?@GPv!I$I$Ia8$I$I$I$I$I4iі pb]={s a>ϨZ;V`V4Q0@_p I$I$IZnD$I$I$) 1$I$I$I$I$I fp'g^Dh[yzr葺8ޅZ;X涥sx$߱n:JUsj6$I$I$I$I$I$Ig8$I$I$I$I$I4ts݋/W1 N0Bڭ3r w֡3IJr F:-H$I$I$I$I$IIp I$I$I$I$I$ihkՋ>wc)r!mcVl:ήZ[Y3o+TАulKcW4I$I$I$I$I$I4 !I$I$I$I$I$MzBfOMuJt"޻SGս7ǎ$)_})FF21:S\6$I$I$I$I$I$I$`8$I$I$I$I$IJpIĮ(o >Vv=<~;FsGam* VƵ1:݆$I$I$I$I$I$I ǐ$I$I$I$I$IZ܊MG`̚3Q7msF%.νRʼIj%PvkE6$I$I$I$I$I$I$I$I$I$I$IR [uG?05 % fQ0{w7'ϔΪ~tHR#u`A/h`0@_p I$I$I$I$I$ITcH$I$I$I$I$I-jގ8nüX[)@c6e,cz?/ľLf߱n:# a}vې$I$I$I$I$I$I$I$I$I$I$IR {'[@!vu`]UkKX;J6Ԍ 3gF7}]6I$I$I$I$I$I4!I$I$I$I$I$pEƀ]=5pLx$cmUkܱ~vf$5c2-H$I$I$I$I$IIp I$I$I$I$I$L5P$RޞOmsFgq-;îGV_r˲˒lA8`m !5a2, dS[]6;Kvf&CJvlLel11p 8XX‡d˶޾}}oϧ}TVwWd<PSL!VGZ2R*t)q*SͬOdrE#l;R-ny6X.jؑѶ:(?6\ّZQc2P)^㕆B= )`x㩌O? 0X?s9F=l_eQbhKG4|@w7:, R+`jϦݧ?I2TQ,-p+$MY?n IDATٔcsαgt_JT-ot3ZQ2^ZR_cr 8:xQ.<Ӕٓ錮<빎ɶ\ئ7vNs[7v8SFX! Z$CZXc9k3'[ˮT+u^Xϣ͓hPBc}9Q1Xou`R砍zs M19zL7osN:Oe}QVBj-PK[F*ZXc9f`wx|c tGeס S)uF=<~\F'X9mۜΩ-0\ّZZXc9ڑ~94iVgcLra{N3ڧOd6X?s9F=i 8po[S5#I;R+'zrٓmt˅p3y l?w@Uc~t)`k4IR*'/s7V۟ΉfXM.v\.L|/@Zmݖ5mKss,eO^4Bg2]59JU`eg֏?䛇w-N)* y-,Br񑾬ɶgrwV]-)/N))ԓߜu#kto=s1Ƴ ?LsCCm;SO1Ԗlg-ml[}/Z\\p{Wg?3Y= 7?*2]htN\Õmr hO_G׵:)=n8l;LH3cԓq\\_v<1~dL|C1hle ,Hr cDOv1~F}2.:VGXM.v.ɮLd[V\Z:'*Ed2ݣˉT;Sou@ά集w5}(%3ڑ;%ڹXLel<֛$.2|Yh:3S8%6ԾkI1+Mp61:&*ٽK3VGYL1v$Iȉα tDh+S-N L=ҼZ2Rִs2UՏ^ynts=[KOfk4]MuDRhqPst#m9ui;+;RK[vp){tL7&_;QډJ6|TPXrs4] pE /2@)&(ԓhKzF;Z%3 uo3IRO=k'25:[]dy)Zk?w"1d87(F'W<)OCRH>6>fO,jyS)ת =^ZR_,r h??Ry u&Idc<]j3-N Xc 7|&B[VMe bIx{:&6]Ly˟gҟzyG-mlkL(:v/jua}2ǺGstHuV:@L;2ڶ9S6s#5lb)E꭮~%ZeYͱjNud#,J1| /:@#)EdS)ƘhHNtUS+[ ez[PE9pnQ >Y+8mӥVG9Պ tXw5GzGRhu$sF)ΔkE/DG9,@yfd['s{4{9;bՑQ UvoEOj@RT[=Q)b=]9]j֎:1XoH9`{4r B=9odm*S93[qg$ӥZ#,KCSL! :ZK[F* 1c\Փ˞ܘ [ejzFs{chx#ŎmNH-m L1`v>}A6]Kn2c=ezF2]:4X_T9`{4r -Gˎgַ:ƒj֎:0ޟMջ~87)0؝1j}*Ǻ9SͱLjT˛2]LVL `c76/NX35##9SPxRhu*ծ?4C]M14[RֈɶɑޑtH`AMH1:&G/Ny( V/$9;= u'+`b {Q-mlkb*Q/6]G/N{dLHHd47WmۜΩs3\ّZښ `q߿&*Պ}`k:+2'B21Frw8CIթXJ1`cIzs6wQh6=tᤳ~ eftf`թ{jaZΧ'5<2?s}.7=zj|8?ǟn{[X|^Vj!%)sd޺ԑ7&y},j1X̭w)?:xK7^f0y>X?y|n_zcs@=M%>U_8M3?߳39r V)_y[R9ՑT=\uP=`z(S /6srɥϲTz |sKRHǖ&f1XY|rbIg;~%_vCz^gc9wsog}J T:㵉ͱ')Lt_xxI"oZΧl>W'́eӮץ6=990SGgo=ޞdEHgThƤNj/"r~ϱ$[vW81wRd$IgVGh,_䶿y[!3?6]QᇦKdgf?J~ *(Ɵ"ӹ\;rky)Mr 9_ƍy[RBTx/oN$InxL`mwE!ι-g6N}åk7;f1X>|rbI^l 6mMtȮKh|O=(fRmZΧ[26X~ ;~~9}&8 53&dQzכ򣃷dzz&`=) S&POlϝRmCÕ-Mr XuI^hgZV9_ƍyu}PJ_=y+?y sT{sPH|OsR1`n*IM7+N~lNr7I?דO-MLX|rbI_.Ȧ ;dsډg,^{4cu$) Iޔg{N>$OrkƄelZ'>{O\kdo+z'X&ʵj\b!wfLR\r 8+$|ɏJGI4}{ߜ'z{g_z:̕=Y]QHmB3}~ҵ P/tuK$&X$@N&s}ޜ~<{_(Lᕟk<ф253?Tsxr h\in)ƋmMrkL$KZ㎻{Wf!ʥt-wd֡=g_Ip w߷7߸1. c ޛ7y͞'XN<s:gޓF~B9{YISbdN~|8Gj<%TOᎻ{Wf'l-wd֡VrZ.׫1mf )던Rj$1I g$PO,p$/e_Ir[7%o|sϾoĎXӾ?;ޙlpB`XBjUmKo<`r Vsb/MIj\sI~~w;ܚ̖mc߁u7牧ޞ0seox>`|l.ԓNtO=(FR\73[gi'D\qo$mI^$ ܞI ooWqc9z]y/&{oO5{lBB`j3 g(H53&/zY&o'9?%neEZ$]g8$oKc17$$=ϻ?V3qוy{ޜO\:M|>7:j7R}b{#gjwOln\X͔cPH+I> /znk䯖:T~gx~*;3[b,CIK$y'l/7:p[>ed|bǂfiߟl-tw.7, 4r.f1V$M$/=ùec\g9IYg{$)UI ~b߁u7牧nL~myuix>Y;fyzO{1V#MI>9ݓ$_kjHL(KܗyۚL OIr߷ܐg^z}űl8+y=O6!!(תrfLRތ(XdnP_9ƫYuK$7&y-4yXpC9z]y/DzἯu{ok<ل7R}b{3wjwOlnX c^$?|w$4ps8D-#I4?罫#ɭ O1S+3_ڝyG&){Ft,.Bn\ sK{qPH|O3j`uI>dϿ?ǒL7`wof6ޟdcIn=s8w$e~bp-wf|bft?]oϻ;ݝpk'9$,.Xɔc,&yg+ Iӂ,I>m:? IDATIޑӋع#z<[IE}2M4`B' y$b ^h߁uސ!33 POoZ`ʵj\^]m3CdVHX_}RH.e3N3:$8/$}I.q?N;I*s<,U'ޘt#gc1^Ͳ=?)ݑ$cI74ۗwnsߘz}űl8+y/|&xRX(ֹS_pZ?")h$%/̛|3%"IO'$I^s8[N$dYήOkIss<׻=s̖=.9CI(ɟ$9\,W3B>ywdpxo¼gKDz/nϥ[y&Ё'#{Qqr ?ɷZXU>3c<UMRkj{9F2[pr$K$c|<_6s/bٔܔRq﷒|4ɭIlp-wf|bft?]oϻ;ݝӍ 4gV7?g[3s|oO6t'21$LrAU;sIޚs͉sJ&jۓlO$ɿH2랏$78ϻ/t]df^7?l)ȶyޭ%̖bL,w?/y23ӻ v?/-; &P/(lJ˻'uW /^XIcՖ$_JI8 >I<ܔ-HfKZQJ$}k|( 'cI$%yzwOeϭM3ɾܑߏ_KAFY%9Ѐ,w}|_N^bq4λ;9؄Ko=I KPH|RD9]ق_4ˮyIS0sIJ[IY&$dtO3{mUY\9ƫ2[ rc_ r0 d`XIfj|K#,䍟˥ctru-Í :k'ӊ3OIrO+vJX$=[L8 r퓙8Zf*9I sw&ɝ4߷|fwIޑ3M?djYo~ebrۂ&tavm#zWҹ_C\Vn&3BJz+rWNrEC4ٻ| Ͽ1ɧܜdzIp O17#I_y$5]vUu_Z]y{NյwsVۢG//`_z~[;:ߝp>umY,}ulvS1=oVW̉ՅO%'9s%Ϋ~q,գWކjs&#߷cU=zZu1 McU_R_*ߏSBꨱvʾ&7xs'<$llġ6m]pwVRRI'T7hUW/F(`zx \|I77r?m9X=3P델~RF\;#_U=ccotO6r9Y 33^ݽr `O3W=z{uff5daJ=\%խTYdb,\DFnb߭::oĵks~v1mJ1S;Kݽw{L:F֯}k6h[^pѻ s|kx7-==7UO6V1֌ROhz U_LuӉ.VwN76]yW;:ߝd8l-7oZk;4}Mkmek8n՛{WR.iS7SދYK.:}: t1sp1¾ e3lrvuZ;#p5' `7q3o1:G!_#/t: ,k f:Fuٯz{uiWYPֶ7sObڹ}5ݲ (W.^d;çvꨵk՗wOVOnP=:zQ=x&mcjV'7 ۗݩ36YQ}kd8zfu :d'4qE_zԁ.8psF`եYcuTZh8 )`vYo8_T߿Kv/ɓB߬7ƺmi%M3,rdP=ꗪTϯ.X:=!?8jfs\L׸CGuHcGT/2s \Nn`m'{Un(ʸ/Woa;Uw\q1\I1.h9:Buzuo^ݥ}Ǭu]u ֫k`y[ ,T(xl~bnPq+|:qW"poUOX쩍Vqr #'^:jxhUiݶGT{ 5X۷lβ[SI{9zBꭍWqvKM`vk`*ƒ|O9ž<=`$ֿU[7V/.^T]JOr5t,㫃V)r]P(2#'O6ʊ{k_sZAvUT_Nn6۫;W^]]>|k[>1}羵(XӫW,1;oN! +zLEf|~v\կU-0w'0pU27^yWיpqܰEM57(,X=:dT5<>4tC~M[Xٴңvk6},n{~Տ ]V=:sGV/N{WTWk6-c݉j^Y}{DZΫru6Zjyl |WS̲6YQ]P5UϬnI`9T3kc133^9`#P*ַ7t"3O38 P<#Tu؄3-{GaU2߭1ƺ4WV(P}bJY6'TZc=n(I≥`Im1ka9pC`yv*̦o{O%+3^`u틪Us}%e#̟ZAF1׾:rrQF \~Y6/TW7q\WSy͛ns,^sܡ3k;wIu|EfַW M'{{T>Y=|҄Y 70U2?>4ƺ7pIjyruEV}tJYֻ5Y|zvuȈ/i|ި[̴}αs~תL6 YEfZ5Dėw.r}ᐬN'{UgT?:Z=z-ǩ#Ο*)F3WP}w^W5D {T\l{Sʲ^6YQ܎WT]:$0~h~m1 r ]PEfp qT/ սV={av:IMpc)?CRQP0^dPՖUα^P=dCi=M~.֥}.^_eK|ܷWC1>ŋ\ 㰩$bN^՛ۯZ"v3-uC ï^ĭR|zxU{I8uSV+Ȉ^Q٘kR:hrqlgV)ӫ՝v~nC~M[Xشңvk39jSIrSX]m77Bu꤆Ҍ+ZP+ճWO>Puh˧NxcL^̓L! ur덹m a]2Dl{7^cu Z+X#?$cZ\ivh|W8Ǜ[3YVluՇ#qnCu/M2 kcDݾsZcLӫ-1{;X=?S:tS=a_>S4{K?tzX 4-|zӔr ~jӘ/nV}:pk>Svǯώy_LN5Jr6˜Eu+~o6WO~k\oP<18z@u =-羲sWu\oNx?U L0~[z̤olk^/OU?w1&'f:n,2 SrOi8PlbVǷbV4dQr`nCj:c\/0 56>,ׯVL8 ϭ~jqa Eg=[Kn\=ݯc=TNhb7|\\C1nn-7o$o_f/|ߜoIwK|%;}JwSIznuZk=svdg`nP7J^Rݺ_-}-ZYiE uȗc'pMՃ3Qu+-_nX]gk Uzyuz ~:['&g=9Pr18zxuՖ`];|˙iog1W]qtA!_#'.6u=E@Ir`ꧫON)b:ᰥbsW_8,  2PScK4g,=Ŭi3;oT]~Cr<6{>z"׏Y}mؕ/Ow#a_Ѷ-՟4:`NjctѾ}ÿ/iA˼cv 0_~"3ר:bqJ IDATv{Wm2տ7j-êW7oxN>:ž70> ew\`.GcGb+AC,2sK쳘Vߪqáh{ǾwU_ݦؿC}_>ェ-0EyqgxSjzcmfOr]Z37]=~YՇ# L2 ![?qGtq1v c_zԁ.8pD9tGqjSI1]D>+cK{;{knxm8 uչ˫pՍ4 _6fT_V5 gswTaJ2K՛Fku %dl(8zku18Lf 3N,!?Jo_>Oc=Shf+gfk\Wn6u=?Wh8jnp=JCn=zj?=j~ܙq˜iuޘTmme'Uo>{GpbP #}J5̕})~y&۫zu[|mSYO9nк?o=ו}Sv ֭ٵ{uu3^ߪnY}1PJ1bm/47UB=U'TYݳc:;^_ocT]Z>->WtZ檋'_}puBc|#g%\]Zb:gf{Vsnn3L9\`mV=o_t Yֳ;VZ]{{b~e#-c՟bas On>>O6U7uk7xiCrZAF|rd!3澻VwcT7^PK^ǏtU1vZiAc?>(X{pb\Q֥{VT2Tw6D z2gOYv'n(CWՃ1lV_X~{WX,2P\T^7ZF %_^T]އuu\j~͌W1bVR1ݒr 7P~UϮq֕VosTTmX!r:hltVgT~8aRNu}1-0;SZAWG\C¨ȵoVgggTWq׫7klN޵ܟT]8cV{o6Lk9wW3^cJZmT2G{0`zokCBlv7W>\C3/O0{f:o֤c1 26m{V&HcU>LSIv~zeT5WZáLk_G mo(IX%Ջ{X\ҟ?:fZAgWF˜G-7pAՉy[JaFuV _N,{a|M1v c'추c/VYdfS7OO%tTkf۪GUO2Q/sv!8E l(L~_U}kk_^]::zO}i;ώ0jUʲ+6?T[iջCox>v7qyc?mk[W5|]-XѧWueb(ȸO,Vc?iы`uSI4*;puW5}T0Iz[S_/UR]k[^?^ݸzaupkm{P8=M^}nmJ^ySZ A^ggT %&7S}l`Kfgz)ՓX %wbcx=zUOn kfV4z[{,ys٫zX4NY}yl~6j)_ĪG3ݽRu)DuM9u^CbX]:T-n?[ڼĺV_q~5pHպ+tUTS]}CG/pmуF Km#̟ZAUߪV[׃#,.^T]o[4Wd@X̱tꖋ|mvĴ2Gݳſo=VM+G1ֿ~ᠢDSIf`ZoR]|տ6D 6;հzYsgWwo1-=l.^2ƺNk uiz`.w=9Ljz~uKf:gvk3uRG3ݽ#= {GTsdjߩ$:+ YluјWZbݫ~گzRF\#N(˫-#̈́Navuiv3ՙ+ͫpf˜5|P=6|/pk3u1>Ι-P1ӂ16PF^_mF+wEfSzuՍǼ?6c|cK̼Տ05׮RgMFX;W]u\ul V~B^k7CW'ʢP#^ nPrӭs7TwcT7^\}b`LG^LqCcDA} ``cyU;Kc=;u٫TZApy'V R˫G4VrC(Fpd_^T]o[ޓUCZxjPwEE1Ndvtr y/1膃 2*sWUj8kzWá\sWKa^իU'T{׫&h>W}5^ k \JYWnPqx?z}5ʸP}zjcת7|Sǡ~WnUB۷=r _,1 HMۿUNpӪ_icK}~*Yn{\{A*)o{iy[Sx`{Wkxo4Nhx rݲc]dx91clLՓ7,1 "Mc[y!|[Չ;~QyS-U:1W5]9|q##߱:juL+o0)nx ;gUnPUr)]?; Nw:C2\uB%^Xq[ UO^08SGK:*hϻ%ՋVgVW'ڏY~eURLߖ#ߺgwdCŗF7k>z[\=e3_ʹctM[G v16mCi8SI^V=ku۪GV18LK'.~::: ߕM5W-ُz}u'T{Ri;u:y3^]w⎫P}zju1'&dfÊ1vbALy`-(.W}t7V?5D?o-zm)շ4E:~e՟0e.l܏noP}vCMޣ]=H俫'6ΜR}cr`>[;] jf)=\\\u"3WVn*~oVoW޳qգ._`j~k^P :y0;St?X=D|+NI8::zCáH6˫O2 ZӇ9=?舵Ӷy01Vά_`]L)W 9Zuꁋ}:{*֯UWնoe(wqm׳W÷WUO^q\DO-s1_VB~::߸zJk1om(x~C V9Ou#`5s{x¡.QoV[mF5: ;;ᠲO5<߬S}zuӏ l`m-icT>V+8u٫4<ŋ1v:yDJ->]=ы1Z~u݆R얔c~>Vݷڲ 29D?ًP{X/1C^S*}d{FN{Wkxzbur ӿU?_m]d&۫.>cL'T_=\'-S/W/3=`ws՗UwZ8SsXÁ_Nk(/i(=~ ,Tq:rIO&zupubunm¿?ɀ^)}S?[}臾^ݫ"3O~{:qV~ O[ {WP*tu$}ιs'_lKnso6Vj&k_74+5c[ }NYoopKT?RRh(9zqïI\VYN9OWjV?_}u̟T.&Z}cZS6^ z'5ΨS=a{E:8y13׮Pn(kE^yY7\~6<¬Ba7Y\]ۄZ=L3M˪?ZbWV|:x ,&LܨzCrRUﮞ⿧Ә;T.(j8s7l8GYiT9ƍEx]Cr=9 #Vn=Vl8{~^͙[{vn(xCM~ gM1ϮQՉ Ȳ3UxzuuMǪr>gVY`]̢ !VèIj9eQvl(X+N0Pu9e=c3Tw`! e36|fٍ4|^Tp™Ww^^]:ˀ-SꯗyR ȲWR}7Tw]XlojUβ5j1[]47V'o\ij[sܼAssWN; ?Zpݲ3tR}ruH%O\zg# I65\\zSu | IDAT~[zu1_V0jZu37v]@Qh(Xt]hQ%˵Cggj9FՋ'^yw7}|zdu`uBu*fZ,_jÄ{|::3 vM9_[}s^ջhmPM33ՏW^MNm`x ~ ȲWV'å=+-Ǩ<Տ`*j^ d۱;7_YFSJO~l E_ݱzyKZ6êKPB=X=.V]]DF ]]1skccuDuzj%P=q`{\m߆Ífר\;6_-7 upCTY]k_Qݴ:,[k:HaӘP=^ږnWڌZVl p2fUq҄/Vo=[ wox)ej-}W,\XfWwS~.'TW&#o0lo'-1s]aZ'Ձ3oCuru \TWxjfy;w3.Ǹi~_+JzR13SM~SNթ Seu3^P^ݱzݡ{aQXij_Y`-v -cV?;7}(::EeE׭>3ŞWjs xV1znyE+7[}iѶ+NjpwW?P8Y&QY`GV/Z@ E#R)ƻ<{Rؽ<;ڶrSq p{WGU;N [,*اFu)Yӆ:듖$;UO窧4TݫҭoW5fu zFu`u}5@9n%f~oYnS^]ji;=Șgw^PֶT~~"Ml9Ouj(xK!^Zru)2,׸rêoNTO&TKZ m8NSS=1?iڀ. eӟho26zׅ3̀r ƹR\b֓ߟr;__P22UMI5Ɔ ~ݱ:::Ϗ[x]эg?Xh0MAϪ^P8e]G#~OV6]IljՑS1^<գSPZdp^SfNi(Ħ ewjYfG9K136Ehkѝ{M}Ճ˶ gYoj)9-sms~?kꈆTGU;_u嚅//cf9Wo%z쳔P|zxY$66ܜr2 $/X暝Zq䆲6O;˫mݳ ̇r ҇XႼmST%\ DFEzS{v^n֖.TX:vfe}uYY+ذ\=jwRkFNLZ0oU1 kO0VEٖmh}c \k=.QXS,庸3cfv:x!c!FuK=zݘwZ6&K/<}vRz٣˜ceuɌW'ӫÛ߿.a>`Ac0W3[CÏ/"ܽWVGVg.cvS^3s&Ȱ\]u^VߝSsOϵ*ԜͲ㟫OoWi(2ƆB{MnFPT;Ϫ^2ŻnTkr*XMV>߯^_Lu_ߟk^ EUT?:M 3,r &u^eT_3s݆nDu SW7f~ }w]̴"lNΨj6Ov>6r]/l8eWxϘu69L /17c\r ^TE3Iݡ:dN#w2zc0/T5ffƹ9;[^}|CL(VYn3|'[{x߽._Sq {MCo4jAu5 l&\sMտLTX7{Z4٦k+5m8CB&XݪT6|=qCy E(p՟!՝>>3)`Np(?UeVf˜z֌ <3ݫ1w9h?^SݴWpF\ǭ$|hݛm^Vn_yF9F_m(QlT(:]Lݳ m1V̽v̹.{?pwFI\P=:糳 lc0+Q=s_p<^=Zew@Ň fvz|e~z{]pP r2fn2_Y=)F_J;S$qM;UP_Ui,|zg6?[u ʵZnPd3;3A9իwe>z\uՈ{lp%٩:~-U{Mj(]]spiuD)Ik+xݪNߛ'Nnխȵ?/Nj޽ǫo0o7|f7o=pO9vU?-113o,c?瞢^Q=eV3n~OvSZ 5-P<0 4{GPE/5ź5o+36pnm]V}zlZ>Κ]<`=Q<\Y=E.Q{B-.cSsO1QnR{+_{_&q9eYRQu~ '\Cgx緪x] %ڿaTZYx`uˆsoREٵ:duzuxÙ$Ψ}fߜe@`Q|:ႬQ I.ʻ`A62j9 ލ;JFvϞb ߉7 +pi qvm򲀕M^ƻ_W9F>6c666*V}zlI|zju@ulR"P137NZH3n՞ r Wkݫ=? &?͜Ţ)~U9ź6d\{J(.jޟ5c̮'b3GVW}kfr շpY IuoZ-]p'ff &P=i^AV`cTE)TՎ˘`⽣P3k'{W'4UE5VwRZ}r C/Vyjυ$a>7d+.|xytF Y`NY1~zۼns1|;+gֹֻ;6YS]Tݬ3Mr ߪT3c۫k/$;e̳CbwsQ-SW&ݭ:f^A1j1GTZb_G ŇT`KPdqzj Rձٳ %T6. I^^}oij{VX\bu+7fuM gRj9eZ/ǨzP)Auܘ磊vv}K>gfYov[}SqFVo,1X-]bU-ϯ_NlC[ 2'êKX nՍkS[=dNY-cT})־:zijQULۖ>eSڱ-ۻ:::7Uom8Kg`I1XM/6-7x1խe~|5gT38?߫/UZw0?54ͮJ9F7TLnC[yvaUQù\,lPdqNu|u _\Tݬ:zLL@9폪/1 rM_7N[e~:lokW[pލn u2/WٯaYІRITV}eĚyP^)ͳcPdqzߝ&Ձ g2 4%떘yEz~ Ucf.p)<=d_vufr9`N`yW٩kYSa%ۣzG#[|.:!$rݫ6|R{=Ψ^X!-1gկ?{}sg?1 /1[!sWWSIb2^]95}V9F {W>8Dk> 缓Tku301X+ =cf64\$P5;T~rqqzN|SÅuM?7\Զ|xgVOh?6(`o13;WPe!jSCèҎ=WXPq[S՟]׭:lܠzkC Ɏ+ ܽhΫV۶\Q%SgujYQu^ux[5^{ïO71{=Ψ=,`I֢UT1Gg; :p+wm(8+ե'\weuwVw~߯Z=`F0 ]4n^}5oTw]{VՉ%3KF(`[  .}RE8˜?SgVU?P1( IDATI ?o7\x>?m}嘽w^PPzfԆg:35,U8qM0Ǥl8gjdUk(X)٧:: G&\EgJ`]RկUW2.j(nxz竿mo-9I ֝=#fvJlG~pC9,K1t'9:|,˹˘SΙ}op\ZS_9To^Y}`(lKNիZܟykI]oT}v]QVDmcV?6K_=귪W6fj ޳:vlrb>P>{ gFYֳ 3GU;NηmuƤ6.k{ꘙW5\(l(x8sxǦavNuvy V?ʊ8֣ &XzWwCuLk zT+cZ߫60zQ=VS꟪+g `qz3Q,tY-gU_ѾW^ݶzH|WoUl3c\Pp~?t1FnU6Eƫ]dc5r7UOrrvM^qyꧫ7b `mU㫿_b}zZu@-kz}udtY}vתoTh(ٚ#-.귗9{NC!ƃONW.r'V;Mv%s9FJ{'ܹ:|ï='\QuRC46lSTU]BQ_}z"Bm{6op1 7VWߪjtCէ~ۣT֋=z[˻`=geξz`oO7pµ5vFYkcT=ڻzkSm갆2M?^Q]2\r eWT:x̆ꯪZP7j<񧪃T;4\vvufEʳ=Xh"eot/T8]5Z|9y˘1W] e9_R]6PkկVOn:Nl8{r6'l[^{#fv^Sݯ"ꗫTKuwgTngW޵<k >˘B;jv`ݝW9LoC.cfnT>EzD٨R]PR "m(՛T=X~]TݷܘkUoito{th =ءHWןGfGc?ڰ,kmXܕQ gQSXUyKl[@y^CiGW=q-_W_'ۛ>0,W{C99de?qQCiY'W7my4mՇb&=Bg_ulK1XOάS]8fՁ InSp,ݠ:3{K߯N9`ٻ:rϙg&X=qNYFٞ1o(}c e$'V]P GCř[M~ݺ3dfC1Xo>YZ}gM 2nDwi(xQuZ;%ZUs-}z\ukU94[cPXꟛ{-:znuN&/IzCSY^)`=ecfnph~:v^RmN8tij^˜W8:OQfݫ_S1wN>W=F/Nn^Y}pP}Bw DcWo`%b5Ƃ11(FhlĈ-(PPi:ə=p5s~Zf}u257xVܸz_cZz`oե< %Jg\d#+xVuOv\P/خ{3CaFIl `[s-M?4mKt]ծWVW^?[[-<-u"YS_z$1loY|zw~ZaisMVGOmU}\q9Uy[OXwNE62ΜE5oOP#ct9ՉKL 6~WX[OsP5eQVe{>P=NuF(ťTj [ɕy 3dzT7m'V~Ԣ\jKTA/LfkS=\ի/ε;&" VZ_A/voVэw^]}:}s?>ZZ]zxZjjj.6stn 8dQl;.XvݫWR0_T]q?9cpaqn10mk3=&?z]uEu)T.1cou# c9][KTNXK[߄|zwoyxupg-gc=kVUͭ;f% BrN+Խz»ucf\f!s:.Տ~V^q j u{NQ}uE52MQ}#aфcly^=:zIu)ןYquTƄcpasfucԬ^YuM:ZUΰߪ[TXh [n୪vaTؚ6e>Q}rW{,rWQ}zJ\=qPu\`ncpatZu Xt4;ΰ3=S7scݴw6oM/f)믽.f)jwF6ym_ݽx!MIUVߝc,p .~^nˇ*\:t4[MY|u9qB# ejW{޶zuߩ~:ž[B۫|#vQNF8]:35Alcpa\zcږZSԞ[=ɂzn25=^Wdw`%UZ]rlh#ӆ<(gW~> FJ\6WoY|zIwkgVo_4>w x1;Sej.SC4CX}hQlV~=e\ γCu_54U}::yGCXOouS^W & k_X_}zb\=qPu\`M 1TK٫q5h2;79Nŵ_W/]g³NTV;Ww^_T>]XGuFA.ߍMqꖋhdF߮pE8 5k|Ύ}OTn|)z*SγA60|gu25Vi |:~ du`uϯ^YY}缡, TϩS:*ۣ?V~Up5XD#OS?y!] ߙfx35êp 81sij5hyg4Yd#K8zPKWoh[S쿾zjiV=̜߮zHFZ[Z8; E8|^nFγA 17z 5kMvV 뾴.-=nPcLkQ#cݪu >;Ø6cꖋhdF>0E E8_Y}zbT=~rPs-p j~ΰ+th܉ byUr_TO#`[t꟪TwXUTov\9a#W{,۱oF}&Zꪍ;ͱ?`1`z 5[^ ~.V rp9l}X-/+l\u*x`uD T kW]|}ƇX 8i#[̰Ǫ{Tjܩ>*YjPLXBSWaT>ouջ+LPj#վ߬:`~ WϪNWϞa$]}s5VX@/:kP0>3Aͅ=cFw4qFzQչlp XڹգPŷIm E\U$έS}n{|=mu\ j? mzPCkTNVrͤ& KGν)\g=LZ_Y}zb#`e?[]:vVI8,YPݦ::usΪ~)׾rfWdލ·fX9pRG=us>wKSu̱>~=V={sp Xٙս/S]cIG+,c]93?q:F;V/^濔_VnY3sOnWk.ٸT?a*|g57n=A OX'+\gnvQY|6~Q]:*91X͚ttW/_fk˦P1do'WTWCVR?Z _b]Ϲΰ ˬNqݓy* /nIB:> 6Bm:ϨP]qwwT#(6I8LHc܍֢ FKپ5v:h?i :FnBݱo-_V_z5/vsaͺF`ĖݳFݼzKꏫ7<~V0c_Y}zbT=ڳNp wa ?Xf5פ'՟P֠ njf).ܮP{+k|)nХ| ?^fیo:l_ݧxF`R#d)pVo5WmGĄcQݩ:yVlm置z28Y\p;3Zuy5lԸc uWngS?M7?ΰf]91Kb6ZrkUwY&8okؽ:::ٔϭiްzew &fկrkVwPu25w>X[ߌ?Wݦ:F{V^;A.pr)7l'nPE{\v:E-w&Xgm)W鿆37T׫j=̄c>QXLuIGc}Ӗ :Z3HuGsZݻuV^6_LQȜ?z UWϷx~zjej& k>#TO.:6fl~ip&VejnVyM:OWYuՃ|Wq{;7PI~!-x] O8z7Ϝ{xMW=vνLjjJs{Um.e[ةձ>6 գVV?_{0{Sjn_K۩Fǃx~F@Ɵ鼃.37UNS?FO^Xf+E7[eSJm_s}Ցi[SݣQl.hK?gPsպŷS0W,۫=g<2Wqë4B7ZVm_sۛ!y,kS߽Ɯ{xUu vm8vuQ=vFMrAV_]:ȔϪ~B͎wNlM{wFQ X(0?UpV/^^սS9rg{fkճ'VWIJS+WOgWX#kN=fv 6O`U[խa W_bsW}:1h7n*z::=][Tn T\)UVku\To.^ަu<^=UϨNO[US=Wc?rix]ցKE۪wo_4PNYک#Tz3?S_050_Vvoϩ~^r-1#Zzx}6|,ë^˹RgXFmgV_j |CuzQӫg/eY`>Vg0 HTT]{ڸ?z'Uwh}LUpꭍΨ^c/717B__\N߬^Z9\f~ozO66w :z`#`)몗W& l ^<{AWjcܼ[ճ-cSSzþiEz'Vb'l1zƵ4*_;ݮq?[xK{h#TsgSnVݦz{#v߸3jkrRuPvs 0Yfmxkư}5'bA1 ?QBu5[=ZkX_dWjuVIu)ոgVNiꙫokv);WכrF!$g!Tx%jvo rܿ12euugc3ajvmv#x;q?q}mQ=zDkZڸg9kB8oTwj $c+VGW6m-fƟ̫\1 pցy6Q~=ܔ'QOlXݭsuj pt#4{ n0=~F4N^WָٔwTp{T,jFɬ.^=rG!Yݧ:nlmcY7To|O`n.Dw25{7).&-ձkqSmyyncZw 5Weٽz\<j{U'6 ~gm.NŸo`WA/bmMKu8r{R'=qv|jա `+n.d>^ݿz{K/nPSר گ@u׿5zn-?{W> _rFYb5B;NiM0o+t3_߸< G"1`zxD#4aSn^14ii[zDuY_UϘURpu Wcev۰ZLpȺ՟NЏ7ν#׫OKCi~n]{/1aga]ř/vFVqJΪT}FZX|Z["[=} OmE9[E6Wo7Pݩ1To1|1~zr0Fշ?bkU\EKپzUcԸjv{U}F@UX3Mꦭ.F S?qgNuW4ٽj|ml[ ؿ9m0gUW` a1`yYW+n}&\չ |5[ĦYڧۦ9U~LݟT7MٹzK#c?R|nݫ1dqm ػpԾ~~);s'1s;T[c5ꡍC3M3PFhZTu^Vn9n8yN{1%ߡ}ݪTqS}rNyX ԟxOFݪ3\zu5Uj={Tש}Ϩa ْ\zJu\qs@>y6[6w@\Q=|U?&C#헨yhcߧ֠G4gVO^L8oj |_c0Q]*θd3?cc#[KVGUK+7V?]v՟W7u_m/gX?4֭Pwz=/!^ܤ g)_[=}}FK9Vꃭ.\cKoEfXj#hō.cwncս{nurh^=jù*[#՗ǝaW^Pc䦾_'U0Wl ΌXݹز\: u_iEq1wnx@ 3WElV{[\0y^X*ttc9+Tk̜Yyfq-_=RʦzEg`[ m:zpejUXwM:M^K5p^mAUsxuqTo^kj4{0Ƨ'`[q1WϮn4cconP3Tq& XLQjŴ23O6}i]}m7c!%wk`U\` c-S}#$Nko:ebc|ՑaF(ďVt_t_͔{ݠόl>;5.LսWgWwniMǦܦzu'5:ʛݾJY6,N^!(TUGLc嗍_YZ~C,rFs<-V/6!YKv}n]}}W3`^<9{N O"{̵ju/~)|VXw]'4 g=ze#L1l%1`ej.ZttsUG.SsFuVA_7z\^Ѧ`^kw>1_Q=zmg+Y#rέ_ kjYܤk,iSV=rA0T~q_գ-~*գ[>H8lNjdpK5&!ٿz*ٮzUϬ[y}é6$a#W=Z?z<^B͇ظ_MXszh#V \p z:xjz|umu=z_uՎ= |s#a3l9_=*_ 5'T?\9lݸz*ν{TY]cu35\#յ\zON&s`0._=}T?hM:?aλ9B;{g/ϸٱeu֍@7Z]zouz#z;VwmBܭc߮nT}~3von3=Oߪ^V2k6?y3OmĮK6k5ڮ,zpm {|zeuXuZVn!տPנyuu)ϭVh!m6ՉG=6{m W]}zG#ta`L;IGWlQcA%is\9ծE\[M$0ToV=m߮sS\͜^MmAr|gmvTGU3'TOmB8l~Qݩ25;Uok eRQ]vW^RՃ]#sRC߭„X=t_]q1Ooa6|,¾7z>yO=lu N]P[]m>]znu<C8l~\P}{U~kM:ޤC>^QF왍!q9Y=7KLuge9{TV}zSouu-s<>7ukϹOJWOê_7=nZݶzGWV'Uo{?G-`c;V?]Uע)m<0r9YXu걫SWScj}I1žWi>AT7Tר[}|L\p9S{KnWoVWX}f=ln=V#PRS?ziu01``SR# rkvs c^vlx찊=Ω( ںj{fi]R#jD0A`fSDS|c(D%1 Ć-;]td̞g<5|מ>p}^\7qn1ܤi,ydVc=" fw'Xww7׵ߙoWZ&bYKվ_8sNCȶ kd>'Wܾh+qbzzzzF` V'T^s6#\[ݵ>s{L՗瘻rs\gƘ߼ڳzY:::Z>iY^Y n]z~ ^]?nt!/߯. ׆lꏪ_p3Ow XO1`b7hZ]>\\sgkثXˆON'cz~9f7LCŪ5PqT=k{M0wj]ƼNJ2AUpoW~TG +{TPjj]Y=y6Dw^{37`+y1@aZmU爵Z];Xku:Ν3)|.?~pcnxlQPO2ou\zJ]q5{T_[ͪαO+~UT?zuVe]W>wڼzfkWT+h|gWh/XPrjÿ㜆mR>GU1UoAQj8 q.w ݷ [MjSK@ iylvu5?k-|z`Cĸ~Pq wyqs_պ=3n?_W4qfɍ |ګھ:tUCI9 cV/l݊8 +_Z{V E2_Nj59aSK,:0I`^p#f^RzLi=昹,icm'\^co j5̞S=znCy .s/Vwc]Ć23\;{uj|r75̷ ^U5YAʆUb.`[XUzsT/4kuiC>ǫ;NcR0-N,b뼴_-Mxfc\u 5^1FǜSqij1Ǝo5vny뷟=Z=}x1P[w)%G9,+1s?T]=gdPnq ן\=dj-}/5>uwWWWv$L꠆O^7j~՛Sڳh(|xm),k(8bJ\]ڮ:\Xcrm60 IDATFlRKԙ$Uh(Mge.>Qvrj[Wέ^{&Cnt{Ց7y|^;9o5{]Tکz[u\^v_^nU[}qfaSKlcsE#f6P1koͫ?E>:$P5X*]su~7cVTF׎l<]Q}z ,]==Պ);q}>r}znT_YOmx?^  ͗;VTUGWs m}h,KWU;e =>:sF>U}3Pq,s}44 ~:j=dVT>{L<%տ46?Dgs\bKw9vVՕՏ1b[Ձ3㺴--`a!' pͫWI뽪Z9ݫQ,zKcO1nPV[o{T> jNZU^0W,_ՅշWgqc,vNm(Eݻ1ή^Ym[]=ň4L >_9+G K |˫gUoZ"M8{yu{oPxCg~V~P6kegbi٪ڷFCic'jj꒩6Z1UO.1s$ϫF\߬zo<[áO;` /l(ضzzu{FL`͝d n:o$p꼆ߡ{a.^1|FN9P}轪T$Q]ѐ3>dYYoU{T'O-k^zOmlh(\L~}_IoU6b՘/jxe+1|zFboT~m&[ kՃxmzTi#UwwThĆRI*\HWWo`ݽL9z?;eթ {W;UTO3 (n]GW6E#3]qZ;nQFc+A}'W1:jl1Y8juT7n.>? }8z{^Uݻ:|j%M9pco^5ۚ{ ﮞ^]3bL%['XLUWO/OϬn2$?f-\IC=36[ʆC_0bՏIPiu6l\r`UWm:ƚSi~ux8nRPrڡՋF\je,v;߫P|JFZEkU9:pV eQ)XΩ7暝#ȔMuUbY#} uo&+8ګ?2 )FYQ]}x=iwEuPթ3zZu`ǜsu2E5ykO1˺ڡ?[8,UVUG7~㸦:zhWL1:QGlRS6Dz7.S-}g3\2ePx0ͫTnn[\_^Z9Dձw[jjsSM0:AzR"5ټ0'TM%߮pm;.Zqnņ!զ/ZջƘJuPH1-n(x7U}bj YV|ڻl=[Xݩ|4'Ψ~z)3If7k(xƄ\N_SKB;Z1d_{P=jFnMqwgէ{^!XYzMl}Տ/~PqսyG'̴{Lѿ>?\ b0sb9f~zW SՏ'X)XVWyIESգ E_ݷu/XYKSw1ձշ^)61ixSC (OZm2q~wU ~~^^}n6~bj jj߆Ͳjߩ/T{7~Aw;UTL3 BSLT?Ց3rc_v._OU[t>(1+ ա vc:j4,61iZp smu_N]13IB[^V0OYfjIջ -wϏ7ܩڻ:}=\v]\m3VU2tf 謨Yݼz~\knߪ[To\[TY5P,zj(-cV.գW0bO/W_Ψ>Vr.en՟Vp4/SAQ̇kV6&˪cK(u]\]цA?P,UCu?3ofﲆ{[Vwl(ZPꪙ[7Sv=NNixe h]QQ}c{G$ѯ::|mUIՊ1 w3W|@rc/ƸGuhu^ŸTUo(<91yqչ#fnpPfFzۈ;6dl58,sƘJuyع:vup͘ZPOթ)]E3ItU G<@u$`>K+3i{ ޫ7TVw^>7V.1s]gz+gV1ꭳ9k({dsqzGCSKRWݪGl[}L]j̵\z@cvyq C.ժiX)fS\ 2I]Z^}3q;:) ӳKu|jj1:{OթXcvjj则Wn2Diku?fzUO,ͫ=:O[c+ `)Q,wV1]fW\P՗g|1ec|a"7W_=t=NSY]9tKr `S9f~zsߩ3KƘPyºgCŅ]\uu\ujj4,e1sa&Xcvys\[_eóR_U_m9TU6t3briC7GlQLO'Tc~j}oY{ujjlVt=2nlΨN[cݪWVN1P,6?W7bf)o$Kյǘ߮z꯷m(I-UWsc S]9̀LN9]PZx->D,UT1 ϶GW;ÚW;a]\uu\uNn(`Q,Vߩ_]VQ=:jJ{/VW_=xٕ߯ RlNpY=Z>b~-g. EgUW7sTw9tlԔcV+G9fvޖIxܮ::z{ӘWV4<3Vulu4l?VwlVW/I";TzAu _V:s)?1g_$L.n&QutuT)J9t}3Q]R~&&SꪅI ωRuDյS˲0#+}1}B`Aܮ::z{cNv~:63Rrm3TG[nV, ;TGVVVwse1ՎǦFQ,5W63VV=n&wՋ:n[M\5v W3X._5bf=gh<V yqjkiՓMճJ1~1|0Rj3[W~}&VM TݮՎc_YsuluDcK'V?1snnz_PչիEqYuLujc K9} 3wn(ȸL[ Ll՗}u5v E9@}zruՈVInv?f!0TT_m(b|RCQ8X=S* IDATS`3r&T-D,.^8)ծձյ?{mi]w=gM%^pDްa"@X8Ĉa4ܔI*R*4RӪ4j*rWјA3cOR 9gϹ^k=}f:k=1QI3զY<@Tmٮ[xO;[j9vA9NʚUk춅VM;[:luRg|z{uu Ӥ[S5)dْ-cT|iAvN5*W\1WV'6*X]}c`1엪neӧv.YyAэ,T]P>˪TϨV1 J9m^_}p+V7BVQjiXV]Ҩ˪#gTkƘ<ٵ:bYv݆5op[ӫGq5 r mWfYg ؖr]S&`).>[j.nNVm5ssScϲfj]m\j dXjvNnt?y̸:cKr zIY聪ٻa.ic>`)yLRuAuo.Qc>Xc_VeV㹻qKWgW7WWoUkcMKr XYX6conekcTtuv g,"?ШsjMuPutuX2`|Nʚ~e;uVVXY ,3,U:q3nnt_w@u`9S0>WmeݯU'湯5!۱ݫo eڽ::j3Nά[:__Yhu聫qUW=@uYTؿ;O13CX6TϲfEs|Cmv\uv&1.6΂jmu[uzs/fzBut1`*cL˪;˚Ursce9㪏Uզ,3,UWV7ӪU5)Q09wT/neͮեӷq:z㳢Z]e{Uhؽ::j3NltLr ɺzAYY}Zf30ݫ6RFE!VgWqesgTkQ0y7V/~5SY)+,`>^b ?;oYFE6* yZST]}jyS0WU/edTQnN1߬TO>=tD){_ʚWU+nrf_Ƿa2-3 UWT jjUqc`|CwY_Qi(xq Գ? k]dLq՛C9c}uVuqur:2ܭ7uB96Wշ&fVWVg7b{UϩZ1,; `Tګz,YU6/~rQol~VQjT2Ϡ.~s r _jꅳ{Wusz bF%?5}U^Z:j3n~ύ%jfTW峬1 GV?<[}cԨ$bxժF4bձ!Y)%O9qw3ڀgbէgc}7XXQZ{sPF|_<`!aSCWUhoUP}vuouG!w5*}wl^1ǪhU/ p6,f.ht1V__9ZU>_oW_c.`Q,7'8wK5*ҸQlc̸ޞr V+Vfcy33KS WQ )۫W7*!cc5T/> KW6zrF~XU'L`iT1 GTkSjM3cM,+1ށ auTGW>a3nNVU_g@`yPCmsWuZ}G2mW;tTW9Y{6*zI9\Q=z]uYgd W7U㌯UgVONg@IQ0]VVV]~u N.0wuOT+&0juBC5F6z0,T;T_==cը``Q0OT?P\}k,qZ!` QC{ TGVVMc F]LS~U~x sar F2U{ 6{aCFW'W6ALr ϫ5z@]gTo:T'UV{ dΪ3lQ0Ot,jClX:{F0q1UT eV4tGT^7t98Qg+å۫{ݼ{,+T~d sG&A9ju#F٢7 eSdL_jucIn& ՋcPo>tsz^uvuG`;)X.^m8?ګ:y,Y+3F8piثQ'kS}M01S?6?ys=C`٣FፊAT]Tݨ,`S4\W:^KǑէÆWZ}jAlr Fh!<`xm dW]}yקc,]'V;o=,~+sβ]T]Y]Qa{QmVTkSE#wvO;iӦtyiӦ-X}rبX %L9Z[]'=n7n|Ђs9w]`n6l;><}٧:߿ۯۯ+W=Sސ eM9r?/?3ꪫ'{<`<fÆ {ޝr)=qko}mG+Vj1=Lաڹۇ?wuwT5k N:l IDATX~fffzK__ܫ_fffg?;t4`̔c0=ܳ-~s9CGL9;{cjʕwqmڴi()`AYb=﫞R=ڷzljjk;MշU_nn>W]S5,@1`yۻG6_T+q÷竏mg`RϮ1OU/v93ᛯzՆ 23t`jVU7VhT1b-٥zUuqu[z޽Uy%M6=aiK9hi WJG@fD0(08 : x) r*Lˡ)s4@wZyWGbԁkM I3uMe$$SR!Ԑy{[5bHlȈ$%+u@ 6sKނ{LrLIrzxNLN$_K$m@!# Ǿ".}<uI!@54;lޑ1j$7dJl9MrM15[\djsGc@9F%yS MMrY!5Y-2]2D88^kK{Lrem$@4;lXvyw*!+Rk,M:ɳI֤+۲ׇ'dPIk@GKsLIrzV\jp (+YIL\/z/&Cۓܙ$$y2IgMCZd${'iCJrY_ p HIH 䋽ܻ8I.MrCuTCg'7[dN'݋#UI1#5XzFoKm0Fd{~IMrt_z1z*ɯ|$6IޝgLNU@ɚ<]6 ,Ȃ x3<5kdʕ/5555ښ*cƌѣ3vl0`@ $$cpW$'EVC{ -WTޏ%vrPmcUrߞn- ,HGGuEԔgҤI8qb&OI&e-Pi=dIz+I4;??'YUvi|9ɱ$W 1赎\>mmmioo3]7n\kޙ1cFbR@AI&t$]>WPӑ#IWeYcIKrZ16cutt+ȹ瞛Gyn9{nnnk_~o~/~Y|y }U1bzY$u>Yz[n%w*?C*1VCCC|V[mUճ x=vu[O不!#c֖SO=5տg>455i„ 򗿜F_U1;اzz/=O6 .ȣ>ZQs'nw54t&ib=\_X>`k1ikkO~j|&;^h*n0sUSo+fmy-{c/_:O=TEzhnow޹= \kIu-OwC0VWGЙdU7k2g82/+=JLҽvک.}k6p!*x냪أ5M@ȪU?)Sdw,7P7n Uqo7GTG04IS7k21xVQYJNӽ}CЇ>$iooϪU$Æ [&x\d^zt7cX1ITOMap MDc]wUqiJLR ><ÇOCCC\߫=nNݪا޺{/JZ ^䡇vĉ%&Mc\]k7TO4}b8"-n=zti`4&٪}~UG=$9Kk1x'|C6Ywts}@#$ߔ$UOI2joKrmc/bŊ Vrd=n>d`Irz7k^20)ݬZ  :::VQУ뺹}WϷU9ɭIB$O?XIdPzU$kGֿZ-[< .ŋ֖+W-ICfĈr-[fܸq1bDWڵkpG[[[-[u֥)Lkkk .nmƎ;zf͚<Y`A{,_|x1cdvʶnF6!'dR5[&KrWkiI>?"]C2Kax&DWm`#Iz?5@l֖,Y믿>sܹshѢ^3bĈ?>SLɔ)SnemrrZ{qwc::: 1hРL2%{GkL>%K+ȯ̟??ׯhߨQ2}MoٳRrRn 7ܐn) .xosssN/ۗ*hOrr +}KO$kXƳ>]&H$#^}$bt $MrxÒ nq#\Q\xyUs[vm\y啹 6l3fd̙={vZ[[KrڵkzQrCN;Tճ{/|!rKUnKf…U=sʕݮY&_io_{ڸ+k$wO~ikkIO $߬wzy哟d,YRhߐ!C2cƌL<9o}FeY|y,Y{7wuWVX댷zkn\|az}<9s7pL6-kvqnj92iooիdɒ<ù; =ٲe9swKSӦ˲ej+ȨQr֬'я~O?=5}5cXg5@>d$sji{@*nݺ5{̜9a y衇rUW?yM%T˱gy='N{pimmhϓO>K/4_|q}ي\py衇>Er1}g̙3kޛ}{;hР̘1#{vqnj5*---ڵkdɒ,X s͝wޙkn[n%rJ򕯤/oZ$g&`@lr.|sKGGGE&Lȉ'3fؘɓ'g9ꨣryO{{{p=c+1bD>яCIccc^cǎG>}˙g.ޚc=676 |gfϞѺ;3_~y;::r'駟zCCC&MiӦewȑ#ښ+Vgͳ>G}4v[zB3zj.477ߝS椓Nh]{{{N;arG5Zu]/~Q=K{5cy{ߛ:(---YjUG?ʣ>k&g}v>bTzސYfmnȑ|8cINI2q|c`7?A!CrG oxC?,YgŃ>>:+V~׾޺O}N8!fʧ>\r{Ν~932'ɀ 8s2o޼]>|xwmo{[ƌs:::r?AnB.\g9 ɸq2nܸ֭YGΜ9sWc|\hOKKK>]ʀŞyښ9sCO|[J[[ۋj:^Ν:ujݾ I=5I~dǺ5;@͛Wh0;| _`5eʔs9fmznK.?cn9<o͞=;w2dȐ{:;;~uY/ַ5^zi>W4#I3cƌqҗuoҥ?Wg̘19s񷚚r?AƎ|K_J{{{T7$Is(&a9VQN8! ;vlo{ޞ?>O>dEo}N?R׾V?zj9餓r)dĈ>g<{ݓ)KVgԨQw]wݵj9&M:e{~?ZnU$Io>1~3+~qr'_9J!sN͛WQmsssN= ><3f?<<yk}ѵ|33gNUΟ6mZ>_*)וW^ogƍW,[ouolN?3|!$'^uMUb8]z饹+?!C=L8&d9s+%&rGfն糟l֭[Wrb.#8"|pU{̙3$7pC۫ZjU8B{9.%%Jvip @ړ\I%=I$'Y^(1~g駟^qӧsqլߩZP*KNԥ1|Ϗ0aB>hllGQqsW=s穧~y]b.|p{P$\$d$c$-?0=ܬ\چ?Sɉ^n̙aJsue޼y׿ϠAJLbӧOό3*~˗N8477r 8ݪUrsǦp IDAT?444cICCC$lƯeջUԓ@xg?~֬Y0aBwwё3c%&B_%&ڰٳggĈ5 wYti{wZZZJLqSL)ܥK뮫~ذa>}z)Y*Qt%\RRfΜ7e˖e%7B{w^R 0 O-1~7Mګ$:uj)wK{{{{Gw߽Р;K,)1QfΜY>cƌ)5zgJLCo\{3j֬Yu C馛 c=JJSW>|_}Vq>}z{UCsssv};;; DzL<&}2f̘}PԢEpB{sϒlܫ_HzxsfŊ777g„ %&̮kg[n)g]vje] 3gΜm\cccƏ_~cǎ͓O>YQ5kJNC 777lʆ7.C-sB'NLS9 ի 4iRIi0wtvv2m]ZZZjo׮[$5o޼BǏϨluy>-ؤ=wa_~ȑ1bDIi*7~B˖-ˢEۖh&LP~ $?PN;TRʍ7.{ocP{['X/^$5_J$CIt,Jn֞ep ߹ o%%1˽M￿1FY~vvv":::ڳ6۔r[ne#PIܻ$ќIM${}*5I.I$+$߫w֪UxB{Ǝ[R+r/b?4)ZZZjڏXdIV^]hO\2$w5$9:|?[`$]I.LX+Ec$]6WEGEk_McP%K޳[Z/IUC$Id^1&7M2J!1~7f8/>ѝ$g%ٿcR]2lbРA{~֬YSxO\jN_7$){{9U8'8nI >2Wso/ڦ$7Rsvݓ<$HuɡIP~_ }Ud%kii)g͚5%$M^^PAJJ\xϚ5k2lذn#_No%>_t 6?tIC_^d$ӓLN]qI&^IMxٚ2Wz3 ? ܋)T4Ko@f8F\.Ny1%٢g|#]yIޞI^&{՜INwQ$#M}IHreMlof@M^қa Pk粣C$*$sUI>H'|+$K/|o99ɗ,L$K5':$M{ } WUϗaȑ{jժBCmY$Ŭ\IYJ%~Sm ^sas{̮wޘ[{;c8hѢ_{xⴷr?xL<4P]'N,T#rI^АdH@}'9IB_&Y['٢ I~d:qI~dz: S?~IIo]w-TnݺO*)M~M&LAkw.T~FX&iD \'IG}!I^N?v9'eI;H Kru|3w^פŋgٲe%&ѣGgĉ<%C=T~֬Y%%s= /]4O=TIi6nՆc2ҒVa$V\S=U.!w^jԨQe]r}U>Sb{dzhѢkvm <йg.4lb޼yy[RGy睅wߒ@iiiڵk+sw,1Utttԥ755eNUJ~m'ػ?u7$^)Hrq61,/u@/K_zk݇c~ozKKKoryU\-QM-… +>|xMVb"L>=7xc{nֺ ǘ;wn]Ps\ĞoK]X˻^lR=l~K2JgNpR<X8f9IvH2!ɸT/Iޒ Pc!|p+fB_>zk5ӦMKssw=o}>hx}妛n*TA,g6-p@믿>{52U<4Ű MHe9(i{ΚY33u]~k`ciiiP{Q(F <ĺuI~ZņclZ>Л|:]wyd$;$iH$ ח|m8cԆ3vNrd$y(IW8Xx/5f8#mv0aBO=T.\XD{'bEﴚ8qbή'>Ns]wuW9}ݝ?+*gʔ)vdɒ<#LԶKz_j 2/ů,-s"e$:yI.K-IIL&s%$lל^.fֆ2c=1ө;SBI:v=_(я~ztMK;ܫ;.]xqew`"gɝ[nLvziiiz_jbMwl~%\C@/ IM LO_+ɺ jp||%"@X&M{ޒΝ &j۪Ur뭷[>drÇ+gC=~]u7vA_**NTyꫯV(ͻ]6sέZ?jPbC$7LEߪ@/6KrZ uk|=$g|df]{NMu%CP~c=V]]]MVrK/ᄏϗ\?<[yKׯ_8óJN=Օ\7矯`V˗/W\Q>(K>*ɠ2\J}J$7SCUI@YxlM<̒민쒇p=:|g=g|̜9by~䮻W==H} $X1ɲ SLrTWC۩9JY(1^ᨣaVRuկ~5-XƜyYn]њBs=7Byww9RWWWR|e𶖖PRСC3sl&[笳=swݬ_Yn\zV(2uԲ*1e:.D-IV~hzAO7R|hNo:2ٟT,t@qggر%.Y$ӦMKcccs,\0ӧOϫn 'ګcX>ϔT~\xᅹ[ʚ9^xa㎒sEeȑe=TO2>0gf${*UR)O&Ibp ߿oe]v)_ԩS_lnYxquƍַ-ӦM^RmKKK?̚5+WvKfҞgT(2cƌLЙg%Aryuz]wݕc9&wygZZZ:s/~Oӹ馛޵?rȜvi>^7LR׍7INԿWll> $URIfZ]}.W1|\~>}z~WX:+&Mʴi2f̘N\~},Xk6Fʿ˿ӽ:P(K~2wܒ2|K_ɓS(tnrSSSΝ*ofI_z>Njϫ38ú˗|ٳ;:3&3f:+/r5K,)9۬YrUW[3t\zE\~̀'|2'xbu̕W^Yҙ^xaz^K% rϕ|;544__~6/b-Y$sN.~8qbƌOҒ~:n E|[JCCC^I^I#>\ų}Q`&z݊j:~m\,t@3lذ\~9ss}t37.SL?EC455#<;>lI}vq{^B>;mYfϞwx… |%o}>L81[6d6|<Ù?~͛+WoРA 2iҤ)ڵkK/ŋۭ߿IgNccc9b%i3ooQYSSSIg<3˞^K/an{vPF.,ӧOϢE:u… se.KCCCvil8p`ZZZ[oeҥyzv* 93jԨz$%Zd$/'xT:yufIOiEI&| +ɛ Rb8@f8+ 2$wrW<"I|)~PlI>O~̙3'˖-XѣG礓NB!G>/sO<U(2qĜ|s=+z<0'N5\9s椩92'|r;յYRy@|7IJ\d2v՝g$S'C;&%܉Oz'{B_p$K 6)21>eРA:ujN<,X |ng?EBS p9?)=ܓƲ{-ܲ K7dȐs9U$#F(Nʕ++om5%kJ9+vکbg744dڴi9餓r?iy.׿|)St8gڵ%%zד\[Uۘ.72:BoqoV=Eu)21>P(d„ 0aB֯_{.?p~,Z( .K/wۯ_lvm;g{3f̘?ѣGgљ>}z-[z(O:BE 4({l=,Y${jrG#+Wf͚5ׯ_Z^+V(ve @5'wI(-UN&YZ5cX$뫜v.fUSecBP}}0N|kzfInKe??d+x~{_&P}Z^,g.+:+\[V0 50/ IVI> ߞ`cR-:>Rd: /T7`1i%Y$?%92]v%Biۓ?UI,y$dyz*e866Y7j *d5unЇ-[,˖-+vwpj1AIKro$$9"tSE)*k`s콖_z~@<%^L@rۆk$&h=lF$OrwyIRFc%iIRx$&C?'پIW1 `8@7=CyGߵ~Gd5H?~ߕTW__}CN@Ć d$$+k (kIK2uINOrFQIlgj ߵ6䠃AVׯϯ~jǎCV6=ݛIuS~wHIdiuAInH렏O W=5`,YC=iYj)iInMi;5%y:q(1*OKKK?.nС9蠃* MEEM*f鮑InIrYM:q('1*_̽[O=Tϟ_RGT8Вvg'ٯJYj$3<GM@PAs̩zϖ\r%iiivذa9餓 "ɪ"{$$C|:O<$x7P.*p Z`A~Wٳ#T{)dȐ!N@d^3U%2ɸ$'&$'y5 ILүg6 .;6m]{q+K7n\>OV8UpQlQd$NrhS72nܸnrg۸JS1iȑ0`@V^ݭsV^E)UuQ93Pֳ^Lo9I>^,Ք$:c8@7r!4iRnvmyꩧj)I2nܸ|_̞{Y(TIMrJo' oK$:e8@ 2$w\; _:?xZZZ>_9昌?j}'2ɽiq`mXInu*p 29rdN>|y7Σ>~:֭+kd}ĉ3ylVe=^$%98ɹI&6$3ܖAQcTСC3eʔL2%Iܜŋ1/B,\0JV\z+VJSSSg1"lMfm2f̘߿/ ׄ$&9:*gS\U @PEa;tXo9NPB:6p?I>$*/I~ ׳@f8@5hРZGoK2*.IvJ24$7\=+G瓼QjJ WgIfYd$O$yt>91$G$dDmVI-yxE'Pc@ߵE%\5Fp O2;2f )I~1 e$g!|$:p ;%Y .d8 L2! Zu,-fU>$,IҜdeKWӓIN+cxTR}EIUdQ0={!ד\"+:66{uM[Ȱ$;3cc@/EIV- B6b05d8~ņc,Ors5;_I\ Nc@7UMm0zEߪj h-/YUS@ ";U3_ݶ"ISwO24UO4:D7mWO`X I.z".ux[msZ ΉOrwb $MEvH$?IrX j(78g%9zõ&ɓIH,kIf%YQlt $' OmWp *P@GV;e8PK\dJ߱iTP@Op_C~z$XD@O$$snp '4X<ɵIW@YQ~>$MIIvр"$Y_ e֒'}|2ɏ16N$9&ɔ>l~_K$$q+(|!*I&١o&dmUeZܒև[`$,ɡI$?M ehN뀌cՋ@wInNroÒw~IL@Iu;T+g8}G%y<e>$%٧gC V_$%`K$G&E8?IN?.zIRp Nraz Ir[$m D$7&Yޅ^@ﰲ1W(:PJU90ϒBM9W%f8=M$u5uU}rzUW%f8-$W'ia| }'ٽ }زͪn3[IO2 }>SS"W*];د :p ;6I2!6&;Qw\dE';3dTer3PhIBKrk$y*ɫ]<$JS$?{wl]y7Nw 1 !l e5(FaTj EveA,5pa"b@ H[Vdת9s׫T~}nWOZݶՃvs!~+ *_Yym~:zHja?Ž[eV'WTn?:kwS9!~+ biJ\SC 2&cT]W}{WxcVxn9T^s+NճVp3(pjzHub5ufՏws!>k_ASm[pT3(p9W)귪vqf;Y&n\9;q]} bvQ&0կW_T'̿=TEwWYjFe`ʶ#vF{կUMOiկ.˔f^1->` y30c1`QիgxeK ]oL9KkG{{?v,}-bosr nK/cjev =gpM0O9v@ٱCr7 췋[}v mt%WPjJw׵(o.v(v;d|z\9̿WUgQ߮>RL.c˄ͦx|z!X=h bw8{.0?hwGVWS؝7Xbnh;ձ(5f }[9̗VOT]8rA9l~?.t M.6;0fw㹤:}q^[L׫.3?a 2&tl1~4;a]]}MY&Zsx;n2a~wwU>_>W^}xP̎r NXf~ n 66LY\bƕ-=k c|ݪS 2c;O؝;dY]=vZ;0gU]f~ߡ/U&c| IDATl@1`~i1KM` r T`zYfa SeOn>pYxRu SϪ`jqPAX0_.gmߴX+ӆj( zYkսwqG`'VlKvVWk5nRzn-W{WvgaL\UT}z蠉Vn}1f6/TMx]=b.7c::mݝTUݽ2Xs-V^]3\ֱSWGm]^/_}Auk0@u}WuVy׫76 Rx!KYkZX]5g0 cXT;l>Z=}r `3bu\uAXfr1꼑sJ17߬.9 ku^՟WuPlWV;Vo_MN90+sVZz.3ӧ˪(waCgZ;K93Q(`fc031cէ} c8y_1̌r fF93u+tdueV8 +,|g160c031̌r fF93Ql;0-#;UWW7X:Q4a~꟧xϑS|M9̿GTevT?=5gcՑc`ϵ0v`?>b FuTm^[=~ P0v`b 60?nQtp}1`~0?' rVǪbZ ӝTTZ3n[zt0S ܱ8y㪿jUTXztu.}iROz:fN9̇MoQ=6\`@/^KVUO>l0v=cUle%{MF9̇'^VUIujr-ޝwTw`VՓ5~L0u1`>\Sn7gn]=huqA3Mw:ztY/UGX+{UO}I9̏WT2~cV9z.WXa5s'Vcm$`*c8z!zK"=\X=`,Lr /]z ˫* ]*ϓSnׄ9Ջ2UO.N[LݵI+ƍ35ov.3p ܥ:`%եfV!M(uJՉY- 7 d^S=ԖSO- WU^_}nP̄ˇ 2cT!v[ ^U۫۴Xq#40N|RAr 3]Q}el>M~Ø=:h lahƤ5SrWV dOO*`:vKue߯>O0haLcl1`9wv-'4k6s&=haM)KXq 5S&̏2Ȍr Sg˖ߨzYfՁv_2k6&Nv e:a~U!v[lUnگ7^ȱAo]IaL݃c&>_]=`A9왎XݫGuq5xGurmݓUhz^C`cN~f,_:~ݖq? j V3iX;0s7^[}zQ1` V.κ_n SZX=r`Xeջ7Uh펮>Y=p7άΙyF9̧WU7n`ƞ]]3'T_Q4Ds k~X_ {VkІlS_ϚVÑ+8segyu}8(*R3FlL(1bĨŘmbP18&Ӡ@#Ԛ 'D cm ==^u_c?ʴu@㪇`e3 V c#V]{T6kơv`n|z MׇڥV;,`L;ܒr X^wO=C9~UE%}g7"ܒr X53IPu]ܿvVAcw}u.oU%6||Y 6|xY C&՞WՖ{3M%WT{U{ϭaϯUIֆTOަa4mPUYݽ:s]m?UV>ruM9T_mޓZ{W{ͳΌZrX1ë=x.s/έ`uNF#Lǫf\]7!C*zdտkW3k/W6*c Xcj=|bRuhuZuq~6i1`9oZkqݦQ { e>js(ͳ~#gOW'Wβ;WV:֮߫~qW-C]W?t&/ߪ.s.~z֡j,~n Lr XΫ_y{Oճ;40 'TkuЦ-S;p[VGWsk+?NR?=z[rnSC`y6U[+k湷:joVUWߝs뒓.3]61~hF?8 K֎]ʴG/Yk.|{ߝ۷og_OWur1Վ )uWX}:\_=zHFꗫ& RՋ;>Vt9[WUW'TO^VD0u,KVϩ>1泣h 0i!n[r0E1`::aU=zsu4C-Ѝ=h˖-C1eha m:vahz=y{^gyf]w`=͘4li l#/|a/yK;;{_w~UT]9hXX̳vSuAfci lq{\'xcǎ>Ow˫U/1ۄW񝙧oYWJ(~vs̮l]Z}DL=m;Y41>,r `6nK_{.#߫>[C IkYuk3Ƭ=`R,˞{+^<>zxWWU6M4 /~zw L|;: 0u񐡀n}fb6=f}F90={^ƍϐT'VmRW.`XCgb6:f}F901G}t|3'y՟W_lTqIUc;ӷ:v̽qVDtI8z՛oTS4a}q2ȔLuȘ{01{)T]zqu)#1~sAlr}Y`ciӦO3::bFEG`6 c=z L տsM32(㫯x}e\T}_WfYP:`Y&Ovqg PL[}o^X]XSuZu\y<0I{WU3,t@سF90mVX8!ɍ^uuFE{ wqչ&VCUŞ fIP fNlTq]uVj!C"޸=?Y}z,q'fg WQ]<:h~qơճ>?\lCE<돫VW.mT}3ݪQݫWZy~`@1!|zbF/ZmӨ$ٍ2˹`hO:@u@FM4*~ܨg)RC;Wߪn=6ՖvJ06,1|N~0C1|zV,կ]Wݨ,㯫m`C3liT`1b{f;&|6'V=6V]CX܁3 ko^1t ::zouuj!ClRu23e1F=tJj[C+'U?:K wb NlTq]uVj!C.:dzTuШPE|^ SViؒm_TGTϨ6`L9R=:w+С{77*ʸՐX)>q{Exi1PEg'Twkok!E5ryujuXsg8(Vm%ﭾVՖ!C0{Y_}gy5K13a=JT?Y^FOMǫWZb3X6`U=huǁtW??w}@uV`v6f<1վ ˘uO8ֻ>hjǫVlTqNm=[=FlTsEꋍ6.~0J9}::2hcsk쌹s0]YĔ瞰/v.s_59y./n>6wm:\X=4o=T]PC`6Tͳ S{zfmyn30jCX6V^Y}Qȓ8mV>ŹU{/p'Kc6CX߭~:q U'nv}fDLƬOoG \uy &D9쬞QR3lOϘoOq~,p}u+r `zr;"OV23i~Nj}DŽg+{sjg%Es1?o{uggB:!j ~#5&`>f})ͻ] ޘM(VVV>teY]֏a|֐XUn>r'wOacֿ?Y,r `{]uCY ELøR4E}?f)`ck 0ty\Տa|fDE׍YfT=|{?ۨ,vn7f)̂lSKp2ȔRј{6FuA9쨞ZW=x(Rr h9[߭ڻaNjBx}Ƭ_5yRx1nU4,p1r U㖿ZP=ASQuNuYXUγl~y;?%OaˤXKW}w 7sluIuAX4fpYL1S i9g`W E=1?tLaXW!qluIuAX>5fտ[ڳzƘ{1V(Z3zNΩj"֛Ž?\ٛ.b7,s|NsO9 X˞S;Y_N_cK1_T=pQݴywq'< Qe۪'Uh=S+)`}^o_W=j >O|K+VosFe@1㪯`Qs]Qm⾓ u]ܻUզEd"TŞvNp&_fXRWWm✣n)YiAyv}oTq4]]]iWOTQ-P=z^u2U%`cyS:WU[S,pwNΩ^^4֑WTx泫oxm3 ")֣'UMݕc luIuRBlNѼwWghͽz,r `::sXޭ)s9\Ӫsujǔ\p6\A9}zbuPgήN_cK~6S}cJm:>X=z<:wlNΪN=QI{՛}'x>2A%W}luu1A-I 2]ޟ>Xw"?һ;ICHCIDuMA~).Lm7x`#]Ύ?@ $agG*Xqd=ˀ`J$8R$ӬϽUկ9yy>=>_TwǨڬVթ KZ; h ߭zD3d}c 8T?cc|F\O{Չh`g>\꿩n{Ys'f8^Qrg/.]fZV&/UkTGFӺ #{PK0;1y#gL6j}߫NTGk;j+^S=e gYfe+ju:5A~Zk82b/{= `clձS8ƗlTDu|F0_nw/nU-}GU`cL~إ%cF\O_NTGkqm[|^[{yuh]%y} fϫ9blVjaXȈ`;wVzK1f5f mTDu|F?8˽8q ]H {ajZV&/Uk GF[Y.Wv8^=8ƗlTDu|Fpfv{9"q cmg\Z=`.m&YVGS䗪a##{m?z.-5gToqQ3jZ0RWms`׌c޻t{ujZV&/Uk GF_̥f`6~[_Q-WWp{6c1fMg16hujR0qd^eտshf囶\Zk1f9%sosr>a~:Qgrq@>-׬m(WϨ{\;zbfZNM_5 smy`c-ӫǵG-nmTDu|F ~6?Gv88>X_16hujR0qd^L^8Ac`6cGe IDAT-Ϟ~jZ0RKY.^t`\Nu ~.̈q qzeuiubfZNM_j+ب Q]0;[؁'Vi20^=rEfhZ'̯T' `]zu ,/0'UU #u`ƌcճ~AYVGS 6##a_q1W9FV.UdSWϨΛ95jТ #>[΢KdZk#JuZG4]CwT^P}n`xYuׂ}=sfZ]_U/;}㣶 ;,=9E{ ]ɝ(r>a~:0\;[;~KaGE.tc0kju:5A~a`:2b/;W>M1' WVXt clTDpMc3;Y.0#{O]`Lju:5A~a`:2b/;W>M1' ؝gT_Xtgyب +Չ؉5 el;Y ؑWO>"q eZV&/Uk GFSf/n.0?~c`2mTDu|FTUWOyM6/_V`cjuɆa##8zuugX=}F|7|'96Due>b/0噗U7rwTZ\}zE`<1XjZo_:;vGm~tY)|f.(Ϯ\t/1ng/t`NcWlTu +Չj}^/W.5>8(0H gdZ֪N5 j 'LyꆯvQ h{'Uw,8{F\]0zdue>b/˪<ꥧ|W[߯\) pN8` ju:5A~Zk8a~:Y{o;wU=W?_\5 16hujR0qx^ UWwWOqWn0gK1O6j}Ju:>Z#E.zgu]uVX,7ju:5A~Zk82{,0(|16hujR0qx^Wvx=nЄg>0qEcէxߪzg`;/c`Z'̯T'5blϯ}?wFV9ŹVW=rؗcpPlV˪wp3ӪabzJSz{`_0AQ-WW1K4Q\4[[m}wMS=7.{q j:Z T5 kۧ< S՝ _blZ'̯T'{.W7;ΗTUH]tju:5A~Zk8A~:Y]Y령zɔgnnR=zPRx1`w6hujR0qx^\bOkNyf/9Tz{7w9Vw8cll4 CO_NVGk4TQPk[zwTlGolyIuuzGKSߢD1`v6hujR0qx^շ5 CM;V"}W3~ >]5 U﮾~}`k/^t8F\O_NVGk4p Uq|5wn[dϺzS#շT_U=UUzsmը 뾳zբK41`ju:5A~Zk83;nh]<ƆQONqTa;Β wWT_šEsfZ]_U/k85jFt #ӸΪ-Գ c|zN]v[Bu2~:Zm`n^t]|{n=n;|Zp1`~6꺆ыT'+{wW\}ߌyS8~mտ#݆a͙4V\a+]XVČ b<#ꏶ&acTOliNn~0j:dmZk8Zzc]ﯞ<~՝gw̥fj}Ju:>Zzc\n3hU]\tF]&qW3}W{ϧ,+&=(paZUL_֪f3OLG WYj.h7ϿzLUo?˪SXWV=W8-7 BO_NVGk4[oM}tuCuUuhR3#wxWwήT~m?]7Hϯnëϱ cj:V2A~Z6#[gWϬ>M} !{ё%8s3o>˫̩ ,ʧ_#1{ Wud]P\uhR;vqM  Φ>U6gͣ,zuyEmVձ KZQ׬Z=a|d/0a~:Yl n~aXb~׌uyچ!ym>"Lܱ^۷usmpcZUL_֪fgU&{auM8\={;'|-`޿ϵc+ +;$174|MҏV\^adlKd1z X?1fZn TU{ʭUϪ>ywTϭng{801[VO>6;nwMoL;^] 1du|FwMDu[a ?ۜzou]u޹ o  {gpm1`۬Vc-䗪j:OX.NP]0!jsF-x3\PRrK̮R}i qe܀#Iܫ\eW[2<Ͻ/d}r j[uTu>Pа|wĹM56˫]޺`uҘg|z_C,2s窏VOlrVsܕ Q\#Rmά6N1מ#Λv95ܻ"KȵGWshxՏM:k͚XvWCY0KmQ#߯p[hr3zvVu 뛔Np+ܤbNΓu5ZH9;V'V.ajkufqafi՗&ߗ 8I5x\{{k=[M`O֖ kn[,أmk(xVCbW}zrusTYd撕TϩNh(XTߟT95=Wߙ{UveP=ӹ^r eR,y IDATjkuE{@CSM>ׯz?|VoY{S Tuӕkr `TG}Ŧj{ej&cW9Uu'x;^ڱ _LѷZ{ j6ϽfGPmUk.~z"sWWdhw×qΨU7n?: Zh`jP?f fFTmL-]^=:,c>׭^]auE揝;!c1^u^ѹkwju XG'W"E16[m{͎0Zm6N1r=Y=rՓn?wwέUtX//2{sIzj u\ujCyRPkVTl(mx6iV;V^%lV۪S̵_o(xpu*^-_VoyWu/4d\b9t.y1=LCŸՖ%ZW?P8oWC1{uV\> f꿪ߩWb%Rfs7T[m)ZUGV7VW__^6|M}FuyG;Nhϣ<VgU?@9i(7q~S2DwnubuO/M`]ջ_n(xܯ&׸~`eu#]>^b.1ly5;jk8\VwUTT4w}_;b]/c0e/j`~azwuZuSӧk.o=U4^^V7woJc)eYW߭n[ݱ:=n^Kw8n bvZqb3r `o3[mn(88b!n2^sU/WYsT~~o{GȱzsU>Zf _G.0u`f>bS2DW?ĵ_ZU߬^Tݺgy/sZdaGس#έNүU_w1ly5;jk8\{-qgJ57]۫~"n(yf5Fw1vj\߯:r `_0S5qՖ%{=%tu8eՑkFy/ՋrՍF<܆ ϩn#DN퇿Ccjkv j[q6/aͷJ5F#W/2rvQcjxis:@֯v ]V7j{+Zc ]}zYά^P=bٛWRz|hTTOP@2#w5;SOՍ^7hx萆k|xՎy p^P1}lE7T[W'6 nLLuQlW[hv1/: qf_3}>5W]ջS ս.zAZz1F5l(>}QɆRW4p&5_"uնjs퉎s՗d6SsyFճ/W+p GWέ *S3QsG^mZ=MƘP"ά^=.<8kc0{Dirmu)6VOP8}r `-6ϽfGPa}k1{và W˅#W_YWVhFݯ:4fGէGUW[0Wq`[g2^}U0W;*]V7j{ګj16|/׫nPNrPC׫MKCǘU=sJY`orc'pTS1cV\8Lr `67cVjku[Տ8_ 2W7s7e6<scwW}h%+q>ix88^]u Y`o\31Zq{ݦ:zbU[l:j(6U۫-SKz4ySK1TǏSȲu/VU^=KcT]cuR-vW漆XZwLc,ժTh*ƸK r8@V^#oV۪S̵>6Ω! Em(X)GVY=:xRP}zcur%աcjȺڿO`O>-Wix\(_3QsG^mZ1fZStթc9zy/oxd_W;K`f q~S2Dwyg1 8:}5ISrUW[GKCǘ5c^czCcٛ<Ҭ_{qsӪPm_X?tj"skVߝzFc9{9-T/;3s`Ciȑ5T,Cc\8wq_K8Vs'OW_X]X}k.Y]FuPuՏT7k(We kU_dT,SJ*; eا[  e!w^KX;նjsMW0)g!_42[=v Ypx#'YBӫWTo>?'4~U:t]Ֆj2}] ^C[^1}/aݻWo(A=uzq3Z{ {=Ē"cg::cM 2D#ݸ)gF+2YUn7_u:bSuDώoU<sͫ.<Wu{W^m[c_jxO0TVݾ:kU0PLr k(0|aCVmbIzQ#=LaQ=j53 w4YݥzLC8.o(uÃ4w3w@j1~Iu~?c Gs}z8aĹTm(SCu_?8Lr QWmnxb6T[m)暄/.2wMկO[4<1םhߋxAOW}[Pr<3od>:v :X S]{krEa=Ni:ὗQ74,Lr ɘq~S2D-2w5i euՏnf5 'V]˫߮~x"o|j"{^:6s?y^73n4~wBg[5 {a7T[m)Z/V?[ou%acNZ⹋XIœC{7~e!/] .;Xkɏs# epE_>7'tJzu|{YWqe\1&o::cM=W_^9쭫3wTa솢F+%5߯bΩ3ϽX8^Uu5W qE ["3} C(qF+lPmUk9Pp_Ϊ.^V=cuD"\FCy\bW> e'KqyMo_Vթcqnuʘk`W}Tٖ^^1'TXºjͫաc챫:9PF|o7oi9g}[,پF; PmUkڞ_h5'5h)U=zkO5,˵U~կǫM&)+Ǩ<{ՁܛkF9e::cMjM}Gff5-YºFY~㖨[2,W#<\^InOX E9gGu\a~Ci]:)e#['dnʲv>\8WYmwJc (sTGWg8၀[hάnU>!)SsKXyβ\ny6,{[Tl>Zq^Ls\g\Xm?h v.fgg w֯_ծvstauް:hoSVR>\rFYՖEfX}z^'a)|ϟ{,cj礂߫. W>}b׫^uB缶j\oux Z~nްYXp%]tQg}vsN۷o|_|֭[ npnu[u1t;ܡ~+5dGuE7T[W'VO5]ؐs[C P=[u|e}\sNe;-XW4sWxm4ֶ <`7نW~@bϽsrO5A9k_u9t饗_߻6n~6mG1m3{؃65<)S5)gV4|~-2{?[]3R]m5RZytVKXwn gY-GTon3e+?SݽOv_yNտNgV7~aB笴;VoU/\,,Ӻeǎ/>O'҇?%c5yM~;{߮]{Kh(\͎0a۪S5) (5gU[ysJ=1"1Xsvs}ߞyvs OxB|#G>2Xsf3FTmL-dTG}\g-[9Zu2ΛV_ºWUop^wu~a0)o^mL'ms3DWnz`˲k׮7u׽K.d3|SꤓNO~r+~>qjlhxjsMl6wY8c^Z2Κ߮5sSe%]{j1׾WI`;U=ogOul'pδmR{7>S=ڵ |=OoΝe׮]~pgu֪f`3S]1j{ej&k:j㕽2oo($\zܘkvU'T?Hs\4]^|'WyP*{D 3-w>RnWݳъa)`я~__jG]tQ3.ՎcGu\Zm6N1פ6|n.>S0?Nixzu1׼z>\nu_~zzU?]wY]Cݽzr?5|~?/WϭQ}}X$N=py[[[~}?S?ս}|;@[nk鳟l۾}{_җtW^ _=܎>w0S}::m@~C::g$߬~??hǴ,/UyES2m?R]P=lsߪ~Ŷ:/.ng_FCu9wU7UUhboc{Ѡ=6*~c^qOTlGq 91uꩧvI'u;a;c;c{ߞ={>__m۶ouUW]Ջ_=t;-5^VgWɯTO^P]3vKT}vEtLc)tG5z9fsTVL _=xݶeս'OOgrV#LCF1=ַ'=i0mYlY}{^ֻrʱ㳟lx+ڽ{:swkٲeSf͚7җ+V >/|jU[7T;Skta`Sm117MuQ޽׾xSnanXmvȯT۪5S><0z-c4$N>U873#.uX,18]wumٲep~vzիZb[ݶ>-oiժUW_}uo{ۦ U[7T;Skڀ]dWO]}>hT#o9K]q zg}v7eO=^򒗴lٲ)ڻowvNT5[m6U;WU[m՚)Z Tk@)wٗu<˧eVTgU\a̳V=jҥ`^:rT /pPN/c|<̎8bַy睷i4°u`~C湛_U}cҥ`ߪ Lq jo۽{ny-_|Z U=vsS7L,W?^=zOu6 2AkΝ}+^ѪUh|+ҽ}ys;j]u`~Cя3/O~x≋ ~LF ovTgTL$}Ѡ}ɽ9zK)yR1Ͻhu&FO-+m4=յsO,qŲ.#,ĕW^ٕW^9oN[F9yΠ>)7yVMUՖj[f&/USqj1]]LKՓec6m4&<~oޞ=4j]u`~C./fݶ.촬j4qF5otkwF+7,TϪV۫ |.`Ν;׾6oa{"5ktΛg?m`l 6U;WU[ 'bue ީAȀ3GW^c ٞy S{Ϋ.~'yթդ~3[kn<e L7^^8nȋGM&oHo={,Bj]u`~Cnh}{WuSִJ.XfզjjKZ3^mucS2q#Ƹ%-0Fգ;_Q^]VJ#Èq L:0QmZFCW}m:uuJFq\]=7[[mΪVN8KmXmvȯT۪5S5mϪ1s eջ}eo4)qvEuz'SbL:0h`sL1kvdF$+<{NF(KF_Wkm:Z98l@2[m6U;W5mVbI1{T_N/y^P}{ղ =׊F%U'<fuց ՎjMG\=Z>F?qէգ?x9yT9 hufcp6V-նj{M8v몏Ur\TmsV]6F sDٛwTh95O͍@q t3պjjGLv|N.NOF##9(mҥf/}\W_5Çq jSs@~Um՚)^ېwduVpCs'Շ8jc9 RmLפl~_ߑYG_}R}:fs۫V,i_.gӪSWg/Ί._箽y3vZ-jPd˗/빏<.fUg7.φjGuFu{MdS}c_Yzjsu.n70j;^e%|:k*}wOk6{^󝾟3FH'R=zAuTզ}/yT[GU<N#{ZK]&`Zha ՎjMLv_}1vU'wp cԨPZ cpȘ6V-նj{MF֯oW1G{yVROu)v8P3Soh4yj&֯W]uvƼՉUc[J'hj-cph6V-նj{Mw^w.^47WgT\8٥68Sk1.8j]u`~C2.{:_ǫK`QP7[m6U;WU[m՚)gV~lsWtErbu d,  b,V4ʸcL:0QmZXhxϪ;yK|aҥcd/ZFY `L IDATV,uؗ#8#rTgTO};겁١fk Up8,Fc`FC\TY}:}Ul}80q X<3պjꊹ|q(ncKGs7N.L`@vm:Z9R8,Fc]`ā{זUQ:fgWxݫi4[ְQէLp0KcZ[]00b'tV?s5؛V/n wY_]3s/FCtuIuy3Kg`j׀):Н2F['O6?W6fc uc 8:Tu v31`Tk 7VW4ájq |uIuyWY@e_8d<:~?燪p2jSk@~uuc{hVVzwUwGvw)W=fQ}t5ջ^Pr彼OFK1jmт!6VW̽=<{ߨ#3A,cd~ܵ7movsF_7oo5RÍq 8VU]@pهW'j}uт}8.}Aե>eWu3ޯ/6и->cO15S.X]1Pu x;U#_W'wI}{yL;V_KnK;Ϭn?^]pbliզj׀)Z T|yՍx_iBPQuyTeܿUVz܏WT. caZ[]00bj~>ǿ4x'hBwH7կ͓ {T6~}m:#!a8NjQ:e%տ/ N|=몟n˪GU0OvE vB16S.X]1`ngzH w~'hw<:uW'WgTx|.~GV۫VRg~iզj׀)7z};zD5Y^c<}m c\7V/U<ի311S.X]1`0S#ssuFAXN'Tfw5jEuV'՝I1.^W2OvmS@ce:ha>kRfy~]S=Pb9y>\>O[ Z}c.tcAG78NJFSC 8@CL``~cuۃ_CEqًW]V=bc-ct00%KW8PCluZ5 :z&; u|={,^^]\}TxbxCu1+/L p1jmuꊹG醟 ۞2rFuȝ[M؁ceZ8\}r=b1Qz&zBuÈ;_6ry -I@9|֫ jcj_ M]goݯzuu}՛func'O~t^β쫾;lյ旫o.sd=#f-Փ7|҆꺑;]-êT>Yq-'zV}5՛#ۖvpr[.h(z& jC9ƥ-۹Ɣcn1_Vz\UCF<^T}W }z;w*Eu=^?MՏ6,FkRu`#w~W=e^\^c\=]b1m _T͋1j(8oW=yW˾㞞Lv(]֫ jcj_ i\}ՌV{7;wv3FV>Q BwU< JLf}Ov4=UKՁ痫#fzoC|zQ:n2rk9gyjUis>hN^0rYS@9po֫ J6f_V0a{F~gMfC-':~V]!Kg=/c;O"1Y3/Wi(8=:gVu^uhgN9 %*ϭUR}x >w-|}CtcYo(X6f_h(X'N9_~zzCYnpUch #z]cǫT.S;{wE9ݗ[_;1YUKՁ痫C 'ግ{\1󎇧Umq3 ;}ƽWϺsm#;zeuudyF9`WPPvPņRja\éw\=csܜY]Wi(h}ճώ{~umdQ~y8a(bZ8\j(ح[=__}[縹zS_R툝6|S[_>4>8)p4'{_1vÇowPv\] kե-nq%կ1-콮ᙏ7N~~3Tÿsr{u~[չGG;x7*1>tM7mw nzgCA旫4d0ay-ygF9.άޯVk2wU\t3ߙs\=އV]֫ jcņRja\tyf{UkwcT]U=d΍M[q\݈nwGTj(yLCb Nz1yZ3/W5vQBu~#V]4rՏU_3M{y~;jcņRja\rjq5/gT׎է>}lƹVkVe{ZTq~:P<UgPۖh>6rgLh>7oW~׾ ;.VJ'V s]mocꢑ;7T fM9k-ecZTq~:P<}C3F;A͜Z=aUk?R̞SQǜ21ebژa~TcZ0y^uY6Gy_Vok9})enwN^|#⋷;I3wYY][a~zJuiuÄNfh['2彿{/U&gSQues[ |r 99眳1 Vd~Tc$Iӝ\ά^;rpG/#;E'ꇫ 匽 !Ϭ>֪ա^8TsiuYfgO{]ihsTWV܍r `7]T3/6jV :`uȝh2)QwJ3fql߹sG%*αV-5{ruXά^7rpdيX}OuIu[CyZCo0Ta#ʞQ$ e+ „NDWW lko~}gUՏVdvouE[o.QF9UKՁ痫C l4Aq} _OUf=zOCvyT۫UVlcO1jbژa~TcZ0nwF;T/ NqzuȝOU ,ToKe_SJ۔ r `[3/W5J6ry 'WW8rYf{yIՇAǯnPvRm0PZ-Lk‘;$Wsck;U(?zqIP&kR5,W5NDo.0{f~zl]&TϞ ,To0 P6 e+ „Ǫ_n5ՃGqIuhƫܹ;^R=~TL9V-Uf_5kh^SIuygGޱoWxuŜsztuzN9GZo(X6f_l(xX&u<]X=W]YzQ濾z;ntnOՑ&s_}k R=֪աb5x[?.o(WWyϥ %G\UP]V=z;n?>\P1#m$@9ΰPvRm0PZ-Lkmuau&s~#︴ c;\V7r `gY3/W5vG71Նo;Ubǫ+v;zCJ1bC)j0ay|ب^0S7bH3$D9εV-Uf_5Och.i(/sUuY`Q7]T3/6NV 暗woVy;U|ǫMv$ZTq~:P<}lT/Xszf)N`Xo(Xi(bCj0ay.nq篪,]V7r]d9^Ne_=zE`ڻmzgumC)fTV7Lk^QMfϮ>Xzui7^1r?ʤVV=q^;/LWT+ „pMfPRuSȉsPki#vT?P|kƆ;˫lbj(xY_T\I)֪աb{ O>XlgLUܹy,tJꏫwW?^9szQ_UOÙ"1vjcņRja\rzzuau&~zY]bG e$Nx5 `QpX3/W5v3>`9޿;Gncy:zS-ŸV^Ph L9ebژa~TcZ0׼^]XݺoϯsUu3ӷ7T6ZuEmqĴV-Ug_5k7<[2s+F|a'7 ﭞAr zZ6f_l(X&5/WV;n]N19?/{TRyCzT꺆r'V7Vx;r jRm0PZ-Lk^YMs^T='wT88UiC;2]՛St'D9k::8ru`D;'ʲUWt GWW_K// yrv1'jژa~TcZ0NF?DY՛=[\ua%Jm( Qf^U=a LL95^]q~:T]0Y#fuuTA׫3{[}몽! P]WjcņRja\T?7e-xi[buaofs9S=5Cq~:T]0Y֪?2H\qNY?;?8e`cpoWjcņja\Ӿ箞4x6{{5s2Gw3g(̓r f::8ru`D, zAF껶e+޶/N9A>Įn B g (йvY! 6 +0w6B-Ѡ,,B0XB.9w4ЂHiU<˖#w2(v7ҝs>L3s~w7'Gljٸ`ļ!𮩽cM_1އT}꾓9C>ꯜ!q ZW;zz`wN~xP}1qoC Ǿ~c&%QljY k;U_}1USuwP }'̱|Wp1j_TOTLv{wl|߯^]z|NT}0VW73N{Glrw'@۵veu0b?oj{&$@[|G?u~Y]7yիwT_pozm~!]=T6q6bSN7r좓B OTՏWw}bOƗUaMkFlnfLcF>u8am[-yCTcU3]'4-~V[lW}koU/W?{߭Qz]=T69ƋG>r0q NҺکF]t2US|u'4D0lU7OF5bo}o?s{͈[=^=8I>r'pcpҶn1g :VS!U?Tf >m{X}}gU3}+yC[[r0!q vEDd߯V/~pJ{F_~{oc ?׌xֽ?X11)`JjZV#ƪg»ofusGg?UݍSlRw{܊c\ uS/'&~Y-QG5^3b{oxpC0㨾߾pF18-jZV#ƪg»zӫUO諭YGؿ毣QzC[O;;>!1?3&i[W;zz`W4CUꓫQ=xdz=s{͈/T73wp_;3vYi[6/f QuMLz{G-ߩ^\}fIՇTxǟ9vUݨ^YmKo~痞cGWwa,kd\_T_zw_S#l7{NݏU;? A﮾U;»?mjYbj6]Q zzE!ۻW?^q Fb]4>䰨8~nd`j\dۆŲ:WjU&봼!q+}эՓ=]}it1 N7rݿz1, eu0b?6ժMxPhtQ=XzY{?88Q\6j_T a{[!58r IDAT%գ7mSgw'EhXV#jSلwMeUf#qzzy}g| 'Dl]T{#jxWU" Ow 8ݶ!vFզZU :i慨T NcpXW;ok\U?{ϿztN8wmCbYϫMfuRW}u-6PPuķU.ƿNvE8Y}a;|]Mz}Չ jXV#jS.*qRk]|QKOݟS7Qk0q tj_T amV^_D)|GVzclw{6.Tj6]D쾤!\էW}{7W/v#'HuS/]t|OVSCv/^]bV_X|7U?YfmGꋏsfoXTTCjxuu0uVz}ՇgzǸw~yYԺکF~CX/U?r}x7'3cs6.Tj6]k/)=wT/~ 9![W;okWzIկO 9%lb`~^mU5x_՟x/o'L.(q 8uS/yUR7|VUxɞ EtYж!vfզ!`׫ëO>'_lc>Onߺ@E NxIyWn>.m[-yVl»\vEKKNζ!vFզZU 3#'o]T{#j!8LcXV#jSلwijo~Q75mb`~^mU5.8uS/\Hp eu0b?6ժMxLBƺکF~CX. q 8;ۆŲ:WjU& N8uS/kp>lb`~^mU5.8q 8_N7rpcm],yVl»1ZW;ok ۶!vFզZU Q1bXW;okcXV#jSلwsg]T{#j!J.mCbYϫMf8\lj_T a 8\|ۆŲ:WjU& 1YW;ok$1r6.Tj6]ܡ1rZW;okkXV#jSلwpo]T{#j!"wmCbYϫMf%'wuS/8y eu0b?6ժMx8ܹN7r08ٶ eu0b?6ժMx8PvE$~x\7v{Qp&u.t Ea乿71·VipSWj{]Xn0Go{g6h ~x,'='QV$8_nu0~:jFT^p?_@o7}A?;Q#p]LŀjU#jY=G9?Uwdpic_j:xW k#YN:pu27ՏNj \qŴZ ߩY=.~kO_ 8}o7˯IՓ׺?&rnu0~:jꏗӁ?>ڽ4kѤ~w>~xc.7q UbZ-Tլq\9:|wި;dAouw rR'wxnu0~:j`ш2NƨZvwI"p1]LŀjU#+=zɯm$q0FŧrM"pͫ`^u*޲wi Uwֽ1.VipSWj{]p-'gx0F[5ǼڭUGKzzoNQƨ@j;nV;~5Gʤ_x՗\~y[ ߫Z5"ZV6=w6=18+ǭbj1~گf`I"[}!YWUX.gwf;kj`[.bN_ͪw|^ɦw|I}fO71ؤy[ ߫Z5ܛ g4ۯ `1شVipSWj{]ʖ['D(&g4[̛Hb^VVa 8߃M/>՗\'ǭbj1~گfഖսM;\-Gj:xW kt j18[.bN_ͪwKyzo6㗞Ļ'Û=Ļ1lzwEK:|r.''`D\FմZ ߩY=. 8Zvw'Ll^]n߫wG[T_kV_;8ݣF5wjVm iY;w9q yu=~:mWrYo}cp9wM7{zxq GՍjZ-TլqW_x\{p1zwE\z˭gk< 1U7ipSWj{]\RAȯ[}9;N$m^]n߫wG[e;#?`q uմZ ߩY=..1ոJۼ^xWUKݫO_Hx)|մZ ߩY=..>ٓ͞l%'W׫p)L=w99gHyTݨbN_ͪwp-NXLNܭewOM3WUGջ-z~/>՗k~&c{TݨbN_ͪwp,{k~5?cͫ{Qhp<W%QuV;~5GU߬qO_zu{^u*{=^'x֤>~xxcOo:8k^]n߫>oXV^w1`1`|Z.bNa5qr|W>X1̫{\r^^S~k_uUbZ-TVwae4ߝotݛA6c^]n߫>oi{gA6QŴZ ߩ>f[#`C[\l--"7WU kp՗cXq 8]LŀjV5.ֲ߹3ue^]n߫>oXN:|^ଉcUbZ-TVwp޽ꛁO_{^8_zu{^ygxY otMBηGbj1~Uo Mpr2`Ʀ̫V7~g~u4.F{{GtAk?wLտoKտ~!~y_ߟݞkUp{8Ƕ6=xiGM`<^xUp1=nTj-_9O<^WdWw?~oCX~ɿo~^甿8Ǿ# Qug!gN˼{w:=!{HHŽȢ슠(((.826̈< *ƪ Bd[d_HBtwz 0䜤NU'|woUTeqgPXs s`gJ(~sL^9 ;*o^^<PJ<qV%RWQmHSZݫ1+-uvTYRy1Ȍr 2( 31Ȍr ._;N9@W+u( 31Ȍr 2( 31Ȍr 2( 3yG~׼⎩lr SW?^-H}U:zri21U7ĺͫbI'K/,u<HE9tUqߏwz04 <4.8?"OKS]@.rq;o83U5~\Y18u%,xO..8ULزns#"$`G).c?x2>pq?aҚ9]\YnP1Ͼ3JJ_eR%9-ٞA(,Pb̎֎-]9=4PX1ĚZ7#_^94PX1 _֍fz)~7@a(.`9ߜ݂Ƨn=;Z[ vr "&ϻ/ 9F>;gdݼ%*Jgƕ}5=bU’)iˆRԔc@fxdݥ;}) 31Ȍr 2( 31Ȍr 2T:$USY{?=n Q[-j#hnͭcxeXn~,Y7/Z[J}ԯwL _uUѽglͭuSl\W̎RGRu?{ѭ!*˫[uC5E[ǖ7UƚMc9iu*bpϽcDcHQѽgUGuemlZ6DSXvn,\=#9/u.G =*{uݣ&MbcX~A,]0_s5ʫc߽QFϺP+֍7.e^Ek33YL9]VMe]38tqccQ+Oux~cI1y}22R^Vnj>=}FLuj 9"WߧGf1ǘvQǎ>#~\4ݭ]ym_RtjX~aajz^GsZIQ_#٦MݱzӲ v=uUqg#N{?r\kČeO KS 'u+Q'6qڸ AGEYesMcЌC3P]zD7AC#]4&<9:8as޴eCL3/?Rko˿_q z۟~2ڂ^sG4wxa3=7>?DgssG<%~py#T|ҽg}ȥqٞS sϹ76gkw4xQƾ'+jwzxz^6g?x㬃/靯uWܚ {5>8q̹QSYWk7C 1m^{k>pg❇}׼G͙~GrM}EQY^SZ[xsMĺc/ ЅU:DD-3.9W$:c_GgߓپW{,5j{ziUWG\/9\e],WxA|7xݼMg{)gG"|vqcَ^q!ه\ϼpbʼde9;Gm=Y+KL\r VUUQwG<%TOF:M[6d "s#o&nק^ZF䅸\JcWq>t*˕G{\{Q[٭(;0'ǧco ><-/1qԩ{\y\[}ճo|_\Qe#ۯxu~o?b>e(!Fů?45b:fqEw{R`ԫ_|]Ƅߚ\Gg{uڵQ++N}Mo-닾gw*{:vxr1w h͜شeCljY5={Mhz OqC&r\58jiq7z-;t]]UEM|] Sݹ\.>֯EϺqW}Ψϟy}yRkjϼpdJzV4M-룶>j*bpOqbd_{|1>lOJ_A?z(qC&u{|3#1H IDAT(1gNi7Ǧ-igxϑA=Nt#+~ur޴,q=[Cg6S ig%\YTW^=Ű>ACFuU:c_O|<Or;ЌmO|/-~<:Εb߽_ritnؙ{{G}_qiLw_\t,\3+66[>+jbH}bT'ājqTh\LvgNt{ߟcܐ_Roo-͉ _{]G|$j+%:މA=Gg{Aw%WJ=ߟB:fqO2kQ@Q _\_AZ7?y]ttؼ6nrmG:.; Q]Qs#?o_+7j7֘+ 8qI_M}nqߎ?psluS߫*j/*FىN+Of\{U[BioјԻ:1s31s3q#'|)=c[-arec<:yw|:7m~9\N2zy앧|3歜O U_7Mv\uڵq$:wsw_)yhcɺy۝kTS_ӿ#՟_R<ñ`ՌXiy4ʺY'zvCz#zwD'}%o wcP4z_2)L4xܸsbvjo[GkWcʼ}v o\ɤgG;?"EsQvrF_Ok//TL6yNkd<""bƥq#O^W8+^y7yygLyvDuEm\w_ch\W|F]N]iy| QmףN9Xye|ߩ_y"k<~ѩ1N8}Eox|K{s1S?Ek$~EG_g8kʺЉ_g(+u uUܕcqٍt1ktj\cbEDzo^pGTW,îg]߸7EEYe37-qB4۩1^3w>sC\bњogN~TWts'w?}*xԅk6-/uw'G83#"bw].:=1\.rw:Fh~ͦqůOW<˲ /~}Z4nL4o9v_:gѿk7x' a9q/NgFƥ}`{ eq;o\_۸eʵǾYgU[2۱ݭ׹L^E[Έ k wĕ񆂌s,zt|:qDS[ϊe^.xI3wU/{ts%}.j{?3R[ִ%+7LUyuse;( szY8D?w~ V6.,ς3";PٙzR=ζsbϲf[Ί浯{|# kgTWŗ먭h%S{|&T_y"n|䫙|oF/Z3E#ĢZP|_X.Q];c'}%ɘbZfyn}VvO~fYvԀC>~W~>/]0 #O~S`r 2տaH|xgSݟawMȬ?&GCm u-޷'})^X2%DK{Cy@WO4u׿D{g[ƩߎkW0vq'ol=Ժ1LN)3*//scTU&bq׳?8Up4mN4;qԩ]ϋgORAw<Ѹ1爂fl1U]=nڲ!~d蟮hhM4۷~`|_8Qމ='_zf3L}L}Kl۞^,\=3D?x Eiߎ\.xO0f-6D*n^ 1M4ۙêKI<ѷ~-r&J,_Zߴ:z"I}<R@fxXy?ERkƟ_9ه\C{a&.xe3ٖQ|GQv|9*ͶįouM9dqqco?e[/FQvQW='x]1{&z[|77'kTr'1?XvNC&f3) 39+vtO~?DotD>O4[QVzbq :m@Ekfǣ0-\=3|鎢KbCO<ﴛb&zsm)ޮ\jgoƖuy;^Vm_N2zK<k3LFoO<ɱ0͎YѸ8썙`Ra`OLw`?1XѸ8Donҧϟ:1ahJ]$oat;3J~􏋺o{wԧDD/ vsvw{ cGx>oAhK{s;Eݹ-U#?x~ڹG3L~kGf&?|g#x7&zsSL*IʛQ'޺ymx9-~MIE!M]0)[%?}EI'=2=Ͻ{+4D9WUQg8|g3}OYw?ˢ,WQ:m܅[XiYFi.xl͜q&|3LmNjfh^Y7?_xÐ?  j(Mwv[8/2JKKrA=G>ǥ:3Jmw{g]8|$/dE1zO0MrSP$T%=fؓ(}zg^~$$M]Ti㮠O^wRy:VH_yd_գO21ՙg>mhlYR{5|c˺l%ǏmQ+O<’)ڱ%D6kٳ''Rlن* ;T3=Qf-{6I<3~>Dw'E.K<ߙ({jXqi>dh\Ve(9NޥQtF+Bwfff,{d_h%If'<9nx䚌$7E۵l}rڪn&$eÐsD3s?M4V˰OqQ_#41nT΍I5JaǤ=Y{|Ίi%IfњّKaǥ/u+R}.F.Ku.p-=#eGK/&L 뛒cDDpPFI){%߹_? @AoT7,(IzRw@FIg]Q'ՙmX݃{TgmPQ*LW7;gܚˣ)4ɭۜ#m1P5n*-EQQ^4 M5iuFI[ߔ H\~>vӊ Ӵecvh̆5$)8q̹Ef!z#+g$u)?Qd]Ѧ(>Tˆ%I/lGJJ]5+)ަ֍11Lu|>-mMic\) `Jy>߁M(MriloQdZwu@r ,W}Ju+-(R[D(6\/yk=]9FK{S#Q6_tx2J CJ'(+uv5Q+Ou })xڢE.4U[-PldkS ٞ&=U Z[J[u.S A9S]QLW* hnݔj>EUEMFir޻f=5+V[gS>%۽#"}(`v(k|}ĎeّRhWJ&{磥94lO{|O#vϷrQ[խ1vk1(g::3Hc;R( IAG#$\=ѹf+}6ʎ|.쪺Uu/uZEhikN}[IvLme,[?hK{K35u$u|rQUQ٬S(M6n,uݚr fG"Vէ>֔AkhM}+wвrhkߒj ʖG7,?+S@"+Jm/oh׎|udw5kl^A˞.޴2ʮQQuꌒS@wEc˺hLWe6(IoZLWxJY~gkVhm^۵iue9Y^kS~UWe]]Y{Yٸ$|WgVo\LWxU7l5u$!(e~2+]r jņt]fI]M""kzf$nUK;QW]{)^+$OYӳoFI)^Y7?%Io@tY>׮le+њ^=e&R,] |3dY~a3]/eJ8lr jgR14r۫*jORI\ζxyͬTgQkzlƖtTgٞjtr3=j>z( UѿaHFikdrTgvr+^H5?~%InP%ݟk=7,M[6:3x.r1g UmS{TFiؕ)z)ZښRwAInS76%ke4f/.>EY0>%ҧR͏NyQ8ϧ:3ğMDuEmw;bֲgSާ9t=1(ζxrI.miyG>) jU1_?e}GeҰ=SNJ5_?F[祻txFIؕU:Ϲ7adztܛQҙhlY 59th Sm>EUEMIvjΊiqIoh6F?qma1m<IMz]W|wIk&Gޙh4ܠ݇~2t,(M}{J;we:w[Qޛ^`5ΌzfHr +ˬSK9At,˔9)0QZV4x}YGj'/)yME=SG2zº[2s;"=ժ̘?A){o~xvY`k[.mzg&~1/O<>cP\K٩o&ZG<祶D-OA^M}*+s7۾R>L7v84ֻ{y[RU5ںclЙcvrAiVnH-F{8Q IDAT7չ葉ٶs6-[q'o +) 1(̭cțs_+y]{{Eye>%Ͽ6?uָ{e-0{qsfWs+y~z[g6͔˲j^CXmHS^KYYY++3ɨq7<._M6 Lj9~ׯ;j^n}F9@sQ@ajj{ih9p&z÷BZl]]]~qWS[W[pנ'd]w#TƸ?iO<_Y`ztOKyD9;9g4ۻ9ߧ,pwUޱwIV:' )y~p.e|gv:;lu{[V]ڬr/y?l{c倭w扉X^S ~v{lAy}_/3v%VO1];,8;~mTw7'oLC9dvy㑩^=6;8QayK]^S^xV+,Ok5wStf-7$eM]jjs?Ҥ{F$f; 5:WX )?ԟYV4?2Vrk%ϗCޛIshycv{6tך7o<*N+i~PMsMǶ] յ}\|gxsvOnyBA1hcx(?O[^,eM,e9cٝoKӞl k%i?|/-];\m+\pQpӗ7[M6˱ym %i~s}u`g3wcodʜW|9J(O!'~N[sw(~5X2mhTdœJ.g\󅧲}dyYyN⤇Wsϋ'~${FK/W;4 |tػ3uuu=?=vfF?bsu͠t[gi{v\/tt3W4:޼gg-۞2-=ں++/Cρ#tni|Ů?slvo.>r5eΫMGtA~s¨lku[/7x_yYEvhNbv]TU/iu6vݓ|7]]_+SN\\Ƕ͆{d9&u[+3ɿ-UK"xVI9>M2`61%b$SsثJ>Sopq&T'ܑ/5o rqwgp-}[/gYYX?Y]H.g@MVP[Wsn>&xC4Tz>Gf&g~9J>SWW'_?%O/S漺R{g;gĠ]ߖGgn7]q??2`K:K[7~:'Ѡly[ ӱm& f\܅G5{nvH3$zo7^>|c*[:N<f>)}(LN}s̎g\0%o/E,t#:+>|Oeu~]7k|=cnVwim+eh:[S[so9k9gԫΗ GxfIgэGLNȔ'd^m:kڡGzvL|cRUgevwNjd~;ҭCFѵ}l5`F>q~.X[<;yt>{74fP9isr$yiւ7xقIv]ҵCt+];#_Ќlдcb^x툜r6'Au~]qںexԷj߷.[tNNzd×1k[7G^Y5\kw\7n2Mk uk|ߺ腩Kˏ>F4ƫϙ< l;[W},9esӱmѳSԷI1\$ZVyKm9mW>HN)(]g?2Q^G툵?=<uɖfͺ{˶c5#a\YV]u s]k1eEmMyyŸS[3<}LNfߜc"1hNy8N9}ĤQ+lW]]]xoNj]k]6zErѨoR,2eΫ>ӯ'%?_s3wB=;yt'? 3+t׺dEmM.9כ厱WgEmMx||5kV:SQVQ}Edl 䜛?#.<>sE#;%:_yb}aZ:d%'zolxp35~ ߖGg-NyYW["/O&xCy̘?fAepͺ_f̟G^{;Uv[C;%[nc?^jIm!KjV;߷m聍ڵ*ɨnj;SQ^֡9m͹s5?slo rv'}Ϝ3'/ǬvO^ö=xx&5يҩ]!ˣF{}k|M[rK7e iiTVi}IfYn{jva3$z^ʂhJoH澴ʧ}$@+ŞP:sn1hl5` im/ͼ%3}:wbx{|󱌝p/[BK}}3b٨l{/[P57o/I_ʤ'ĤQ^B[^YʲqznlA٠UK2kͼ:L16cx(o}xl{Xvm6y61&5YT5?Mk^̤eiO]zUq|IrLzCޕfWY&.#}Ȁw?{t꛶>0*o/Nϔ92qK?cCJ9ˋ=`ݡ6mӡM$ɒRS[‰XӾCjjdVԮk**$/̊ښNǧzIX$wѷfJO|{e9Mtm߿9r WeK5QbyW,o`yMUTte Z:(ӣSߒ檪IjnKG-X%c\0$x1jS6w߰٩s'O9Bz IyYEI/OSph< mgM{$fc@+-i.Ͻhi*[:O|u{^u %?;޷qӟۋfO9Zl=h<~3{HzF4LyKf h8ں1Q&hm+mO.i wfڼI'5g-JN}[d;4~^pXs1}V;{vvvIvwt`)8~eޛ7۾kҹ]ήɯz35V}צmEw,9l^%d MD9°ͯ+u+lG9ﰫs'4ѹr },`m=GrVǖ4?y΄|S[ɳ@Q*[:vCէ<^k0gFm69nf-?򲊒Z85_f]2{v@sS%(/+[>1z!ݜqӞk^ȴy{O|]fgp =q9dk@P 4wzI,Y|QjVT]ܮ[uowo9. 5Ethv1ze3j)/X{ڷ7jPZ|Q~qW/k{ttK2oeO6fNk~]mO[=tɭ\8/o-qZ7$s_ZӾ_bOXw(549'iusr۳MO&Sڬ1^^ Єt6wɈAfޛIwhFpgFO3'ܑ5UMz?D9ˋ=`ݡ TY&{lA=7͠^C3צst3uK6ҾMtm=/[fƼ72kL;1=ԹSz.1^-5ՙL~{|2zg۷阚թntz)&VU#@QXw)01(r (01(r (01(r (01(r (01(r (01(r (01(r (01(r (01(r (01(r (01(r Tt(Xw%c=uQ$ H2'Ɍ$33c(K2<ɞIv|%҈jNI$SK\I&&mYYݓ|:Ik{+ x%y!cIF%y0& &96W oݝۗ$y"e\dj3VM9k}2_*IvymLc{(`m)oA)`ma; oII^HzyI$NF$&<ɰ$](1D9&IF%Ԁ3uILrmILrx$[:ԣW{Ұb;lcIHË1&rdHI{ IDAT'n=֪3?]}ĵz-Phkqdu&];jkIMnMu`QOTw6`K'U'4)`GVje;)|N>!6,;075Q9q N0G_}c}}tqs&7oOmhR UeܻzМT)ZJbkx%ջ7y-As}TӽO.~D&UpHmvsV;ݿn\}}3V ׮Q=T]PIv\sHu֜ϭڷx}y'U/lqW}eMNY`KkTVO6.E:|yOV?}YU_~cT_=\uPՉU-9՛uFeߴz[7x^Z}%?oU׸}W4yد&,6A.?ֱT_=oU'WgUg:Im۽>z~窷^x`WugX{v˚\V?ߥHU3Y[=s=^8Z{ g?:եK8۷׽默=kuukU7q[v׀5TZФ$dgn\=OQSon/n[qߪ>Zg g3׫?3syjunAMJ/nӤfg+x؃(aR3`_N&e~Gc^԰TOZmrիw^R9V7)2d@ΝZSu w1sa&&;~hu&e"pnM NھrQ]J<դfPf+8mWw1{>M k~g;3>Sx`>=`IczWO^E;~գ5)^X?6`S;Qk8Ś7Vo>o\{ʭX q5Ն<W[}t{lT\k{q&o\3OTwb{-뫟M^76}yMJ4VݬI#3{MJ>7 ǫ `k`PiߌܵWWս,ܰbVc\VU?rê챽:j_z~uWbF|&%WꀁkX_j }\?T?_}jmjwƽC(ZOꧫꧪ FW~I9ŴSdo٤ bO4ByO+jud=\=mG\wWyk_XfZ'UnRteuD9حH~Z2R`IQȴ>Wv,Q6R7?T ?r3̟P{,W+e : vS^Vd,; 81MBjHYv=솭կ +SXAm׭;p T[y0}1`տeWp7V0Fʲ3>9`qG2wUm~/}U=I˴>V}d,; >!b~cwOU70q_ݷzXAf 8cH9~M n%}`(oW?Ri}dĽ7pMΛ-AӅ 8+3/kgc^nIBVi*yPuW玔eZiC_R}`,8󷨎)PkR2Z fO=F),~ر;JaN.aV{du9C?c}bJjq/M+fCkYj#Vs_؍}X5R!>c4Z f:bdJT'U\bL9\ \1#!P87G2"10j5\~>h{-;#Ο]>FpA:tdT_^y wϮN#|ocU -I!VТ{.1pͲ1BqрM`w8QR rL9\՝Ο:Jp2u[y.P8p*S|X.9 +bha*]Y[?F)-cc>j|.W%R2ÚUX^UMEKzM0KY^XfX ?._vK9l3K9*Bҵ%sO5K9*}.R;Úb5l1R<43Y{(%3fҁkf),E=̗QK1` gXsSc,Y.{ ̰f>\C)mf)Gؓo5F[b/;˥jh9 :{;d[U\3ueU#Z,r OW PHYmkk#@w8z1>4Úyݒ`ɔcU]R{#">p.fh7)ꔁk#s%R?lh!ĽGI1wwx -8os>燾ްl8!#Ֆ% P?8Ri=ڼAՇg0_uΙ]zHY`,o8du1L% Q?5Ri=`ϟX5ޯ83335Յ#e:c~M>% Q7ߏb:wn7ӰboXA<=bֆΰ .0p#.ܨ`(H󏨮=R]y.Zq7zêvYݼzV_' eMJ26#eّV|&+H9د =zH9v:գv1kyKo^^}ttI]=m,('V1`~cj8q;WO s59]FruT]g3^myUhW/;s|ro#eE{Nu#syHW:vOrv ՖTkqyk9^̜3\횼>zMÞiyO}IջV0ꏛ,+Sn]\&O܀5?^פcSMq +L€o X3myT]WW]@}/;x^27y/,) #w_W}:::RݶǦ&e#4)Qqս wXGvM;{Lo=XԤH&{=܄~Ho5"]\=zvmkN Mnnui_I][͐u/69?\S|WͰ& 'W0WWOmRJqY{˹Cl^X}zuukT[{˹M:^>=a=|7Uw[RsXa{-;c[Uݡzے2:c\{\U,)TO1VxZqucp51`ZWݫzǂP}.UujjRm\w&9&BA6-;.>-~MJ/kR|yQuFKŋ _Z{bUhr=6WWVv-ΩNZ}yaOUkW_u|/kNurY\{3 SuNu k`y˖gk=ڡfO-K- IDAT̞6Z F90 R411aʹoT3ˡ>ՏO91<-{Wϯ.^pj)g9fgevwx3]aSmN3ˣLG/㧜HuYX"gwյx1-1\1nP=ziuؒ0U\ϜrMc`6-; :շ619{_^sV{T?1 K`϶:IƱ+=wfb SxS~zňYX1,#cUW3wFuE`I^^}گz}u)f/P]2j"N9:zhulygVO15[&}gsPwW8^Vr gSuduL^mr}RpMsC/mW?>玐`}R]=:<gVO15mV/^X7=~zzk>[=x(XMÛYK[W?T1acux06,)˙}t>kêWn} g&לT}b1f:mfB1;sfk+;iUtW?ΨNU]>r^1:meGTc0/,;{.;:mfc+K96VqDavM1Ҕct[VyiQ<5aQmXj(5bl^jݧX7c{m+8ڰDXWc{-m+ؼ4P;1yM06,7ʨcr `eXXY[/^~{IjkuPekc.;zX)_~:uمNt6.;@9t_X}KuN<);V.8j*ؘ;V2 czY [-ls b% sL耉;y|3<pX޻ua}WA {߭h;>Y}\~\yx[yv??񇵗Z߮Am w?3ՓxS_=WYyI'K)q ঻ΪW?T~S}^2goeui_Uգz]ɪ[&q 6ΖTmCa7@pc6eui_ e<[nq ஺ΖTmCa7@nG.eNm(Qu/ `clMu6q\{g42k^|j:jxT_o"$q 7sQ-چ2=)q wY/sZWGmC+$q sQ-T.r:T_T]u}@ p\V/g^z^?N=_kolMuP/s̹ϓ^f:~R_e+~I슟 ^^jMuP/s7?v>c|j:jxT,m2A2,^IEcGuYU{y5:^vQ-T.rUN/aGY.ճauz\=^mY{k/lMuP/srq :_گچ2~#{.e6Ճk.Nweb%k^`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1`8c1#q ƈc0F1hl7sl\{rצ Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8dWy IDATq 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8q 2d1Ȉc #@F8 t9~ ^sq_9XcI?vzK8q 2d>zŵcIENDB`oidc-agent-4.2.6/gitbook/user.md0000644000175000017500000000070614120404223016034 0ustar marcusmarcus# User Guide Using oidc-agent is made as easy as possible. In case you are lost, oidc-agent and relating components provide a lot of information with their 'help' command, just call `oidc-agent --help` or refer to this documentation. We will describe all options for each component in detail. You can also check [tips](tips.md) for some useful use cases. If you need help with a specific provider please refer to [this section](provider/provider.md). oidc-agent-4.2.6/gitbook/agent-clients.md0000644000175000017500000000101314167074355017627 0ustar marcusmarcus# Other agent clients Any application that needs an access token can use on of the provided [`libraries`](api/api.md) or our [IPC-API](api/api-ipc.md) to obtain an access token from oidc-agent. The following applications are already integrated with oidc-agent: - [wattson](https://github.com/indigo-dc/wattson) - [orchent](https://github.com/indigo-dc/orchent) - [UNICORE command line client](https://www.unicore.eu) - [feudalSSH](https://git.scc.kit.edu/feudal/feudalSSH) - [mc_ssh](https://github.com/dianagudu/mc_ssh) oidc-agent-4.2.6/gitbook/api/0000755000175000017500000000000014167074355015326 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/api/api.md0000644000175000017500000000122314120404223016373 0ustar marcusmarcus# API If you want to use `oidc-agent` to easily obtain an access token in your application you can choose between - [liboidc-agent4](api-c.md): Library for the C programming language, - [liboidcagent-go](api-go.md): Library for the Go programming language, - [liboidcagent-py](api-py.md): Library for the Python programming language, - [IPC-API](api-ipc.md): Communicate directly with `oidc-agent`. When you have integrated your application with `oidc-agent` you can contact us at [oidc-agent-contact@lists.kit.edu](mailto:oidc-agent-contact@lists.kit.edu) and we will add your application to the [list of agent clients](oidc-token.md#other-agent-clients). oidc-agent-4.2.6/gitbook/api/api-ipc.md0000644000175000017500000000772314167074355017203 0ustar marcusmarcus## IPC-API Alternatively an application can directly communicate with the oidc-agent through UNIX domain sockets. The socket address can be obtained from the environment variable which is set by the agent (`OIDC_SOCK`). The request has to be sent json encoded. We use a UNIX domain socket of type `SOCK_STREAM`. All Clients should ignore additional fields returned in a response from oidc-agent, if the client does not understand these fields. Vice versa oidc-agent ignores fields that it does not understand. The following fields and values have to be present for the different calls: ### Access Token: #### Request | field | value | Requirement Level | |------------------|----------------------------------------|-------------------| | request | access_token | REQUIRED | | account | <account_shortname> | REQUIRED if 'issuer' not used | | issuer | <issuer_url> | REQUIRED if 'account' not used | | min_valid_period | <min_valid_period> [s] | RECOMMENDED | | application_hint | <application_name> | RECOMMENDED | | scope | <space delimited list of scopes> | OPTIONAL | | audience | <audience for the token> | OPTIONAL | Note that one of the fields `account` and `issuer` has to be present. Use `account` to request an access token for a specific account configuration and `issuer` when you do not know which account configuration should be used but you do know the issuer for which you want to obtain an access token. Do not provide both of these options in the same request. ##### Examples The application `example_application` requests an access token for the account configuration `iam`. The token should be valid for at least 60 seconds and have the scopes `openid profile phone` and the audiences `foo` and `bar`. ``` {"request":"access_token", "account":"iam", "min_valid_period":60, "application_hint":"example_application", "scope":"openid profile phone", "audience": "foo bar"} ``` The application `example_application` requests an access token for the provider `https://example.com/`. There are no guarantees that the token will be valid longer than 0 seconds and it will have all scopes that are available for the used account configuration. ``` {"request":"access_token", "issuer":"https://example.com/", "application_hint":"example_application"} ``` #### Response | field | value | |--------------|----------------| | status | success | | access_token | <access_token> | | issuer | <issuer_url> | | expires_at | <expiration time> | example: ``` {"status":"success", "access_token":"token1234", "issuer":"https:example.com/", "expires_at":1541517118} ``` #### Error Response | field | value | |--------|---------------------| | status | failure | | error | <error_description> | | info | <help_message> | The help message in the `info` key is optionally and therefore might be omitted. example: ``` {"status":"failure", "error":"Account not loaded"} ``` ### List of Accounts: #### Request | field | value | Requirement Level | |------------------|----------------------------------------|-------------------| | request | loaded_accounts | REQUIRED | ##### Examples ``` {"request":"loaded_accounts"} ``` #### Response | field | value | |--------------|----------------| | status | success | | info | <list of loaded accounts> | example: ``` {"status":"success", "info":["kit", "google", "iam"]} ``` #### Error Response | field | value | |--------|---------------------| | status | failure | | error | <error_description> | example: ``` {"status":"failure", "error":"Internal error"} ``` oidc-agent-4.2.6/gitbook/api/api-c.md0000644000175000017500000003663014167074355016651 0ustar marcusmarcus## liboidc-agent4 The C-API provides functions for getting an access token for a specific configuration as well as the associated issuer. These functions are designed for easy usage. The C-API is available as a shared library through the `liboidc-agent4` package. The developement files (i.e. header-files) and the static library are included in the `liboidc-agent-dev` package. The library depends on `libsodium` therefore the `-lsodium` linker flag must be included when linking `liboidc-agent`. If the library was build with `liblist` then `-llist` must be included. If the library was build with `libcjson` then `-lcjson` must be included. On modern distros this is usually the case. ### Requesting an Access Token For an Account Configuration The following functions can be used to obtain an access token for a specific account configuration from `oidc-agent`. If you / your application does not know which account configuration should be used, but you know for which provider you need an access token you can also [request an access token for a provider](#requesting-an-access-token-for-a-provider). #### getAccessToken3 It is recommended to use [`getAgentTokenResponse`](#getagenttokenresponse) instead. ```c char* getAccessToken3(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) ``` This function requests an access token from oidc-agent for the `accountname` account configuration. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `accountname` is the shortname of the account configuration that should be used. - If `min_valid_period` is `0` no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. - If `scope` is `NULL`, the default scopes for that account are used. So usually it is enough to use `NULL`. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. - If `audience` is `NULL`, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns only the access token as a `char*`. To additionally obtain other information use [`getTokenResponse3`](#gettokenresponse3). After usage the return value MUST be freed using `secFree`. On failure `NULL` is returned and `oidc_errno` is set (see [Error Handling](#error-handling)). ##### Example A complete example can look the following: ```c char* token = getAccessToken3(accountname, 60, NULL, "example-app", NULL); if(token == NULL) { oidcagent_perror(); // Additional error handling } else { printf("Access token is: %s\n", token); secFree(token); } ``` #### getAccessToken2 This function is deprecated and should not be used in new applications. Use [`getAccessToken3`](#getaccesstoken3) or [`getAgentTokenResponse`](#getagenttokenresponse) instead. #### getAccessToken This function is deprecated and should not be used in new applications. Use [`getAccessToken3`](#getaccesstoken3) or [`getAgentTokenResponse`](#getagenttokenresponse) instead. #### getAgentTokenResponse ```c struct agent_response getAgentTokenResponse(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) ``` This function requests an access token from oidc-agent for the `accountname` account configuration. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `accountname` is the shortname of the account configuration that should be used. - If `min_valid_period` is `0` no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. - If `scope` is `NULL`, the default scopes for that account are used. So usually it is enough to use `NULL`. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. - If `audience` is `NULL`, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns an `agent_response struct`. The `type` element indicates which type is returned, i.e. if an error occurred. On success the response has a `token_response struct` that contains the requested access token, the url of the issuer that issued the token and the time when the token expires (in seconds since the Epoch, `1970-01-01 00:00:00 +0000 (UTC)`). The values can be accessed the following way: ```c struct agent_response response = getAgentTokenResponse(...); if (response.type == AGENT_RESPONSE_TYPE_TOKEN) { // assert that we actually have a token response struct token_response tok_res = response.token_response; tok_res.token // access token tok_res.issuer // issuer url tok_res.expires_at // expiration time } ``` **After usage the return value MUST be freed using `secFreeAgentResponse`.** On failure `response.type` will be `AGENT_RESPONSE_TYPE_ERROR` and `response.error_response` can be accessed (see [Error Handling](#error-handling)). So applications should check `response.type` before accessing any of the token response values. ##### Example A complete example can look the following: ```c struct agent_response response = getAgentTokenResponse(accountname, 60, NULL, "example-app", NULL); if(response.type == AGENT_RESPONSE_TYPE_ERROR) { oidcagent_printErrorResponse(response.error_response); // Additional error handling } else { struct token_response tok_res = response.token_response printf("Access token is: %s\n", tok_res.token); printf("Issuer url is: %s\n", tok_res.issuer); printf("Token expires at: %lu\n", tok_res.expires_at); } secFreeAgentResponse(response); ``` #### getTokenResponse3 This function is deprecated and should not be used in new applications. Use [`getAgentTokenResponse`](#getagenttokenresponse) instead. #### getTokenResponse This function is deprecated and should not be used in new applications. Use [`getAgentTokenResponse`](#getagenttokenresponse) instead. ### Requesting an Access Token For a Provider The `getAccessTokenForIssuer3` and `getAgentTokenResponseForIssuer` methods can be used to obtain an access token for a specific OpenID Provider (issuer). This is useful for applications that only work with a specific provider and therefore know the issuer for which they need an access token, but do not require the user to provide an account configuration shortname. #### getAccessTokenForIssuer3 ```c char* getAccessTokenForIssuer3(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) ``` This function requests an access token from oidc-agent for the provider with `issuer_url`. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `issuer_url` is the issuer url of the provider for which an access token should be obtained. - If `min_valid_period` is `0` no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. - If `scope` is `NULL`, the default scopes for that account are used. So usually it is enough to use `NULL`. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. - If `audience` is `NULL`, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns only the access token as a `char*`. To additionally obtain other information use [`getTokenResponseForIssuer3`](#gettokenresponseforissuer3). After usage the return value MUST be freed using `secFree`. On failure `NULL` is returned and `oidc_errno` is set (see [Error Handling](#error-handling)). ##### Example A complete example can look the following: ```c char* token = getAccessTokenForIssuer3("https://example.com/", 60, NULL, "example-app", NULL); if(token == NULL) { oidcagent_perror(); // Additional error handling } else { printf("Access token is: %s\n", token); secFree(token); } ``` #### getAccessTokenForIssuer This function is deprecated and should not be used in new applications. Use [`getAccessTokenForIssuer3`](#getaccesstokenforissuer3) or [`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) instead. #### getAgentTokenResponseForIssuer ```c struct agent_response getAgentTokenResponseForIssuer(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) ``` This function requests an access token from oidc-agent for the the provider with `issuer_url`. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `issuer_url` is the issuer url of the provider for which an access token should be obtained. - If `min_valid_period` is `0` no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. - If `scope` is `NULL`, the default scopes for that account are used. So usually it is enough to use `NULL`. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. - If `audience` is `NULL`, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns an `agent_response struct`. The `type` element indicates which type is returned, i.e. if an error occurred. On success the response has a `token_response struct` that contains the requested access token, the url of the issuer that issued the token and the time when the token expires (in seconds since the Epoch, `1970-01-01 00:00:00 +0000 (UTC)`). The values can be accessed the following way: ```c struct agent_response response = getAgentTokenResponseForIssuer(...); if (response.type == AGENT_RESPONSE_TYPE_TOKEN) { // assert that we actually have a token response struct token_response tok_res = response.token_response; tok_res.token // access token tok_res.issuer // issuer url tok_res.expires_at // expiration time } ``` **After usage the return value MUST be freed using `secFreeAgentResponse`.** On failure `response.type` will be `AGENT_RESPONSE_TYPE_ERROR` and `response.error_response` can be accessed (see [Error Handling](#error-handling)). So applications should check `response.type` before accessing any of the token response values. ##### Example A complete example can look the following: ```c struct agent_response response = getAgentTokenResponseForIssuer("https://oidc.example.com", 60, NULL, "example-app", NULL); if(response.type == AGENT_RESPONSE_TYPE_ERROR) { oidcagent_printErrorResponse(response.error_response); // Additional error handling } else { struct token_response tok_res = response.token_response printf("Access token is: %s\n", tok_res.token); printf("Issuer url is: %s\n", tok_res.issuer); printf("Token expires at: %lu\n", tok_res.expires_at); } secFreeAgentResponse(response); ``` #### getTokenResponseForIssuer This function is deprecated and should not be used in new applications. Use [`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) instead. #### getTokenResponseForIssuer3 This function is deprecated and should not be used in new applications. Use [`getAgentTokenResponseForIssuer`](#getagenttokenresponseforissuer) instead. ### Error Handling Since version `4.2.0` it is recommended to use functions that return an `agent_response struct`. This approach is described in [Using the Error Response Structure](#using-the-error-response-structure). For functions that do not return an `agent_response struct` [`oidc_errno`](#using-oidc_errno) must be used. This approach can also be used in addition to the `error_response struct`. #### Using the Error Response Structure Since version `4.2.0` it is recommended to use functions that return an `agent_response struct`. This struct can hold either a `token_response` or an `agent_error_response` depending on the success of the call. The `agent_error_response struct` holds an error message and MIGHT additionally hold a help message (however, the help message might also be `NULL`). If the help message is available it SHOULD be displayed to the user, since it gives useful information how the user can solve the problem. Before accessing the `agent_error_response struct` in an `agent_response` one MUST ensure that the `agent_response.type` is `AGENT_RESPONSE_TYPE_ERROR`. This is also how one checks for the presence of an error. ```c struct agent_response response = getAgentTokenResponse(...); if (response.type == AGENT_RESPONSE_TYPE_ERROR) { // error struct agent_error_response err_res = response.error_response; err_res.error // the error message err_res.help // the help message (before using it assert that != NULL } else { // success } ``` `liboidcagent4` also provides a helper function to easily print an `agent_error_response`: ```c oidcagent_printErrorResponse(response.error_response); ``` #### Using `oidc_errno` If an error occurs in any API function, `oidc_errno` is set to an error code. An application might want to check this variable and perform specific actions on some of the errors. A list of important error codes can be found at [Error Codes](#error-codes); for all error codes refer to the `oidc_error.h` header file. In most cases it is enough to print an error message to the user. For that usage `liboidc-agent4` provides some helperfunctions: ```c void oidcagent_perror(); char* oidcagent_serror(); ``` `oidcagent_perror()` can be used similar to `perror()` and prints an error message describing the last occurred error to `stderr`. `oidcagent_serror()` returns the string that describes the error without printing it. The return string MUST NOT be freed. This function behaves similar to `strerror(errno)`. #### Error Codes | error code | explanation | |------------|-------------| | OIDC_SUCCESS | success - no error | | OIDC_EERROR | general error - check the error string| | OIDC_ENOACCOUNT | the account is not loaded| | OIDC_EOIDC | an error related to OpenID Connect happened - check the error string| | OIDC_EENVVAR | the environment variable used to locate the agent is not set| | OIDC_ECONSOCK | could not connect to the oidc-agent socket - most likely the agent is not running| | OIDC_ELOCKED| the agent is locked and first has to be unlocked by the user| | OIDC_EFORBIDDEN|the user forbid this action| | OIDC_EPASS | wrong password - might occur if the account was not loaded and the user entered a wrong password in the autoload prompt| oidc-agent-4.2.6/gitbook/api/api-py.md0000644000175000017500000002070414120404223017026 0ustar marcusmarcus## liboidcagent-py A `python` library for `oidc-agent` is available at https://github.com/indigo-dc/liboidc-agent-py. To use it in your `python` application install it with ``` pip install liboidcagent ``` and import it with: ```python import liboidcagent as agent ``` ### Error Handling The library will raise an exception of type `OidcAgentError` if something goes wrong. Error Handling can be done the following way: ```python try: print(agent.get_access_token(account_name)) except agent.OidcAgentError as e: print("ERROR oidc-agent: {}".format(e)) ``` ### Requesting an Access Token For an Account Configuration The following functions can be used to obtain an access token for a specific account configuration from `oidc-agent`. If you / your application does not know which account configuration should be used, but you know for which provider you need an access token you can also [request an access token for a provider](#requesting-an-access-token-for-a-provider). #### get_access_token ```python def get_access_token(account_name, min_valid_period=0, application_hint=None, scope=None, audience=None) ``` This function requests an access token from oidc-agent for the `account_name` account configuration. The access token should have `scope` scopes, be valid for at least `minValidPeriod` seconds, and have the `audience` audience. ##### Parameters - `account_name` is the shortname of the account configuration that should be used. - If `min_valid_period` is `0` (default) no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. Can be omitted. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. Can be omitted. - If `scope` is None, the default scopes for that account are used. So usually it is enough to use `None` or to omit this parameter. - If `audience` is None, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns only the access token. To additionally obtain other information use [`get_token_response`](#get_token_response). ##### Example A complete example can look the following: ```python token = agent.get_access_token(account_name, 60, "example-app") print("Access token is: {}".format(token)) ``` #### get_token_response ```python def get_token_response(account_name, min_valid_period=0, application_hint=None, scope=None, audience=None) ``` This function requests an access token from oidc-agent for the `account_name` account configuration. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `account_name` is the shortname of the account configuration that should be used. - If `min_valid_period` is `0` (default) no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. Can be omitted. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. Can be omitted. - If `scope` is None, the default scopes for that account are used. So usually it is enough to use `None` or to omit this parameter. - If `audience` is None, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns three values: the requested access token, the url of the issuer that issued the token and the time when the token expires. The values can be accessed the following way: ```python token, iss, exp = agent.get_token_response(account_name, 60, "example-app") token // access token iss // issuer url exp // expiration time token_response = agent.get_token_response(account_name, 60, "example-app") token_response[0] // access token token_response[1] // issuer url token_response[2] // expiration time ``` ##### Example A complete example can look the following: ```python token, iss, exp = agent.get_token_response(account_name, 60, "example-app") print("Access token is: {}".format(token)) print("Access token issued by: {}".format(iss)) print("Access token expires at: {}".format(exp)) ``` ### Requesting an Access Token For a Provider The following functions can be used to obtain an access token for a specific OpenID Provider (issuer). This might be useful for applications that only work with a specific provider and therefore know the issuer for which they need an access token, but do not require the user to provide an account configuration shortname. #### get_access_token_by_issuer_url ```python def get_access_token_by_issuer_url(issuer_url, min_valid_period=0, application_hint=None, scope=None, audience=None) ``` This function requests an access token from `oidc-agent` for the provider with `issuer_url`. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `issuer_url` is the issuer url of the provider for which an access token should be obtained. - If `min_valid_period` is `0` (default) no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. Can be omitted. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. Can be omitted. - If `scope` is None, the default scopes for that account are used. So usually it is enough to use `None` or to omit this parameter. - If `audience` is None, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns only the access token. To additionally obtain other information use [`get_token_response_by_issuer_url`](#get_token_response_by_issuer_url). ##### Example A complete example can look the following: ```python token = agent.get_access_token_by_issuer_url("https://example.com", 60, "example-app") print("Access token is: {}".format(token)) ``` #### get_token_response_by_issuer_url ```python def get_token_response_by_issuer_url(issuer_url, min_valid_period=0, application_hint=None, scope=None, audience=None) ``` This function requests an access token from oidc-agent for the provider with `issuer_url`. The access token should have `scope` scopes, be valid for at least `min_valid_period` seconds, and have the `audience` audience. ##### Parameters - `issuer_url` is the issuer url of the provider for which an access token should be obtained. - If `min_valid_period` is `0` (default) no guarantee about the validity of the token can be made; it is possible that it expires before it can be used. Can be omitted. - `application_hint` should be the name of the application that requests an access token. This string might be displayed to the user for authorization purposes. Can be omitted. - If `scope` is None, the default scopes for that account are used. So usually it is enough to use `None` or to omit this parameter. - If `audience` is None, no special audience is requested for this access token. This parameter is used to request an access token with a specific audience. ##### Return Value The function returns three values: the requested access token, the url of the provider that issued the token and the time when the token expires. The values can be accessed the following way: ```python token, iss, exp = agent.get_token_response_by_issuer_url(issuer_url, 60, "example-app") token // access token iss // issuer url exp // expiration time token_response = agent.get_token_response_by_issuer_url(issuer_url, 60, "example-app") token_response[0] // access token token_response[1] // issuer url token_response[2] // expiration time ``` ##### Example A complete example can look the following: ```python token, iss, exp = agent.get_token_response_by_issuer_url("https://example.com", 60, "example-app") print("Access token is: {}".format(token)) print("Access token issued by: {}".format(iss)) print("Access token expires at: {}".format(exp)) ``` oidc-agent-4.2.6/gitbook/api/api-go.md0000644000175000017500000001227214167074355017030 0ustar marcusmarcus# liboidcagent-go A `go` library for `oidc-agent` is available at https://github.com/indigo-dc/liboidcagent-go. To use it in your `go` application include: ```go import "github.com/indigo-dc/liboidcagent-go" ``` ## Requesting an Access Token The following functions can be used to obtain an access token for a specific account configuration or for a given OpenID provider from `oidc-agent`. ### Token Request All functions take a `TokenRequest` struct. This struct describes the request: ```go // TokenRequest is used to request an access token from the agent type TokenRequest struct { // ShortName that should be used (Can be omitted if IssuerURL is specified) ShortName string // IssuerURL for which an access token should be obtained (Can be omitted // if ShortName is specified) IssuerURL string // MinValidPeriod specifies how long the access token should be valid at // least. The time is given in seconds. Default is 0. MinValidPeriod uint64 // The scopes for the requested access token Scopes []string // The audiences for the requested access token Audiences []string // A string describing the requesting application (i.e. its name). It might // be displayed to the user, if the request must be confirmed or an account // configuration loaded. ApplicationHint string } ``` ### GetAccessToken ```go func GetAccessToken(req TokenRequest) (token string, err error) ``` This function requests an access token from oidc-agent according to the passed [`TokenRequest`](#token-request). #### Return Value The function returns only the access token as a `string` and an error. To additionally obtain other information use [`GetTokenResponse`](#gettokenresponse). On failure an error is returned. #### Examples ##### Account Configuration Example A complete example can look the following: ```go token, err := liboidcagent.GetAccessToken(liboidcagent.TokenRequest{ ShortName: accountName, MinValidPeriod: 60, Scopes: []string{"openid", "profile"}, ApplicationHint: "Example-App", }) if err != nil { var agentError *OIDCAgentError if errors.As(err, &agentError) { fmt.Printf("%s\n", agentError.ErrorWithHelp()) } // Additional error handling } else { fmt.Printf("Access token is: %s\n", token) } ``` ##### Issuer Example A complete example can look the following: ```go token, err := liboidcagent.GetAccessToken(liboidcagent.TokenRequest{ IssuerURL: "https://openid.example.com", MinValidPeriod: 60, Scopes: []string{"openid", "profile"}, ApplicationHint: "Example-App", }) if err != nil { var agentError *OIDCAgentError if errors.As(err, &agentError) { fmt.Printf("%s\n", agentError.ErrorWithHelp()) } // Additional error handling } else { fmt.Printf("Access token is: %s\n", token) } ``` ### GetTokenResponse ```go func GetTokenResponse(req TokenRequest) (resp TokenResponse, err error) ``` This function requests an access token from oidc-agent according to the passed [`TokenRequest`](#token-request). #### Return Value The function returns an `TokenResponse struct` that contains the requested access token, the url of the issuer that issued the token and the time when the token expires. The values can be accessed the following way: ```go response, err := liboidcagent.GetTokenResponse(...) response.Token // access token response.Issuer // issuer url response.ExpiresAt // expiration time ``` #### Examples ##### Account Configuration Example A complete example can look the following: ```go resp, err := liboidcagent.GetTokenResponse(liboidcagent.TokenRequest{ ShortName: accountName, MinValidPeriod: 60, Audiences: []string{"https://storage.example.org"}, ApplicationHint: "Example-App", }) if err != nil { var agentError *OIDCAgentError if errors.As(err, &agentError) { fmt.Printf("%s\n", agentError.ErrorWithHelp()) } // Additional error handling } else { fmt.Printf("Access token is: %s\n", resp.Token) fmt.Printf("Issuer url is: %s\n", resp.Issuer) fmt.Printf("The token expires at: %s\n", resp.ExpiresAt) } ``` ##### Issuer Example A complete example can look the following: ```go resp, err := liboidcagent.GetTokenResponse(liboidcagent.TokenRequest{ Issuer: "https://openid.example.com", MinValidPeriod: 60, Audiences: []string{"https://storage.example.org"}, ApplicationHint: "Example-App", }) if err != nil { var agentError *OIDCAgentError if errors.As(err, &agentError) { fmt.Printf("%s\n", agentError.ErrorWithHelp()) } // Additional error handling } else { fmt.Printf("Access token is: %s\n", resp.Token) fmt.Printf("Issuer url is: %s\n", resp.Issuer) fmt.Printf("The token expires at: %s\n", resp.ExpiresAt) } ``` ## Getting Loaded Accounts ```go func GetLoadedAccounts() (accountNames []string, err error) { ``` This function requests the list of currently loaded accounts from oidc-agent. #### Return Value The function returns a list of the currently loaded accounts as a `[]string` on success and an `OIDCAgentError` on failure. ## Getting Configured Accounts ```go func GetConfiguredAccounts() (accounts []string) { ``` This function checks the oidc-agent directory for the configured accounts. #### Return Value The function returns a list of the configured accounts as a `[]string`. oidc-agent-4.2.6/gitbook/security/0000755000175000017500000000000014167074355016424 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/security/confirmation.md0000644000175000017500000000041514120404223021412 0ustar marcusmarcus## User Confirmation When loading an account configuration into the agent using `oidc-add` a user can specify that he wants to confirm each usage of this configuration. Therefore, an application can only obtain an access token from the agent if the user approves it. oidc-agent-4.2.6/gitbook/security/credentials.md0000644000175000017500000000344114167074355021245 0ustar marcusmarcus## Credentials (User) credentials are very sensitive information and have to be handled with adequate caution. ### User Credentials The user only has to pass its credentials (for the OpenID Provider) to `oidc-agent` when using the password flow. This flow has to be explicitly enabled to use it with `oidc-gen`. Furthermore, it is not even supported by most providers and if it might require manual approval from an OpenID Provider administrator. However, when user credentials are passed to `oidc-agent` we handle them carefully and keep them as short as possible in memory. Credentials are also overwritten before the memory is freed (see also [memory](memory.md)) and never stored on disk. ### Refresh Tokens OpenID Connect refresh tokens can be used to obtain additional access tokens and must be kept secret. The refresh token is stored encrypted in the account configuration file (s. [account configuration files](account-configs.md)). The refresh token is only read by `oidc-gen` (during account configuration generation) and `oidc-add` (when adding a configuration to the agent). When using the autoload feature (see [account autoload](../tips.md#autoloading-and-autounloading-account-configurations)) also `oidc-agent` reads the refresh token from the encrypted account configuration file. However, `oidc-gen` and `oidc-add` do not use the refresh token, they only pass it to `oidc-agent`. `oidc-agent` uses the refresh token to obtain additional access tokens. **The agent has to keep the refresh token in memory. However, when not being used it will be obfuscated, so it is harder to extract it from a memory dump. The password used for this obfuscation is dynamically generated when the agent starts.** **The refresh token cannot be requested from `oidc-agent`and is never send to any other application.** oidc-agent-4.2.6/gitbook/security/privilege-separation.md0000644000175000017500000000466014120404223023061 0ustar marcusmarcus## Privilege Separation & Architecture We followed the security by design principle and split the system’s functionalities into multiple components. In that way we also achieved privilege separation. The oidc-agent project consists of the following components: - `oidc-agent`: The actual agent managing the tokens and performing all communication with the OpenID Provider; internally also has two components: - oidc-agent-proxy: A proxy daemon that forwards requests to oidc-agent-daemon. It handles encryption passwords and file access for oidc-agent-daemon when it has to read (autoload) or write (changing refresh token) an account configuration file. - oidc-agent-daemon: The daemon that holds the loaded accounts and performing all communication with the OpenID Provider - `oidc-gen`: A tool for generating account configuration files for usage with `oidc-agent` and `oidc-add`. - `oidc-add`: A tool that loads the account configurations into the agent. - `oidc-token` and third party applications: Applications that need an OIDC access token can obtain it through the agent’s [API](../api/api.md). One example application for obtain access tokens is `oidc-token`. ![Architecture of the oidc-agent project](https://raw.githubusercontent.com/indigo-dc/oidc-agent/master/gitbook/images/architecture.png) ### Privileges Needed by Different Components The following list might not be complete when it comes to implementation details (e.g. privileges needed for obtain the current time). Privileges for the different components: - oidc-agent-daemon: - pipe ipc - socket ipc - internet - also reads the CA bundle file - starts web server - reads time - reads random - oidc-agent-proxy: - creates directory in `/tmp` - creates socket in the created temporary directory - pipe ipc - socket ipc - reads time - reads random - reads files in the oidc-agent directory - writes files in the oidc-agent directory - oidc-gen: - reads files in the oidc-agent directory - writes files in the oidc-agent directory - writes files in `/tmp` - writes files passed to `--output` - reads files passed to `--file`, `--print` - reads random - socket ipc - might call `xdg-open` to open the authorization url in a browser - might execute the user provided `--pw-cmd` - oidc-add: - reads files in the oidc-agent directory - reads random - socket ipc - might execute the user provided `--pw-cmd` - oidc-token: - socket ipc oidc-agent-4.2.6/gitbook/security/agent-locking.md0000644000175000017500000000127114120404223021445 0ustar marcusmarcus## Agent Locking The agent can be locked using a locking password. While being locked the only operation allowed are: - checking if the agent is running - unlocking the agent Every other request will result in an error `Agent locked`. This allows a user to temporarily forbid all operations / requests without removing the loaded account configurations. When the agent is locked refresh tokens, access tokens, and client credentials are encrypted using the locking password provided by the user. The agent also offers brute force protection. When trying to unlock the agent with a wrong password a small delay is added, which will increase with the number of failed attempts up to 10 seconds. oidc-agent-4.2.6/gitbook/security/memory.md0000644000175000017500000000171114120404223020232 0ustar marcusmarcus## Memory Programming in `C` always requires caution when it comes to memory security. Because we handle sensitive data, we decided to clear all allocated memory before freeing it. To do this we wrote our own memory allocator (wrapper) and a custom free. By clearing all allocated memory and not only the parts known to be sensitive we ensure that all sensitive data is overwritten before freed. (Even if there is a refresh token as part of a server response.) Sensitive data on the stack is explicitly overwritten after usage. Refresh tokens and client credentials are the most sensitive information that have to be kept in memory by `oidc-agent` for the whole time. To make it harder for an attacker to extract this information from the agent, it is obfuscated when not being used. The password used for obfuscation is dynamically generated when the agent starts. Additional encryption is applied when the agent is locked (see [Agent Locking](agent-locking.md)). oidc-agent-4.2.6/gitbook/security/tracing.md0000644000175000017500000000042714120404223020354 0ustar marcusmarcus## Tracing We disabled the possibility to attach to any oidc-agent component with tracing. (Among others, tracing can be used to get a memory dump). However, this only holds for non privileged users, as root it is still possible to trace oidc-agent and to obtain memory dumps. oidc-agent-4.2.6/gitbook/security/encryption-passwords.md0000644000175000017500000000606114167074355023166 0ustar marcusmarcus## Encryption Passwords Generally, the encryption password provided by the user to encrypt / decrypt account configurations is kept in memory as short as possible. However, `oidc-agent` can keep them longer in memory (encrypted) so it can update the configuration file when a provider issues a new refresh token. This option is not enabled on default and has to be enabled explicitly by the user. If the user did not enable any password caching feature and `oidc-agent` needs an encryption password because it has to update the account configuration file, the user will be prompted for the password. However, if a provider uses rotating refresh tokens, this might be impractical, because the user has to enter his encryption password whenever a new access token is issued. We therefore implemented different password caching features: - `oidc-agent` can keep the encryption password in memory. The encryption password will be well encrypted; and the password used for this encryption is dynamically generated when the agent starts. The time how long the encryption password should be kept in memory can also be limited, i.e. after that time the password will be cleared from memory. To use this approach use the `--pw-store` option of `oidc-add`. - `oidc-agent` can also save the encryption password in the system's password manager (keyring). Because any other application running as the same user then has access to this password, `oidc-agent` still applies encryption on the user's password, even though the keyring will save the password encrypted. However, to prevent other applications to obtain the plain password, we only store the encrypted password. To use this approach use the `--pw-keyring` option of `oidc-add`. - `oidc-agent` can also retrieve the encryption password from a user provided command; the output of this command will be used as the encryption password. The command used will be kept encrypted in memory, because it is used to obtain the encryption password without any additional checks, it should be treated the same way as the encryption password itself. Because with this option the user does not have to enter his password at any point (also not when loading the account configuration with `oidc-add`) it might be especially useful when writing scripts. To use this apporach use the `--pw-cmd` option of `oidc-add` or `oidc-gen`. - `oidc-agent` can also retrieve the encryption password from a user provided file; the content of this file will be used as the encryption password. The filepath used will be kept encrypted in memory, because it is used to obtain the encryption password without any additional checks, it should be treated the same way as the encryption password itself. Because with this option the user does not have to enter his password at any point (also not when loading the account configuration with `oidc-add`) it might be especially useful when writing scripts. To use this apporach use the `--pw-file` option of `oidc-add` or `oidc-gen`. oidc-agent-4.2.6/gitbook/security/account-configs.md0000644000175000017500000000070514120404223022006 0ustar marcusmarcus## Account Configuration Files The generated account configuration files contain sensitive information (i.e. client credentials and the refresh token) and are therefore stored in an encrypted way. All encryption done in the `oidc-agent` project are done through the [`libsodium library`](https://github.com/jedisct1/libsodium), which is also used by software such as `Discord`, `RavenDB`, or `Wire`. The encryption uses an `XSalsa20` stream cipher. oidc-agent-4.2.6/gitbook/security/communication.md0000644000175000017500000000353514120404223021575 0ustar marcusmarcus## Communication Because the oidc-agent project consists of multiple components and also other applications can interface with `oidc-agent`, communication between these components is an important part of the project. Other applications (including `oidc-gen`, `oidc-add`, and `oidc-token`) can communicate with `oidc-agent` through a UNIX domain socket. This socket can be located through the `$OIDC_SOCK` environment variable. The socket is created when `oidc-agent` starts. The access control on that socket is handled by the file system. The socket is created with user privileges, allowing every application running as the same user as the user that started `oidc-agent` to communicate with the agent. A man-in-the-middle attack on this socket would be possible, e.g. using `socat`. However, it requires an attacker to already have user privileges. Also sensitive information is encrypted. `oidc-gen` and `oidc-add` encrypt all their communication with the agent (their communication might contain sensitive information like user credentials (only for `oidc-gen` when using the password flow), OIDC refresh token, client credentials, lock password, etc.) Communication done with `liboidc-agent` (including `oidc-token`) is not encrypted. However, this communication only contains OIDC access token as sensitive information and these can be requested by any application with access to the agent socket. If an application communicates directly through the UNIX domain socket with `oidc-agent` encryption is theoretically supported. However, it requires usage of libsodium and the implementation details (used functions, parameters, etc.) are not documented and have to be retrieved from the source. Internally `oidc-agent` consists of two components that communicate through unnamed pipes. This communication is not encrypted, because it cannot be accessed by other processes. oidc-agent-4.2.6/gitbook/security/seccomp.md0000644000175000017500000000513114120404223020353 0ustar marcusmarcus## `seccomp` With version 2.0.0 we integrated `seccomp` into `oidc-agent` to restrict the system calls that each component is allowed to perform. However, the system calls needed may vary for different operating system (versions) and architectures. Therefore, we decided to disable this feature by default. (expert) users with higher security standards can turn seccomp on by using the `--seccomp` option. (It does make sense to define aliases.) However, these users will have to maintain the white-listed system calls on their own. The configuration files are located in `/etc/oidc-agent/privileges/` Because seccomp can not restrict string parameters (e.g. paths) the added security is rather small. This is because even though privilege separation is in place and e.g. `oidc-agent` does not have to read and write regular files, it still needs system calls that can be sued to read and write regular files, because `oidc-agent` also needs to create a tmp directory, create sockets, read and write from sockets and pipes, read time and random, ... Users that want to restrict `oidc-agent` further might want to have a look at [AppArmor](https://help.ubuntu.com/lts/serverguide/apparmor.html.en). ### Missing Seccomp System Calls A missing system call can be very hard to debug. The first step is to ensure that there really is a missing system call. For all client components (`oidc-add`, `oidc-gen`, etc.) this is very easy. If they are killed by the kernel you probably will see a `Bad Syscall` message on your terminal. However, for `oidc-agent` this message will not appear so it's not that easy to tell if `oidc-agent` was killed because of a seccomp violation or if it crashed for some other reason. Furthermore, `oidc-agent` forks another process for some operations. These might be killed as well without effecting the actually `oidc-agent` process. If that happens you might experience unrelated error messages. The name of the syscall that could not be performed can be obtained the following way: - You must have `auditd` installed and running. (Before the bad syscall happens.) - Use [ `getBadSysCall.sh` ](https://github.com/indigo-dc/oidc-agent/blob/master/src/privileges/getBadSysCall.sh) to get the syscall name. Call it with the name of the broken component as a parameter: Example: `getBadSysCall.sh oidc-gen`. Note: If the httpserver of oidc-agent breaks you might have to use `getBadSysCall.sh MHD-listen`. If you know the name of the violating syscall you can add it to the white-list. The list is split into multiple files located at `/etc/oidc-agent/privileges`. Choose the one that seems to fit best. oidc-agent-4.2.6/gitbook/security/autounload.md0000644000175000017500000000142614120404223021100 0ustar marcusmarcus## Autounload (Lifetime) Generally, we keep all information in memory as short as possible, but sometimes we have to keep information for a longer time, e.g. the account configuration. As well as an encryption password that is kept encrypted in memory can automatically removed after a specified time, an account configuration can also be removed after some time. A user can use the lifetime option to control how long a configuration will live in the agent, after that time it is automatically unloaded. This feature plays very well with the autoload feature, because it makes it easy to use small lifetimes on default, because an unloaded configuration can be loaded again into the agent without running oidc-add, but simply when it is required, but it still requires user interaction. oidc-agent-4.2.6/gitbook/security/final.md0000644000175000017500000000044014120404223020011 0ustar marcusmarcus## Final Note While we do our best to make it as hard as possible to extract any sensitive information from `oidc-agent`, it is impossible to fully protect any application from an attacker that has the same or even higher rights as the user that runs the application (i.e. `oidc-agent`). oidc-agent-4.2.6/gitbook/macos/0000755000175000017500000000000014120404223015633 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/macos/installation.md0000644000175000017500000000167514120404223020667 0ustar marcusmarcus## Installation ### Install with Homebrew oidc-agent can be installed easiest using homebrew. ``` brew tap indigo-dc/oidc-agent brew install oidc-agent ``` To use GUI prompting one must install `pashua`: ``` brew cask install pashua ``` ### Building oidc-agent manually #### Download source code - git clone: `git clone https://github.com/indigo-dc/oidc-agent` #### Install dependencies: - Install [brew](https://brew.sh) to install dependencies - pkgconfig `brew install pkgconfig` - argp `brew install argp-standalone` - libsodium `brew install libsodium` - libmicrohttpd `brew install libmicrohttpd` - help2man `brew install help2man` #### Build oidc-agent Run make ``` make ``` #### Install using make ``` make install make post_install ``` This installs all necessary components to correct locations. oidc-agent-4.2.6/gitbook/macos/macos.md0000644000175000017500000000035614120404223017263 0ustar marcusmarcus# oidc-agent on MacOS oidc-agent can be used on MacOS. Some functionality might not be supported (yet) (see [What does not work](state.md#what-does-not-work)). However, all main features can be used on MacOS in the same way as on linux. oidc-agent-4.2.6/gitbook/macos/state.md0000644000175000017500000000146514120404223017303 0ustar marcusmarcus## State of Feature Support ### What does work: - [Installing oidc-agent using homebrew](installation.md#install-with-homebrew) - [Building oidc-agent manually](installation.md#building-oidc-agent-manually) - password / consent prompts using `pashua` -> requires installing pashua: `brew cask install pashua` ### What does not work: - syslog -> we implemented a custom logging behavior. The log file can be found in the oidc-agent directory. - Obviously, now oidc-agent has to write to disk, which sort of breaks privilege separation. - bash completion - seccomp - storing a password in the keyring -> if one can make it work, pull requests / instructions are welcome. - Xsession integration - some enhancements might not work properly (e.g. http-server might not be killed in all cases when the agent dies) oidc-agent-4.2.6/gitbook/oidc-agent-service/0000755000175000017500000000000014120404223020201 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-agent-service/oidc-agent-service.md0000644000175000017500000000422114120404223024172 0ustar marcusmarcus# oidc-agent-service `oidc-agent-service` can be used to easily restart `oidc-agent`. `oidc-agent-service` is called in the same way as `oidc-agent`, this means that `oidc-agent-service` will print out the needed shell commands to set environment variables. Therefore `oidc-agent-service` is usually called with `eval` or its output is piped to a file. ## Quick Start Make `oidc-agent` available in the current terminal: ``` eval `oidc-agent-service use` ``` Restart the agent (it will still be usable in all terminals as before after the restart): ``` oidc-agent-service restart-s ``` ## Configuration The behavior of `oidc-agent-service` can be configured through a configuration file. Among others, this file can be used to set the command line options used when starting the agent. The system-wide configuration file `/etc/oidc-agent/oidc-agent-service.options` can be adapted to change the behavior of `oidc-agent-service` for the whole system. You can also add a `oidc-agent-service.options` file to your [oidc-agent directory](../configuration/directory.md). Options specified in this file will overwrite any option defined in `/etc/oidc-agent/oidc-agent-service.options`. ## Commands ### `use` `use` will give you an usable agent. This is usually the command you want to use to start an agent. It starts an agent and makes it available (it prints the needed environment variables). If `oidc-agent-service` has already started an agent for you, this agent will we reused and made available. ### `start` `start` starts an agent. If `oidc-agent-service` already started an agent, `start` will fail. If you want to reuse that agent in this case, use `use`. ### `restart` `restart` restarts the agent. This means that the current agent is stopped and a new agent is started. On default the new agent is started with the same options as the old one. This behaviour can be changed (see [configuration](#configuration)). ### `restart-s` `restart-s` is the same as `restart`, but does not print any output. Therefore, you can call `oidc-agent-service restart-s` instead of ``eval `oidc-agent-service restart-s` ``. ### `stop` `stop` stops the running agent. ### `kill` Same as `stop`. oidc-agent-4.2.6/gitbook/configuration/0000755000175000017500000000000014120404223017400 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/configuration/default-accounts.md0000644000175000017500000000057114120404223023166 0ustar marcusmarcus## Default Account Configuration for a Provider The `issuer.config` file in the [oidc-agent directory](directory.md) can also be used to set an default account configuration file for each provider by adding the shortname of this account configuration after the issuer url. A line in the `issuer.config` file should look the following: ``` [] ``` oidc-agent-4.2.6/gitbook/configuration/other.md0000644000175000017500000000100214120404223021034 0ustar marcusmarcus## Other Configuration Generally oidc-agent does not use any configuration files. (Configuration files for [seccomp](../security/seccomp.md) and the already mentioned `issuer.conf` excluded.) Configuration needed is done mostly through command line options to the different components and in some cases through environement variables. If some command line options are used for every call, it makes sense to define an alias for it in `.bashrc` or `.bash_aliases`, e.g. `alias oidc-add="oidc-add --pw-store=3600"`. oidc-agent-4.2.6/gitbook/configuration/forwarding.md0000644000175000017500000000220014120404223022056 0ustar marcusmarcus## Agent Forwarding When using `ssh` to connect to a remote server there might be the use case where the user or an application on the remote server wants to receive an access token from the local agent. This is possible by forwarding the UNIX domain socket used for communicating with the agent. This can be done using the `-R` option of `ssh`. Example: ``` ssh -R /tmp/oidc-forward:$OIDC_SOCK user@host ``` However, if you do this you still have to manually export the `OIDC_SOCK` environment variable on the server (`export OIDC_SOCK=/tmp/oidc-forward`). **To use agent forwarding we recommend the following configurations:** Put the following in your `.bash_profile` on the server: ``` test -z $OIDC_SOCK && { export OIDC_SOCK=`/bin/ls -rt /tmp/oidc-forward-* 2>/dev/null | tail -n 1` } ``` and this into your `.bash_logout` on the server: ``` if [ -e $OIDC_SOCK ]; then rm -f $OIDC_SOCK fi ``` Add this to `.bash_aliases` on your local machine: ``` alias OA='echo -R /tmp/oidc-forward-$RANDOM:$OIDC_SOCK' alias ssh-oidc="ssh \`OA\`" ``` You can then call ssh the following way: ``` ssh-oidc user@host ``` or ``` ssh user@host `OA` ``` oidc-agent-4.2.6/gitbook/configuration/directory.md0000644000175000017500000000205114120404223021724 0ustar marcusmarcus## oidc-agent Directory An oidc-agent directory will be created when using oidc-gen for the first time. Depending on the existence of `~/.config` it will be located at one of these locations: - `~/.config/oidc-agent` - `~/.oidc-agent` Alternatively the location can also be set through an environment variable called `OIDC_CONFIG_DIR`. However, we note that this environment variable has to be present whenever you use one of the `oidc-` binaries. It is therefore recommend to set it in the `.bash_profile` or similar. All account configuration files generated by `oidc-gen` are saved in this oidc-agent directory. Additionally there is a config file named `issuer.config`. This file can be used to specify a list of issuers (one issuer per line) that are used as suggestions by `oidc-gen`. `oidc-gen` will also update this issuer list after an account configuration was created successfully. oidc-agent installs a similar file under `/etc/oidc-agent`, however, that file should not be edited by the user, but it might be updated with new oidc-agent versions. oidc-agent-4.2.6/gitbook/configuration/integration.md0000644000175000017500000000225514120404223022251 0ustar marcusmarcus## oidc-agent Integration ### Xsession Integration oidc-agent has support for integration with Xsession, which is enabled by default if the `oidc-agent-desktop` package is installed. This means oidc-agent will automatically start at the beginning of an Xsession and then be available throughout that session (i.e. you can connect to the agent from every terminal). To disable / re-enable this behavior (system-wide) edit the `/etc/oidc-agent/oidc-agent-service.options` file. To disable it on a per user basis, copy this file to your [oidc-agent directory](directory.md) and edit it there. To disable XSession integration uncomment / add the line: ``` START_AGENT_WITH_XSESSION="False" ``` To re-enable Xsession integration change it to `START_AGENT_WITH_XSESSION="True"` or comment it out. To pass command line options to the automatically started agent edit the `OIDC_AGENT_OPTS` variable. Note that from version 4.1.0 on the agent can be restarted without losing the integration in existing terminals. To do so run: ``` oidc-agent-service restart-s ``` We also want to note that [`oidc-agent-service`](../oidc-agent-service/oidc-agent-service.md) can be used without Xsession integration. oidc-agent-4.2.6/gitbook/tips.md0000644000175000017500000002050314120404223016032 0ustar marcusmarcus# Tips Here we want to share some useful tips on how `oidc-agent` and the other components can be used in your everyday work. * [Xsession Integration](#xsession-integration) * [Command Line Integration of oidc-token](#command-line-integration-of-oidc-token) * [Obtaining More Information From oidc-token](#obtaining-more-information-from-oidc-token) * [Autoloading and Autounloading Account Configurations](#autoloading-and-autounloading-account-configurations) * [Obtaining access tokens on a server](#obtaining-access-tokens-on-a-server) * [Updating an Expired Refresh Token](#updating-an-expired-refresh-token) * [Applications that run under another user](#applications-that-run-under-another-user) * [Non-interactive oidc-gen](#non-interactive-oidc-gen) ## Xsession Integration See [Xsession Integration](configuration/integration.md#xsession-integration). ## Agent Forwarding See [Agent Forwarding](configuration/forwarding.md). ## Using oidc-token With an Issuer Instead of the Shortname Instead of using `oidc-token ` you also can do `oidc-token `. Usually the usage of the shortname is shorter than using the whole issuer url. However, there are use cases where this option might be quite useful. Generally it is a more universal way to obtain an access token for a specific provider. While `oidc-token mySuperFancyShortname` might work on your machine it can fail for other users because they do not have `mySuperFancyShortname`. Using the issuer url - when writing scripts that are shared with other users, - opening issues that mention calls to `oidc-token`, or - sharing other `oidc-token` related commands makes it easier for other users to run the same commands without any changes. ## Command Line Integration of oidc-token If you have to pass an access token to another application on the command line you can substitute the token with ```oidc-token ` ``. E.g. you can do an `curl` call with an OIDC token in the authorization header: ``` curl example.com -H 'Authorization: Bearer `oidc-token `' ``` This syntax can be used with many applications and is quite useful. ## Obtaining More Information From oidc-token As described under [oidc-token](oidc-token/options.md#information-available-from-oidc-token) you can obtain more information when calling `oidc-token` and not only the access token. If you want to do this we recommend the `--env` option and call `oidc-token` the following way: ``eval `oidc-token --env ` ``. This way the environment variables `OIDC_AT`, `OIDC_ISS`, and `OIDC_EXP` or populated with the correct values. A way that is **not** recommended, is to do multiple successive calls to `oidc-token` and only providing one of the `--token`, `--issuer`, `--expires-at` options on each call. This would make three independent token requests to `oidc-agent`. This is not only inefficient but also does not guarantee to return correct results. It might happen that the token requested in the first call is only valid for a very short time and not valid anymore when doing the last request; in this case a new token will be requested that has a different expiration time that does not relate to the token from the first call. ## Autoloading and Autounloading Account Configurations Since version `2.3.0` there is support for the so called `autoload` of account configurations. If an application requests an access token for an configuration that is not loaded, it can be automatically loaded. The user will be prompted to enter the encryption password for this account configuration and through that can decide if the account should be loaded or not. This means we can do a call to `oidc-token example` and even if `example` is currently not loaded, it will be loaded and an access token is returned. The autoloading feature makes it quite easy to also use the autounload (limited lifetime of account configuration). When starting `oidc-agent` with the `--lifetime` option you can specify for how long account configuration should be loaded (the default is forever). However, we now can use a limit and load account configuration only for e.g. 300 seconds. After that time the account configuration will automatically be removed from the agent. But if an application needs an access token for an account configuration it can be easy loaded through the autoload feature. This way the time sensitive information is kept in memory by `oidc-agent` can be limited without reducing usability much (the user does not always have to run `oidc-add`). Of course there are use cases where the autounload-autoload combination is not useful, e.g. if a script runs periodically and needs an access token and should run with no user interaction at all. ## Obtaining access tokens on a server `oidc-agent` can run on a remote server and then be used as usual to obtain access tokens. However, if you are planning to do this, you should check your use case, if this is really necessary. There are also alternatives: - [Agent forwarding](configuration/forwarding.md) can be used to access a local agent on a remote server. - The [mytoken service](https://github.com/oidc-mytoken/server) is properly what you want. When running oidc-agent on a server to obtain tokens, generating a new account configuration file on that server can be more difficult, because there is neither a webbrowser nor a desktop environment. But because oidc-agent is designed for command line usage, it is still possible. There are several ways of generating a new account configuration on a remote server: 0. Generate it locally and copy it to the remote server 1. Using the password flow, which can be done entirely on the command line 2. Using the device flow, where a second device is used for the web-based authentication. 3. Using the authorization code flow with a 'manual redirect' Option 1 and 2 are not supported by all providers. However, if the device flow is supported by your provider, we recommend option 2. When doing option 3 you should be aware of the following: - Make sure that the agent uses the `--no-webserver` and `--no-scheme` options or pass these options to `oidc-gen` - Also add the `--no-url-call` option when calling `oidc-gen` - Copy the printed authorization url and open it in a browser (local). - Authenticate and authorize oidc-agent as usual - You will be redirected to localhost. Because there is no webserver listening your browser will display an error message. - Copy the url to which you are redirected from the address bar of your browser - Head over to the remote server and pass the copied url to `oidc-gen` in the following call: ``` oidc-gen --code-exchange='' ``` ## Updating an Expired Refresh Token If a refresh token expired the user has to re-authenticate to obtain a new valid refresh token. Until version `2.3.0` this would require the user to use `oidc-gen -m `, which allows the user to change all data of this account configuration (and therefore has to confirm all existing data). Because the user only wants to re-authenticate to update the refresh token, confirming, that all other data should be unchanged, is annoying. Instead use `oidc-gen --reauthenticate `. This option will only start the re-authentication and update the refresh token. Easier and faster. ## Applications that run under another user On default only applications that run under the same user that also started the agent can obtain tokens from it. Whens tarting the agent the `--with-group` option can be used to allow other applications to also access the agent. This can be useful in cases where applications must run under a specific user. The user first has to create a group (e.g. named `oidc-agent`) and add himself and all other users that need access to the agent to this group. It is the user's responsibility to manage this group. Then he can pass the group name to the `--with-group` option to allow all group members access to the agent. If the option is used without providing a group name, the default is `oidc-agent`. ## Non-interactive oidc-gen To run `oidc-gen` completely non-interactively (i.e. without user interaction) one needs to pass several parameters: - Pass all required information with command line arguments, e.g. `--iss`, `--scope` - Use `--prompt=none` to disable prompting - Use `--pw-file` or `--pw-cmd` to pass an encryption password - Use `--confirm-default`, `--confirm-yes` or `--confirm-no` to automatically confirm with the default, yes or no. oidc-agent-4.2.6/gitbook/installation/0000755000175000017500000000000014167074355017256 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/installation/install.md0000644000175000017500000001124014167074355021244 0ustar marcusmarcus# Installation This document describes how to install oidc-agent on linux. To install oidc-agent on MacOS refer to the [MacOS documentation](macos/installation.md). ## From Package We provide packages for Debian, Ubuntu and CentOS, Suse, and more. They are available at http://repo.data.kit.edu/ or at [GitHub](https://github.com/indigo-dc/oidc-agent/releases). Follow the instructions for your distro, then: - `sudo apt-get update` - `sudo apt-get install oidc-agent` ## From Source If you want to build oidc-agent from source you can do so. ### Dependencies #### Basic Dependencies To be able to build oidc-agent, you need at least the following dependencies installed on your system: - gcc - make - [libcurl](https://curl.haxx.se/libcurl/) (libcurl4-openssl-dev) - [libsodium (>= 1.0.14)](https://download.libsodium.org/doc/) (libsodium-dev) - [libmicrohttpd](https://www.gnu.org/software/libmicrohttpd/) (libmicrohttpd-dev) - libseccomp (libseccomp-dev) - libsecret (libsecret-1-dev) We note that libsodium-dev might not be available by default on all systems with the required version of at least `1.0.14`. It might be included in backports or has to build from source. ##### Debian/Ubuntu ``` sudo apt-get install \ libcurl4-openssl-dev \ libsodium-dev \ libseccomp-dev \ libmicrohttpd-dev \ libsecret-1-dev \ libqrencode-dev ``` ##### CentOS 7 ``` sudo yum install \ libcurl-devel \ libsodium-devel \ libsodium-static \ libmicrohttpd-devel \ libseccomp-devel \ libsecret-devel \ libqrencode-devel ``` #### Additional Build Dependencies oidc-agent can be installed easiest from package. So even when building from source it is recommended to build the package and install it. Building the deb/rpm package might have additional dependencies. - help2man - check - debhelper - pkg-config - perl - sed - fakeroot - devscripts #### Optional Runtime Dependencies Some features require additional dependencies. - yad through oidc-agent-prompt (required for password prompting by the agent) - qrencode (required for generating an optional QR-Code when using the device flow) ### Download oidc-agent After installing the necessary dependencies, one has to obtain a copy of the source. Possible ways are: - clone the git repository - download a [release version](https://github.com/indigo-dc/oidc-agent/releases) - download the source from [GitHub](https://github.com/indigo-dc/oidc-agent) #### Using git ``` git clone https://github.com/indigo-dc/oidc-agent cd oidc-agent ``` #### Using curl ``` curl -L https://github.com/indigo-dc/oidc-agent/archive/master.tar.gz -o /tmp/oidc-agent-master.tar.gz tar xzf /tmp/oidc-agent-master.tar.gz cd oidc-agent ``` ### Build and install oidc-agent As already mentioned, oidc-agent can be installed easiest by using a debian or rpm package. It is therefore recommend to build such a package. #### Building a package ##### Debian / Ubuntu To build a debian package and install it run the following commands inside the oidc-agent source directory: ``` make deb sudo dpkg -i ../liboidc-agent4__amd64.deb sudo dpkg -i ../oidc-agent__amd64.deb ``` ##### CentOS 7 To build an rpm package and install it run the following commands inside the oidc-agent source directory: ``` make rpm sudo yum install ./rpm/rpmbuild/RPMS//oidc-agent--1..rpm ``` #### Building Binaries The binaries can be build with `make`. To build and install run: ``` make sudo make install_lib sudo make install sudo make post_install ``` This will: - build the binaries - create man pages - install the binaries - install the man pages - install configuration files - install bash completion - install a custome scheme handler - enable a linker to use the newly installed libraries - update the desktop database to enable the custome scheme handler If you want to install any of these files to another location you can pass a different path to make. E.g. `sudo make install BIN_PATH=/home/user` will install the binaries in `/home/user/bin` instead of `/usr/bin`. One could also run only `make` and manually copy the necessary files to another location and / or add the binaries' location to `PATH`. However, this is not recommend, because some files have to be placed at specific locations or additional configuration is needed. But it is also possible to install only a subset of all files, by calling the different `make install_` rules. Available targets are: ``` sudo make install_bin sudo make install_man sudo make install_conf sudo make install_bash sudo make install_priv sudo make install_scheme_handler sudo make install_xsession_script sudo make install_lib sudo make install_lib-dev ``` oidc-agent-4.2.6/gitbook/provider/0000755000175000017500000000000014170031216016366 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/provider/egi.md0000644000175000017500000000450114170031216017454 0ustar marcusmarcus## EGI Check-in EGI Check-in supports dynamic registration, but dynamically registered clients will not have any scopes. Therefore, users have to either register a client manually or use a preregistered public client (recommended). Example: ``` $ oidc-gen --pub --issuer https://aai.egi.eu/oidc \ --scope "email \ eduperson_entitlement \ eduperson_scoped_affiliation \ eduperson_unique_id" ``` You will need to follow the OIDC-flow, which usually involves authentication in a web-browser. If the browser does not start, you can copy paste the displayed URL. ``` [...] Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] Polling oidc-agent to get the generated account configuration .....success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. ``` Finally, you will be be asked for a password on the commandline to safely store your credentials. ``` Enter encryption password for account configuration '': Confirm encryption Password: ``` **Note**: You need to run the webbrowser on the same host as the `oidc-gen` command. \ If you operate on a remote machine, you need to use the device code flow, by adding `--flow=device` to the above commandline. Advanced users may succeed by otherwise ensuring that the browser you are using can connect to the host on which `oidc-gen` and `oidc-agent` run on ports 4242, 8080 or 43985. oidc-agent-4.2.6/gitbook/provider/general.md0000644000175000017500000000354514120404223020331 0ustar marcusmarcus## A Provider not Listed If your provider was not listed above, do not worry - oidc-agent should work with any OpenID Provider. Please follow these steps. ### Try Dynamic Client Registration If you already have a registered client you can see [Generate the Account Configuration](#generate-the-account-configuration). Dynamic client registration is not supported by all OpenID Providers, so it might fail. Anyway, try registering a client dynamically by calling oidc-gen and providing the issuer url when being prompted. If dynamic client registration is not supported, oidc-gen will tell you this. In that case you have to register the client manually through the provider's web interface (see [Client Configuration Values](client-configuration-values.md) for help with manual client registration) and then go to [Generate the Account Configuration](#generate-the-account-configuration). Some providers have a protected registration endpoint which is not public. If so oidc-agent needs an initial access token for authorization at the endpoint. Please call oidc-gen with the `--at` option to pass the access token to oidc-gen. When the client was successfully registered the account configuration should be generated automatically and you should be able to save and use it. ### Generate the Account Configuration If you registered a client manually call oidc-gen with the `-m` flag or if you have a file containing the json formatted client configuration pass it to oidc-gen with the `-f` flag. After entering the required information oidc-agent should be able to generate the account configuration which is then usable. ### Still no Success? If you still were not be able to get oidc-agent working with that provider, please contact the provider or us at . We will try to figure out if the problem is with oidc-agent or the provider. oidc-agent-4.2.6/gitbook/provider/known-issues.md0000644000175000017500000000117314120404223021354 0ustar marcusmarcus## Known Issues ### Expiring Refresh Tokens oidc-agent assumes that refresh tokens do not expire. But some providers might use refresh tokens that expire after a certain time or when they are not used for a specific time. To prevent the latter use oidc-agent / oidc-token regularly (you also can use a cron job). oidc-agent is able to update a stored refresh token. However, therefore it has to receive a new refresh token from the provider. If a refresh token expired (e.g. because the token was used within the lifetime of that token), use `oidc-gen --reauthenticate ` to re-authenticate and update the refresh token. oidc-agent-4.2.6/gitbook/provider/helmholtz.md0000644000175000017500000000636714170031216020732 0ustar marcusmarcus## Helmholtz AAI Helmholtz AAI does not support dynamic client registration, but there is a preregistered public client which can be used so that account configuration is as easy as with dynamic client registration. ### Use Preregistered Public Client Enter the following command and follow the instructions to take advantage of the preregistered public client: ``` $ oidc-gen --pub --issuer https://login.helmholtz.de/oauth2/ \ --scope "email \ eduperson_scoped_affiliation \ eduperson_unique_id \ eduperson_assurance \ eduperson_entitlement \ ``` You will need to follow the OIDC-flow, which usually involves authentication in a web-browser. If the browser does not start, you can copy paste the displayed URL. ``` $ oidc-gen --pub --issuer https://login.helmholtz.de/oauth2/ [...] Space delimited list of scopes [openid profile offline_access]: Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] Polling oidc-agent to get the generated account configuration ..... success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. ``` Finally, you will be be asked for a password on the commandline to safely store your credentials. ``` Enter encryption password for account configuration '': Confirm encryption Password: ``` **Note**: You need to run the webbrowser on the same host as the `oidc-gen` command. \ If you operate on a remote machine, you need to use the device code flow, by adding `--flow=device` to the above commandline. Advanced users may succeed by otherwise ensuring that the browser you are using can connect to the host on which `oidc-gen` and `oidc-agent` run on ports 4242, 8080 or 43985. ### Manual Client registration - Make sure you **don’t** have an active login in unity and visit the /home endpoint (i.e. https://login.helmholtz.de/home ) - **Don't login** - Click "Register a new account" on the top right - Click "Oauth2/OIDC client Registration" - Specify the required information, but note the following: - "User name" is your `client_id` - Needs to be globally unique, "`oidc-agent`" will clash. Use "`oidc-agent_`" instead. You Must Not include a colon. - "Password credential" is your `client_secret` - "Service Admin Contact": `Your email address` - "Service Security Contact": `Your email address` - "Service DPS URL": `https://github.com/indigo-dc/oidc-agent/blob/master/PRIVACY` - "Service Description": `https://github.com/indigo-dc/oidc-agent` - "OAuth client return URL (1)": `http://localhost:4242` - click "`+`" to add more URLs - "OAuth client return URL (2)": `http://localhost:8080` - "OAuth client return URL (3)": `http://localhost:43985` - "OAuth client return URL (4)": `edu.kit.data.oidc-agent:/redirect` Note also that you have to enter at least one valid redirect uri, even if they are not mandated by Helmholtz AAI (see [Client Configuration Values](client-configuration-values.md#redirect-uri) for more information). After the client is registered, call oidc-gen with the `-m` flag and enter the required information. oidc-agent-4.2.6/gitbook/provider/client-configuration-values.md0000644000175000017500000000533714120404223024335 0ustar marcusmarcus# Client Configuration Values When registering a client manually you might have to provide quite a number of specific configuration values. And even when using dynamic client registration `oidc-gen` prompts you for some values. If you are not familiar with one of these values, please check this section. When registering a client an OpenID Provider might be using default values for some of these configurations so you might not have to provide all of them. ## Scope OpenID Connect clients use scope values to specify what access privileges are being requested for access tokens. Required scopes for oidc-agent are: `openid` and `offline_access`. Additional scopes can be registered if needed. Most likely you also want to register at least the `profile` scope. When using dynamic client registration the user will be prompted to enter scopes that will be registered with that client. The keyword `max` can be used to request all supported scopes. Example Scope: `openid profile offline_access` ## Redirect Uri The Redirect Uri is used during the Authorization Code Flow. The Redirect Uri must be of the following scheme: `http://localhost:` where `` should be an available port. It is also possible to specify an additional path, e.g. `http://localhost:8080/redirect`, but this is not required. It is important that this port is not used when generating the account configuration with oidc-gen. Multiple Redirect Uris can be registered to have a backup port if the first one may be already in use. `oidc-gen` also supports a custom redirect scheme, that can be used to redirect directly to oidc-gen. In that case the redirect uri has to be of the form `edu.kit.data.oidc-agent:/`. We recommend registering the following redirect uris: - `http://localhost:4242` - `http://localhost:8080` - `http://localhost:43985` - `edu.kit.data.oidc-agent:/redirect` Note: Only pass the `edu.kit.data.oidc-agent:/redirect` uri to oidc-gen, if you wish to directly redirect to oidc-gen without using a webserver started by oidc-agent. ## Response Type The following response types must be registered: - 'token' when using the Password Flow (see also [flow](../oidc-gen/options.md#password-flow)) - 'code' when using the Authorization Code Flow (see also [flow](../oidc-gen/options.md#authorization-code-flow)) ## Grant Type The following grant types must be registered: - 'refresh_token' if available - 'authorization_code' when using the Authorization Code Flow (see also [flow](../oidc-gen/options.md#authorization-code-flow)) - 'password' when using the Password Flow (see also [flow](../oidc-gen/options.md#password-flow)) - 'urn:ietf:params:oauth:grant-type:device_code' when using the Device Flow (see also [flow](../oidc-gen/options.md#device-flow)) oidc-agent-4.2.6/gitbook/provider/elixir.md0000644000175000017500000000250414120404223020202 0ustar marcusmarcus## Elixir Elixir supports dynamic registration, but dynamically registered clients will not have any scopes. Therefore users have to either register a client manually (and get approval for the needed scopes) or use a preregistered public client (recommended). Example: ``` $ oidc-gen --pub [...] Issuer [https://login.elixir-czech.org/oidc/]: Space delimited list of scopes [openid profile offline_access]: Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. Enter encryption password for account configuration '': Confirm encryption Password: ``` ### Advanced options #### Manual Client Registration If you register a client manually, please see https://docs.google.com/document/d/1vOyW4dLVozy7oQvINYxHheVaLvwNsvvghbiKTLg7RbY/ #### Device Flow To use the device flow with Elixir, the client has to have the device grant type registered. This is the case for our public client, however, it might most likely not be the case for a manually registered client. To use the device flow instead of the authorization code flow with the preregistered public client include the `--flow=device --pub` options. oidc-agent-4.2.6/gitbook/provider/google.md0000644000175000017500000000236414167074355020212 0ustar marcusmarcus## Google Google does not support dynamic client registration, but there is a preregistered public client so that account configuration generation is as easy as with dynamic client registration. ### Quickstart Example: ``` $ oidc-gen --pub --issuer https://accounts.google.com/ --flow=device The following scopes are supported: openid profile Scopes or 'max' (space separated) [openid profile]: [...] Using a browser on any device, visit: https://www.google.com/device [...] success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. Enter encryption password for account configuration '': Confirm encryption Password: ``` ### Advanced options #### Manual Client registration A client can be registered manually at There is documentation on how to do this at (just the first section "Setting up OAuth 2.0"). After the client registration you can download the client configuration as a json file. You can pass this file to `oidc-gen` using the `-f` flag. If you don't do this you have to enter the configuration manually (you than have to call `oidc-gen` with the `-m` flag). oidc-agent-4.2.6/gitbook/provider/hbp.md0000644000175000017500000000203414120404223017455 0ustar marcusmarcus## Human Brain Project (HBP) HBP supports dynamic registration, but has a protected registration endpoint. Therefore, a user has to be a member of the Human Brain Project and has to pass an initial access token to oidc-gen using the `--at` option. One way to obtain such an access token is using [WaTTS](https://watts.data.kit.edu/). Example: ``` $ oidc-gen --at= [...] Issuer [https://services.humanbrainproject.eu/oidc/]: Space delimited list of scopes [openid profile offline_access]: Registering Client ... Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. Enter encryption password for account configuration '': Confirm encryption Password: ``` Alternatively it is also possible to use a preregistered public client by using the `--pub` option (`--at` is not required in that case). oidc-agent-4.2.6/gitbook/provider/kit.md0000644000175000017500000000266414120404223017504 0ustar marcusmarcus## KIT The KIT OIDP supports dynamic client registration, but a special access token is required as authorization. The easiest way is too use the preregistered public client. ### Quickstart Example: ``` $ oidc-gen --pub [...] Issuer [https://oidc.scc.kit.edu/auth/realms/kit/]: Space delimited list of scopes [openid profile offline_access]: Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. Enter encryption password for account configuration '': Confirm encryption Password: ``` The KIT OpenID Provider issues a new refresh token when the current refresh token was used in the refresh flow (whenever a new access token is issued). When the refresh token changes oidc-agent has to update the client configuration file and therefore needs the encryption password. Because with rotating refresh tokens, this will happen quite often it is recommended to allow oidc-agent to keep the password in memory by specifying the `--pw-store` option when loading the account configuration with `oidc-add`. ### Advanced options To get an initial access token please contact the [provider](https://www.scc.kit.edu/dienste/openid-connect.php). The token can then be used as authorization through the `--at` option. oidc-agent-4.2.6/gitbook/provider/b2access.md0000644000175000017500000000246614120404223020402 0ustar marcusmarcus## B2ACCESS B2ACCESS does not support dynamic client registration and you have to register a client manually at or (depending on the issuer url). There is documentation on how to do this at After the client registration call oidc-gen with the `-m` flag and enter the required information. **Note**: In general for B2ACCESS and UNITY OPs the following information may be helpful (depending on the instance you use) - `User Name` is the OIDC `client_id` (you can choose it) - `Password` is the OIDC `client_secret` (you choose it) - `Email Address` is an email address for contacting the admin of the service - `Service Security Contact` is the security responsible of the service. This may be additional people, for example in a hosted VM setup - `Site Security Contact`is your computer centre security contact. Typically your CERT. - Service PP URL: This is your Privacy Policy (PP). Required by law. Find a [PP template here](policies/README.md) - The `well_known` configuration of `login` is here: ``` /oauth2/.well-known/openid-configuration ``` oidc-agent-4.2.6/gitbook/provider/provider.md0000644000175000017500000000043314120404223020537 0ustar marcusmarcus# Integrate With Different Providers In this section we describe how to generate a working account configuration for some of the supported OpenID Providers. If you have to register a client manually check the [Client Configuration Values](client-configuration-values.md) section. oidc-agent-4.2.6/gitbook/provider/eduteams.md0000644000175000017500000000146514120404223020522 0ustar marcusmarcus oidc-agent-4.2.6/gitbook/provider/iam.md0000644000175000017500000000304514120404223017455 0ustar marcusmarcus## IAM (INDIGO/DEEP/WLCG) IAM supports dynamic registration and a simple call to oidc-gen is therefore enough to register a client and generate the account configuration. ### Quickstart Example: ``` $ oidc-gen [...] Issuer [https://iam-test.indigo-datacloud.eu/]: Space delimited list of scopes [openid profile offline_access]: Registering Client ... Generating account configuration ... accepted To continue and approve the registered client visit the following URL in a Browser of your choice: https://[...] [...] success The generated account config was successfully added to oidc-agent. You don't have to run oidc-add. Enter encryption password for account configuration '': Confirm encryption Password: ``` ### Advanced options Instead of using the authorization code flow one could also use the password flow or device flow instead. #### Password Flow Using IAM the password grant type is not supported in dynamic client registration. The client is registered without it and you have to contact the provider to update the client config manually. After that is done, you can run oidc-gen again with the same shortname. oidc-gen should find a temp file and continue the account configuration generation. Afterwards the config is added to oidc-agent and can be used by oidc-add normally to add and remove the account configuration from the agent. You have to provide the `--flow=password` option to all calls to `oidc-gen`. #### Device Flow To use the device flow with IAM simply include the `--flow=device` option when calling `oidc-gen`. oidc-agent-4.2.6/gitbook/SUMMARY.md0000644000175000017500000000617514167074355016245 0ustar marcusmarcus# Summary * [Introduction](README.md) * [Quickstart](quickstart.md) * [Installation]() * [Linux](installation/install.md) * [MacOS](macos/installation.md) * [Configuration](configure.md) * [oidc-agent Directory](configuration/directory.md) * [Set Default Account for a Provider](configuration/default-accounts.md) * [oidc-agent Integration](configuration/integration.md) * [Agent Forwarding](configuration/forwarding.md) * [Other Configuration](configuration/other.md) * [Usage](user.md) * [oidc-agent](oidc-agent/oidc-agent.md) * [Starting oidc-agent](oidc-agent/start.md) * [General Usage](oidc-agent/general.md) * [Detailed Information About All Options](oidc-agent/options.md) * [oidc-agent-service](oidc-agent-service/oidc-agent-service.md) * [oidc-keychain](oidc-keychain/oidc-keychain.md) * [General Usage](oidc-keychain/general.md) * [Detailed Information About All Options](oidc-keychain/options.md) * [oidc-gen](oidc-gen/oidc-gen.md) * [General Usage](oidc-gen/general.md) * [Detailed Information About All Options](oidc-gen/options.md) * [Integrate With Different Providers](provider/provider.md) * [B2Access](provider/b2access.md) * [EGI](provider/egi.md) * [Elixir](provider/elixir.md) * [Google](provider/google.md) * [HBP](provider/hbp.md) * [Helmholtz AAI](provider/helmholtz.md) * [IAM (INDIGO/DEEP)](provider/iam.md) * [KIT](provider/kit.md) * [Any Other Provider](provider/general.md) * [Known Issues](provider/known-issues.md) * [Client Configuration Values](provider/client-configuration-values.md) * [oidc-add](oidc-add/oidc-add.md) * [General Usage](oidc-add/general.md) * [Detailed Information About All Options](oidc-add/options.md) * [oidc-token](oidc-token/oidc-token.md) * [General Usage](oidc-token/general.md) * [Detailed Information About All Options](oidc-token/options.md) * [Other Applications Using oidc-agent](agent-clients.md) * [Tips](tips.md) * [oidc-agent-server](oidc-agent-server/oidc-agent-server.md) * [MAC OS](macos/macos.md) * [State of Feature Support](macos/state.md) * [Installation](macos/installation.md) * [Security]() * [Privilege Separation & Architecture](security/privilege-separation.md) * [Account Configuration Files](security/account-configs.md) * [Credentials](security/credentials.md) * [Memory](security/memory.md) * [Agent Locking](security/agent-locking.md) * [Communication](security/communication.md) * [Encryption Passwords](security/encryption-passwords.md) * [Autounload (Lifetime)](security/autounload.md) * [User Confirmation](security/confirmation.md) * [Tracing](security/tracing.md) * [seccomp](security/seccomp.md) * [Final Note](security/final.md) * [API](api/api.md) * [liboidc-agent4](api/api-c.md) * [liboidcagent-go](api/api-go.md) * [liboidcagent-py](api/api-py.md) * [IPC-API](api/api-ipc.md) oidc-agent-4.2.6/gitbook/README.md0000644000175000017500000000237214120404223016014 0ustar marcusmarcus![oidc-agent logo](https://raw.githubusercontent.com/indigo-dc/oidc-agent/master/logo_wide.png) # oidc-agent oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command line. We followed the [`ssh-agent`](https://www.openssh.com/) design, so users can handle OIDC tokens in a similar way as they do with ssh keys. `oidc-agent` is usually started in the beginning of an X-session or a login session. Through use of environment variables the agent can be located and used to handle OIDC tokens. The agent initially does not have any account configurations loaded. You can load an account configuration by using `oidc-add`. Multiple account configurations may be loaded in `oidc-agent` concurrently. `oidc-add` is also used to remove a loaded configuration from `oidc-agent`. `oidc-gen` is used to initially generate an account configurations file [(Help for different providers)](provider/provider.md). We have a low-traffic **mailing list** with updates such as critical security incidents and new releases: [Subscribe oidc-agent-user](https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user) Current releases are available at [GitHub](https://github.com/indigo-dc/oidc-agent/releases) or http://repo.data.kit.edu/ oidc-agent-4.2.6/gitbook/oidc-keychain/0000755000175000017500000000000014120404223017240 5ustar marcusmarcusoidc-agent-4.2.6/gitbook/oidc-keychain/oidc-keychain.md0000644000175000017500000000142314120404223022271 0ustar marcusmarcus# oidc-keychain We recommend usage of [oidc-agent-service](../oidc-agent-service/oidc-agent-service.md) instead. oidc-keychain enables re-using [`oidc-agent`](../oidc-agent/oidc-agent.md) between login sessions. It stores oidc-agent environment variables in a file and takes care of starting oidc-agent when needed, loading any given accounts when needed (using [`oidc-add`](../oidc-add/oidc-add.md)), and setting the `OIDCD_PID` and `OIDC_SOCK` environment variables. It is commonly used inside `.bash_profile` or similar to start oidc-agent when needed. For example this line in `.bash_profile` ``` eval `oidc-keychain --accounts ` ``` will start oidc-agent when needed, load the account if it isn't already loaded, and set the oidc environment variables. oidc-agent-4.2.6/gitbook/oidc-keychain/options.md0000644000175000017500000000070714120404223021261 0ustar marcusmarcus## Detailed Information About All Options * [`--accounts`](#accounts) * [`--kill`](#kill) ### `--accounts` Makes sure that the given ACCOUNT short names are loaded by calling oidc-add when they aren't already loaded. ### `--kill` Kills any running oidc-agent, whether or not the environment variables are loaded in the current shell. When used in backquotes, this will unset the `OIDCD_PID` and `OIDC_SOCK` environment variables in the current shell. oidc-agent-4.2.6/gitbook/oidc-keychain/general.md0000644000175000017500000000051114120404223021174 0ustar marcusmarcus## General Usage ``` Usage: oidc-keychain [-?|--help|--usage|-V|--version] [-k|--kill] or: oidc-keychain [oidc-agent options] [--accounts ACCOUNT ...] ``` Any given oidc-agent options will get passed to oidc-agent when it needs to be started. See [Detailed Information About All Options](options.md) for more information. oidc-agent-4.2.6/Makefile0000644000175000017500000007763314170031216014556 0ustar marcusmarcusUNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) MAC_OS = 1 endif ifeq (, $(shell which dpkg-buildflags 2>/dev/null)) NODPKG = 1 endif # Where to store rpm source tarball RPM_OUTDIR =rpm/rpmbuild/SOURCES # Executable names AGENT = oidc-agent GEN = oidc-gen ADD = oidc-add CLIENT = oidc-token KEYCHAIN = oidc-keychain AGENT_SERVICE = oidc-agent-service PROMPT = oidc-prompt VERSION ?= $(shell cat VERSION) # DIST = $(lsb_release -cs) LIBMAJORVERSION ?= $(shell echo $(VERSION) | cut -d '.' -f 1) # Generated lib version / name LIBVERSION = $(VERSION) ifdef MAC_OS SONAME = liboidc-agent.$(LIBMAJORVERSION).dylib SHARED_LIB_NAME_FULL = liboidc-agent.$(LIBVERSION).dylib SHARED_LIB_NAME_SO = $(SONAME) SHARED_LIB_NAME_SHORT = liboidc-agent.dylib else SONAME = liboidc-agent.so.$(LIBMAJORVERSION) SHARED_LIB_NAME_FULL = liboidc-agent.so.$(LIBVERSION) SHARED_LIB_NAME_SO = $(SONAME) SHARED_LIB_NAME_SHORT = liboidc-agent.so endif # These are needed for the RPM build target: #BASEDIR = $(PWD) BASEDIR = $(shell pwd) BASENAME := $(notdir $(BASEDIR)) SRC_TAR = oidc-agent-$(VERSION).tar.gz PKG_NAME = oidc-agent # Local dir names SRCDIR = src OBJDIR = obj PICOBJDIR= pic-obj BINDIR = bin LIBDIR = lib APILIB = $(LIBDIR)/api MANDIR = man CONFDIR = config PROVIDERCONFIG = issuer.config PUBCLIENTSCONFIG = pubclients.config SERVICECONFIG = oidc-agent-service.options TESTSRCDIR = test/src TESTBINDIR = test/bin # USE_CJSON_SO ?= $(shell /sbin/ldconfig -N -v $(sed 's/:/ /g' <<< $LD_LIBRARY_PATH) 2>/dev/null | grep -i libcjson >/dev/null && echo 1 || echo 0) USE_CJSON_SO ?= 0 USE_LIST_SO ?= $(shell /sbin/ldconfig -N -v $(sed 's/:/ /g' <<< $LD_LIBRARY_PATH) 2>/dev/null | grep -i liblist >/dev/null && echo 1 || echo 0) ifeq ($(USE_CJSON_SO),1) DEFINE_USE_CJSON_SO = -DUSE_CJSON_SO endif ifeq ($(USE_LIST_SO),1) DEFINE_USE_LIST_SO = -DUSE_LIST_SO endif ifndef MAC_OS DIALOGTOOL ?= yad else DIALOGTOOL ?= pashua endif LSODIUM = -lsodium LARGP = -largp LMICROHTTPD = -lmicrohttpd LCURL = -lcurl LSECCOMP = -lseccomp LSECRET = -lsecret-1 LGLIB = -lglib-2.0 LLIST = -llist LCJSON = -lcjson LQR = -lqrencode LAGENT = -l:$(SHARED_LIB_NAME_FULL) ifdef MAC_OS LAGENT = -loidc-agent.$(LIBVERSION) endif # Compiler options CC := $(CC) # compiling flags here CFLAGS := $(CFLAGS) -g -std=c99 -I$(SRCDIR) -I$(LIBDIR) -Wall -Wextra -fno-common ifndef MAC_OS ifndef NODPKG CFLAGS +=$(shell dpkg-buildflags --get CPPFLAGS) CFLAGS +=$(shell dpkg-buildflags --get CFLAGS) endif CFLAGS += $(shell pkg-config --cflags libsecret-1) CFLAGS += $(shell pkg-config --cflags libseccomp) endif TEST_CFLAGS = $(CFLAGS) -I. # Linker options LINKER := $(CC) ifdef MAC_OS LFLAGS = $(LSODIUM) $(LARGP) else LFLAGS := $(LDFLAGS) $(LSODIUM) $(LSECCOMP) -fno-common -Wl,-z,now ifndef NODPKG LFLAGS +=$(shell dpkg-buildflags --get LDFLAGS) endif endif ifeq ($(USE_CJSON_SO),1) LFLAGS += $(LCJSON) endif ifeq ($(USE_LIST_SO),1) LFLAGS += $(LLIST) endif AGENT_LFLAGS = $(LCURL) $(LMICROHTTPD) $(LQR) $(LFLAGS) ifndef MAC_OS AGENT_LFLAGS += $(LSECRET) $(LGLIB) endif GEN_LFLAGS = $(LFLAGS) $(LMICROHTTPD) $(LQR) ADD_LFLAGS = $(LFLAGS) ifdef MAC_OS CLIENT_LFLAGS = -L$(APILIB) $(LARGP) $(LAGENT) $(LSODIUM) else CLIENT_LFLAGS := $(LDFLAGS) -L$(APILIB) $(LAGENT) $(LSODIUM) $(LSECCOMP) ifndef NODPKG CLIENT_LFLAGS += $(shell dpkg-buildflags --get LDFLAGS) endif endif LIB_LFLAGS := $(LDFLAGS) -lc $(LSODIUM) ifndef MAC_OS ifndef NODPKG LIB_LFLAGS += $(shell dpkg-buildflags --get LDFLAGS) endif endif ifeq ($(USE_CJSON_SO),1) CLIENT_LFLAGS += $(LCJSON) LIB_LFLAGS += $(LCJSON) endif ifeq ($(USE_LIST_SO),1) CLIENT_LFLAGS += $(LLIST) LIB_LFLAGS += $(LLIST) endif TEST_LFLAGS = $(LFLAGS) $(shell pkg-config --cflags --libs check) # Install paths ifndef MAC_OS PREFIX ?= BIN_PATH ?=$(PREFIX)/usr# /bin is appended later BIN_AFTER_INST_PATH ?=$(BIN_PATH)# needed for debian package and desktop file exec PROMPT_BIN_PATH ?=$(PREFIX)/usr# /bin is appended later LIB_PATH ?=$(PREFIX)/usr/lib/x86_64-linux-gnu LIBDEV_PATH ?=$(PREFIX)/usr/lib/x86_64-linux-gnu INCLUDE_PATH ?=$(PREFIX)/usr/include/x86_64-linux-gnu MAN_PATH ?=$(PREFIX)/usr/share/man PROMPT_MAN_PATH ?=$(PREFIX)/usr/share/man CONFIG_PATH ?=$(PREFIX)/etc CONFIG_AFTER_INST_PATH ?=$(CONFIG_PATH) BASH_COMPLETION_PATH ?=$(PREFIX)/usr/share/bash-completion/completions DESKTOP_APPLICATION_PATH ?=$(PREFIX)/usr/share/applications XSESSION_PATH ?=$(PREFIX)/etc/X11 else PREFIX ?=/usr/local BIN_PATH ?=$(PREFIX)# /bin is appended later BIN_AFTER_INST_PATH ?=$(BIN_PATH)# needed for debian package and desktop file exec PROMPT_BIN_PATH ?=$(PREFIX)# /bin is appended later LIB_PATH ?=$(PREFIX)/lib LIBDEV_PATH ?=$(PREFIX)/lib INCLUDE_PATH ?=$(PREFIX)/include MAN_PATH ?=$(PREFIX)/share/man PROMPT_MAN_PATH ?=$(PREFIX)/share/man CONFIG_PATH ?=$(PREFIX)/etc CONFIG_AFTER_INST_PATH ?=$(CONFIG_PATH) endif # Define sources SRC_SOURCES := $(sort $(shell find $(SRCDIR) -name "*.c")) ifneq ($(USE_CJSON_SO),1) LIB_SOURCES += $(LIBDIR)/cJSON/cJSON.c endif ifneq ($(USE_LIST_SO),1) LIB_SOURCES += $(LIBDIR)/list/list.c $(LIBDIR)/list/list_iterator.c $(LIBDIR)/list/list_node.c endif SOURCES := $(SRC_SOURCES) $(LIB_SOURCES) GENERAL_SOURCES := $(sort $(shell find $(SRCDIR)/utils -name "*.c") $(shell find $(SRCDIR)/account -name "*.c") $(shell find $(SRCDIR)/ipc -name "*.c") $(shell find $(SRCDIR)/defines -name "*.c")) ifndef MAC_OS GENERAL_SOURCES += $(sort $(shell find $(SRCDIR)/privileges -name "*.c")) endif AGENT_SOURCES_TMP := $(sort $(shell find $(SRCDIR)/$(AGENT) -name "*.c")) ifdef MAC_OS AGENT_SOURCES= $(filter-out $(SRCDIR)/$(AGENT)/oidcp/passwords/keyring.c, $(AGENT_SOURCES_TMP)) else AGENT_SOURCES = $(AGENT_SOURCES_TMP) endif GEN_SOURCES := $(sort $(shell find $(SRCDIR)/$(GEN) -name "*.c")) ADD_SOURCES := $(sort $(shell find $(SRCDIR)/$(ADD) -name "*.c")) CLIENT_SOURCES := $(sort $(filter-out $(SRCDIR)/$(CLIENT)/api.c $(SRCDIR)/$(CLIENT)/parse.c, $(shell find $(SRCDIR)/$(CLIENT) -name "*.c"))) KEYCHAIN_SOURCES := $(SRCDIR)/$(KEYCHAIN)/$(KEYCHAIN) TEST_SOURCES := $(sort $(filter-out $(TESTSRCDIR)/main.c, $(shell find $(TESTSRCDIR) -name "*.c"))) PROMPT_SRCDIR := $(SRCDIR)/$(PROMPT) AGENTSERVICE_SRCDIR := $(SRCDIR)/$(AGENT_SERVICE) # Define objects ALL_OBJECTS := $(SRC_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) AGENT_OBJECTS := $(AGENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/$(GEN)/qr.o GEN_OBJECTS := $(GEN_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(OBJDIR)/oidc-agent/httpserver/termHttpserver.o $(OBJDIR)/oidc-agent/httpserver/running_server.o $(OBJDIR)/oidc-agent/oidc/device_code.o $(OBJDIR)/$(CLIENT)/parse.o $(OBJDIR)/$(CLIENT)/api.o $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) ADD_OBJECTS := $(ADD_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) API_OBJECTS := $(OBJDIR)/$(CLIENT)/api.o $(OBJDIR)/$(CLIENT)/parse.o $(OBJDIR)/ipc/ipc.o $(OBJDIR)/ipc/cryptCommunicator.o $(OBJDIR)/ipc/cryptIpc.o $(OBJDIR)/utils/crypt/crypt.o $(OBJDIR)/utils/crypt/ipcCryptUtils.o $(OBJDIR)/utils/json.o $(OBJDIR)/utils/oidc_error.o $(OBJDIR)/utils/memory.o $(OBJDIR)/utils/string/stringUtils.o $(OBJDIR)/utils/colors.o $(OBJDIR)/utils/printer.o $(OBJDIR)/utils/ipUtils.o $(OBJDIR)/utils/listUtils.o $(OBJDIR)/utils/logger.o $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) ifdef MAC_OS API_OBJECTS += $(OBJDIR)/utils/file_io/oidc_file_io.o $(OBJDIR)/utils/file_io/file_io.o endif PIC_OBJECTS := $(API_OBJECTS:$(OBJDIR)/%=$(PICOBJDIR)/%) CLIENT_OBJECTS := $(CLIENT_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(API_OBJECTS) $(OBJDIR)/utils/disableTracing.o ifndef MAC_OS CLIENT_OBJECTS += $(OBJDIR)/utils/file_io/oidc_file_io.o $(OBJDIR)/utils/file_io/file_io.o $(OBJDIR)/privileges/privileges.o $(OBJDIR)/privileges/token_privileges.o endif rm = rm -f # RULES .PHONY: all all: build man -include docker/docker.mk # Compiling .PHONY: build build: create_obj_dir_structure $(BINDIR)/$(AGENT) $(BINDIR)/$(GEN) $(BINDIR)/$(ADD) $(BINDIR)/$(CLIENT) $(BINDIR)/$(AGENT_SERVICE) $(BINDIR)/$(KEYCHAIN) $(BINDIR)/$(PROMPT) ## pull in dependency info for *existing* .o files -include $(ALL_OBJECTS:.o=.d) ## Compile and generate depencency info $(OBJDIR)/$(CLIENT)/$(CLIENT).o : $(APILIB)/$(SHARED_LIB_NAME_FULL) $(OBJDIR)/%.o : $(SRCDIR)/%.c @$(CC) $(CFLAGS) -c $< -o $@ -DVERSION=\"$(VERSION)\" -DCONFIG_PATH=\"$(CONFIG_AFTER_INST_PATH)\" $(DEFINE_USE_CJSON_SO) $(DEFINE_USE_LIST_SO) @# Create dependency infos @{ \ set -e ;\ depFileName=$(OBJDIR)/$*.d ;\ $(CC) -MM $(CFLAGS) $< -o $${depFileName} ;\ mv -f $${depFileName} $${depFileName}.tmp ;\ sed -e 's|.*:|$@:|' < $${depFileName}.tmp > $${depFileName} ;\ cp -f $${depFileName} $${depFileName}.tmp ;\ sed -e 's/.*://' -e 's/\\$$//' < $${depFileName}.tmp | fmt -1 | \ sed -e 's/^ *//' -e 's/$$/:/' >> $${depFileName} ;\ rm -f $${depFileName}.tmp ;\ } @echo "Compiled "$<" successfully!" ## Compile lib sources $(OBJDIR)/%.o : $(LIBDIR)/%.c @$(CC) $(CFLAGS) -c $< -o $@ @echo "Compiled "$<" successfully!" ## Compile position independent code $(PICOBJDIR)/%.o : $(SRCDIR)/%.c @$(CC) $(CFLAGS) -fpic -fvisibility=hidden -c $< -o $@ -DVERSION=\"$(VERSION)\" -DCONFIG_PATH=\"$(CONFIG_AFTER_INST_PATH)\" @echo "Compiled "$<" with pic successfully!" $(PICOBJDIR)/%.o : $(LIBDIR)/%.c @$(CC) $(CFLAGS) -fpic -fvisibility=hidden -c $< -o $@ @echo "Compiled "$<" with pic successfully!" # Linking $(BINDIR)/$(AGENT): create_obj_dir_structure $(AGENT_OBJECTS) $(BINDIR) @$(LINKER) $(AGENT_OBJECTS) $(AGENT_LFLAGS) -o $@ @echo "Linking "$@" complete!" $(BINDIR)/$(GEN): create_obj_dir_structure $(GEN_OBJECTS) $(BINDIR) @$(LINKER) $(GEN_OBJECTS) $(GEN_LFLAGS) -o $@ @echo "Linking "$@" complete!" $(BINDIR)/$(ADD): create_obj_dir_structure $(ADD_OBJECTS) $(BINDIR) @$(LINKER) $(ADD_OBJECTS) $(ADD_LFLAGS) -o $@ @echo "Linking "$@" complete!" $(BINDIR)/$(CLIENT): create_obj_dir_structure $(CLIENT_OBJECTS) $(APILIB)/$(SHARED_LIB_NAME_FULL) $(BINDIR) @$(LINKER) $(CLIENT_OBJECTS) $(CLIENT_LFLAGS) -o $@ @echo "Linking "$@" complete!" $(BINDIR)/$(KEYCHAIN): $(KEYCHAIN_SOURCES) @cat $(KEYCHAIN_SOURCES) >$@ && chmod 755 $@ @echo "Building "$@" complete!" $(BINDIR)/$(PROMPT): $(PROMPT_SRCDIR)/$(PROMPT) @sed -n '/OIDC_INCLUDE/!p;//q' $< >$@ @cat $(PROMPT_SRCDIR)/$(PROMPT)_$(DIALOGTOOL) >>$@ @sed '1,/OIDC_INCLUDE/d' $< >>$@ @chmod 755 $@ @echo "Building "$@" complete!" $(BINDIR)/$(AGENT_SERVICE): $(AGENTSERVICE_SRCDIR)/$(AGENT_SERVICE) $(AGENTSERVICE_SRCDIR)/options @sed -n '/OIDC_INCLUDE/!p;//q' $< | sed 's!/usr/bin/oidc-agent!$(BIN_AFTER_INST_PATH)/bin/$(AGENT)!' >$@ @sed 's!/etc/oidc-agent!$(CONFIG_AFTER_INST_PATH)/oidc-agent!' $(AGENTSERVICE_SRCDIR)/options >>$@ @sed '1,/OIDC_INCLUDE/d' $< >>$@ @chmod 755 $@ @echo "Building "$@" complete!" # Phony Installer .PHONY: install ifndef MAC_OS install: install_bin install_man install_conf install_bash install_priv install_scheme_handler install_xsession_script else install: install_bin install_man install_conf install_scheme_handler endif @echo "Installation complete!" .PHONY: install_bin install_bin: $(BIN_PATH)/bin/$(AGENT) $(BIN_PATH)/bin/$(GEN) $(BIN_PATH)/bin/$(ADD) $(BIN_PATH)/bin/$(CLIENT) $(BIN_PATH)/bin/$(KEYCHAIN) $(BIN_PATH)/bin/$(AGENT_SERVICE) $(PROMPT_BIN_PATH)/bin/$(PROMPT) @echo "Installed binaries" .PHONY: install_conf install_conf: $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG) $(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG) $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG) @echo "Installed config files" .PHONY: install_priv install_priv: $(CONFDIR)/privileges/ @install -d $(CONFIG_PATH)/oidc-agent/privileges/ # ifdef MAC_OS @install -m 644 $(CONFDIR)/privileges/* $(CONFIG_PATH)/oidc-agent/privileges/ # else # @install -m 644 -D $(CONFDIR)/privileges/* $(CONFIG_PATH)/oidc-agent/privileges/ # endif @echo "installed privileges files" .PHONY: install_bash install_bash: $(BASH_COMPLETION_PATH)/$(AGENT) $(BASH_COMPLETION_PATH)/$(GEN) $(BASH_COMPLETION_PATH)/$(ADD) $(BASH_COMPLETION_PATH)/$(CLIENT) $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE) $(BASH_COMPLETION_PATH)/$(KEYCHAIN) @echo "Installed bash completion" .PHONY: install_man install_man: $(MAN_PATH)/man1/$(AGENT).1 $(MAN_PATH)/man1/$(GEN).1 $(MAN_PATH)/man1/$(ADD).1 $(MAN_PATH)/man1/$(CLIENT).1 $(MAN_PATH)/man1/$(AGENT_SERVICE).1 $(MAN_PATH)/man1/$(KEYCHAIN).1 $(PROMPT_MAN_PATH)/man1/$(PROMPT).1 @echo "Installed man pages!" .PHONY: install_lib install_lib: $(LIB_PATH)/$(SHARED_LIB_NAME_FULL) $(LIB_PATH)/$(SHARED_LIB_NAME_SO) @echo "Installed library" .PHONY: install_lib-dev install_lib-dev: $(LIB_PATH)/$(SHARED_LIB_NAME_FULL) $(LIB_PATH)/$(SHARED_LIB_NAME_SO) $(LIBDEV_PATH)/$(SHARED_LIB_NAME_SHORT) $(LIBDEV_PATH)/liboidc-agent.a $(INCLUDE_PATH)/oidc-agent/api.h $(INCLUDE_PATH)/oidc-agent/ipc_values.h $(INCLUDE_PATH)/oidc-agent/oidc_error.h $(INCLUDE_PATH)/oidc-agent/export_symbols.h @echo "Installed library dev" .PHONY: install_scheme_handler ifndef MAC_OS install_scheme_handler: $(DESKTOP_APPLICATION_PATH)/oidc-gen.desktop @echo "Installed scheme handler" else install_scheme_handler: @osacompile -o oidc-gen.app config/scheme_handler/oidc-gen.apple @(awk 'n>=2 {print a[n%2]} {a[n%2]=$$0; n=n+1}' oidc-gen.app/Contents/Info.plist ; cat $(CONFDIR)/scheme_handler/Info.plist.template ; tail -2 oidc-gen.app/Contents/Info.plist) > oidc-gen.app/Contents/Info.plist endif .PHONY: install_xsession_script install_xsession_script: $(XSESSION_PATH)/Xsession.d/91oidc-agent @echo "Installed xsession_script" .PHONY: post_install post_install: ifndef MAC_OS @ldconfig @update-desktop-database else @open -a oidc-gen #open the app one time so the handler is registered endif @echo "Post install completed" # Install files ## Binaries $(BIN_PATH)/bin/$(AGENT): $(BINDIR)/$(AGENT) $(BIN_PATH)/bin @install $< $@ $(BIN_PATH)/bin/$(GEN): $(BINDIR)/$(GEN) $(BIN_PATH)/bin @install $< $@ $(BIN_PATH)/bin/$(ADD): $(BINDIR)/$(ADD) $(BIN_PATH)/bin @install $< $@ $(BIN_PATH)/bin/$(CLIENT): $(BINDIR)/$(CLIENT) $(BIN_PATH)/bin @install $< $@ $(BIN_PATH)/bin/$(KEYCHAIN): $(BINDIR)/$(KEYCHAIN) $(BIN_PATH)/bin @install $< $@ $(BIN_PATH)/bin/$(AGENT_SERVICE): $(BINDIR)/$(AGENT_SERVICE) $(BIN_PATH)/bin @install $< $@ $(PROMPT_BIN_PATH)/bin/$(PROMPT): $(BINDIR)/$(PROMPT) $(PROMPT_BIN_PATH)/bin @install $< $@ ## Config $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG): $(CONFDIR)/$(PROVIDERCONFIG) $(CONFIG_PATH)/oidc-agent @install -m 644 $< $@ $(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG): $(CONFDIR)/$(PUBCLIENTSCONFIG) $(CONFIG_PATH)/oidc-agent @install -m 644 $< $@ $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG): $(CONFDIR)/$(SERVICECONFIG) $(CONFIG_PATH)/oidc-agent @install -m 644 $< $@ ## Bash completion $(BASH_COMPLETION_PATH)/$(AGENT): $(CONFDIR)/bash-completion/oidc-agent $(BASH_COMPLETION_PATH) @install -m 644 $< $@ $(BASH_COMPLETION_PATH)/$(GEN): $(BASH_COMPLETION_PATH) @ln -s $(AGENT) $@ $(BASH_COMPLETION_PATH)/$(ADD): $(BASH_COMPLETION_PATH) @ln -s $(AGENT) $@ $(BASH_COMPLETION_PATH)/$(CLIENT): $(BASH_COMPLETION_PATH) @ln -s $(AGENT) $@ $(BASH_COMPLETION_PATH)/$(KEYCHAIN): $(BASH_COMPLETION_PATH) @ln -s $(AGENT) $@ $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE): $(CONFDIR)/bash-completion/oidc-agent-service $(BASH_COMPLETION_PATH) @install -m 644 $< $@ ## Man pages $(MAN_PATH)/man1/$(AGENT).1: $(MANDIR)/$(AGENT).1 $(MAN_PATH)/man1 @install $< $@ $(MAN_PATH)/man1/$(GEN).1: $(MANDIR)/$(GEN).1 $(MAN_PATH)/man1 @install $< $@ $(MAN_PATH)/man1/$(ADD).1: $(MANDIR)/$(ADD).1 $(MAN_PATH)/man1 @install $< $@ $(MAN_PATH)/man1/$(CLIENT).1: $(MANDIR)/$(CLIENT).1 $(MAN_PATH)/man1 @install $< $@ $(MAN_PATH)/man1/$(AGENT_SERVICE).1: $(MANDIR)/$(AGENT_SERVICE).1 $(MAN_PATH)/man1 @install $< $@ $(MAN_PATH)/man1/$(KEYCHAIN).1: $(MANDIR)/$(KEYCHAIN).1 $(MAN_PATH)/man1 @install $< $@ $(PROMPT_MAN_PATH)/man1/$(PROMPT).1: $(MANDIR)/$(PROMPT).1 $(PROMPT_MAN_PATH)/man1 @install $< $@ ## Lib $(LIB_PATH)/$(SHARED_LIB_NAME_FULL): $(APILIB)/$(SHARED_LIB_NAME_FULL) $(LIB_PATH) @install $< $@ $(LIB_PATH)/$(SHARED_LIB_NAME_SO): $(LIB_PATH) @ln -sf $(SHARED_LIB_NAME_FULL) $@ $(LIBDEV_PATH)/$(SHARED_LIB_NAME_SHORT): $(LIBDEV_PATH) @ln -sf $(SHARED_LIB_NAME_SO) $@ $(INCLUDE_PATH)/oidc-agent/api.h: $(SRCDIR)/$(CLIENT)/api.h $(INCLUDE_PATH)/oidc-agent @install $< $@ $(INCLUDE_PATH)/oidc-agent/ipc_values.h: $(SRCDIR)/defines/ipc_values.h $(INCLUDE_PATH)/oidc-agent @install $< $@ $(INCLUDE_PATH)/oidc-agent/oidc_error.h: $(SRCDIR)/utils/oidc_error.h $(INCLUDE_PATH)/oidc-agent @install $< $@ $(LIBDEV_PATH)/liboidc-agent.a: $(APILIB)/liboidc-agent.a $(LIBDEV_PATH) @install $< $@ $(INCLUDE_PATH)/oidc-agent/export_symbols.h: $(SRCDIR)/$(CLIENT)/export_symbols.h $(INCLUDE_PATH)/oidc-agent @install $< $@ ## scheme handler $(DESKTOP_APPLICATION_PATH)/oidc-gen.desktop: $(CONFDIR)/scheme_handler/oidc-gen.desktop @install -D $< $@ @echo "Exec=x-terminal-emulator -e bash -c \"$(BIN_AFTER_INST_PATH)/bin/$(GEN) --codeExchange=%u; exec bash\"" >> $@ ## Xsession $(XSESSION_PATH)/Xsession.d/91oidc-agent: $(CONFDIR)/Xsession/91oidc-agent @install -m 644 -D $< $@ @sed -i -e 's!/usr/bin!$(BIN_AFTER_INST_PATH)/bin!g' $@ # Uninstall .PHONY: purge ifndef MAC_OS purge: uninstall uninstall_conf uninstall_priv else purge: uninstall uninstall_conf endif .PHONY: uninstall ifndef MAC_OS uninstall: uninstall_man uninstall_bin uninstall_bashcompletion uninstall_scheme_handler uninstall_xsession_script else uninstall: uninstall_man uninstall_bin uninstall_scheme_handler endif .PHONY: uninstall_bin uninstall_bin: @$(rm) $(BIN_PATH)/bin/$(AGENT) @$(rm) $(BIN_PATH)/bin/$(GEN) @$(rm) $(BIN_PATH)/bin/$(ADD) @$(rm) $(BIN_PATH)/bin/$(CLIENT) @$(rm) $(BIN_PATH)/bin/$(KEYCHAIN) @$(rm) $(BIN_PATH)/bin/$(AGENT_SERVICE) @$(rm) $(PROMPT_BIN_PATH)/bin/$(PROMPT) @echo "Uninstalled binaries" .PHONY: uninstall_man uninstall_man: @$(rm) $(MAN_PATH)/man1/$(AGENT).1 @$(rm) $(MAN_PATH)/man1/$(GEN).1 @$(rm) $(MAN_PATH)/man1/$(ADD).1 @$(rm) $(MAN_PATH)/man1/$(CLIENT).1 @$(rm) $(MAN_PATH)/man1/$(AGENT_SERVICE).1 @$(rm) $(MAN_PATH)/man1/$(KEYCHAIN).1 @$(rm) $(PROMPT_MAN_PATH)/man1/$(PROMPT).1 @echo "Uninstalled man pages!" .PHONY: uninstall_conf uninstall_conf: @$(rm) $(CONFIG_PATH)/oidc-agent/$(PROVIDERCONFIG) @$(rm) $(CONFIG_PATH)/oidc-agent/$(PUBCLIENTSCONFIG) @$(rm) $(CONFIG_PATH)/oidc-agent/$(SERVICECONFIG) @echo "Uninstalled config" .PHONY: uninstall_priv uninstall_priv: @$(rm) -r $(CONFIG_PATH)/oidc-agent/privileges/ @echo "Uninstalled privileges config files" .PHONY: uninstall_bashcompletion uninstall_bashcompletion: @$(rm) $(BASH_COMPLETION_PATH)/$(CLIENT) @$(rm) $(BASH_COMPLETION_PATH)/$(GEN) @$(rm) $(BASH_COMPLETION_PATH)/$(ADD) @$(rm) $(BASH_COMPLETION_PATH)/$(AGENT) @$(rm) $(BASH_COMPLETION_PATH)/$(AGENT_SERVICE) @$(rm) $(BASH_COMPLETION_PATH)/$(KEYCHAIN) @echo "Uninstalled bash completion" .PHONY: uninstall_lib uninstall_lib: @$(rm) $(LIB_PATH)/$(SHARED_LIB_NAME_FULL) @$(rm) $(LIB_PATH)/$(SHARED_LIB_NAME_SO) @echo "Uninstalled liboidc-agent" .PHONY: uninstall_libdev uninstall_libdev: uninstall_lib @$(rm) $(LIB_PATH)/$(SHARED_LIB_NAME_SHORT) @$(rm) -r $(INCLUDE_PATH)/oidc-agent/ @echo "Uninstalled liboidc-agent-dev" .PHONY: uninstall_scheme_handler uninstall_scheme_handler: ifndef MAC_OS @$(rm) $(DESKTOP_APPLICATION_PATH)/oidc-gen.desktop else @$(rm) -r oidc-gen.app/ endif @echo "Uninstalled scheme handler" .PHONY: uninstall_xsession_script uninstall_xsession_script: @$(rm) $(XSESSION_PATH)/Xsession.d/91oidc-agent @echo "Uninstalled xsession_script" # Man pages .PHONY: create_man create_man: $(MANDIR)/$(AGENT).1 $(MANDIR)/$(GEN).1 $(MANDIR)/$(ADD).1 $(MANDIR)/$(CLIENT).1 $(MANDIR)/$(AGENT_SERVICE).1 $(MANDIR)/$(KEYCHAIN).1 $(MANDIR)/$(PROMPT).1 @echo "Created man pages" $(MANDIR)/$(AGENT).1: $(MANDIR) $(BINDIR)/$(AGENT) $(SRCDIR)/h2m/$(AGENT).h2m @help2man $(BINDIR)/$(AGENT) -o $(MANDIR)/$(AGENT).1 -s 1 -N -i $(SRCDIR)/h2m/$(AGENT).h2m $(MANDIR)/$(GEN).1: $(MANDIR) $(BINDIR)/$(GEN) $(SRCDIR)/h2m/$(GEN).h2m @help2man $(BINDIR)/$(GEN) -o $(MANDIR)/$(GEN).1 -s 1 -N -i $(SRCDIR)/h2m/$(GEN).h2m $(MANDIR)/$(ADD).1: $(MANDIR) $(BINDIR)/$(ADD) $(SRCDIR)/h2m/$(ADD).h2m @help2man $(BINDIR)/$(ADD) -o $(MANDIR)/$(ADD).1 -s 1 -N -i $(SRCDIR)/h2m/$(ADD).h2m $(MANDIR)/$(CLIENT).1: $(MANDIR) $(BINDIR)/$(CLIENT) $(SRCDIR)/h2m/$(CLIENT).h2m $(LIB_PATH)/$(SHARED_LIB_NAME_SO) $(LIB_PATH)/$(SHARED_LIB_NAME_FULL) @export LD_LIBRARY_PATH=$(LIB_PATH):$$LD_LIBRARY_PATH && help2man $(BINDIR)/$(CLIENT) -o $(MANDIR)/$(CLIENT).1 -s 1 -N -i $(SRCDIR)/h2m/$(CLIENT).h2m $(MANDIR)/$(AGENT_SERVICE).1: $(MANDIR) $(BINDIR)/$(AGENT_SERVICE) $(SRCDIR)/h2m/$(AGENT_SERVICE).h2m @help2man $(BINDIR)/$(AGENT_SERVICE) -o $(MANDIR)/$(AGENT_SERVICE).1 -s 1 -N -i $(SRCDIR)/h2m/$(AGENT_SERVICE).h2m --no-discard-stderr $(MANDIR)/$(KEYCHAIN).1: $(MANDIR) $(BINDIR)/$(KEYCHAIN) $(SRCDIR)/h2m/$(KEYCHAIN).h2m @help2man $(BINDIR)/$(KEYCHAIN) -o $(MANDIR)/$(KEYCHAIN).1 -s 1 -N -i $(SRCDIR)/h2m/$(KEYCHAIN).h2m --no-discard-stderr $(MANDIR)/$(PROMPT).1: $(MANDIR) $(BINDIR)/$(PROMPT) $(SRCDIR)/h2m/$(PROMPT).h2m @help2man $(BINDIR)/$(PROMPT) -o $(MANDIR)/$(PROMPT).1 -s 1 -N -i $(SRCDIR)/h2m/$(PROMPT).h2m --no-discard-stderr # Library $(APILIB)/liboidc-agent.a: $(APILIB) $(API_OBJECTS) @ar -crs $@ $(API_OBJECTS) $(APILIB)/$(SHARED_LIB_NAME_FULL): create_picobj_dir_structure $(APILIB) $(PIC_OBJECTS) ifdef MAC_OS @$(LINKER) -dynamiclib -fpic -Wl, -o $@ $(PIC_OBJECTS) $(LIB_LFLAGS) else @$(LINKER) -shared -fpic -Wl,-z,defs,-soname,$(SONAME) -o $@ $(PIC_OBJECTS) $(LIB_LFLAGS) endif .PHONY: shared_lib shared_lib: $(APILIB)/$(SHARED_LIB_NAME_FULL) @echo "Created shared library" # Helpers $(LIB_PATH): @install -d $@ ifneq ($(LIB_PATH), $(LIBDEV_PATH)) $(LIBDEV_PATH): @install -d $@ endif $(INCLUDE_PATH)/oidc-agent: @install -d $@ $(BIN_PATH)/bin: @install -d $@ ifneq ($(BIN_PATH), $(PROMPT_BIN_PATH)) $(PROMPT_BIN_PATH)/bin: @install -d $@ endif $(CONFIG_PATH)/oidc-agent: @install -d $@ $(BASH_COMPLETION_PATH): @install -d $@ $(MAN_PATH)/man1: @install -d $@ ifneq ($(MAN_PATH), $(PROMPT_MAN_PATH)) $(PROMPT_MAN_PATH)/man1: @install -d $@ endif $(BINDIR): @mkdir -p $(BINDIR) $(MANDIR): @mkdir -p $(MANDIR) $(APILIB): @mkdir -p $(APILIB) $(OBJDIR): @mkdir -p $(OBJDIR) $(PICOBJDIR): @mkdir -p $(PICOBJDIR) $(TESTBINDIR): @mkdir -p $@ .PHONY: create_obj_dir_structure .NOTPARALLEL: create_obj_dir_structure create_obj_dir_structure: $(OBJDIR) @cd $(SRCDIR) && find . -type d -exec mkdir -p -- ../$(OBJDIR)/{} \; @cd $(LIBDIR) && find . -type d -exec mkdir -p -- ../$(OBJDIR)/{} \; .PHONY: create_picobj_dir_structure .NOTPARALLEL: create_picobj_dir_structure create_picobj_dir_structure: $(PICOBJDIR) @cd $(SRCDIR) && find . -type d -exec mkdir -p -- ../$(PICOBJDIR)/{} \; @cd $(LIBDIR) && find . -type d -exec mkdir -p -- ../$(PICOBJDIR)/{} \; # Cleaners .PHONY: clean clean: cleanobj cleanapi cleanpackage cleantest .PHONY: cleanobj cleanobj: @$(rm) -r $(OBJDIR) @$(rm) -r $(PICOBJDIR) .PHONY: cleanpackage cleanpackage: @$(rm) -r debian/.debhelper @$(rm) -r rpm/rpmbuild @$(rm) -r debian/files @$(rm) -r debian/liboidc-dev* @$(rm) -r debian/liboidc-agent2 @$(rm) -r debian/liboidc-agent2.debhelper.log @$(rm) -r debian/liboidc-agent2.substvars @$(rm) -r debian/liboidc-agent3 @$(rm) -r debian/liboidc-agent3.debhelper.log @$(rm) -r debian/liboidc-agent3.substvars @$(rm) -r debian/liboidc-agent4 @$(rm) -r debian/liboidc-agent4.debhelper.log @$(rm) -r debian/liboidc-agent4.substvars @$(rm) -r debian/liboidc-agent-dev @$(rm) -r debian/liboidc-agent-dev.debhelper.log @$(rm) -r debian/liboidc-agent-dev.substvars @$(rm) -r debian/oidc-agent @$(rm) -r debian/oidc-agent.debhelper.log @$(rm) -r debian/oidc-agent.substvars @$(rm) -r debian/oidc-agent-prompt @$(rm) -r debian/oidc-agent-prompt.debhelper.log @$(rm) -r debian/oidc-agent-prompt.substvars @$(rm) -r debian/oidc-agent-cli @$(rm) -r debian/oidc-agent-cli.debhelper.log @$(rm) -r debian/oidc-agent-cli.substvars @$(rm) -r debian/oidc-agent-desktop @$(rm) -r debian/oidc-agent-desktop.debhelper.log @$(rm) -r debian/oidc-agent-desktop.substvars .PHONY: cleantest cleantest: @$(rm) -r $(TESTBINDIR) .PHONY: distclean distclean: cleanobj clean @$(rm) -r $(BINDIR) @$(rm) -r $(MANDIR) .PHONY: cleanapi cleanapi: @$(rm) -r $(APILIB) .PHONY: remove remove: cleanobj cleanapi cleanpackage cleantest distclean # Packaging .PHONY: preparedeb preparedeb: clean @quilt pop -a || true @debian/rules clean ( cd ..; tar czf ${PKG_NAME}_${VERSION}.orig.tar.gz \ --exclude=.git \ --exclude=.pc \ --transform='s_${PKG_NAME}_${PKG_NAME}-$(VERSION)_' \ ${PKG_NAME}) .PHONY: debsource debsource: distclean preparedeb dpkg-source -b . .PHONY: buster-debsource buster-debsource: distclean reduce_debhelper_version_13_12 reduce_libjson_version preparedeb dpkg-source -b . .PHONY: focal-debsource focal-debsource: distclean reduce_debhelper_version_13_12 undepend_libcjson use_own_cjson preparedeb dpkg-source -b . .PHONY: bionic-debsource bionic-debsource: reduce_debhelper_version_13_12 undepend_libcjson use_own_cjson distclean preparedeb # re-add the desktop triggers by hand, because I'm not sure about the # debhelpers for this in ubuntu. This is a dirty, but short-term fix. @echo "activate-noawait update-desktop-database" > debian/oidc-agent-desktop.triggers dpkg-source -b . .PHONY: reduce_debhelper_version_13_12 reduce_debhelper_version_13_12: @mv debian/control debian/control.bck @cat debian/control.bck \ | sed s/"Build-Depends: debhelper-compat (= 13),"/"Build-Depends: debhelper-compat (= 12),"/ \ > debian/control @mv debian/liboidc-agent-dev.install debian/liboidc-agent-dev.install.bck cat debian/liboidc-agent-dev.install.bck \ | sed s/"\$${DEB_TARGET_MULTIARCH}"/`dpkg-architecture -qDEB_TARGET_MULTIARCH`/ \ | sed s/"\$${DEB_HOST_MULTIARCH}"/`dpkg-architecture -qDEB_HOST_MULTIARCH`/ \ > debian/liboidc-agent-dev.install @mv debian/liboidc-agent4.install debian/liboidc-agent4.install.bck cat debian/liboidc-agent4.install.bck \ | sed s/"\$${DEB_TARGET_MULTIARCH}"/`dpkg-architecture -qDEB_TARGET_MULTIARCH`/ \ | sed s/"\$${DEB_HOST_MULTIARCH}"/`dpkg-architecture -qDEB_HOST_MULTIARCH`/ \ > debian/liboidc-agent4.install .PHONY: reduce_libjson_version reduce_libjson_version: @mv debian/control debian/control.bck @cat debian/control.bck \ | sed s/"libcjson-dev (>= 1.7.14)"/"libcjson-dev (>= 1.7.10-1.1)"/ \ > debian/control .PHONY: undepend_libcjson undepend_libcjson: @mv debian/control debian/control.bck @cat debian/control.bck \ | sed s/"libcjson-dev (>= 1.7.10-1.1)"// \ > debian/control .PHONY: use_own_cjson use_own_cjson: @mv debian/rules debian/rules.bck @cat debian/rules.bck \ | sed s/^"export USE_CJSON_SO = 1"/"export USE_CJSON_SO = 0"/ \ > debian/rules @chmod 755 debian/rules .PHONY: deb deb: cleanapi create_obj_dir_structure preparedeb debsource debuild -i -b -uc -us @echo "Success: DEBs are in parent directory" .PHONY: buster-deb buster-deb: cleanapi create_obj_dir_structure preparedeb buster-debsource deb buster-cleanup-debsource .PHONY: buster-cleanup-debsource buster-cleanup-debsource: @mv debian/control.bck debian/control .PHONY: bionic-deb bionic-deb: cleanapi create_obj_dir_structure preparedeb bionic-debsource deb bionic-cleanup-debsource .PHONY: bionic-cleanup-debsource bionic-cleanup-debsource: @mv debian/control.bck debian/control @rm debian/oidc-agent-desktop.triggers .PHONY: deb-buster deb-buster: buster-deb .PHONY: deb-bionic deb-bionic: bionic-deb ###################### RPM ############################################### .PHONY: centos7_patch centos7_patch: @mv src/utils/file_io/fileUtils.c src/utils/file_io/fileUtils.c.bck @cat src/utils/file_io/fileUtils.c.bck \ | sed s/"define _DEFAULT_SOURCE 1"/"define _BSD_SOURCE"/ \ > src/utils/file_io/fileUtils.c .PHONY: rpmsource $(RPM_OUTDIR)/$(SRC_TAR) rpmsource: $(RPM_OUTDIR)/$(SRC_TAR) test -e $(RPM_OUTDIR) || mkdir -p $(RPM_OUTDIR) @(cd ..; \ tar czf $(SRC_TAR) \ --exclude-vcs \ --exclude=debian \ --exclude=windows \ --exclude=docker \ --exclude=gitbook \ --exclude=.pc \ --exclude $(PGK_NAME)/config \ --transform='s_${PKG_NAME}_${PKG_NAME}-$(VERSION)_' \ $(PKG_NAME) \ ) mv ../$(SRC_TAR) $(RPM_OUTDIR) # Fix RPM source in spec file (it points to github for build systems # that build from only using the spec file @(cat rpm/oidc-agent.spec \ | grep -v ^Source \ > rpm/oidc-agent.spec.bckp) @( grep -q "\#DO_NOT_REPLACE_THIS_LINE" rpm/oidc-agent.spec && {\ VERSION=$(shell head debian/changelog -n 1|cut -d \( -f 2|cut -d \) -f 1|cut -d \- -f 1);\ RELEASE=$(shell head debian/changelog -n 1|cut -d \( -f 2|cut -d \) -f 1|cut -d \- -f 2);\ sed "s/\#DO_NOT_REPLACE_THIS_LINE/Source0: oidc-agent-${VERSION}.tar.gz/" -i rpm/oidc-agent.spec.bckp;\ rm -f rpm/oidc-agent.spec;\ mv rpm/oidc-agent.spec.bckp rpm/oidc-agent.spec;\ }\ || true\ ) .PHONY: rpms rpms: srpm rpm .PHONY: rpm rpm: rpmsource rpmbuild --define "_topdir ${PWD}/rpm/rpmbuild" -bb rpm/${PKG_NAME}.spec .PHONY: srpm srpm: rpmsource rpmbuild --define "_topdir ${PWD}/rpm/rpmbuild" -bs rpm/${PKG_NAME}.spec # Release # .PHONY: gitbook # gitbook: $(BINDIR)/$(AGENT) $(BINDIR)/$(GEN) $(BINDIR)/$(ADD) $(BINDIR)/$(CLIENT) # @perl -0777 -pi -e 's/(\$$ $(GEN) --help)(.|\n|\r)*?(```\n)/`echo "\$$ $(GEN) --help"; $(BINDIR)\/$(GEN) --help; echo "\\\`\\\`\\\`" `/e' gitbook/oidc-gen.md # @perl -0777 -pi -e 's/(\$$ $(ADD) --help)(.|\n|\r)*?(```\n)/`echo "\$$ $(ADD) --help"; $(BINDIR)\/$(ADD) --help; echo "\\\`\\\`\\\`" `/e' gitbook/oidc-add.md # @perl -0777 -pi -e 's/(\$$ $(AGENT) --help)(.|\n|\r)*?(```\n)/`echo "\$$ $(AGENT) --help"; $(BINDIR)\/$(AGENT) --help; echo "\\\`\\\`\\\`" `/e' gitbook/oidc-agent.md # @perl -0777 -pi -e 's/(\$$ $(CLIENT) --help)(.|\n|\r)*?(```\n)/`echo "\$$ $(CLIENT) --help"; $(BINDIR)\/$(CLIENT) --help; echo "\\\`\\\`\\\`" `/e' gitbook/oidc-token.md # @echo "Updated gitbook docu with help output" # .PHONY: release # release: deb gitbook $(TESTBINDIR)/test: $(TESTBINDIR) $(TESTSRCDIR)/main.c $(TEST_SOURCES) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) @$(CC) $(TEST_CFLAGS) $(TESTSRCDIR)/main.c $(TEST_SOURCES) $(GENERAL_SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o) $(LIB_SOURCES:$(LIBDIR)/%.c=$(OBJDIR)/%.o) -o $@ $(TEST_LFLAGS) .PHONY: test test: $(TESTBINDIR)/test @$< # .PHONY: testdocu # testdocu: $(BINDIR)/$(AGENT) $(BINDIR)/$(GEN) $(BINDIR)/$(ADD) $(BINDIR)/$(CLIENT) gitbook/$(GEN).md gitbook/$(AGENT).md gitbook/$(ADD).md gitbook/$(CLIENT).md # @$(BINDIR)/$(AGENT) -h | grep "^[[:space:]]*-" | grep -v "debug" | grep -v "verbose" | grep -v "usage" | grep -v "help" | grep -v "version" | sed 's/.*--/--/' | sed 's/\s.*$$//' | sed 's/=.*//' | sed 's/\[.*//' | xargs -I {} sh -c 'grep -c -- ^###.*{} gitbook/$(AGENT).md>/dev/null || echo "In gitbook/$(AGENT).md: {} not documented"' # @$(BINDIR)/$(GEN) -h | grep "^[[:space:]]*-" | grep -v "debug" | grep -v "verbose" | grep -v "usage" | grep -v "help" | grep -v "version" | sed 's/.*--/--/' | sed 's/\s.*$$//' | sed 's/=.*//' | sed 's/\[.*//' | xargs -I {} sh -c 'grep -c -- ^###.*{} gitbook/$(GEN).md>/dev/null || echo "In gitbook/$(GEN).md: {} not documented"' # @$(BINDIR)/$(ADD) -h | grep "^[[:space:]]*-" | grep -v "debug" | grep -v "verbose" | grep -v "usage" | grep -v "help" | grep -v "version" | sed 's/.*--/--/' | sed 's/\s.*$$//' | sed 's/=.*//' | sed 's/\[.*//' | xargs -I {} sh -c 'grep -c -- ^###.*{} gitbook/$(ADD).md>/dev/null || echo "In gitbook/$(ADD).md: {} not documented"' # @$(BINDIR)/$(CLIENT) -h | grep "^[[:space:]]*-" | grep -v "debug" | grep -v "verbose" | grep -v "usage" | grep -v "help" | grep -v "version" | sed 's/.*--/--/' | sed 's/\s.*$$//' | sed 's/=.*//' | sed 's/\[.*//' | xargs -I {} sh -c 'grep -c -- ^###.*{} gitbook/$(CLIENT).md>/dev/null || echo "In gitbook/$(CLIENT).md: {} not documented"' oidc-agent-4.2.6/.gitattributes0000644000175000017500000000066614120404223015776 0ustar marcusmarcussrc/oidc-gen/oidc-gen_options.h linguist-language=C src/oidc-add/oidc-add_options.h linguist-language=C src/utils/file_io/oidc_file_io.h linguist-language=C src/utils/listUtils.h linguist-language=C gitbook/* linguist-documentation book.json linguist-documentation LICENSE linguist-documentation README.MD linguist-documentation .clang-format linguist-detectable=false rpm/* linguist-detectable=false debian/* linguist-detectable=false oidc-agent-4.2.6/src/0000755000175000017500000000000014167074355013706 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-gen/0000755000175000017500000000000014167074355015373 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-gen/device_code.c0000644000175000017500000000155514167074355017776 0ustar marcusmarcus#include "device_code.h" #include "oidc-agent/oidc/device_code.h" #include "oidc-gen/qr.h" #include "utils/logger.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" void printDeviceCode(struct oidc_device_code c) { printNormal("\nUsing a browser on any device, visit:\n%s\n\nAnd enter the " "code: %s\n", oidc_device_getVerificationUri(c), oidc_device_getUserCode(c)); char* qr = getQRCode(strValid(oidc_device_getVerificationUriComplete(c)) ? oidc_device_getVerificationUriComplete(c) : oidc_device_getVerificationUri(c)); if (qr == NULL) { logger(NOTICE, "Could not create QR code"); } else { printNormal("Alternatively you can use the following QR code to visit the " "above listed URL.\n\n%s\n", qr); secFree(qr); } } oidc-agent-4.2.6/src/oidc-gen/gen_signal_handler.c0000644000175000017500000000173314167074355021346 0ustar marcusmarcus#include "gen_signal_handler.h" #include #include #include #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" static char* global_state = NULL; #ifndef __APPLE__ static __sighandler_t old_sigint; #else static sig_t old_sigint; #endif void gen_http_signal_handler(int signo) { switch (signo) { case SIGINT: if (global_state) { _secFree(ipc_cryptCommunicate(0, REQUEST_TERMHTTP, global_state)); secFree(global_state); global_state = NULL; } break; default: logger(EMERGENCY, "oidc-gen caught Signal %d", signo); } exit(signo); } void registerSignalHandler(const char* state) { global_state = oidc_sprintf(state); old_sigint = signal(SIGINT, gen_http_signal_handler); } void unregisterSignalHandler() { secFree(global_state); global_state = NULL; signal(SIGINT, old_sigint); } oidc-agent-4.2.6/src/oidc-gen/oidc-gen_options.h0000644000175000017500000000335614167074355021013 0ustar marcusmarcus#ifndef OIDC_GEN_OPTIONS_H #define OIDC_GEN_OPTIONS_H #include #include "wrapper/list.h" #define OPT_LONG_CLIENTID "client-id" #define OPT_LONG_CLIENTSECRET "client-secret" #define OPT_LONG_REFRESHTOKEN "rt" #define OPT_LONG_REFRESHTOKEN_ENV "rt-env" #define OPT_LONG_USERNAME "op-username" #define OPT_LONG_PASSWORD "op-password" #define OPT_LONG_CERTPATH "cert-path" #define OPT_LONG_ISSUER "issuer" #define OPT_LONG_AUDIENCE "aud" #define OPT_LONG_SCOPE "scope" #define OPT_LONG_REDIRECT "redirect-uri" #define OPT_LONG_DEVICE "dae" struct optional_arg { char* str; short useIt; }; struct arguments { char* args[1]; /* account */ char* print; char* rename; char* updateConfigFile; char* codeExchange; char* state; char* device_authorization_endpoint; char* pw_cmd; char* pw_file; char* pw_env; char* pw_gpg; char* file; char* client_id; char* client_secret; char* issuer; char* redirect_uri; char* scope; char* dynRegToken; char* cert_path; char* refresh_token; char* cnid; char* audience; char* op_username; char* op_password; list_t* flows; list_t* redirect_uris; unsigned char delete; unsigned char listAccounts; unsigned char reauthenticate; unsigned char manual; unsigned char usePublicClient; unsigned char seccomp; unsigned char _nosec; unsigned char noUrlCall; unsigned char noWebserver; unsigned char noScheme; unsigned char pw_prompt_mode; unsigned char prompt_mode; unsigned char debug; unsigned char verbose; unsigned char confirm_yes; unsigned char confirm_no; unsigned char confirm_default; unsigned char only_at; unsigned char noSave; }; void initArguments(struct arguments* arguments); extern struct argp argp; #endif // OIDC_GEN_OPTIONS_H oidc-agent-4.2.6/src/oidc-gen/promptAndSet/0000755000175000017500000000000014167074355020013 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-gen/promptAndSet/username.c0000644000175000017500000000222614167074355022000 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedUsername(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readUsername(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("username", OPT_LONG_USERNAME)); char* res = _gen_prompt("Username", account_getUsername(account), 0, optional); if (res) { account_setUsername(account, res); } } int readUsername(struct oidc_account* account, const struct arguments* arguments) { if (arguments->op_username) { account_setUsername(account, oidc_strcopy(arguments->op_username)); return 1; } if (prompt_mode() == 0 && strValid(account_getUsername(account))) { return 1; } return 0; } void askUsername(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedUsername(account, arguments, 1); } void needUsername(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedUsername(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/clientsecret.c0000644000175000017500000000241514167074355022645 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedClientSecret(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readClientSecret(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("client secret", OPT_LONG_CLIENTSECRET)); char* res = _gen_prompt("Client_secret", account_getClientSecret(account), 1, optional); if (res) { account_setClientSecret(account, res); } } int readClientSecret(struct oidc_account* account, const struct arguments* arguments) { if (arguments->client_secret) { account_setClientSecret(account, oidc_strcopy(arguments->client_secret)); return 1; } if (prompt_mode() == 0 && strValid(account_getClientSecret(account))) { return 1; } return 0; } void askClientSecret(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedClientSecret(account, arguments, 1); } void needClientSecret(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedClientSecret(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/refreshtoken.c0000644000175000017500000000241514167074355022660 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedRefreshToken(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readRefreshToken(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("refresh token", OPT_LONG_REFRESHTOKEN)); char* res = _gen_prompt("Refresh token", account_getRefreshToken(account), 0, optional); if (res) { account_setRefreshToken(account, res); } } int readRefreshToken(struct oidc_account* account, const struct arguments* arguments) { if (arguments->refresh_token) { account_setRefreshToken(account, oidc_strcopy(arguments->refresh_token)); return 1; } if (prompt_mode() == 0 && strValid(account_getRefreshToken(account))) { return 1; } return 0; } void askRefreshToken(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedRefreshToken(account, arguments, 1); } void needRefreshToken(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedRefreshToken(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/clientid.c0000644000175000017500000000222414167074355021752 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedClientId(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readClientId(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("client id", OPT_LONG_CLIENTID)); char* cid = _gen_prompt("Client_id", account_getClientId(account), 0, optional); if (cid) { account_setClientId(account, cid); } } int readClientId(struct oidc_account* account, const struct arguments* arguments) { if (arguments->client_id) { account_setClientId(account, oidc_strcopy(arguments->client_id)); return 1; } if (prompt_mode() == 0 && strValid(account_getClientId(account))) { return 1; } return 0; } void askClientId(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedClientId(account, arguments, 1); } void needClientId(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedClientId(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/issuer.c0000644000175000017500000000405214167074355021472 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "account/issuer_helper.h" #include "defines/settings.h" #include "promptAndSet.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/listUtils.h" #include "utils/prompt.h" #include "utils/string/stringUtils.h" void _useSuggestedIssuer(struct oidc_account* account, int optional) { list_t* issuers = getSuggestableIssuers(); size_t favPos = getFavIssuer(account, issuers); char* iss = promptSelect("Please select issuer", "Issuer", issuers, favPos, CLI_PROMPT_NOT_VERBOSE); secFreeList(issuers); if (!strValid(iss)) { printError("Something went wrong. Invalid Issuer.\n"); if (optional) { return; } exit(EXIT_FAILURE); } account_setIssuerUrl(account, iss); } void askOrNeedIssuer(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readIssuer(account, arguments)) { stringifyIssuerUrl(account); return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("issuer url", OPT_LONG_ISSUER)); if (!oidcFileDoesExist(ISSUER_CONFIG_FILENAME) && !fileDoesExist(ETC_ISSUER_CONFIG_FILE)) { char* res = _gen_prompt("Issuer", account_getIssuerUrl(account), 0, optional); if (res) { account_setIssuerUrl(account, res); } } else { _useSuggestedIssuer(account, optional); } stringifyIssuerUrl(account); } int readIssuer(struct oidc_account* account, const struct arguments* arguments) { if (arguments->issuer) { account_setIssuerUrl(account, oidc_strcopy(arguments->issuer)); return 1; } if (prompt_mode() == 0 && strValid(account_getIssuerUrl(account))) { return 1; } return 0; } void askIssuer(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedIssuer(account, arguments, 1); } void needIssuer(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedIssuer(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/_helper.c0000644000175000017500000000264514167074355021604 0ustar marcusmarcus#include "_helper.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/prompt.h" #include "utils/string/stringUtils.h" char* _gen_prompt(char* label, const char* init, int passPrompt, int optional) { char* input = NULL; promptFnc prompter = prompt; if (passPrompt) { prompter = promptPassword; } do { char* text = oidc_sprintf("Please enter %s:", label); input = prompter(text, label, init, CLI_PROMPT_NOT_VERBOSE); secFree(text); if (strValid(input)) { return input; } secFree(input); if (optional) { return NULL; } } while (1); } char* _gen_promptMultipleSpaceSeparated(char* label, const char* init_str, int optional) { list_t* init = delimitedStringToList(init_str, ' '); list_t* input = NULL; do { char* text = oidc_sprintf("Please enter %s:", label); input = promptMultiple(text, label, init, CLI_PROMPT_NOT_VERBOSE); secFree(text); if (listValid(input)) { char* output = listToDelimitedString(input, " "); logger(DEBUG, "In %s produced output '%s' for label '%s' with %lu elements", __func__, output, label, input->len); secFreeList(input); secFreeList(init); return output; } secFreeList(input); if (optional) { secFreeList(init); return NULL; } } while (1); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/name.c0000644000175000017500000000236614167074355021106 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/prompt.h" #include "utils/string/stringUtils.h" void askOrNeedName(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readName(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, "No account short name given."); char* shortname = NULL; do { secFree(shortname); shortname = prompt("Enter short name for the account to configure", "short name", NULL, CLI_PROMPT_VERBOSE); } while (!strValid(shortname) || optional); if (shortname) { account_setName(account, shortname, arguments->cnid); } } int readName(struct oidc_account* account, const struct arguments* arguments) { if (arguments->args[0]) { account_setName(account, oidc_strcopy(arguments->args[0]), arguments->cnid); return 1; } if (prompt_mode() == 0 && strValid(account_getName(account))) { return 1; } return 0; } void askName(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedName(account, arguments, 1); } void needName(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedName(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/redirecturis.c0000644000175000017500000000341714167074355022670 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/listUtils.h" #include "utils/prompt.h" #include "utils/uriUtils.h" void askOrNeedRedirectUris(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readRedirectUris(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("redirect uri", OPT_LONG_REDIRECT)); while (1) { list_t* redirect_uris = promptMultiple( "Please enter Redirect URIs", "Redirect_uris", account_getRedirectUris(account), CLI_PROMPT_NOT_VERBOSE); oidc_error_t err = checkRedirectUrisForErrors(redirect_uris); if (err == OIDC_SUCCESS) { account_setRedirectUris(account, redirect_uris); return; } secFreeList(redirect_uris); if (err != OIDC_EERROR) { // If err == OIDC_EERROR a not valid redirect_uri // was entered, so the user want to provide input. // If err != OIDC_EERROR no input was provided if (optional) { return; } } } } int readRedirectUris(struct oidc_account* account, const struct arguments* arguments) { if (arguments->redirect_uris) { account_setRedirectUris(account, arguments->redirect_uris); return 1; } if (prompt_mode() == 0 && account_getRedirectUris(account)) { return 1; } return 0; } void askRedirectUris(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedRedirectUris(account, arguments, 1); } void needRedirectUris(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedRedirectUris(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/certpath.c0000644000175000017500000000246714167074355022002 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedCertPath(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readCertPath(account, arguments)) { return; } if (strValid(account_getCertPath(account))) { optional = 1; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("cert path", OPT_LONG_CERTPATH)); char* res = _gen_prompt("Cert Path", account_getCertPath(account), 0, optional); if (res) { account_setCertPath(account, res); } } int readCertPath(struct oidc_account* account, const struct arguments* arguments) { if (arguments->cert_path) { account_setCertPath(account, oidc_strcopy(arguments->cert_path)); return 1; } if (prompt_mode() == 0 && strValid(account_getCertPath(account))) { return 1; } if (account_getCertPath(account) == NULL) { account_setOSDefaultCertPath(account); } return 0; } void askCertPath(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedCertPath(account, arguments, 1); } void needCertPath(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedCertPath(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/promptAndSet.h0000644000175000017500000000661114120404223022564 0ustar marcusmarcus#ifndef OIDCGEN_PROMPTANDSET_H #define OIDCGEN_PROMPTANDSET_H #include "account/account.h" #include "oidc-gen/oidc-gen_options.h" int readClientId(struct oidc_account*, const struct arguments*); void askClientId(struct oidc_account*, const struct arguments*); void needClientId(struct oidc_account*, const struct arguments*); void askOrNeedClientId(struct oidc_account*, const struct arguments*, int); int readClientSecret(struct oidc_account*, const struct arguments*); void askClientSecret(struct oidc_account*, const struct arguments*); void needClientSecret(struct oidc_account*, const struct arguments*); void askOrNeedClientSecret(struct oidc_account*, const struct arguments*, int); int readIssuer(struct oidc_account*, const struct arguments*); void askIssuer(struct oidc_account*, const struct arguments*); void needIssuer(struct oidc_account*, const struct arguments*); int readRefreshToken(struct oidc_account*, const struct arguments*); void askRefreshToken(struct oidc_account*, const struct arguments*); void needRefreshToken(struct oidc_account*, const struct arguments*); void askOrNeedRefreshToken(struct oidc_account*, const struct arguments*, int); int readAudience(struct oidc_account*, const struct arguments*); void askAudience(struct oidc_account*, const struct arguments*); void needAudience(struct oidc_account*, const struct arguments*); void askOrNeedAudience(struct oidc_account*, const struct arguments*, int); int readUsername(struct oidc_account*, const struct arguments*); void askUsername(struct oidc_account*, const struct arguments*); void needUsername(struct oidc_account*, const struct arguments*); void askOrNeedUsername(struct oidc_account*, const struct arguments*, int); int readPassword(struct oidc_account*, const struct arguments*); void askPassword(struct oidc_account*, const struct arguments*); void needPassword(struct oidc_account*, const struct arguments*); void askOrNeedPassword(struct oidc_account*, const struct arguments*, int); int readCertPath(struct oidc_account*, const struct arguments*); void askCertPath(struct oidc_account*, const struct arguments*); void needCertPath(struct oidc_account*, const struct arguments*); void askOrNeedCertPath(struct oidc_account*, const struct arguments*, int); int readName(struct oidc_account*, const struct arguments*); void askName(struct oidc_account*, const struct arguments*); void needName(struct oidc_account*, const struct arguments*); void askOrNeedName(struct oidc_account*, const struct arguments*, int); int readScope(struct oidc_account*, const struct arguments*); void askScope(struct oidc_account*, const struct arguments*); void needScope(struct oidc_account*, const struct arguments*); void askOrNeedScope(struct oidc_account*, const struct arguments*, int); int readRedirectUris(struct oidc_account*, const struct arguments*); void askRedirectUris(struct oidc_account*, const struct arguments*); void needRedirectUris(struct oidc_account*, const struct arguments*); void askOrNeedRedirectUris(struct oidc_account*, const struct arguments*, int); int readDeviceAuthEndpoint(struct oidc_account*, const struct arguments*); void askDeviceAuthEndpoint(struct oidc_account*, const struct arguments*); void needDeviceAuthEndpoint(struct oidc_account*, const struct arguments*); void askOrNeedDeviceAuthEndpoint(struct oidc_account*, const struct arguments*, int); #endif // OIDCGEN_PROMPTANDSET_H oidc-agent-4.2.6/src/oidc-gen/promptAndSet/_helper.h0000644000175000017500000000226214167074355021604 0ustar marcusmarcus#ifndef OIDC_PROMPTANDSET_HELPER_H #define OIDC_PROMPTANDSET_HELPER_H #include #include "utils/printer.h" #include "utils/prompt_mode.h" #ifndef ERROR_MESSAGE #define ERROR_MESSAGE(_name, _option) \ "Required argument " _name \ " not given. Please enable a prompt mode or pass " _name \ " to the '--" _option "' option." #endif #ifndef ERROR_IF_NO_PROMPT #define ERROR_IF_NO_PROMPT(_optional, _error_message) \ do { \ if (prompt_mode() == 0) { \ if ((_optional)) { \ return; \ } \ printError("%s\n", (_error_message)); \ exit(EXIT_FAILURE); \ } \ } while (0) #endif char* _gen_prompt(char* label, const char* init, int passPrompt, int optional); char* _gen_promptMultipleSpaceSeparated(char* label, const char* init_str, int optional); #endif // OIDC_PROMPTANDSET_HELPER_H oidc-agent-4.2.6/src/oidc-gen/promptAndSet/password.c0000644000175000017500000000222614167074355022023 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedPassword(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readPassword(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("password", OPT_LONG_PASSWORD)); char* res = _gen_prompt("Password", account_getPassword(account), 1, optional); if (res) { account_setPassword(account, res); } } int readPassword(struct oidc_account* account, const struct arguments* arguments) { if (arguments->op_password) { account_setPassword(account, oidc_strcopy(arguments->op_password)); return 1; } if (prompt_mode() == 0 && strValid(account_getPassword(account))) { return 1; } return 0; } void askPassword(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedPassword(account, arguments, 1); } void needPassword(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedPassword(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/audience.c0000644000175000017500000000224514167074355021737 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedAudience(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readAudience(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("audience", OPT_LONG_AUDIENCE)); char* res = _gen_promptMultipleSpaceSeparated( "Audiences", account_getAudience(account), optional); if (res) { account_setAudience(account, res); } } int readAudience(struct oidc_account* account, const struct arguments* arguments) { if (arguments->audience) { account_setAudience(account, oidc_strcopy(arguments->audience)); return 1; } if (prompt_mode() == 0 && strValid(account_getAudience(account))) { return 1; } return 0; } void askAudience(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedAudience(account, arguments, 1); } void needAudience(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedAudience(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/scope.c0000644000175000017500000000471714167074355021301 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "account/issuer_helper.h" #include "defines/agent_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "oidc-gen/gen_handler.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" char* getSupportedScopes(struct oidc_account* account, const struct arguments* arguments) { if (arguments->usePublicClient) { char* pubScopes = getScopesForPublicClient(account); if (strValid(pubScopes)) { return pubScopes; } } if (compIssuerUrls(account_getIssuerUrl(account), ELIXIR_ISSUER_URL)) { return oidc_strcopy(ELIXIR_SUPPORTED_SCOPES); } return gen_handleScopeLookup(account_getIssuerUrl(account), account_getCertPath(account)); } void askOrNeedScope(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readScope(account, arguments)) { if (strequal(account_getScope(account), AGENT_SCOPE_ALL)) { account_setScope(account, getSupportedScopes(account, arguments)); } return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("scope", OPT_LONG_SCOPE)); char* supportedScope = getSupportedScopes(account, arguments); printNormal("The following scopes are supported: %s\n", supportedScope); if (!strValid(account_getScope(account))) { account_setScope(account, oidc_strcopy(DEFAULT_SCOPE)); } char* res = _gen_promptMultipleSpaceSeparated( "Scopes or 'max'", account_getScope(account), optional); if (res) { account_setScopeExact(account, res); } if (strequal(account_getScope(account), AGENT_SCOPE_ALL)) { account_setScope(account, supportedScope); } else { secFree(supportedScope); } } int readScope(struct oidc_account* account, const struct arguments* arguments) { if (arguments->scope) { void (*setter)(struct oidc_account*, char*) = account_setScope; if (strequal(arguments->scope, AGENT_SCOPE_ALL)) { setter = account_setScopeExact; } setter(account, oidc_strcopy(arguments->scope)); return 1; } if (prompt_mode() == 0 && strValid(account_getScope(account))) { return 1; } return 0; } void askScope(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedScope(account, arguments, 1); } void needScope(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedScope(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/promptAndSet/deviceAuthorizationEndpoint.c0000644000175000017500000000312114167074355025675 0ustar marcusmarcus#include "_helper.h" #include "account/account.h" #include "promptAndSet.h" #include "utils/string/stringUtils.h" void askOrNeedDeviceAuthEndpoint(struct oidc_account* account, const struct arguments* arguments, int optional) { if (readDeviceAuthEndpoint(account, arguments)) { return; } ERROR_IF_NO_PROMPT(optional, ERROR_MESSAGE("device authorization endpoint", OPT_LONG_DEVICE)); char* res = _gen_prompt("Device Authorization Endpoint", account_getDeviceAuthorizationEndpoint(account), 0, optional); if (res) { issuer_setDeviceAuthorizationEndpoint(account_getIssuer(account), res, 1); } } int readDeviceAuthEndpoint(struct oidc_account* account, const struct arguments* arguments) { if (arguments->device_authorization_endpoint) { issuer_setDeviceAuthorizationEndpoint( account_getIssuer(account), oidc_strcopy(arguments->device_authorization_endpoint), 1); return 1; } if (prompt_mode() == 0 && strValid(account_getDeviceAuthorizationEndpoint(account))) { return 1; } return 0; } void askDeviceAuthEndpoint(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedDeviceAuthEndpoint(account, arguments, 1); } void needDeviceAuthEndpoint(struct oidc_account* account, const struct arguments* arguments) { return askOrNeedDeviceAuthEndpoint(account, arguments, 0); } oidc-agent-4.2.6/src/oidc-gen/gen_consenter.h0000644000175000017500000000062514120404223020354 0ustar marcusmarcus#ifndef OIDC_GEN_PROMPT_CONSENT_H #define OIDC_GEN_PROMPT_CONSENT_H #include "oidc-gen/oidc-gen_options.h" int gen_promptConsentDefaultNo(const char* text, const struct arguments* arguments); int gen_promptConsentDefaultYes(const char* text, const struct arguments* arguments); #endif // OIDC_GEN_PROMPT_CONSENT_H oidc-agent-4.2.6/src/oidc-gen/oidc-gen_options.c0000644000175000017500000004673414167074355021015 0ustar marcusmarcus#include "oidc-gen_options.h" #include #include "defines/agent_values.h" #include "defines/settings.h" #include "utils/commonFeatures.h" #include "utils/guiChecker.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/portUtils.h" #include "utils/printer.h" #include "utils/prompt_mode.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" /* Keys for options without short-options. */ #define OPT_codeExchange 1 #define OPT_state 2 #define OPT_TOKEN 3 #define OPT_CERTPATH 4 #define OPT_DEVICE 7 #define OPT_CNID 8 #define OPT_SECCOMP 9 #define OPT_NOURLCALL 10 #define OPT_REFRESHTOKEN 11 #define OPT_PUBLICCLIENT 12 #define OPT_PORT 13 #define OPT_PW_CMD 14 #define OPT_NO_WEBSERVER 15 #define OPT_REAUTHENTICATE 16 #define OPT_NO_SCHEME 17 #define OPT_AUDIENCE 18 #define OPT_RENAME 19 #define OPT_PROMPT_MODE 20 #define OPT_PW_PROMPT_MODE 21 #define OPT_ISSUER 22 #define OPT_SCOPE 23 #define OPT_SCOPE_MAX 24 #define OPT_CLIENTID 25 #define OPT_CLIENTSECRET 26 #define OPT_REDIRECT 27 #define OPT_USERNAME 28 #define OPT_PASSWORD 29 #define OPT_PW_FILE 30 // Leave space for Ascii characters #define OPT_CONFIRM_YES 128 #define OPT_CONFIRM_NO 129 #define OPT_CONFIRM_DEFAULT 130 #define OPT_ONLY_AT 131 #define OPT_REFRESHTOKEN_ENV 132 #define OPT_PW_ENV 133 #define OPT_NO_SAVE 134 #define OPT_PW_GPG 135 static struct argp_option options[] = { {0, 0, 0, 0, "Managing account configurations", 1}, {"accounts", 'l', 0, 0, "Prints a list of all configured account configurations. Same as oidc-add " "-l", 1}, {"print", 'p', "FILE", 0, "Prints the decrypted content of FILE. FILE can be an absolute path or " "the name of a file placed in oidc-dir (e.g. an account configuration " "short name)", 1}, {"reauthenticate", OPT_REAUTHENTICATE, 0, 0, "Used to update an existing account configuration file with a new refresh " "token. Can be used if no other metadata should be changed.", 1}, {"rename", OPT_RENAME, "NEW_SHORTNAME", 0, "Used to rename an existing account configuration file.", 1}, {"update", 'u', "FILE", 0, "Decrypts and reencrypts the content for FILE. " "This might update the file format and encryption. FILE can be an " "absolute path or the name of a file placed in oidc-dir (e.g. an account " "configuration short name).", 1}, {"delete", 'd', 0, 0, "Delete configuration for the given account", 1}, {0, 0, 0, 0, "Generating a new account configuration:", 2}, {"file", 'f', "FILE", 0, "Reads the client configuration from FILE. Implicitly sets -m", 2}, {"manual", 'm', 0, 0, "Does not use Dynamic Client Registration. Client has to be manually " "registered beforehand", 2}, {"no-save", OPT_NO_SAVE, 0, 0, "Do not save any configuration files (meaning as soon as the agent stops, " "nothing will be saved)", 2}, {"pub", OPT_PUBLICCLIENT, 0, 0, "Uses a public client defined in the publicclient.conf file.", 2}, {"iss", OPT_ISSUER, "ISSUER_URL", 0, "Set ISSUER_URL as the issuer url to be used.", 2}, {OPT_LONG_ISSUER, OPT_ISSUER, "ISSUER_URL", OPTION_ALIAS, NULL, 2}, {OPT_LONG_SCOPE, OPT_SCOPE, "SCOPE", 0, "Set SCOPE as the scope to be used. Multiple scopes can be provided as a " "space separated list or by using the option multiple times. Use 'max' to " "use all available scopes for this provider.", 2}, {"scope-all", OPT_SCOPE_MAX, 0, 0, "Use all available scopes for this provider. Same as using '--scope=max'", 2}, {"scope-max", OPT_SCOPE_MAX, 0, OPTION_ALIAS, NULL, 2}, {OPT_LONG_CLIENTID, OPT_CLIENTID, "CLIENT_ID", 0, "Use CLIENT_ID as client id. Requires an already registered client. " "Implicitly sets '-m'.", 2}, {OPT_LONG_CLIENTSECRET, OPT_CLIENTSECRET, "CLIENT_SECRET", 0, "Use CLIENT_SECRET as client secret. Requires an already registered " "client.", 2}, {OPT_LONG_REDIRECT, OPT_REDIRECT, "URI", 0, "Use URI as redirect URI. Can be a space separated list. The redirect uri " "must follow the format http://localhost:[/*] or " "edu.kit.data.oidc-agent:/", 2}, {"redirect-url", OPT_REDIRECT, "URI", OPTION_ALIAS, NULL, 2}, {"port", OPT_PORT, "PORT", 0, "Use this port in the local redirect uri. Shorter way to pass redirect " "uris compared to '--redirect-uri'. Option " "can be used multiple times to provide additional backup ports.", 2}, {0, 0, 0, 0, "Generating a new account configuration - Advanced:", 3}, {"at", OPT_TOKEN, "ACCESS_TOKEN", 0, "Use ACCESS_TOKEN for authorization for authorization at the registration " "endpoint.", 3}, {"access-token", OPT_TOKEN, "ACCESS_TOKEN", OPTION_ALIAS, NULL, 3}, {OPT_LONG_AUDIENCE, OPT_AUDIENCE, "AUDIENCE", 0, "Limit issued tokens to the specified AUDIENCE. Multiple audiences can be " "specified separated by space.", 3}, {"audience", OPT_AUDIENCE, "AUDIENCE", OPTION_ALIAS, NULL, 3}, {OPT_LONG_USERNAME, OPT_USERNAME, "USERNAME", 0, "Use USERNAME in the password flow. Requires '--flow=password' to be set.", 3}, {OPT_LONG_PASSWORD, OPT_PASSWORD, "PASSWORD", 0, "Use PASSWORD in the password flow. Requires '--flow=password' to be set.", 3}, {"cnid", OPT_CNID, "IDENTIFIER", 0, "Additional identifier used in the client name to distinguish clients on " "different machines with the same short name, e.g. the host name", 3}, {"client-name-identifier", OPT_CNID, "IDENTIFIER", OPTION_ALIAS, NULL, 3}, {"cp", OPT_CERTPATH, "FILE", 0, "FILE is the path to a CA bundle file that will be used with TLS " "communication", 3}, {OPT_LONG_CERTPATH, OPT_CERTPATH, "FILE", OPTION_ALIAS, NULL, 3}, {"cert-file", OPT_CERTPATH, "FILE", OPTION_ALIAS, NULL, 3}, {OPT_LONG_REFRESHTOKEN, OPT_REFRESHTOKEN, "REFRESH_TOKEN", 0, "Use REFRESH_TOKEN as the refresh token in the refresh flow instead of " "using another flow. Implicitly sets --flow=refresh", 3}, {"refresh-token", OPT_REFRESHTOKEN, "REFRESH_TOKEN", OPTION_ALIAS, NULL, 3}, {OPT_LONG_REFRESHTOKEN_ENV, OPT_REFRESHTOKEN_ENV, OIDC_REFRESHTOKEN_ENV_NAME, OPTION_ARG_OPTIONAL, "Like --rt but reads the REFRESH_TOKEN from the passed environment " "variable (default: " OIDC_REFRESHTOKEN_ENV_NAME ")", 3}, {"refresh-token-env", OPT_REFRESHTOKEN_ENV, OIDC_REFRESHTOKEN_ENV_NAME, OPTION_ALIAS, NULL, 3}, {OPT_LONG_DEVICE, OPT_DEVICE, "ENDPOINT_URI", 0, "Use this uri as device authorization endpoint", 3}, {"device-authorization-endpoint", OPT_DEVICE, "ENDPOINT_URI", OPTION_ALIAS, NULL, 3}, {"flow", 'w', "code|device|password|refresh", 0, "Specifies the OIDC flow to be used. Option can be used multiple times to " "allow different flows and express priority.", 3}, {"only-at", OPT_ONLY_AT, 0, 0, "When using this option, oidc-gen will print an access token instead of " "creating a new account configuration. No account configuration file is " "created. This option does not work with dynamic client registration, but " "it does work with preregistered public clients.", 3}, {0, 0, 0, 0, "Advanced:", 4}, #ifndef __APPLE__ {"seccomp", OPT_SECCOMP, 0, 0, "Enables seccomp system call filtering; allowing only predefined system " "calls.", 4}, #endif {"no-url-call", OPT_NOURLCALL, 0, 0, "Does not automatically open the authorization url in a browser.", 4}, {"pw-cmd", OPT_PW_CMD, "CMD", 0, "Command from which oidc-gen can read the encryption password, instead of " "prompting the user", 4}, {"pw-env", OPT_PW_ENV, OIDC_PASSWORD_ENV_NAME, OPTION_ARG_OPTIONAL, "Reads the encryption password from the passed environment variable " "(default: " OIDC_PASSWORD_ENV_NAME "), instead of prompting the user", 4}, {"pw-file", OPT_PW_FILE, "FILE", 0, "Uses the first line of FILE as the encryption password.", 4}, {"pw-gpg", OPT_PW_GPG, "KEY_ID", 0, "Uses the passed GPG KEY for encryption", 4}, {"pw-pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 4}, {"gpg", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 4}, {"pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 4}, {"pw-prompt", OPT_PW_PROMPT_MODE, "cli|gui", 0, "Change the mode how oidc-gen should prompt for passwords. The default is " "'cli'.", 4}, {"prompt", OPT_PROMPT_MODE, "cli|gui|none", 0, "Change the mode how oidc-gen should prompt for information. The default " "is 'cli'.", 4}, {"confirm-default", OPT_CONFIRM_DEFAULT, 0, 0, "Confirms all confirmation prompts with the default value.", 4}, {"confirm-yes", OPT_CONFIRM_YES, 0, 0, "Confirms all confirmation prompts with yes.", 4}, {"confirm-no", OPT_CONFIRM_NO, 0, 0, "Confirms all confirmation prompts with no.", 4}, {"codeExchange", OPT_codeExchange, "URI", 0, "Uses URI to complete the account configuration generation process. URI " "must be a full url to which you were redirected after the authorization " "code flow.", 4}, {"no-webserver", OPT_NO_WEBSERVER, 0, 0, "This option applies only when the " "authorization code flow is used. oidc-agent will not start a webserver. " "Redirection to oidc-gen through a custom uri scheme redirect uri and " "'manual' redirect is possible.", 4}, {"no-scheme", OPT_NO_SCHEME, 0, 0, "This option applies only when the " "authorization code flow is used. oidc-agent will not use a custom uri " "scheme redirect.", 4}, {0, 0, 0, 0, "Internal options:", 5}, {"state", OPT_state, "STATE", 0, "Only for internal usage. Uses STATE to get the associated account config", 5}, {0, 0, 0, 0, "Verbosity:", 6}, {"debug", 'g', 0, 0, "Sets the log level to DEBUG", 6}, {"verbose", 'v', 0, 0, "Enables verbose mode", 6}, {0, 0, 0, 0, "Help:", -1}, {0, 'h', 0, OPTION_HIDDEN, 0, -1}, {0, 0, 0, 0, 0, 0}}; /** * @brief initializes arguments * @param arguments the arguments struct */ void initArguments(struct arguments* arguments) { arguments->args[0] = NULL; arguments->print = NULL; arguments->rename = NULL; arguments->updateConfigFile = NULL; arguments->codeExchange = NULL; arguments->state = NULL; arguments->device_authorization_endpoint = NULL; arguments->pw_env = NULL; arguments->pw_cmd = NULL; arguments->pw_file = NULL; arguments->pw_gpg = NULL; arguments->file = NULL; arguments->client_id = NULL; arguments->client_secret = NULL; arguments->issuer = NULL; arguments->redirect_uri = NULL; arguments->scope = NULL; arguments->dynRegToken = NULL; arguments->cert_path = NULL; arguments->refresh_token = NULL; arguments->cnid = NULL; arguments->audience = NULL; arguments->op_username = NULL; arguments->op_password = NULL; arguments->flows = NULL; arguments->redirect_uris = NULL; arguments->debug = 0; arguments->manual = 0; arguments->verbose = 0; arguments->delete = 0; arguments->listAccounts = 0; arguments->seccomp = 0; arguments->_nosec = 0; arguments->noUrlCall = 0; arguments->usePublicClient = 0; arguments->noWebserver = 0; arguments->reauthenticate = 0; arguments->noScheme = 0; arguments->confirm_no = 0; arguments->confirm_yes = 0; arguments->confirm_default = 0; arguments->only_at = 0; arguments->noSave = 0; arguments->pw_prompt_mode = 0; set_pw_prompt_mode(arguments->pw_prompt_mode); arguments->prompt_mode = PROMPT_MODE_CLI; set_prompt_mode(arguments->prompt_mode); } void _setRT(struct arguments* arguments, char* rt) { arguments->refresh_token = rt; if (arguments->flows == NULL) { arguments->flows = list_new(); arguments->flows->match = (matchFunction)strequal; } list_rpush(arguments->flows, list_node_new(FLOW_VALUE_REFRESH)); } static void _setScope(const char* arg, struct arguments* arguments) { if (arguments->scope == NULL) { arguments->scope = oidc_strcopy(arg); return; } if (strcaseequal(arguments->scope, AGENT_SCOPE_ALL)) { return; } if (strcaseequal(arg, AGENT_SCOPE_ALL)) { secFree(arguments->scope); arguments->scope = oidc_strcopy(AGENT_SCOPE_ALL); return; } char* tmp = oidc_sprintf("%s %s", arguments->scope, arg); secFree(arguments->scope); arguments->scope = tmp; } static error_t parse_opt(int key, char* arg, struct argp_state* state) { struct arguments* arguments = state->input; switch (key) { // flags case 'd': arguments->delete = 1; break; case 'g': arguments->debug = 1; break; case 'v': arguments->verbose = 1; break; case 'm': arguments->manual = 1; break; case OPT_REAUTHENTICATE: arguments->reauthenticate = 1; break; case OPT_PUBLICCLIENT: arguments->usePublicClient = 1; break; case 'l': arguments->listAccounts = 1; break; case OPT_SECCOMP: arguments->seccomp = 1; break; case OPT_NOURLCALL: arguments->noUrlCall = 1; break; case OPT_NO_WEBSERVER: arguments->noWebserver = 1; break; case OPT_NO_SCHEME: arguments->noScheme = 1; break; case OPT_CONFIRM_NO: arguments->confirm_no = 1; break; case OPT_CONFIRM_YES: arguments->confirm_yes = 1; break; case OPT_CONFIRM_DEFAULT: arguments->confirm_default = 1; break; case OPT_ONLY_AT: arguments->only_at = 1; break; case OPT_NO_SAVE: if (arguments->updateConfigFile) { printError("Update argument cannot be combined with no-save\n"); exit(EXIT_FAILURE); } if (arguments->rename) { printError("Rename argument cannot be combined with no-save\n"); exit(EXIT_FAILURE); } arguments->noSave = 1; break; // arguments case 'u': if (arguments->noSave) { printError("Update argument cannot be combined with no-save\n"); exit(EXIT_FAILURE); } arguments->updateConfigFile = arg; break; case 'p': arguments->print = arg; break; case OPT_PW_ENV: arguments->pw_env = arg ?: OIDC_PASSWORD_ENV_NAME; break; case OPT_PW_CMD: arguments->pw_cmd = arg; break; case OPT_PW_FILE: arguments->pw_file = arg; break; case OPT_PW_GPG: arguments->pw_gpg = arg; break; case OPT_DEVICE: arguments->device_authorization_endpoint = arg; break; case OPT_codeExchange: arguments->codeExchange = arg; break; case OPT_state: arguments->state = arg; break; case 'f': arguments->file = arg; arguments->manual = 1; break; case OPT_RENAME: if (arguments->noSave) { printError("Rename argument cannot be combined with no-save\n"); exit(EXIT_FAILURE); } arguments->rename = arg; break; case OPT_PW_PROMPT_MODE: if (strequal(arg, "cli")) { arguments->pw_prompt_mode = PROMPT_MODE_CLI; } else if (strequal(arg, "gui")) { arguments->pw_prompt_mode = PROMPT_MODE_GUI; common_assertOidcPrompt(); } else { return ARGP_ERR_UNKNOWN; } set_pw_prompt_mode(arguments->pw_prompt_mode); break; case OPT_PROMPT_MODE: if (strequal(arg, "cli")) { arguments->prompt_mode = PROMPT_MODE_CLI; } else if (strequal(arg, "gui")) { arguments->prompt_mode = PROMPT_MODE_GUI; common_assertOidcPrompt(); } else if (strequal(arg, "none")) { arguments->prompt_mode = 0; } else { return ARGP_ERR_UNKNOWN; } set_prompt_mode(arguments->prompt_mode); break; case OPT_TOKEN: arguments->dynRegToken = arg; break; case OPT_CERTPATH: arguments->cert_path = arg; break; case OPT_REFRESHTOKEN_ENV: { const char* env_name = arg ?: OIDC_REFRESHTOKEN_ENV_NAME; char* env_refresh_token = getenv(env_name); if (env_refresh_token == NULL) { printError("%s not set!\n", env_name); exit(EXIT_FAILURE); } // Copy env_pass as subsequent getenv calls might modify our just received // data _setRT(arguments, oidc_strcopy(env_refresh_token)); break; } case OPT_REFRESHTOKEN: _setRT(arguments, arg); break; case OPT_CNID: arguments->cnid = arg; break; case OPT_AUDIENCE: arguments->audience = arg; break; case OPT_CLIENTID: arguments->client_id = arg; arguments->manual = 1; break; case OPT_CLIENTSECRET: arguments->client_secret = arg; break; case OPT_ISSUER: arguments->issuer = arg; break; case OPT_SCOPE: _setScope(arg, arguments); break; case OPT_SCOPE_MAX: _setScope(AGENT_SCOPE_ALL, arguments); break; case OPT_USERNAME: arguments->op_username = arg; break; case OPT_PASSWORD: arguments->op_password = arg; break; // list arguments case 'w': if (arguments->flows == NULL) { arguments->flows = list_new(); arguments->flows->match = (matchFunction)strequal; } list_rpush(arguments->flows, list_node_new(arg)); if (strSubStringCase(arg, "code")) { arguments->_nosec = 1; } break; case OPT_PORT: if (arguments->redirect_uris == NULL) { arguments->redirect_uris = list_new(); arguments->redirect_uris->match = (matchFunction)strequal; arguments->redirect_uris->free = _secFree; } char* redirect_uri = portToUri(strToUShort(arg)); if (redirect_uri == NULL) { oidc_perror(); exit(EXIT_FAILURE); } list_rpush(arguments->redirect_uris, list_node_new(redirect_uri)); break; case OPT_REDIRECT: if (arguments->redirect_uris == NULL) { arguments->redirect_uris = delimitedStringToList(arg, ' '); if (checkRedirectUrisForErrors(arguments->redirect_uris) == OIDC_EERROR) { exit(EXIT_FAILURE); } } else { list_t* tmp = delimitedStringToList(arg, ' '); if (checkRedirectUrisForErrors(tmp) == OIDC_EERROR) { exit(EXIT_FAILURE); } for (size_t i = 0; i < tmp->len; i++) { list_rpush(arguments->redirect_uris, list_node_new(oidc_strcopy(list_at(tmp, i)->val))); } secFreeList(tmp); } break; case 'h': argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { argp_usage(state); } arguments->args[state->arg_num] = arg; break; case ARGP_KEY_END: if (state->arg_num < 1 && arguments->delete) { argp_usage(state); } if (arguments->flows == NULL) { arguments->flows = list_new(); arguments->flows->match = (matchFunction)strequal; if (GUIAvailable()) { list_rpush(arguments->flows, list_node_new("code")); arguments->_nosec = 1; } else { list_rpush(arguments->flows, list_node_new("device")); } } if (arguments->_nosec && !arguments->noUrlCall) { arguments->seccomp = 0; } break; default: return ARGP_ERR_UNKNOWN; } return 0; } static char args_doc[] = "[ACCOUNT_SHORTNAME]"; static char doc[] = "oidc-gen -- A tool for generating oidc account " "configurations which can be used by oidc-add"; struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; oidc-agent-4.2.6/src/oidc-gen/qr.c0000644000175000017500000002160014167074355016160 0ustar marcusmarcus#include "qr.h" #include #include #include #include #include #include "utils/guiChecker.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" #include "utils/string/stringbuilder.h" static const size_t margin = 2; static void getUTF8_margin(str_builder_t* str, size_t realwidth, const char* white, const char* reset, const char* full) { for (size_t y = 0; y < margin / 2; y++) { str_builder_add_str(str, white); for (size_t x = 0; x < realwidth; x++) { str_builder_add_str(str, full); } str_builder_add_str(str, reset); str_builder_add_char(str, '\n'); } } static void getANSI_margin(str_builder_t* str, size_t realwidth, const char* white) { str_builder_t* row = str_builder_create( 3 * realwidth); // we are writing 2*realwidth + color coding; so 3 should // be enough space so no reallocation is needed str_builder_add_str(row, white); char* spaces = repeatChar(' ', realwidth * 2); str_builder_add_str(row, spaces); secFree(spaces); str_builder_add_str(row, "\033[0m\n"); // reset to default colors char* r = str_builder_get_string(row); secFree_str_builder(row); for (size_t y = 0; y < margin; y++) { str_builder_add_str(str, r); } secFree(r); } static char* getUTF8(const QRcode* qrcode, int use_ansi, int invert) { const char *white, *reset; const char* empty = " "; const char* lowhalf = "\342\226\204"; const char* uphalf = "\342\226\200"; const char* full = "\342\226\210"; if (invert) { const char* tmp = empty; empty = full; full = tmp; tmp = lowhalf; lowhalf = uphalf; uphalf = tmp; } if (use_ansi) { if (use_ansi == 2) { white = "\033[38;5;231m\033[48;5;16m"; } else { white = "\033[40;37;1m"; } reset = "\033[0m"; } else { white = ""; reset = ""; } size_t realwidth = (qrcode->width + margin * 2); str_builder_t* str = str_builder_create(1024); /* top margin */ getUTF8_margin(str, realwidth, white, reset, full); /* data */ for (int y = 0; y < qrcode->width; y += 2) { unsigned char* row1 = qrcode->data + y * qrcode->width; unsigned char* row2 = row1 + qrcode->width; str_builder_add_str(str, white); for (size_t x = 0; x < margin; x++) { str_builder_add_str(str, full); } for (int x = 0; x < qrcode->width; x++) { if (row1[x] & 1) { if (y < qrcode->width - 1 && row2[x] & 1) { str_builder_add_str(str, empty); } else { str_builder_add_str(str, lowhalf); } } else if (y < qrcode->width - 1 && row2[x] & 1) { str_builder_add_str(str, uphalf); } else { str_builder_add_str(str, full); } } for (size_t x = 0; x < margin; x++) { str_builder_add_str(str, full); } str_builder_add_str(str, reset); str_builder_add_char(str, '\n'); } /* bottom margin */ getUTF8_margin(str, realwidth, white, reset, full); char* qr = str_builder_get_string(str); secFree_str_builder(str); return qr; } static char* getANSI(const QRcode* qrcode, int ansi256) { const char* white = ansi256 ? "\033[48;5;231m" : "\033[47m"; const char* black = ansi256 ? "\033[48;5;16m" : "\033[40m"; size_t realwidth = (qrcode->width + margin * 2); str_builder_t* str = str_builder_create(1024); /* top margin */ getANSI_margin(str, realwidth, white); /* data */ unsigned char* p = qrcode->data; char* m = repeatChar(' ', 2 * margin); for (int y = 0; y < qrcode->width; y++) { unsigned char* row = (p + (y * qrcode->width)); str_builder_add_str(str, white); str_builder_add_str(str, m); unsigned char last = 0; for (int x = 0; x < qrcode->width; x++) { if (*(row + x) & 0x1) { if (last != 1) { str_builder_add_str(str, black); last = 1; } } else if (last != 0) { str_builder_add_str(str, white); last = 0; } str_builder_add_str(str, " "); } if (last != 0) { str_builder_add_str(str, white); } str_builder_add_str(str, m); str_builder_add_str(str, "\033[0m\n"); } secFree(m); /* bottom margin */ getANSI_margin(str, realwidth, white); char* qr = str_builder_get_string(str); secFree_str_builder(str); return qr; } static int writeXPM(const QRcode* qrcode, FILE* fp, size_t size) { size_t realwidth = (qrcode->width + margin * 2) * size; size_t realmargin = margin * size; char* row = secAlloc((size_t)realwidth + 1); if (row == NULL) { return oidc_errno; } const char fg[7] = {'0', '0', '0', '0', '0', '0', 0}; const char bg[7] = {'f', 'f', 'f', 'f', 'f', 'f', 0}; fputs("/* XPM */\n", fp); fputs("static const char *const qrcode_xpm[] = {\n", fp); fputs("/* width height ncolors chars_per_pixel */\n", fp); fprintf(fp, "\"%lu %lu 2 1\",\n", realwidth, realwidth); fputs("/* colors */\n", fp); fprintf(fp, "\"F c #%s\",\n", fg); fprintf(fp, "\"B c #%s\",\n", bg); fputs("/* pixels */\n", fp); memset(row, 'B', (size_t)realwidth); row[realwidth] = '\0'; for (size_t y = 0; y < realmargin; y++) { fprintf(fp, "\"%s\",\n", row); } unsigned char* p = qrcode->data; for (int y = 0; y < qrcode->width; y++) { for (size_t yy = 0; yy < size; yy++) { fputs("\"", fp); for (size_t x = 0; x < margin; x++) { for (size_t xx = 0; xx < size; xx++) { fputs("B", fp); } } for (int x = 0; x < qrcode->width; x++) { for (size_t xx = 0; xx < size; xx++) { if (p[(y * qrcode->width) + x] & 0x1) { fputs("F", fp); } else { fputs("B", fp); } } } for (size_t x = 0; x < margin; x++) { for (size_t xx = 0; xx < size; xx++) { fputs("B", fp); } } fputs("\",\n", fp); } } for (size_t y = 0; y < realmargin; y++) { fprintf(fp, "\"%s\"%s\n", row, y < (size - 1) ? "," : "};"); } secFree(row); return 0; } static int writeEPS(const QRcode* qrcode, FILE* fp, size_t size) { const float fg[3] = {(float)0., (float)0., (float)0.}; const float bg[3] = {(float)1., (float)1., (float)1.}; size_t realwidth = (qrcode->width + margin * 2) * size; /* EPS file header */ fprintf(fp, "%%!PS-Adobe-2.0 EPSF-1.2\n" "%%%%BoundingBox: 0 0 %lu %lu\n" "%%%%Pages: 1 1\n" "%%%%EndComments\n", realwidth, realwidth); /* draw point */ fprintf(fp, "/p { " "moveto " "0 1 rlineto " "1 0 rlineto " "0 -1 rlineto " "fill " "} bind def\n"); /* set color */ fprintf(fp, "gsave\n"); fprintf(fp, "%f %f %f setrgbcolor\n", bg[0], bg[1], bg[2]); fprintf(fp, "%lu %lu scale\n", realwidth, realwidth); fprintf(fp, "0 0 p\ngrestore\n"); fprintf(fp, "%f %f %f setrgbcolor\n", fg[0], fg[1], fg[2]); fprintf(fp, "%lu %lu scale\n", size, size); /* data */ unsigned char* p = qrcode->data; for (int y = 0; y < qrcode->width; y++) { unsigned char* row = (p + (y * qrcode->width)); int yy = (int)(margin + qrcode->width - y - 1); for (int x = 0; x < qrcode->width; x++) { if (*(row + x) & 0x1) { fprintf(fp, "%lu %d p ", margin + x, yy); } } } fprintf(fp, "\n%%%%EOF\n"); return 0; } #define IMG_FILE_XPM 1 #define IMG_FILE_ESP 2 #ifdef __APPLE__ #define IMG_DEFAULT_FILE_TYPE IMG_FILE_ESP #else #define IMG_DEFAULT_FILE_TYPE IMG_FILE_XPM #endif static int writeIMGFile(const QRcode* qrcode, const char* outfile, unsigned char format) { FILE* fp = fopen(outfile, "wb"); if (fp == NULL) { return errno; } int (*imageWriter)(const QRcode*, FILE*, size_t); switch (format) { case IMG_FILE_XPM: imageWriter = writeXPM; break; case IMG_FILE_ESP: imageWriter = writeEPS; break; default: oidc_setInternalError("unknown QR image file format"); return oidc_errno; } int ret = imageWriter(qrcode, fp, 3); fclose(fp); return ret; } char* getQRCode(const char* content) { return GUIAvailable() ? getUTF8QRCode(content) : getANSIQRCode(content); } char* getUTF8QRCode(const char* content) { QRcode* qr = QRcode_encodeString(content, 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (qr == NULL) { return NULL; } char* ret = getUTF8(qr, 1, 0); QRcode_free(qr); return ret; } char* getANSIQRCode(const char* content) { QRcode* qr = QRcode_encodeString(content, 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (qr == NULL) { return NULL; } char* ret = getANSI(qr, 1); QRcode_free(qr); return ret; } int getIMGQRCode(const char* content, const char* outpath) { QRcode* qr = QRcode_encodeString(content, 0, QR_ECLEVEL_L, QR_MODE_8, 1); if (qr == NULL) { return -1; } int ret = writeIMGFile(qr, outpath, IMG_DEFAULT_FILE_TYPE); QRcode_free(qr); return ret; } oidc-agent-4.2.6/src/oidc-gen/parse_ipc.h0000644000175000017500000000027414167074355017514 0ustar marcusmarcus#ifndef PARSE_IPC_GEN_H #define PARSE_IPC_GEN_H #include "oidc-gen/oidc-gen_options.h" char* gen_parseResponse(char* res, const struct arguments* argumentsg); #endif // PARSE_IPC_GEN_H oidc-agent-4.2.6/src/oidc-gen/oidc-gen.h0000644000175000017500000000033714167074355017234 0ustar marcusmarcus#ifndef OIDC_GEN_H #define OIDC_GEN_H #include "defines/version.h" #include "oidc-gen_options.h" const char* argp_program_version = GEN_VERSION; const char* argp_program_bug_address = BUG_ADDRESS; #endif // OIDC_GEN_H oidc-agent-4.2.6/src/oidc-gen/parse_ipc.c0000644000175000017500000001101614167074355017503 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "parse_ipc.h" #include #include #include "defines/agent_values.h" #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "oidc-gen/gen_handler.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" /** * @param res a pointer to the response that should be parsed. The pointer will * be freed! * @note Depending on arguments->only_at we are looking for an at or the config */ char* gen_parseResponse(char* res, const struct arguments* arguments) { INIT_KEY_VALUE(IPC_KEY_STATUS, IPC_KEY_CONFIG, OIDC_KEY_ERROR, IPC_KEY_URI, IPC_KEY_INFO, OIDC_KEY_STATE, IPC_KEY_DEVICE, OIDC_KEY_ACCESSTOKEN); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); exit(EXIT_FAILURE); } KEY_VALUE_VARS(status, config, error, uri, info, state, device, at); secFree(res); if (_error != NULL) { printError("Error: %s\n", _error); if (_info) { printNormal("%s\n", _info); } SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } if (arguments->only_at && _at) { char* ret = oidc_strcopy(_at); SEC_FREE_KEY_VALUES(); return ret; } if (_config == NULL && _at == 0) { // res does not contain config if (strcaseequal(_status, STATUS_NOTFOUND)) { logger(DEBUG, "%s", _info); SEC_FREE_KEY_VALUES(); oidc_errno = OIDC_EWRONGSTATE; return NULL; } if (strcaseequal(_status, STATUS_FOUNDBUTDONE)) { printNormal("\n%s\n", _info); logger(DEBUG, "%s", _info); SEC_FREE_KEY_VALUES(); return oidc_strcopy(STATUS_FOUNDBUTDONE); } if (strcaseequal(_status, STATUS_ACCEPTED) && _device) { pass; } else if (_uri == NULL) { printError("Error: response does not contain %s\n", arguments->only_at ? "access token" : "updated config"); } } printStdout("%s\n", _status); if (strcaseequal(_status, STATUS_SUCCESS)) { if (arguments->only_at == 0) { printStdout( "The generated account config was successfully added to oidc-agent. " "You don't have to run oidc-add.\n"); } } else if (strcaseequal(_status, STATUS_ACCEPTED)) { if (_info) { printImportant("%s\n", _info); } if (_device) { char* ret = gen_handleDeviceFlow(_device, arguments); SEC_FREE_KEY_VALUES(); return ret; } if (_uri) { char* uri = strreplace(_uri, " ", "%20"); printImportant("To continue and approve the registered client visit the " "following URL in a Browser of your choice:\n%s\n", uri); char* redirect_uri = extractParameterValueFromUri(_uri, OIDC_KEY_REDIRECTURI); int no_statelookup = 0; if (strstarts(redirect_uri, AGENT_CUSTOM_SCHEME)) { printImportant("\nYou are using a redirect uri with a custom scheme. " "Your browser will redirect you to a another oidc-gen " "instance automatically. You then can complete the " "account configuration generation process there.\n"); no_statelookup = 1; } else if (arguments ->noWebserver) { // TODO also when agent has this property printImportant( "\nYou have chosen to not use a webserver. You therefore have to " "do a manual redirect. Your browser will redirect you to '%s' " "which will not succeed, because oidc-agent did not start a " "webserver. Copy the whole url you are being redirected to and " "pass it to:\noidc-gen --codeExchange=''\n", redirect_uri); no_statelookup = 1; } secFree(redirect_uri); if (!arguments->noUrlCall) { char* cmd = oidc_sprintf(URL_OPENER " \"%s\"", uri); if (system(cmd) != 0) { logger(NOTICE, "Cannot open url"); } secFree(cmd); } secFree(uri); if (no_statelookup) { exit(EXIT_SUCCESS); } } if (_state) { sleep(2); char* ret = configFromStateLookUp(_state, arguments); SEC_FREE_KEY_VALUES(); return ret; } } char* ret = arguments->only_at ? oidc_strcopy(_at) : oidc_strcopy(_config); SEC_FREE_KEY_VALUES(); return ret; } oidc-agent-4.2.6/src/oidc-gen/device_code.h0000644000175000017500000000030314167074355017771 0ustar marcusmarcus#ifndef OIDC_AGENT_DEVICE_CODE_H #define OIDC_AGENT_DEVICE_CODE_H #include "oidc-agent/oidc/device_code.h" void printDeviceCode(struct oidc_device_code c); #endif // OIDC_AGENT_DEVICE_CODE_H oidc-agent-4.2.6/src/oidc-gen/oidc-gen.c0000644000175000017500000000417314167074355017231 0ustar marcusmarcus#include "oidc-gen.h" #include #include "gen_handler.h" #include "utils/accountUtils.h" #include "utils/commonFeatures.h" #include "utils/disableTracing.h" #include "utils/file_io/fileUtils.h" #include "utils/listUtils.h" #include "utils/logger.h" #ifndef __APPLE__ #include "privileges/gen_privileges.h" #endif int main(int argc, char** argv) { platform_disable_tracing(); logger_open("oidc-gen"); logger_setloglevel(NOTICE); struct arguments arguments; initArguments(&arguments); argp_parse(&argp, argc, argv, 0, 0, &arguments); if (arguments.debug) { logger_setloglevel(DEBUG); } #ifndef __APPLE__ if (arguments.seccomp) { initOidcGenPrivileges(&arguments); } #endif assertOidcDirExists(); if (arguments.listAccounts) { common_handleListConfiguredAccountConfigs(); exit(EXIT_SUCCESS); } if (arguments.print) { gen_handlePrint(arguments.print, &arguments); exit(EXIT_SUCCESS); } if (arguments.updateConfigFile) { gen_handleUpdateConfigFile(arguments.updateConfigFile, &arguments); exit(EXIT_SUCCESS); } if (arguments.rename) { gen_handleRename(arguments.args[0], &arguments); exit(EXIT_SUCCESS); } if (arguments.codeExchange) { handleCodeExchange(&arguments); exit(EXIT_SUCCESS); } common_assertAgent(0); if (arguments.state) { stateLookUpWithConfigSave(arguments.state, &arguments); exit(EXIT_SUCCESS); } if (arguments.delete) { handleDelete(&arguments); exit(EXIT_SUCCESS); } if (arguments.reauthenticate) { reauthenticate(arguments.args[0], &arguments); exit(EXIT_SUCCESS); } struct oidc_account* account = NULL; if (arguments.only_at) { handleOnlyAT(&arguments); } else if (arguments.manual) { if (arguments.file) { account = getAccountFromMaybeEncryptedFile(arguments.file); } manualGen(account, &arguments); } else { struct oidc_account* account = registerClient(&arguments); if (account) { handleGen(account, &arguments, NULL); } else { secFreeList(arguments.flows); exit(EXIT_FAILURE); } } secFreeList(arguments.flows); exit(EXIT_SUCCESS); } oidc-agent-4.2.6/src/oidc-gen/gen_handler.h0000644000175000017500000000437414167074355020022 0ustar marcusmarcus#ifndef GEN_HANDLER_H #define GEN_HANDLER_H #include "account/account.h" #include "oidc-gen_options.h" #include "utils/oidc_error.h" void manualGen(struct oidc_account* account, const struct arguments* arguments); void reauthenticate(const char* shortname, struct arguments* arguments); void handleGen(struct oidc_account* account, const struct arguments* arguments, const char* cryptPass); struct oidc_account* manual_genNewAccount(struct oidc_account* account, const struct arguments* arguments, char** cryptPassPtr); struct oidc_account* registerClient(struct arguments* arguments); void handleDelete(const struct arguments*); oidc_error_t gen_saveAccountConfig(const char* config, const char* shortname, const char* hint, const char* suggestedPassword, const struct arguments* arguments); void handleCodeExchange(const struct arguments* arguments); void stateLookUpWithConfigSave(const char* state, const struct arguments* arguments); char* configFromStateLookUp(const char* state, const struct arguments* arguments); void gen_handlePrint(const char* file, const struct arguments* arguments); char* gen_handleDeviceFlow(const char* json_device, const struct arguments* arguments); oidc_error_t gen_handlePublicClient(struct oidc_account* account, struct arguments* arguments); void gen_handleList(); void gen_handleUpdateConfigFile(const char* file, const struct arguments* arguments); char* gen_handleScopeLookup(const char* issuer_url, const char* cert_path); void gen_handleRename(const char* shortname, const struct arguments* arguments); void removeFileFromAgent(const char* filename); void writeFileToAgent(const char* filename, const char* data); char* readFileFromAgent(const char* filename, int ignoreError); void handleOnlyAT(struct arguments* arguments); #endif // GEN_HANDLER_H oidc-agent-4.2.6/src/oidc-gen/gen_signal_handler.h0000644000175000017500000000034614120404223021326 0ustar marcusmarcus#ifndef OIDC_GEN__SIGNAL_HANDLER_H #define OIDC_GEN__SIGNAL_HANDLER_H void registerSignalHandler(const char* state); void gen_http_signal_handler(int signo); void unregisterSignalHandler(); #endif // OIDC_GEN__SIGNAL_HANDLER_H oidc-agent-4.2.6/src/oidc-gen/qr.h0000644000175000017500000000040514167074355016165 0ustar marcusmarcus#ifndef OIDC_AGENT_QR_H #define OIDC_AGENT_QR_H char* getQRCode(const char* content); char* getUTF8QRCode(const char* content); char* getANSIQRCode(const char* content); int getIMGQRCode(const char* content, const char* outpath); #endif // OIDC_AGENT_QR_H oidc-agent-4.2.6/src/oidc-gen/gen_consenter.c0000644000175000017500000000212314120404223020342 0ustar marcusmarcus#include "gen_consenter.h" #include "utils/prompt.h" #include "utils/prompt_mode.h" int _gen_prompter(const char* prompt, const struct arguments* arguments, int (*consentFnc)(const char*)) { if (arguments->confirm_yes) { return 1; } if (arguments->confirm_no) { return 0; } int oldMode = prompt_mode(); if (!oldMode) { int newMode = pw_prompt_mode(); // If pw_prompt not set, it will return // PROMPT_MODE_CLI set_prompt_mode(newMode); } int ret = consentFnc(prompt); set_prompt_mode(oldMode); return ret; } int gen_promptConsentDefaultNo(const char* text, const struct arguments* arguments) { if (arguments->confirm_default) { return 0; } return _gen_prompter(text, arguments, promptConsentDefaultNo); } int gen_promptConsentDefaultYes(const char* text, const struct arguments* arguments) { if (arguments->confirm_default) { return 1; } return _gen_prompter(text, arguments, promptConsentDefaultYes); } oidc-agent-4.2.6/src/oidc-gen/gen_handler.c0000644000175000017500000011453414167074355020015 0ustar marcusmarcus#include "gen_handler.h" #include #include #include #include #include #include "account/account.h" #include "account/issuer_helper.h" #include "defines/agent_values.h" #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "ipc/cryptCommunicator.h" #include "oidc-agent/oidc/device_code.h" #include "oidc-gen/device_code.h" #include "oidc-gen/gen_consenter.h" #include "oidc-gen/gen_signal_handler.h" #include "oidc-gen/parse_ipc.h" #include "oidc-gen/promptAndSet/promptAndSet.h" #include "oidc-token/parse.h" #include "utils/accountUtils.h" #include "utils/crypt/crypt.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/errorUtils.h" #include "utils/file_io/cryptFileUtils.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/oidc/device.h" #include "utils/parseJson.h" #include "utils/password_entry.h" #include "utils/printer.h" #include "utils/prompt.h" #include "utils/promptUtils.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" #define IGNORE_ERROR 1 struct state { int doNotMergeTmpFile; } oidc_gen_state; static const unsigned char remote = 0; char* _gen_response(struct oidc_account* account, const struct arguments* arguments) { readDeviceAuthEndpoint(account, arguments); readAudience(account, arguments); cJSON* flow_json = listToJSONArray(arguments->flows); char* log_tmp = jsonToString(flow_json); logger(DEBUG, "arguments flows in handleGen are '%s'", log_tmp); secFree(log_tmp); if (flow_json == NULL || jsonArrayIsEmpty(flow_json)) { list_t* redirect_uris = account_getRedirectUris(account); if (redirect_uris != NULL && redirect_uris->len > 0) { flow_json = jsonArrayAddStringValue(flow_json, FLOW_VALUE_CODE); } if (strValid(account_getRefreshToken(account))) { flow_json = jsonArrayAddStringValue(flow_json, FLOW_VALUE_REFRESH); } if ((strValid(account_getUsername(account)) && strValid(account_getPassword(account))) || flow_json == NULL || jsonArrayIsEmpty(flow_json)) { flow_json = jsonArrayAddStringValue(flow_json, FLOW_VALUE_PASSWORD); } } char* flow = jsonToString(flow_json); secFreeJson(flow_json); logger(DEBUG, "flows in handleGen are '%s'", flow); if (strSubStringCase(flow, FLOW_VALUE_PASSWORD) && (!strValid(account_getUsername(account)) || !strValid(account_getPassword(account)))) { needUsername(account, arguments); needPassword(account, arguments); } char* json = accountToJSONString(account); if (!arguments->only_at) { printStdout("Generating account configuration ...\n"); } struct password_entry pw = {.shortname = account_getName(account)}; unsigned char type = PW_TYPE_PRMT; if (arguments->pw_cmd) { pwe_setCommand(&pw, arguments->pw_cmd); type |= PW_TYPE_CMD; } if (arguments->pw_file) { pwe_setFile(&pw, arguments->pw_file); type |= PW_TYPE_FILE; } if (arguments->pw_env && pw.password == NULL) { pwe_setPassword(&pw, getenv(arguments->pw_env)); if (pw.password) { type |= PW_TYPE_MEM; } } if (arguments->pw_gpg) { pwe_setFile(&pw, arguments->pw_gpg); type |= PW_TYPE_GPG; } pwe_setType(&pw, type); char* pw_str = passwordEntryToJSONString(&pw); char* res = ipc_cryptCommunicate(remote, REQUEST_GEN, json, flow, pw_str, arguments->noWebserver, arguments->noScheme, arguments->only_at); secFree(flow); secFree(pw_str); secFree(json); if (NULL == res) { printError("Error: %s\n", oidc_serror()); secFreeAccount(account); exit(EXIT_FAILURE); } char* ret = gen_parseResponse(res, arguments); return ret; } void handleGen(struct oidc_account* account, const struct arguments* arguments, const char* suggested_password) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } char* json = _gen_response(account, arguments); char* issuer = getJSONValueFromString(json, AGENT_KEY_ISSUERURL); char* name = getJSONValueFromString(json, AGENT_KEY_SHORTNAME); if (!arguments->noSave) { updateIssuerConfig(issuer, name); } secFree(issuer); char* hint = oidc_sprintf("account configuration '%s'", name); gen_saveAccountConfig(json, account_getName(account), hint, suggested_password, arguments); printStdout("Everything setup correctly!\n"); secFree(hint); secFree(name); secFreeAccount(account); secFree(json); } void manualGen(struct oidc_account* account, const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } char* cryptPass = NULL; char** cryptPassPtr = &cryptPass; account = manual_genNewAccount(account, arguments, cryptPassPtr); cryptPass = *cryptPassPtr; handleGen(account, arguments, cryptPass); secFree(cryptPass); } void reauthenticate(const char* shortname, struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } if (shortname == NULL) { printError( "You have to specify a shortname to update the refresh token for it\n"); exit(EXIT_FAILURE); } if (!oidcFileDoesExist(shortname)) { printError("No account configuration found with that shortname\n"); exit(EXIT_FAILURE); } struct resultWithEncryptionPassword result = getDecryptedAccountAndPasswordFromFilePrompt( shortname, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (result.result == NULL) { oidc_perror(); secFree(result.password); exit(EXIT_FAILURE); } if (arguments->pw_gpg == NULL && result.password == NULL) { arguments->pw_gpg = extractPGPKeyIDFromOIDCFile(shortname); } handleGen(result.result, arguments, result.password); exit(EXIT_SUCCESS); } void gen_handleRename(const char* shortname, const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } if (shortname == NULL) { printError("You have to specify a shortname to rename it\n"); exit(EXIT_FAILURE); } if (!oidcFileDoesExist(shortname)) { printError("No account configuration found with that shortname\n"); exit(EXIT_FAILURE); } const char* new_shortname = arguments->rename; if (new_shortname == NULL) { printError("You have to specify the new shortname to rename '%s'\n", shortname); exit(EXIT_FAILURE); } if (oidcFileDoesExist(new_shortname)) { printError("Account configuration '%s' already exists\n", new_shortname); exit(EXIT_FAILURE); } struct resultWithEncryptionPassword result = getDecryptedAccountAndPasswordFromFilePrompt( shortname, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (result.result == NULL) { oidc_perror(); secFree(result.password); exit(EXIT_FAILURE); } struct oidc_account* account = result.result; secFree(account->shortname); account->shortname = oidc_strcopy(new_shortname); // We don't use account_setName since this // also updates the client name char* hint = oidc_sprintf("account configuration '%s'", new_shortname); char* json = accountToJSONString(account); if (gen_saveAccountConfig(json, account_getName(account), hint, result.password, arguments) != OIDC_SUCCESS) { oidc_perror(); } else { if (removeOidcFile(shortname) != 0) { printError("error removing old configuration file: %s", oidc_serror()); } } secFree(json); secFree(hint); } char* _adjustUriSlash(const char* uri, unsigned char uri_needs_slash) { if (uri == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (uri_needs_slash) { return uri[strlen(uri) - 1] == '/' ? oidc_strcopy(uri) : oidc_strcat(uri, "/"); } return uri[strlen(uri) - 1] != '/' ? oidc_strcopy(uri) : oidc_strncopy(uri, strlen(uri) - 1); } void handleCodeExchange(const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } char* error = extractParameterValueFromUri(arguments->codeExchange, "error"); if (error) { char* error_description = extractParameterValueFromUri( arguments->codeExchange, "error_description"); char* err = combineError(error, error_description); logger(ERROR, "HttpRedirect Error: %s", err); secFree(error_description); secFree(error); oidc_seterror(err); oidc_errno = OIDC_EOIDC; secFree(err); oidc_perror(); exit(EXIT_FAILURE); } struct codeState codeState = codeStateFromURI(arguments->codeExchange); if (codeState.state == NULL) { printError("Uri does not contain a state\n"); exit(EXIT_FAILURE); } if (codeState.code == NULL) { printError("Uri does not contain a code\n"); exit(EXIT_FAILURE); } char* tmp = oidc_strcopy(codeState.state); char* uri_slash_s = strtok(tmp, ":"); /* char* random_state = */ strtok(NULL, ":"); char* len_s = strtok(NULL, ":"); char* socket_path_base64 = strtok(NULL, ":"); size_t len = strToULong(len_s); char* socket_path = NULL; if (socket_path_base64 == NULL) { logger(NOTICE, "No socket_path encoded in state"); socket_path = oidc_strcopy(getenv(OIDC_SOCK_ENV_NAME)); if (socket_path == NULL) { printError("Socket path not encoded in url state and not available from " "environment. Cannot connect to oidc-agent.\n"); exit(EXIT_FAILURE); } } else { socket_path = secAlloc(len + 1); fromBase64UrlSafe(socket_path_base64, len, (unsigned char*)socket_path); } unsigned char uri_needs_slash = strToUChar(uri_slash_s); secFree(tmp); char* baseUri = _adjustUriSlash(codeState.uri, uri_needs_slash); char* uri = oidc_sprintf("%s?code=%s&state=%s", baseUri, codeState.code, codeState.state); secFree(baseUri); secFreeCodeState(codeState); char* res = ipc_cryptCommunicateWithPath(socket_path, REQUEST_CODEEXCHANGEGEN, uri); secFree(socket_path); secFree(uri); if (NULL == res) { printError("Error: %s\n", oidc_serror()); exit(EXIT_FAILURE); } char* config = gen_parseResponse(res, arguments); char* short_name = oidc_strcopy(arguments->args[0]); if (!strValid(short_name)) { secFree(short_name); short_name = getJSONValueFromString(config, AGENT_KEY_SHORTNAME); } while (!strValid(short_name)) { secFree(short_name); short_name = prompt("Enter short name for the account to configure: ", "short name", NULL, CLI_PROMPT_VERBOSE); if (oidcFileDoesExist(short_name)) { if (!gen_promptConsentDefaultNo( "An account with that shortname already exists. Overwrite?", arguments)) { secFree(short_name); } } } char* hint = oidc_sprintf("account configuration '%s'", short_name); gen_saveAccountConfig(config, short_name, hint, NULL, arguments); secFree(hint); secFree(short_name); secFree(config); } char* singleStateLookUp(const char* state, const struct arguments* arguments) { char* res = ipc_cryptCommunicate(remote, REQUEST_STATELOOKUP, state); if (NULL == res) { printStdout("\n"); printError("Error: %s\n", oidc_serror()); exit(EXIT_FAILURE); } if (arguments->verbose) { printStdout("%s\n", res); } return gen_parseResponse(res, arguments); } char* configFromStateLookUp(const char* state, const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } registerSignalHandler(state); char* config = NULL; printNormal( "Polling oidc-agent to get the generated account configuration ..."); fflush(stderr); for (unsigned int i = 0; config == NULL && i < MAX_POLL; i++) { config = singleStateLookUp(state, arguments); if (config == NULL) { sleep(DELTA_POLL); printNormal("."); fflush(stderr); } } printNormal("\n"); if (config == NULL) { printNormal("Polling is boring. Already tried %d times. I stop now.\n", MAX_POLL); printImportant("Please press Enter to try it again.\n"); getchar(); config = singleStateLookUp(state, arguments); if (config == NULL) { printError("Could not receive generated account configuration for " "state='%s'\n", state); printImportant( "Please try state lookup again by using:\noidc-gen --state=%s\n", state); _secFree(ipc_cryptCommunicate(remote, REQUEST_TERMHTTP, state)); exit(EXIT_FAILURE); } } unregisterSignalHandler(); if (strequal(config, STATUS_FOUNDBUTDONE)) { exit(EXIT_SUCCESS); } return config; } void stateLookUpWithConfigSave(const char* state, const struct arguments* arguments) { char* config = singleStateLookUp(state, arguments); if (config == NULL) { oidc_perror(); exit(EXIT_FAILURE); } char* issuer = getJSONValueFromString(config, AGENT_KEY_ISSUERURL); char* short_name = getJSONValueFromString(config, AGENT_KEY_SHORTNAME); if (!arguments->noSave) { updateIssuerConfig(issuer, short_name); } secFree(issuer); char* hint = oidc_sprintf("account configuration '%s'", short_name); gen_saveAccountConfig(config, short_name, hint, NULL, arguments); secFree(hint); secFree(short_name); secFree(config); exit(EXIT_SUCCESS); } char* gen_handleDeviceFlow(const char* json_device, const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } struct oidc_device_code* dc = getDeviceCodeFromJSON(json_device); printDeviceCode(*dc); size_t interval = oidc_device_getInterval(*dc); size_t expires_in = oidc_device_getExpiresIn(*dc); time_t expires_at = expires_in ? time(NULL) + expires_in : 0; secFreeDeviceCode(dc); char* ret = pollDeviceCode(json_device, interval, expires_at, remote, arguments->only_at); if (ret == NULL) { oidc_perror(); exit(EXIT_FAILURE); } return ret; } struct oidc_account* manual_genNewAccount(struct oidc_account* account, const struct arguments* arguments, char** cryptPassPtr) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } if (account == NULL) { account = secAlloc(sizeof(struct oidc_account)); } if (!arguments->only_at) { needName(account, arguments); char* shortname = account_getName(account); if (oidcFileDoesExist(shortname)) { struct resultWithEncryptionPassword result = getDecryptedAccountAndPasswordFromFilePrompt( shortname, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (result.result == NULL) { oidc_perror(); secFree(result.password); exit(EXIT_FAILURE); } secFreeAccount(account); account = result.result; *cryptPassPtr = result.password; } else { printStdout( "No account exists with this short name. Creating new configuration " "...\n"); char* tmpFile = oidc_strcat(CLIENT_TMP_PREFIX, shortname); char* tmpData = readFileFromAgent(tmpFile, IGNORE_ERROR); if (tmpData != NULL) { if (gen_promptConsentDefaultYes( "Found temporary file for this shortname. Do " "you want to use it?", arguments)) { secFreeAccount(account); account = getAccountFromJSON(tmpData); } else { oidc_gen_state.doNotMergeTmpFile = 1; } } secFree(tmpData); secFree(tmpFile); } } readCertPath(account, arguments); needIssuer(account, arguments); needClientId(account, arguments); askOrNeedClientSecret(account, arguments, arguments->usePublicClient); needScope(account, arguments); readAudience(account, arguments); readRefreshToken(account, arguments); if (findInList(arguments->flows, FLOW_VALUE_PASSWORD)) { needUsername(account, arguments); needPassword(account, arguments); } int redirectUrisOptional = // redirectUris are not needed if account_refreshTokenIsValid(account) || // RT provided OR (strValid(account_getUsername(account)) && strValid( account_getPassword(account))) || // User Credentials provided OR (arguments->flows && strequal(list_at(arguments->flows, 0)->val, FLOW_VALUE_DEVICE) // Device Flow ); askOrNeedRedirectUris(account, arguments, redirectUrisOptional); return account; } struct oidc_account* registerClient(struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } struct oidc_account* account = secAlloc(sizeof(struct oidc_account)); needName(account, arguments); if (oidcFileDoesExist(account_getName(account))) { printError("An account with that shortname is already configured\n"); exit(EXIT_FAILURE); } char* tmpFile = oidc_strcat(CLIENT_TMP_PREFIX, account_getName(account)); char* tmpData = readFileFromAgent(tmpFile, IGNORE_ERROR); if (tmpData != NULL && !arguments->usePublicClient) { if (gen_promptConsentDefaultYes( "Found temporary file for this shortname. Do " "you want to use it?", arguments)) { secFreeAccount(account); account = getAccountFromJSON(tmpData); handleGen(account, arguments, NULL); exit(EXIT_SUCCESS); } else { oidc_gen_state.doNotMergeTmpFile = 1; } } secFree(tmpFile); secFree(tmpData); readCertPath(account, arguments); needIssuer(account, arguments); readDeviceAuthEndpoint(account, arguments); needScope(account, arguments); if (arguments->usePublicClient) { oidc_error_t pubError = gen_handlePublicClient(account, arguments); switch (pubError) { case OIDC_SUCCESS: // Actually we should already have exited exit(EXIT_SUCCESS); case OIDC_ENOPUBCLIENT: printError("Could not find a public client for this issuer.\n"); break; default: oidc_perror(); } exit(EXIT_FAILURE); } readRedirectUris(account, arguments); char* json = accountToJSONString(account); printStdout("Registering Client ...\n"); char* flows = listToJSONArrayString(arguments->flows); char* res = ipc_cryptCommunicate(remote, REQUEST_REGISTER_AUTH, json, flows, arguments->dynRegToken ?: ""); secFree(flows); secFree(json); if (NULL == res) { printError("Error: %s\n", oidc_serror()); secFreeAccount(account); exit(EXIT_FAILURE); } if (arguments->verbose && res) { printStdout("%s\n", res); } INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_CLIENT, IPC_KEY_INFO, IPC_KEY_MAXSCOPES); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); exit(EXIT_FAILURE); } secFree(res); KEY_VALUE_VARS(status, error, client, info, max_scopes); if (_error) { if (strValid(_client)) { // if a client was registered, but there's // still an // error (i.e. not all required scopes could be // registered) temporarily save the client config cJSON* json_config = stringToJson(_client); jsonAddStringValue(json_config, AGENT_KEY_ISSUERURL, account_getIssuerUrl(account)); jsonAddStringValue(json_config, AGENT_KEY_CERTPATH, account_getCertPath(account)); secFree(_client); char* config = jsonToString(json_config); secFreeJson(json_config); char* path = oidc_strcat(CLIENT_TMP_PREFIX, account_getName(account)); if (arguments->verbose) { printStdout("Writing client config temporary to agent\n"); } writeFileToAgent(path, config); secFree(path); } // if dyn reg not possible try using a preregistered public client if (errorMessageIsForError(_error, OIDC_ENOSUPREG)) { printStdout("Dynamic client registration not supported by this " "issuer.\nTry using a public client ...\n"); } else if (strstarts(_error, "An account with this shortname is already loaded.")) { printError("%s\n", _error); exit(EXIT_FAILURE); } else { printNormal("The following error occurred during dynamic client " "registration:\n%s\n", _error); if (_info) { printNormal("%s\n", _info); } printStdout("Try using a public client ...\n"); } oidc_error_t pubError = gen_handlePublicClient(account, arguments); switch (pubError) { case OIDC_SUCCESS: // Actually we should already have exited exit(EXIT_SUCCESS); case OIDC_ENOPUBCLIENT: printError("Dynamic client registration not successful for this issuer " "and could not find a public client for this issuer.\n"); break; default: oidc_perror(); } printIssuerHelp(account_getIssuerUrl(account)); secFreeAccount(account); exit(EXIT_FAILURE); } if (_info) { printImportant("%s\n", _info); secFree(_info); } secFree(_status); secFree(_error); if (_client) { cJSON* client_config_json = stringToJson(_client); secFree(_client); cJSON* account_config_json = accountToJSONWithoutCredentials(account); cJSON* merged_json = mergeJSONObjects(client_config_json, account_config_json); secFreeJson(account_config_json); secFreeJson(client_config_json); char* new_scope_value = getJSONValue(merged_json, OIDC_KEY_SCOPE); if (!strSubStringCase(new_scope_value, OIDC_SCOPE_OPENID) || !strSubStringCase(new_scope_value, OIDC_SCOPE_OFFLINE_ACCESS)) { printError("Registered client does not have all the required scopes: %s " "%s\nPlease contact the provider to update the client to have " "all the requested scope values.\n", OIDC_SCOPE_OPENID, OIDC_SCOPE_OFFLINE_ACCESS); printIssuerHelp(account_getIssuerUrl(account)); secFree(new_scope_value); secFreeJson(merged_json); exit(EXIT_FAILURE); } const char* requested_scope = account_getScope(account); if (strequal(requested_scope, "max") && _max_scopes) { requested_scope = _max_scopes; } char* scope_diff = subtractListStrings(requested_scope, new_scope_value, ' '); secFree(new_scope_value); if (scope_diff) { printImportant( "Warning: The registered client does not have all the requested " "scopes. The following are missing: %s\nTo update the client to have " "all the requested scope values, please contact the provider.\n", scope_diff); printIssuerHelp(account_getIssuerUrl(account)); secFree(scope_diff); } char* text = jsonToString(merged_json); secFreeJson(merged_json); if (text == NULL) { oidc_perror(); exit(EXIT_FAILURE); } char* path = oidc_strcat(CLIENT_TMP_PREFIX, account_getName(account)); if (arguments->verbose) { printStdout("Writing client config temporary to agent\n"); } writeFileToAgent(path, text); oidc_gen_state.doNotMergeTmpFile = 0; secFree(path); struct oidc_account* updatedAccount = getAccountFromJSON(text); secFree(text); secFreeAccount(account); secFree(_max_scopes); return updatedAccount; } else { printError("Something went wrong. I did not receive a client config!\n"); } secFree(_max_scopes); secFreeAccount(account); return NULL; } void deleteAccount(char* short_name, char* file_json, int revoke, const struct arguments* arguments) { char* refresh_token = NULL; if (file_json) { INIT_KEY_VALUE(OIDC_KEY_REGISTRATION_ACCESS_TOKEN, OIDC_KEY_REGISTRATION_CLIENT_URI, AGENT_KEY_CERTPATH, OIDC_KEY_REFRESHTOKEN); if (CALL_GETJSONVALUES(file_json) < 0) { printError("Could not decode json: %s\n", file_json); printError("This seems to be a bug. Please hand in a bug report.\n"); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } KEY_VALUE_VARS(registration_access_token, registration_client_uri, certpath, refresh_token); refresh_token = oidc_strcopy(_refresh_token); // Save this for later, so the RT can be // given to the user, if not revoked. if (_registration_access_token && _registration_client_uri) { if (gen_promptConsentDefaultYes( "The used OIDC client was dynamically registered by oidc-agent. " "Should this client be automatically deleted?", arguments)) { char* res = ipc_cryptCommunicate(remote, REQUEST_DELETECLIENT, _registration_client_uri, _registration_access_token, _certpath); { // Capsulate json parsing macros INIT_KEY_VALUE(OIDC_KEY_ERROR); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError( "This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(res); KEY_VALUE_VARS(error); if (_error != NULL) { printError("%s\n", _error); secFree(_error); exit(EXIT_FAILURE); } revoke = 0; printNormal("Deleted oidc client.\n"); } } } SEC_FREE_KEY_VALUES(); // refers to the registration_access_token, etc. // values } struct oidc_account* p = getAccountFromJSON(file_json); char* account_json = accountToJSONStringWithoutCredentials(p); secFreeAccount(p); char* res = ipc_cryptCommunicate(remote, revoke ? REQUEST_DELETE : REQUEST_REMOVE, revoke ? account_json : short_name); secFree(account_json); INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(res); KEY_VALUE_VARS(status, error); if (strequal(_status, STATUS_SUCCESS) || strequal(_error, ACCOUNT_NOT_LOADED)) { printStdout( "The generated account was successfully removed from oidc-agent. " "You don't have to run oidc-add.\n"); SEC_FREE_KEY_VALUES(); if (removeOidcFile(short_name) == 0) { printStdout("Successfully deleted account configuration.\n"); } else { printError("error removing configuration file: %s", oidc_serror()); } exit(EXIT_SUCCESS); } if (_error != NULL) { printError("Error: %s\n", _error); if (strstarts(_error, "Could not revoke token:")) { if (gen_promptConsentDefaultNo( "Do you want to unload and delete anyway. You then have to " "revoke the refresh token manually.", arguments)) { if (refresh_token) { printImportant("Please revoke the refresh token '%s' manually!\n", refresh_token); secFree(refresh_token); } deleteAccount(short_name, file_json, 0, arguments); } else { printError( "The account was not removed from oidc-agent due to the above " "listed error. You can fix the error and try it again.\n"); } } else { printError("The account was not removed from oidc-agent due to the above " "listed error. You can fix the error and try it again.\n"); } SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } } void handleDelete(const struct arguments* arguments) { if (arguments == NULL) { oidc_setArgNullFuncError(__func__); oidc_perror(); exit(EXIT_FAILURE); } if (!oidcFileDoesExist(arguments->args[0])) { printError("No account with that shortname configured\n"); exit(EXIT_FAILURE); } char* json = getDecryptedOidcFileFor(arguments->args[0], arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (json == NULL) { oidc_perror(); exit(EXIT_FAILURE); } if (gen_promptConsentDefaultNo( "Do you really want to delete this configuration?", arguments)) { deleteAccount(arguments->args[0], json, 1, arguments); } secFree(json); } /** * @brief encrypts and writes an account configuration. * @param config the json encoded account configuration text. Might be * merged with a client configuration file * @param shortname the account short name * @param suggestedPassword the suggestedPassword for encryption, won't be * displayed; can be NULL. * @param filepath an absolute path to the output file. Either filepath or * filename has to be given. The other one shall be NULL. * @param filename the filename of the output file. The output file will be * placed in the oidc dir. Either filepath or filename has to be given. The * other one shall be NULL. * @return an oidc_error code. oidc_errno is set properly. */ oidc_error_t gen_saveAccountConfig(const char* config, const char* shortname, const char* hint, const char* suggestedPassword, const struct arguments* arguments) { // just skip, if must not save our configs if (arguments->noSave) { return OIDC_SUCCESS; } char* tmpFile = oidc_strcat(CLIENT_TMP_PREFIX, shortname); char* tmpData = readFileFromAgent(tmpFile, IGNORE_ERROR); if (oidc_gen_state.doNotMergeTmpFile || tmpData == NULL) { secFree(tmpFile); if (arguments->verbose) { printStdout("The following data will be saved encrypted:\n%s\n", config); } return promptEncryptAndWriteToOidcFile( config, shortname, hint, suggestedPassword, arguments->pw_cmd, arguments->pw_file, arguments->pw_env, arguments->pw_gpg); } char* text = mergeJSONObjectStrings(config, tmpData); oidc_error_t merge_error = OIDC_SUCCESS; if (text == NULL) { oidc_perror(); merge_error = oidc_errno; printError("Only saving the account configuration. You might want to " "save the following content to another location.\n\n%s\n\n", tmpData); text = oidc_strcopy(config); } secFree(tmpData); if (arguments->verbose) { printStdout("The following data will be saved encrypted:\n%s\n", text); } oidc_error_t e = promptEncryptAndWriteToOidcFile( text, shortname, hint, suggestedPassword, arguments->pw_cmd, arguments->pw_file, arguments->pw_env, arguments->pw_gpg); secFree(text); if (e == OIDC_SUCCESS && merge_error == OIDC_SUCCESS) { removeFileFromAgent(tmpFile); } secFree(tmpFile); return e; } void gen_handlePrint(const char* file, const struct arguments* arguments) { if (file == NULL || strlen(file) < 1) { printError("FILE not specified\n"); } char* fileContent = NULL; if (file[0] == '/' || file[0] == '~') { // absolut path fileContent = getDecryptedFileFor(file, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); } else { // file placed in oidc-dir fileContent = getDecryptedOidcFileFor( file, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); } if (fileContent == NULL) { oidc_perror(); exit(EXIT_FAILURE); } printStdout("%s\n", fileContent); secFree(fileContent); } void gen_handleUpdateConfigFile(const char* file, const struct arguments* arguments) { if (file == NULL) { printError("No shortname provided\n"); } int isShortname = 0; if (file[0] != '/' && file[0] != '~') { // no absolut path isShortname = 1; } char* (*readFnc)(const char*) = isShortname ? readOidcFile : readFile; char* fileContent = readFnc(file); if (fileContent == NULL) { oidc_perror(); exit(oidc_errno); } if (isJSONObject(fileContent)) { oidc_error_t (*writeFnc)(const char*, const char*, const char*, const char*, const char*, const char*, const char*, const char*) = isShortname ? promptEncryptAndWriteToOidcFile : promptEncryptAndWriteToFile; oidc_error_t write_e = writeFnc(fileContent, file, file, NULL, arguments->pw_cmd, arguments->pw_file, arguments->pw_env, arguments->pw_gpg); secFree(fileContent); if (write_e != OIDC_SUCCESS) { oidc_perror(); } else { printStdout("Updated config file format\n"); } exit(write_e); } secFree(fileContent); struct resultWithEncryptionPassword result = _getDecryptedTextAndPasswordWithPromptFor( file, isShortname, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (result.result == NULL) { secFree(result.password); oidc_perror(); exit(EXIT_FAILURE); } oidc_error_t (*writeFnc)(const char*, const char*, const char*, const char*) = isShortname ? encryptAndWriteToOidcFile : encryptAndWriteToFile; char* gpg_key = arguments->pw_gpg; if (result.password == NULL && arguments->pw_gpg == NULL) { char* old_encrypted_content = isShortname ? readOidcFile(file) : readFile(file); if (isPGPMessage(old_encrypted_content)) { gpg_key = extractPGPKeyID(old_encrypted_content); } else { char* hint = isShortname ? oidc_sprintf("account configuration '%s'", file) : oidc_strcopy(file); result.password = getEncryptionPasswordFor( hint, NULL, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); secFree(hint); } secFree(old_encrypted_content); } oidc_error_t write_e = writeFnc(result.result, file, result.password, arguments->pw_gpg); secFree(result.password); secFree(result.result); if (gpg_key != arguments->pw_gpg) { secFree(gpg_key); } if (write_e != OIDC_SUCCESS) { oidc_perror(); } else { printStdout("Updated config file format\n"); } exit(write_e); } oidc_error_t gen_handlePublicClient(struct oidc_account* account, struct arguments* arguments) { arguments->usePublicClient = 1; oidc_gen_state.doNotMergeTmpFile = 1; char* old_client_id = account_getClientId(account); if (updateAccountWithPublicClientInfo(account) == NULL) { return oidc_errno; } if (account_getClientId(account) == old_client_id) { return OIDC_ENOPUBCLIENT; } handleGen(account, arguments, NULL); return OIDC_SUCCESS; } char* gen_handleScopeLookup(const char* issuer_url, const char* cert_path) { char* res = ipc_cryptCommunicate(remote, REQUEST_SCOPES, issuer_url, cert_path); INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_INFO); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(res); KEY_VALUE_VARS(status, error, scopes); if (!strequal(_status, STATUS_SUCCESS)) { printError("Error while retrieving supported scopes for '%s': %s\n", issuer_url, _error); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(_status); return _scopes; } char* readFileFromAgent(const char* filename, int ignoreError) { char* res = ipc_cryptCommunicate(remote, REQUEST_FILEREAD, filename); if (res == NULL) { return NULL; } INIT_KEY_VALUE(OIDC_KEY_ERROR, OIDC_KEY_ERROR_DESCRIPTION, IPC_KEY_DATA); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); return NULL; } secFree(res); KEY_VALUE_VARS(error, error_description, data); if (_error_description) { char* error = combineError(_error, _error_description); SEC_FREE_KEY_VALUES(); if (!ignoreError) { printError("%s\n", error); } secFree(error); return NULL; } if (_error) { if (!ignoreError) { printError("%s\n", _error); } secFree(_error); return NULL; } if (_data == NULL) { return NULL; } char* data = secAlloc(strlen(_data)); fromBase64UrlSafe(_data, strlen(_data), (unsigned char*)data); secFree(_data); logger(DEBUG, "Decoded base64 file content is: '%s'", data); return data; } void writeFileToAgent(const char* filename, const char* data) { char* data64 = toBase64UrlSafe(data, strlen(data) + 1); char* res = ipc_cryptCommunicate(remote, REQUEST_FILEWRITE, filename, data64); secFree(data64); char* error = parseForError(res); if (error != NULL) { printError("%s\n", error); } } void removeFileFromAgent(const char* filename) { char* res = ipc_cryptCommunicate(remote, REQUEST_FILEREMOVE, filename); char* error = parseForError(res); if (error != NULL) { printError("%s\n", error); } } void handleOnlyAT(struct arguments* arguments) { struct oidc_account* account = NULL; if (!arguments->manual) { // On default try using a public client. account = secAlloc(sizeof(struct oidc_account)); needIssuer(account, arguments); updateAccountWithPublicClientInfo(account); arguments->usePublicClient = 1; } if (arguments->file) { account = getAccountFromMaybeEncryptedFile(arguments->file); } account = manual_genNewAccount(account, arguments, NULL); char* at = _gen_response(account, arguments); printStdout("%s\n", at); } oidc-agent-4.2.6/src/oidc-token/0000755000175000017500000000000014167074355015742 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-token/oidc-token.c0000644000175000017500000000742614167074355020153 0ustar marcusmarcus#include "oidc-token.h" #include "api.h" #include "defines/agent_values.h" #include "token_handler.h" #include "utils/disableTracing.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/string/stringUtils.h" #ifndef __APPLE__ #include "privileges/token_privileges.h" #endif int main(int argc, char** argv) { platform_disable_tracing(); logger_open("oidc-token"); logger_setloglevel(NOTICE); struct arguments arguments; initArguments(&arguments); argp_parse(&argp, argc, argv, 0, 0, &arguments); #ifndef __APPLE__ if (arguments.seccomp) { initOidcTokenPrivileges(&arguments); } #endif struct agent_response (*getAgentResponseFnc)(const char*, time_t, const char*, const char*, const char*) = getAgentTokenResponse; unsigned char useIssuerInsteadOfShortname = 0; if (strstarts(arguments.args[0], "https://")) { useIssuerInsteadOfShortname = 1; } if (arguments.idtoken) { token_handleIdToken(useIssuerInsteadOfShortname, arguments.args[0]); exit(EXIT_SUCCESS); } if (useIssuerInsteadOfShortname) { getAgentResponseFnc = getAgentTokenResponseForIssuer; } struct agent_response response = getAgentResponseFnc( arguments.args[0], arguments.forceNewToken ? FORCE_NEW_TOKEN : arguments.min_valid_period, arguments.scopes, strValid(arguments.application_name) ? arguments.application_name : "oidc-token", arguments.audience); // for getting a valid access token just call the // api if (response.type == AGENT_RESPONSE_TYPE_ERROR) { oidcagent_printErrorResponse(response.error_response); // oidcagent_perror(); secFreeAgentResponse(response); exit(EXIT_FAILURE); } struct token_response res = response.token_response; if (arguments.printAll) { printf("%s\n", res.token); printf("%s\n", res.issuer); printf("%lu\n", res.expires_at); } else if ((arguments.expiration_env.useIt + arguments.token_env.useIt + arguments.issuer_env.useIt) > 1) { // more than one option specified printEnvCommands(&arguments, res); } else if ((arguments.expiration_env.useIt + arguments.token_env.useIt + arguments.issuer_env.useIt) == 0) { // none of these options specified printf("%s\n", res.token); } else { // only one option specified if (arguments.issuer_env.useIt) { if (arguments.issuer_env.str == NULL) { printf("%s\n", res.issuer); } else { printEnvCommands(&arguments, res); } } if (arguments.token_env.useIt) { if (arguments.token_env.str == NULL) { printf("%s\n", res.token); } else { printEnvCommands(&arguments, res); } } if (arguments.expiration_env.useIt) { if (arguments.expiration_env.str == NULL) { printf("%lu\n", res.expires_at); } else { printEnvCommands(&arguments, res); } } } secFreeTokenResponse(res); return 0; } void printEnvCommands(struct arguments* arguments, struct token_response response) { if (arguments == NULL) { fprintf(stderr, "passed NULL to %s", __func__); return; } if (arguments->token_env.useIt) { char* env_name = arguments->token_env.str ?: ENV_TOKEN; fprintf(stdout, "%s=%s; export %s;\n", env_name, response.token, env_name); } if (arguments->issuer_env.useIt) { char* env_name = arguments->issuer_env.str ?: ENV_ISS; fprintf(stdout, "%s=%s; export %s;\n", env_name, response.issuer, env_name); } if (arguments->expiration_env.useIt) { char* env_name = arguments->expiration_env.str ?: ENV_EXP; fprintf(stdout, "%s=%ld; export %s;\n", env_name, response.expires_at, env_name); } } oidc-agent-4.2.6/src/oidc-token/token_handler.h0000644000175000017500000000037514120404223020711 0ustar marcusmarcus#ifndef OIDC_TOKEN_HANDLER_H #define OIDC_TOKEN_HANDLER_H #include "oidc-token_options.h" void token_handleIdToken(const unsigned char useIssuerInsteadOfShortname, const char* name); #endif /* OIDC_TOKEN_HANDLER_H */ oidc-agent-4.2.6/src/oidc-token/token_handler.c0000644000175000017500000000300714167074355020723 0ustar marcusmarcus#include "token_handler.h" #include #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/oidc_error.h" #include "utils/printer.h" void token_handleIdToken(const unsigned char useIssuerInsteadOfShortname, const char* name) { unsigned char remote = useIssuerInsteadOfShortname ? 0 : oidcFileDoesExist(name) ? 0 : 1; char* response = ipc_cryptCommunicate(remote, useIssuerInsteadOfShortname ? REQUEST_IDTOKEN_ISSUER : REQUEST_IDTOKEN_ACCOUNT, name, "oidc-token"); if (response == NULL) { oidc_perror(); exit(EXIT_FAILURE); } INIT_KEY_VALUE(/* IPC_KEY_STATUS, */ OIDC_KEY_ERROR, OIDC_KEY_IDTOKEN); if (CALL_GETJSONVALUES(response) < 0) { printError("%s:%d Read malformed data. Please hand in bug report.\n", __FILE__, __LINE__); secFree(response); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(response); KEY_VALUE_VARS(/* status, */ error, id_token); if (_error) { // error printError("%s\n", _error); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } printStdout("%s\n", _id_token); SEC_FREE_KEY_VALUES(); } oidc-agent-4.2.6/src/oidc-token/oidc-token_options.h0000644000175000017500000000133714167074355021726 0ustar marcusmarcus#ifndef OIDC_TOKEN_OPTIONS_H #define OIDC_TOKEN_OPTIONS_H #include #include #include "wrapper/list.h" #define ENV_TOKEN "OIDC_AT" #define ENV_ISS "OIDC_ISS" #define ENV_EXP "OIDC_EXP" struct optional_arg { char* str; short useIt; }; struct arguments { char* args[1]; /* account shortname */ char* scopes; char* application_name; char* audience; struct optional_arg issuer_env; struct optional_arg expiration_env; struct optional_arg token_env; unsigned char seccomp; unsigned char printAll; unsigned char idtoken; unsigned char forceNewToken; time_t min_valid_period; }; void initArguments(struct arguments* arguments); extern struct argp argp; #endif // OIDC_TOKEN_OPTIONS_H oidc-agent-4.2.6/src/oidc-token/oidc-token_options.c0000644000175000017500000001355414167074355021725 0ustar marcusmarcus#include "oidc-token_options.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" #define OPT_SECCOMP 1 #define OPT_NAME 2 #define OPT_AUDIENCE 3 #define OPT_IDTOKEN 4 static struct argp_option options[] = { {0, 0, 0, 0, "General:", 1}, {"time", 't', "SECONDS", 0, "Minimum number of seconds the access token should be valid", 1}, {"issuer", 'i', "OIDC_ISS", OPTION_ARG_OPTIONAL, "Return the issuer associated with the requested access token. If neither " "-e nor -o is set and OIDC_ISS is not passed, the issuer is printed to " "stdout. Otherwise shell commands are printed that will export the value " "into an environment variable. The name of this variable can be set with " "OIDC_ISS.", 1}, {"expires-at", 'e', "OIDC_EXP", OPTION_ARG_OPTIONAL, "Return the expiration time for the requested access token. If neither " "-i nor -o is set and OIDC_EXP is not passed, the expiration time is " "printed to stdout. Otherwise shell commands are printed that will export " "the value into an environment variable. The name of this variable can be " "set with OIDC_EXP.", 1}, {"token", 'o', "OIDC_AT", OPTION_ARG_OPTIONAL, "Return the requested access token. If neither " "-i nor -e is set and OIDC_AT is not passed, the token is printed to " "stdout (Same behaviour as without this option). Otherwise shell commands " "are printed that will export the value " "into an environment variable. The name of this variable can be set with " "OIDC_AT.", 1}, {"env", 'c', 0, 0, "This will get all available information (same as -a), but will print " "shell commands that export environment variables (default names). The " "result for this option is the same as for using 'oidc-token -oie'. With " "the -o -i and -e options the name of each environment variable can be " "changed.", 1}, {"all", 'a', 0, 0, "Return all available information (token, issuer, expiration time). Each " "value is printed in one line.", 1}, {"force-new", 'f', 0, 0, "Forces that a new access token is issued and returned.", 1}, {0, 0, 0, 0, "Advanced:", 2}, {"scope", 's', "SCOPE", 0, "Scope to be requested for the requested access token. Multiple scopes " "can be provided as a space separated list or by using the option " "multiple times.", 2}, {"aud", OPT_AUDIENCE, "AUDIENCE", 0, "Audience for the requested access token. Multiple audiences can be " "provided as a space separated list", 2}, #ifndef __APPLE__ {"seccomp", OPT_SECCOMP, 0, 0, "Enables seccomp system call filtering; allowing only predefined system " "calls.", 2}, #endif {"name", OPT_NAME, "NAME", 0, "This option is intended for other applications / scripts that call " "oidc-token to obtain an access token. NAME is the name of this " "application and might be displayed to the user.", 2}, {"id-token", OPT_IDTOKEN, 0, 0, "Returns an id-token instead of an access token. This option is meant as " "a " "development tool. ID-tokens should not be passed as authorization to " "resources.", 2}, {0, 0, 0, 0, "Help:", -1}, {0, 'h', 0, OPTION_HIDDEN, 0, -1}, {0, 0, 0, 0, 0, 0}}; static error_t parse_opt(int key, char* arg, struct argp_state* state) { struct arguments* arguments = state->input; switch (key) { case 's': { if (arguments->scopes == NULL) { arguments->scopes = oidc_strcopy(arg); break; } char* tmp = oidc_sprintf("%s %s", arguments->scopes, arg); secFree(arguments->scopes); arguments->scopes = tmp; break; } case 't': if (!isdigit(*arg)) { return ARGP_ERR_UNKNOWN; } arguments->min_valid_period = strToInt(arg); break; case OPT_SECCOMP: arguments->seccomp = 1; break; case OPT_IDTOKEN: arguments->idtoken = 1; break; case OPT_NAME: arguments->application_name = arg; break; case OPT_AUDIENCE: arguments->audience = arg; break; case 'i': arguments->issuer_env.str = arg; arguments->issuer_env.useIt = 1; break; case 'o': arguments->token_env.str = arg; arguments->token_env.useIt = 1; break; case 'e': arguments->expiration_env.str = arg; arguments->expiration_env.useIt = 1; break; case 'a': arguments->printAll = 1; break; case 'f': arguments->forceNewToken = 1; break; case 'c': arguments->issuer_env.useIt = 1; arguments->token_env.useIt = 1; arguments->expiration_env.useIt = 1; break; case 'h': argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { argp_usage(state); } arguments->args[state->arg_num] = arg; break; case ARGP_KEY_END: if (state->arg_num < 1) { argp_usage(state); } break; default: return ARGP_ERR_UNKNOWN; } return 0; } static char args_doc[] = "ACCOUNT_SHORTNAME | ISSUER_URL"; static char doc[] = "oidc-token -- A client for oidc-agent for getting OIDC access tokens."; struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; void initArguments(struct arguments* arguments) { arguments->min_valid_period = 0; arguments->args[0] = NULL; arguments->scopes = NULL; arguments->application_name = NULL; arguments->audience = NULL; arguments->seccomp = 0; arguments->expiration_env.str = NULL; arguments->expiration_env.useIt = 0; arguments->token_env.str = NULL; arguments->token_env.useIt = 0; arguments->issuer_env.str = NULL; arguments->issuer_env.useIt = 0; arguments->printAll = 0; arguments->idtoken = 0; arguments->forceNewToken = 0; } oidc-agent-4.2.6/src/oidc-token/api.h0000644000175000017500000003567614167074355016705 0ustar marcusmarcus#ifndef OIDC_API_H #define OIDC_API_H #include #include "export_symbols.h" /** * @struct token_response api.h * @brief a struct holding an access token, the associated issuer, and the * expiration time of the token */ LIB_PUBLIC struct token_response { char* token; char* issuer; time_t expires_at; }; /** * @struct agent_error_response api.h * @brief a struct holding an error message and optionally a help message */ LIB_PUBLIC struct agent_error_response { char* error; char* help; }; #define AGENT_RESPONSE_TYPE_ERROR 0 #define AGENT_RESPONSE_TYPE_TOKEN 1 /** * @struct agent_response api.h * @brief a struct holding either a @c token_response or @c agent_error_response */ LIB_PUBLIC struct agent_response { unsigned char type; union { struct token_response token_response; struct agent_error_response error_response; }; }; /** * @brief gets a valid access token for an account config as well as related * information * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return an agent_response struct containing the access token, issuer_url, and * expiration time in the @c token_response or an @c agent_error_response. * Has to be freed after usage using the @c secFreeAgentResponse function. */ LIB_PUBLIC struct agent_response getAgentTokenResponse( const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets a valid access token for a specific provider as well as related * information * @param issuer_url the issuer url of the provider for which an access token * should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for the used account configuration * should be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return an agent_response struct containing the access token, issuer_url, and * expiration time in the @c token_response or an @c agent_error_response. * Has to be freed after usage using the @c secFreeAgentResponse function. */ LIB_PUBLIC struct agent_response getAgentTokenResponseForIssuer( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets a valid access token for an account config * @deprecated use @c getTokenResponse3 instead to additionally get the * issuer_url and expiration date for the returned access token or if only the * access token is required @c getAccessToken3 * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ LIB_PUBLIC char* getAccessToken(const char* accountname, time_t min_valid_period, const char* scope); /** * @brief gets a valid access token for an account config * @deprecated use @c getAccessToken3 or @c getTokenResponse3 instead * @param issuer_url the issuer url of the provider for which an access token * should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for the used account configuration * should be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ LIB_PUBLIC char* getAccessToken2(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint); /** * @brief gets a valid access token for an account config * @param issuer_url the issuer url of the provider for which an access token * should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for the used account configuration * should be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ LIB_PUBLIC char* getAccessToken3(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets a valid access token for an account config * @deprecated use @c getAccessTokenForIssuer3 or @c getTokenResponseForIssuer3 * instead * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ LIB_PUBLIC char* getAccessTokenForIssuer(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint); /** * @brief gets a valid access token for an account config * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return a pointer to the access token. Has to be freed after usage using * @c secFree function. On failure @c NULL is returned and @c oidc_errno is set. */ LIB_PUBLIC char* getAccessTokenForIssuer3(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets a valid access token for an account config as well as related * information * @deprecated use @c getTokenResponse3 instead * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @return a token_response struct containing the access token, issuer_url, and * expiration time. * Has to be freed after usage using the @c secFreeTokenResponse function. On * failure a zeroed struct is returned and @c oidc_errno is set. */ LIB_PUBLIC struct token_response getTokenResponse(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint); /** * @brief gets a valid access token for an account config as well as related * information * @deprecated use @c getAgentTokenResponse instead to additionally get an error * and help message on failure * @param accountname the short name of the account config for which an access * token should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for that account configuration should * be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return a token_response struct containing the access token, issuer_url, and * expiration time. * Has to be freed after usage using the @c secFreeTokenResponse function. On * failure a zeroed struct is returned and @c oidc_errno is set. */ LIB_PUBLIC struct token_response getTokenResponse3(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets a valid access token for a specific provider as well as related * information * @deprecated use @c getTokenResponseForIssuer3 instead * @param issuer_url the issuer url of the provider for which an access token * should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for the used account configuration * should be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @return a token_response struct containing the access token, issuer_url, and * expiration time. * Has to be freed after usage using the @c secFreeTokenResponse function. On * failure a zeroed struct is returned and @c oidc_errno is set. */ LIB_PUBLIC struct token_response getTokenResponseForIssuer( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint); /** * @brief gets a valid access token for a specific provider as well as related * information * @deprecated use @c getAgentTokenResponseForIssuer instead to additionally get * an error and help message on failure * @param issuer_url the issuer url of the provider for which an access token * should be returned * @param min_valid_period the minium period of time the access token has to be * valid in seconds * @param scope a space delimited list of scope values for the to be issued * access token. @c NULL if default value for the used account configuration * should be used. * @param application_hint a hint indicating what application requests the * access token. This string might be displayed to the user. * @param audience Use this parameter to request an access token with this * specific audience. Can be a space separated list. @c NULL if no special * audience should be requested. * @return a token_response struct containing the access token, issuer_url, and * expiration time. * Has to be freed after usage using the @c secFreeTokenResponse function. On * failure a zeroed struct is returned and @c oidc_errno is set. */ LIB_PUBLIC struct token_response getTokenResponseForIssuer3( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience); /** * @brief gets an error string detailing the last occurred error * @return the error string. MUST NOT be freed. */ LIB_PUBLIC char* oidcagent_serror(); /** * @brief prints an error message to stderr detailing the last occurred error * @note Since version 4.2.0 you most likely want to use @c * oidcagent_printErrorResponse instead */ LIB_PUBLIC void oidcagent_perror(); /** * @brief prints the error and help messages (if set) of the passed @c * agent_error_response struct to @c stderr */ LIB_PUBLIC void oidcagent_printErrorResponse(struct agent_error_response err); /** * @brief clears and frees a token_response struct * @param token_response the struct to be freed */ LIB_PUBLIC void secFreeTokenResponse(struct token_response token_response); /** * @brief clears and frees an agent_error_response struct * @param error_response the struct to be freed */ LIB_PUBLIC void secFreeErrorResponse( struct agent_error_response error_response); /** * @brief clears and frees an agent_response struct * @param agent_response the struct to be freed */ LIB_PUBLIC void secFreeAgentResponse(struct agent_response agent_response); extern LIB_PUBLIC void _secFree(void*); #ifndef secFree #define secFree(ptr) \ do { \ _secFree((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFree #endif // OIDC_API_H oidc-agent-4.2.6/src/oidc-token/parse.c0000644000175000017500000000410514167074355017220 0ustar marcusmarcus#include "parse.h" #include "api.h" #include "defines/agent_values.h" #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/string/stringUtils.h" struct agent_response parseForAgentResponse(char* response) { struct agent_response res; if (response == NULL) { res.type = AGENT_RESPONSE_TYPE_ERROR; res.error_response = (struct agent_error_response){ oidc_strcopy("did not receive any response"), NULL}; return res; } INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_INFO, OIDC_KEY_ACCESSTOKEN, OIDC_KEY_ISSUER, AGENT_KEY_EXPIRESAT); if (CALL_GETJSONVALUES(response) < 0) { secFree(response); SEC_FREE_KEY_VALUES(); res.type = AGENT_RESPONSE_TYPE_ERROR; res.error_response = (struct agent_error_response){ oidc_strcopy("read malformed data"), oidc_strcopy("Please hand in a bug report: " "https://github.com/indigo-dc/oidc-agent")}; return res; } secFree(response); KEY_VALUE_VARS(status, error, info, access_token, issuer, expires_at); if (_error) { // error oidc_errno = OIDC_EERROR; oidc_seterror(_error); res.type = AGENT_RESPONSE_TYPE_ERROR; res.error_response = (struct agent_error_response){oidc_strcopy(_error), oidc_strcopy(_info)}; SEC_FREE_KEY_VALUES(); return res; } else { secFree(_status); oidc_errno = OIDC_SUCCESS; time_t expires_at = strToULong(_expires_at); secFree(_expires_at); res.type = AGENT_RESPONSE_TYPE_TOKEN; res.token_response = (struct token_response){_access_token, _issuer, expires_at}; return res; } } struct token_response parseForTokenResponse(char* response) { struct agent_response agentResponse = parseForAgentResponse(response); if (agentResponse.type == AGENT_RESPONSE_TYPE_TOKEN) { return agentResponse.token_response; } secFreeAgentResponse(agentResponse); return (struct token_response){NULL, NULL, 0}; } oidc-agent-4.2.6/src/oidc-token/export_symbols.h0000644000175000017500000000140014167074355021177 0ustar marcusmarcus#ifndef OIDC_EXPORT_SYMBOLS_H #define OIDC_EXPORT_SYMBOLS_H #if defined _WIN32 || defined __CYGWIN__ #ifdef BUILDING_LIB #ifdef __GNUC__ #define LIB_PUBLIC __attribute__((dllexport)) #else #define LIB_PUBLIC \ __declspec( \ dllexport) // Note: actually gcc seems to also supports this syntax. #endif #else #ifdef __GNUC__ #define LIB_PUBLIC __attribute__((dllimport)) #else #define LIB_PUBLIC \ __declspec( \ dllimport) // Note: actually gcc seems to also supports this syntax. #endif #endif #define LIB_LOCAL #else #if __GNUC__ >= 4 #define LIB_PUBLIC __attribute__((visibility("default"))) #define LIB_LOCAL __attribute__((visibility("hidden"))) #else #define LIB_PUBLIC #define LIB_LOCAL #endif #endif #endif // OIDC_EXPORT_SYMBOLS_H oidc-agent-4.2.6/src/oidc-token/api.c0000644000175000017500000002346514167074355016671 0ustar marcusmarcus#include "api.h" #include #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "parse.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" #ifndef API_LOGLEVEL #define API_LOGLEVEL NOTICE #endif // API_LOGLEVEL #ifndef START_APILOGLEVEL #define START_APILOGLEVEL int oldLogMask = logger_setloglevel(API_LOGLEVEL); #endif #ifndef END_APILOGLEVEL #define END_APILOGLEVEL logger_setlogmask(oldLogMask); #endif // END_APILOGLEVEL #define LOCAL_COMM 0 #define REMOTE_COMM 1 char* communicate(unsigned char remote, const char* fmt, ...) { START_APILOGLEVEL if (fmt == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } va_list args; va_start(args, fmt); char* ret = ipc_vcryptCommunicate(remote, fmt, args); va_end(args); END_APILOGLEVEL return ret; } unsigned char _checkLocalResponseForRemote(struct agent_response res) { if (res.type == AGENT_RESPONSE_TYPE_TOKEN && res.token_response.token != NULL) { return LOCAL_COMM; } const char* err = res.type == AGENT_RESPONSE_TYPE_ERROR && res.error_response.error != NULL ? res.error_response.error : oidc_serror(); if (strequal(err, "No account configured with that short name") || strstarts(err, "Could not connect to oidc-agent") || strequal(err, "OIDC_SOCK env var not set")) { return REMOTE_COMM; } return LOCAL_COMM; } char* _getAccessTokenRequest(const char* accountname, const char* issuer, time_t min_valid_period, const char* scope, const char* hint, const char* audience) { START_APILOGLEVEL cJSON* json = generateJSONObject(IPC_KEY_REQUEST, cJSON_String, REQUEST_VALUE_ACCESSTOKEN, IPC_KEY_MINVALID, cJSON_Number, min_valid_period, NULL); if (strValid(accountname)) { jsonAddStringValue(json, IPC_KEY_SHORTNAME, accountname); } else if (strValid(issuer)) { jsonAddStringValue(json, IPC_KEY_ISSUERURL, issuer); } if (strValid(scope)) { jsonAddStringValue(json, OIDC_KEY_SCOPE, scope); } if (strValid(hint)) { jsonAddStringValue(json, IPC_KEY_APPLICATIONHINT, hint); } if (strValid(audience)) { jsonAddStringValue(json, IPC_KEY_AUDIENCE, audience); } char* ret = jsonToStringUnformatted(json); secFreeJson(json); logger(DEBUG, "%s", ret); END_APILOGLEVEL return ret; } char* getAccessTokenRequest(const char* accountname, time_t min_valid_period, const char* scope, const char* hint, const char* audience) { return _getAccessTokenRequest(accountname, NULL, min_valid_period, scope, hint, audience); } char* getAccessTokenRequestIssuer(const char* issuer, time_t min_valid_period, const char* scope, const char* hint, const char* audience) { return _getAccessTokenRequest(NULL, issuer, min_valid_period, scope, hint, audience); } struct agent_response _getAgentResponseFromRequest(unsigned char remote, const char* ipc_request) { char* response = communicate(remote, ipc_request); return parseForAgentResponse(response); } struct token_response _agentResponseToTokenResponse( struct agent_response agentResponse) { if (agentResponse.type == AGENT_RESPONSE_TYPE_TOKEN) { return agentResponse.token_response; } secFreeAgentResponse(agentResponse); return (struct token_response){NULL, NULL, 0}; } struct token_response getTokenResponse(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint) { return getTokenResponse3(accountname, min_valid_period, scope, application_hint, NULL); } struct token_response getTokenResponse3(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL struct agent_response res = getAgentTokenResponse( accountname, min_valid_period, scope, application_hint, audience); struct token_response ret = _agentResponseToTokenResponse(res); END_APILOGLEVEL return ret; } struct agent_response getAgentTokenResponse(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL char* request = getAccessTokenRequest(accountname, min_valid_period, scope, application_hint, audience); struct agent_response res = _getAgentResponseFromRequest(LOCAL_COMM, request); struct oidc_error_state* localError = saveErrorState(); const unsigned char remote = _checkLocalResponseForRemote(res); if (remote) { struct agent_response remote_res = _getAgentResponseFromRequest(remote, request); if (remote_res.type == AGENT_RESPONSE_TYPE_ERROR) { restoreErrorState(localError); secFreeAgentResponse(remote_res); } else { secFreeAgentResponse(res); res = remote_res; } } secFreeErrorState(localError); secFree(request); END_APILOGLEVEL return res; } struct token_response getTokenResponseForIssuer(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint) { return getTokenResponseForIssuer3(issuer_url, min_valid_period, scope, application_hint, NULL); } struct token_response getTokenResponseForIssuer3(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL struct agent_response res = getAgentTokenResponseForIssuer( issuer_url, min_valid_period, scope, application_hint, audience); struct token_response ret = _agentResponseToTokenResponse(res); END_APILOGLEVEL return ret; } struct agent_response getAgentTokenResponseForIssuer( const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL char* request = getAccessTokenRequestIssuer( issuer_url, min_valid_period, scope, application_hint, audience); struct agent_response res = _getAgentResponseFromRequest(LOCAL_COMM, request); secFree(request); END_APILOGLEVEL return res; } char* getAccessToken(const char* accountname, time_t min_valid_period, const char* scope) { return getAccessToken2(accountname, min_valid_period, scope, NULL); } char* getAccessToken2(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint) { return getAccessToken3(accountname, min_valid_period, scope, application_hint, NULL); } char* getAccessToken3(const char* accountname, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL struct token_response response = getTokenResponse3( accountname, min_valid_period, scope, application_hint, audience); secFree(response.issuer); END_APILOGLEVEL return response.token; } char* getAccessTokenForIssuer(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint) { START_APILOGLEVEL struct token_response response = getTokenResponseForIssuer3( issuer_url, min_valid_period, scope, application_hint, NULL); secFree(response.issuer); END_APILOGLEVEL return response.token; } char* getAccessTokenForIssuer3(const char* issuer_url, time_t min_valid_period, const char* scope, const char* application_hint, const char* audience) { START_APILOGLEVEL struct token_response response = getTokenResponseForIssuer3( issuer_url, min_valid_period, scope, application_hint, audience); secFree(response.issuer); END_APILOGLEVEL return response.token; } char* oidcagent_serror() { return oidc_serror(); } void oidcagent_perror() { oidc_perror(); } void oidcagent_printErrorResponse(struct agent_error_response err) { if (err.error) { printError("Error: %s\n", err.error); } if (err.help) { printImportant("%s", err.help); } } void secFreeTokenResponse(struct token_response token_response) { START_APILOGLEVEL secFree(token_response.token); secFree(token_response.issuer); END_APILOGLEVEL } void secFreeErrorResponse(struct agent_error_response error_response) { START_APILOGLEVEL secFree(error_response.error); secFree(error_response.help); END_APILOGLEVEL } void secFreeAgentResponse(struct agent_response agent_response) { START_APILOGLEVEL switch (agent_response.type) { case AGENT_RESPONSE_TYPE_ERROR: secFreeErrorResponse(agent_response.error_response); break; case AGENT_RESPONSE_TYPE_TOKEN: secFreeTokenResponse(agent_response.token_response); break; } END_APILOGLEVEL } oidc-agent-4.2.6/src/oidc-token/parse.h0000644000175000017500000000024714167074355017230 0ustar marcusmarcus#ifndef OIDC_TOKEN_PARSE_H #define OIDC_TOKEN_PARSE_H #include "api.h" struct agent_response parseForAgentResponse(char* response); #endif /* OIDC_TOKEN_PARSE_H */ oidc-agent-4.2.6/src/oidc-token/oidc-token.h0000644000175000017500000000055014167074355020147 0ustar marcusmarcus#ifndef OIDC_TOKEN_H #define OIDC_TOKEN_H #include "api.h" #include "defines/version.h" #include "oidc-token_options.h" const char* argp_program_version = TOKEN_VERSION; const char* argp_program_bug_address = BUG_ADDRESS; void printEnvCommands(struct arguments* arguments, struct token_response response); #endif // OIDC_TOKEN_H oidc-agent-4.2.6/src/account/0000755000175000017500000000000014167074355015342 5ustar marcusmarcusoidc-agent-4.2.6/src/account/setandget.h0000644000175000017500000001005614120404223017447 0ustar marcusmarcus#ifndef ACCOUNT_SETANDGET_H #define ACCOUNT_SETANDGET_H #include "account/account.h" struct oidc_issuer* account_getIssuer(const struct oidc_account* p); char* account_getIssuerUrl(const struct oidc_account* p); char* account_getConfigEndpoint(const struct oidc_account* p); char* account_getTokenEndpoint(const struct oidc_account* p); char* account_getAuthorizationEndpoint(const struct oidc_account* p); char* account_getRevocationEndpoint(const struct oidc_account* p); char* account_getRegistrationEndpoint(const struct oidc_account* p); char* account_getDeviceAuthorizationEndpoint(const struct oidc_account* p); char* account_getScopesSupported(const struct oidc_account* p); char* account_getGrantTypesSupported(const struct oidc_account* p); char* account_getResponseTypesSupported(const struct oidc_account* p); char* account_getName(const struct oidc_account* p); char* account_getClientName(const struct oidc_account* p); char* account_getClientId(const struct oidc_account* p); char* account_getClientSecret(const struct oidc_account* p); char* account_getScope(const struct oidc_account* p); char* account_getAudience(const struct oidc_account* p); char* account_getUsername(const struct oidc_account* p); char* account_getPassword(const struct oidc_account* p); char* account_getRefreshToken(const struct oidc_account* p); char* account_getAccessToken(const struct oidc_account* p); unsigned long account_getTokenExpiresAt(const struct oidc_account* p); char* account_getCertPath(const struct oidc_account* p); list_t* account_getRedirectUris(const struct oidc_account* p); size_t account_getRedirectUrisCount(const struct oidc_account* p); char* account_getUsedState(const struct oidc_account* p); time_t account_getDeath(const struct oidc_account* p); char* account_getCodeChallengeMethod(const struct oidc_account* p); unsigned char account_getConfirmationRequired(const struct oidc_account* p); unsigned char account_getNoWebServer(const struct oidc_account* p); unsigned char account_getNoScheme(const struct oidc_account* p); unsigned char account_getAlwaysAllowId(const struct oidc_account* p); void account_setIssuerUrl(struct oidc_account* p, char* issuer_url); void account_setClientName(struct oidc_account* p, char* clientname); void account_setName(struct oidc_account* p, char* shortname, const char* client_identifier); void account_setClientId(struct oidc_account* p, char* client_id); void account_setClientSecret(struct oidc_account* p, char* client_secret); void account_setScopeExact(struct oidc_account* p, char* scope); void account_setScope(struct oidc_account* p, char* scope); void account_setIssuer(struct oidc_account* p, struct oidc_issuer* issuer); void account_setScopesSupported(struct oidc_account* p, char* scopes_supported); void account_setAudience(struct oidc_account* p, char* audience); void account_setUsername(struct oidc_account* p, char* username); void account_setPassword(struct oidc_account* p, char* password); void account_setRefreshToken(struct oidc_account* p, char* refresh_token); void account_setAccessToken(struct oidc_account* p, char* access_token); void account_setTokenExpiresAt(struct oidc_account* p, unsigned long token_expires_at); void account_setCertPath(struct oidc_account* p, char* cert_path); void account_setRedirectUris(struct oidc_account* p, list_t* redirect_uris); void account_setUsedState(struct oidc_account* p, char* used_state); void account_clearCredentials(struct oidc_account* a); void account_setDeath(struct oidc_account* p, time_t death); void account_setCodeChallengeMethod(struct oidc_account* p, char* code_challenge_method); void account_setConfirmationRequired(struct oidc_account* p); void account_setNoWebServer(struct oidc_account* p); void account_setNoScheme(struct oidc_account* p); void account_setAlwaysAllowId(struct oidc_account* p); int account_refreshTokenIsValid(const struct oidc_account* p); #endif // ACCOUNT_SETANDGET_H oidc-agent-4.2.6/src/account/issuer.c0000644000175000017500000000107014120404223016772 0ustar marcusmarcus#include "issuer.h" void _secFreeIssuer(struct oidc_issuer* iss) { if (!iss) { return; } issuer_setIssuerUrl(iss, NULL); issuer_setConfigurationEndpoint(iss, NULL); issuer_setTokenEndpoint(iss, NULL); issuer_setAuthorizationEndpoint(iss, NULL); issuer_setRevocationEndpoint(iss, NULL); issuer_setRegistrationEndpoint(iss, NULL); issuer_setDeviceAuthorizationEndpoint(iss, NULL, 0); issuer_setScopesSupported(iss, NULL); issuer_setGrantTypesSupported(iss, NULL); issuer_setResponseTypesSupported(iss, NULL); secFree(iss); iss = NULL; } oidc-agent-4.2.6/src/account/issuer.h0000644000175000017500000001264014120404223017004 0ustar marcusmarcus#ifndef ISSUER_H #define ISSUER_H #include "utils/memory.h" struct device_authorization_endpoint { char* url; int setByUser; }; struct oidc_issuer { char* issuer_url; char* configuration_endpoint; char* token_endpoint; char* authorization_endpoint; char* revocation_endpoint; char* registration_endpoint; struct device_authorization_endpoint device_authorization_endpoint; char* scopes_supported; // space delimited char* grant_types_supported; // as json array char* response_types_supported; // as json array }; void _secFreeIssuer(struct oidc_issuer* iss); inline static char* issuer_getIssuerUrl(struct oidc_issuer* iss) { return iss ? iss->issuer_url : NULL; }; inline static char* issuer_getConfigEndpoint(struct oidc_issuer* iss) { return iss ? iss->configuration_endpoint : NULL; }; inline static char* issuer_getTokenEndpoint(struct oidc_issuer* iss) { return iss ? iss->token_endpoint : NULL; }; inline static char* issuer_getAuthorizationEndpoint(struct oidc_issuer* iss) { return iss ? iss->authorization_endpoint : NULL; }; inline static char* issuer_getRevocationEndpoint(struct oidc_issuer* iss) { return iss ? iss->revocation_endpoint : NULL; }; inline static char* issuer_getRegistrationEndpoint(struct oidc_issuer* iss) { return iss ? iss->registration_endpoint : NULL; }; inline static char* issuer_getDeviceAuthorizationEndpoint( struct oidc_issuer* iss) { return iss ? iss->device_authorization_endpoint.url : NULL; }; inline static int issuer_getDeviceAuthorizationEndpointIsSetByUser( struct oidc_issuer* iss) { return iss ? iss->device_authorization_endpoint.setByUser : 0; } inline static char* issuer_getScopesSupported(struct oidc_issuer* iss) { return iss ? iss->scopes_supported : NULL; } inline static char* issuer_getResponseTypesSupported(struct oidc_issuer* iss) { return iss ? iss->response_types_supported : NULL; } inline static char* issuer_getGrantTypesSupported(struct oidc_issuer* iss) { return iss ? iss->grant_types_supported : NULL; } inline static void issuer_setIssuerUrl(struct oidc_issuer* iss, char* issuer_url) { if (iss->issuer_url == issuer_url) { return; } secFree(iss->issuer_url); iss->issuer_url = issuer_url; } inline static void issuer_setConfigurationEndpoint( struct oidc_issuer* iss, char* configuration_endpoint) { if (iss->configuration_endpoint == configuration_endpoint) { return; } secFree(iss->configuration_endpoint); iss->configuration_endpoint = configuration_endpoint; } inline static void issuer_setTokenEndpoint(struct oidc_issuer* iss, char* token_endpoint) { if (iss->token_endpoint == token_endpoint) { return; } secFree(iss->token_endpoint); iss->token_endpoint = token_endpoint; } inline static void issuer_setAuthorizationEndpoint( struct oidc_issuer* iss, char* authorization_endpoint) { if (iss->authorization_endpoint == authorization_endpoint) { return; } secFree(iss->authorization_endpoint); iss->authorization_endpoint = authorization_endpoint; } inline static void issuer_setRevocationEndpoint(struct oidc_issuer* iss, char* revocation_endpoint) { if (iss->revocation_endpoint == revocation_endpoint) { return; } secFree(iss->revocation_endpoint); iss->revocation_endpoint = revocation_endpoint; } inline static void issuer_setRegistrationEndpoint(struct oidc_issuer* iss, char* registration_endpoint) { if (iss->registration_endpoint == registration_endpoint) { return; } secFree(iss->registration_endpoint); iss->registration_endpoint = registration_endpoint; } inline static void issuer_setDeviceAuthorizationEndpoint( struct oidc_issuer* iss, char* device_authorization_endpoint, int setByUser) { if (iss->device_authorization_endpoint.url == device_authorization_endpoint) { return; } secFree(iss->device_authorization_endpoint.url); iss->device_authorization_endpoint.url = device_authorization_endpoint; iss->device_authorization_endpoint.setByUser = setByUser; } inline static void issuer_setScopesSupported(struct oidc_issuer* iss, char* scopes_supported) { if (iss->scopes_supported == scopes_supported) { return; } secFree(iss->scopes_supported); iss->scopes_supported = scopes_supported; } inline static void issuer_setGrantTypesSupported(struct oidc_issuer* iss, char* grant_types_supported) { if (iss->grant_types_supported == grant_types_supported) { return; } secFree(iss->grant_types_supported); iss->grant_types_supported = grant_types_supported; } inline static void issuer_setResponseTypesSupported( struct oidc_issuer* iss, char* response_types_supported) { if (iss->response_types_supported == response_types_supported) { return; } secFree(iss->response_types_supported); iss->response_types_supported = response_types_supported; } #ifndef secFreeIssuer #define secFreeIssuer(ptr) \ do { \ _secFreeIssuer((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeIssuer #endif // ISSUER_H oidc-agent-4.2.6/src/account/setandget.c0000644000175000017500000002171314167074355017470 0ustar marcusmarcus#include "setandget.h" #include "utils/hostname.h" #include "utils/string/stringUtils.h" struct oidc_issuer* account_getIssuer(const struct oidc_account* p) { return p ? p->issuer : NULL; } char* account_getIssuerUrl(const struct oidc_account* p) { return p ? p->issuer ? issuer_getIssuerUrl(p->issuer) : NULL : NULL; } char* account_getConfigEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getConfigEndpoint(p->issuer) : NULL : NULL; } char* account_getTokenEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getTokenEndpoint(p->issuer) : NULL : NULL; } char* account_getAuthorizationEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getAuthorizationEndpoint(p->issuer) : NULL : NULL; } char* account_getRevocationEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getRevocationEndpoint(p->issuer) : NULL : NULL; } char* account_getRegistrationEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getRegistrationEndpoint(p->issuer) : NULL : NULL; } char* account_getDeviceAuthorizationEndpoint(const struct oidc_account* p) { return p ? p->issuer ? issuer_getDeviceAuthorizationEndpoint(p->issuer) : NULL : NULL; } char* account_getScopesSupported(const struct oidc_account* p) { return p ? p->issuer ? issuer_getScopesSupported(p->issuer) : NULL : NULL; } char* account_getGrantTypesSupported(const struct oidc_account* p) { return p ? p->issuer ? issuer_getGrantTypesSupported(p->issuer) : NULL : NULL; } char* account_getResponseTypesSupported(const struct oidc_account* p) { return p ? p->issuer ? issuer_getResponseTypesSupported(p->issuer) : NULL : NULL; } char* account_getName(const struct oidc_account* p) { return p ? p->shortname : NULL; } char* account_getClientName(const struct oidc_account* p) { return p ? p->clientname : NULL; } char* account_getClientId(const struct oidc_account* p) { return p ? p->client_id : NULL; } char* account_getClientSecret(const struct oidc_account* p) { return p ? p->client_secret : NULL; } char* account_getScope(const struct oidc_account* p) { return p ? p->scope : NULL; } char* account_getAudience(const struct oidc_account* p) { return p ? p->audience : NULL; } char* account_getUsername(const struct oidc_account* p) { return p ? p->username : NULL; } char* account_getPassword(const struct oidc_account* p) { return p ? p->password : NULL; } char* account_getRefreshToken(const struct oidc_account* p) { return p ? p->refresh_token : NULL; } char* account_getAccessToken(const struct oidc_account* p) { return p ? p->token.access_token : NULL; } unsigned long account_getTokenExpiresAt(const struct oidc_account* p) { return p ? p->token.token_expires_at : 0; } char* account_getCertPath(const struct oidc_account* p) { return p ? p->cert_path : NULL; } list_t* account_getRedirectUris(const struct oidc_account* p) { return p ? p->redirect_uris : NULL; } size_t account_getRedirectUrisCount(const struct oidc_account* p) { return p ? p->redirect_uris ? p->redirect_uris->len : 0 : 0; } char* account_getUsedState(const struct oidc_account* p) { return p ? p->usedState : NULL; } time_t account_getDeath(const struct oidc_account* p) { return p ? p->death : 0; } char* account_getCodeChallengeMethod(const struct oidc_account* p) { return p ? p->code_challenge_method : NULL; } unsigned char account_getConfirmationRequired(const struct oidc_account* p) { return p ? p->mode & ACCOUNT_MODE_CONFIRM : 0; } unsigned char account_getNoWebServer(const struct oidc_account* p) { return p ? p->mode & ACCOUNT_MODE_NO_WEBSERVER : 0; } unsigned char account_getNoScheme(const struct oidc_account* p) { return p ? p->mode & ACCOUNT_MODE_NO_SCHEME : 0; } unsigned char account_getAlwaysAllowId(const struct oidc_account* p) { return p ? p->mode & ACCOUNT_MODE_ALWAYSALLOWID : 0; } void account_setIssuerUrl(struct oidc_account* p, char* issuer_url) { if (!p->issuer) { p->issuer = secAlloc(sizeof(struct oidc_issuer)); } issuer_setIssuerUrl(p->issuer, issuer_url); } void account_setClientName(struct oidc_account* p, char* clientname) { if (p->clientname == clientname) { return; } secFree(p->clientname); p->clientname = clientname; } void account_setName(struct oidc_account* p, char* shortname, const char* client_identifier) { if (p->shortname == shortname) { return; } secFree(p->shortname); p->shortname = shortname; char* hostname = getHostName(); char* clientname = !strValid(shortname) ? NULL : oidc_sprintf( "oidc-agent:%s-%s", shortname, strValid(client_identifier) ? client_identifier : hostname); secFree(hostname); account_setClientName(p, clientname); } void account_setClientId(struct oidc_account* p, char* client_id) { if (p->client_id == client_id) { return; } secFree(p->client_id); p->client_id = client_id; } void account_setClientSecret(struct oidc_account* p, char* client_secret) { if (p->client_secret == client_secret) { return; } secFree(p->client_secret); p->client_secret = client_secret; } void account_setScopeExact(struct oidc_account* p, char* scope) { if (p->scope == scope) { return; } secFree(p->scope); p->scope = scope; } void account_setScope(struct oidc_account* p, char* scope) { account_setScopeExact(p, scope); if (strValid(scope)) { char* usable = defineUsableScopes(p); account_setScopeExact(p, usable); } } void account_setIssuer(struct oidc_account* p, struct oidc_issuer* issuer) { if (p->issuer == issuer) { return; } secFreeIssuer(p->issuer); p->issuer = issuer; if (issuer && strValid(account_getScope(p))) { account_setScopeExact(p, defineUsableScopes(p)); } } void account_setScopesSupported(struct oidc_account* p, char* scopes_supported) { if (!p->issuer) { p->issuer = secAlloc(sizeof(struct oidc_issuer)); } if (p->issuer->scopes_supported == scopes_supported) { return; } issuer_setScopesSupported(p->issuer, scopes_supported); char* usable = defineUsableScopes(p); account_setScopeExact(p, usable); } void account_setAudience(struct oidc_account* p, char* audience) { if (p->audience == audience) { return; } secFree(p->audience); p->audience = audience; } void account_setUsername(struct oidc_account* p, char* username) { if (p->username == username) { return; } secFree(p->username); p->username = username; } void account_setPassword(struct oidc_account* p, char* password) { if (p->password == password) { return; } secFree(p->password); p->password = password; } void account_setRefreshToken(struct oidc_account* p, char* refresh_token) { if (p->refresh_token == refresh_token) { return; } secFree(p->refresh_token); p->refresh_token = refresh_token; } void account_setAccessToken(struct oidc_account* p, char* access_token) { if (p->token.access_token == access_token) { return; } secFree(p->token.access_token); p->token.access_token = access_token; } void account_setTokenExpiresAt(struct oidc_account* p, unsigned long token_expires_at) { if (p->token.token_expires_at == token_expires_at) { return; } p->token.token_expires_at = token_expires_at; } void account_setCertPath(struct oidc_account* p, char* cert_path) { if (p->cert_path == cert_path) { return; } secFree(p->cert_path); p->cert_path = cert_path; } void account_setRedirectUris(struct oidc_account* p, list_t* redirect_uris) { if (p->redirect_uris == redirect_uris) { return; } if (p->redirect_uris) { list_destroy(p->redirect_uris); } p->redirect_uris = redirect_uris; } void account_setUsedState(struct oidc_account* p, char* used_state) { if (p->usedState == used_state) { return; } secFree(p->usedState); p->usedState = used_state; } void account_clearCredentials(struct oidc_account* a) { account_setUsername(a, NULL); account_setPassword(a, NULL); } void account_setDeath(struct oidc_account* p, time_t death) { p->death = death; } void account_setCodeChallengeMethod(struct oidc_account* p, char* code_challenge_method) { if (p->code_challenge_method == code_challenge_method) { return; } secFree(p->code_challenge_method); p->code_challenge_method = code_challenge_method; } void account_setConfirmationRequired(struct oidc_account* p) { p->mode |= ACCOUNT_MODE_CONFIRM; } void account_setNoWebServer(struct oidc_account* p) { p->mode |= ACCOUNT_MODE_NO_WEBSERVER; } void account_setNoScheme(struct oidc_account* p) { p->mode |= ACCOUNT_MODE_NO_SCHEME; } void account_setAlwaysAllowId(struct oidc_account* p) { p->mode |= ACCOUNT_MODE_ALWAYSALLOWID; } int account_refreshTokenIsValid(const struct oidc_account* p) { char* refresh_token = account_getRefreshToken(p); int ret = strValid(refresh_token); return ret; } oidc-agent-4.2.6/src/account/issuer_helper.h0000644000175000017500000000103314167074355020361 0ustar marcusmarcus#ifndef ISSUER_HELPER_H #define ISSUER_HELPER_H #include "account.h" #include "wrapper/list.h" list_t* getSuggestableIssuers(); size_t getFavIssuer(const struct oidc_account* account, list_t* suggestable); void printSuggestIssuer(list_t* suggestable); void printIssuerHelp(const char* url); char* getUsableResponseTypes(const struct oidc_account* account, list_t* flows); char* getUsableGrantTypes(const struct oidc_account* account, list_t* flows); int compIssuerUrls(const char* a, const char* b); #endif // ISSUER_HELPER_H oidc-agent-4.2.6/src/account/issuer_helper.c0000644000175000017500000002062614167074355020365 0ustar marcusmarcus#define _GNU_SOURCE #include "issuer_helper.h" #include #include "defines/agent_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" char* getUsableGrantTypes(const struct oidc_account* account, list_t* flows) { const char* supported = account_getGrantTypesSupported(account); if (supported == NULL || flows == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_t* supp = JSONArrayStringToList(supported); list_t* wanted = list_new(); wanted->match = (matchFunction)strequal; list_rpush(wanted, list_node_new(OIDC_GRANTTYPE_REFRESH)); list_node_t* node; list_iterator_t* it = list_iterator_new(flows, LIST_HEAD); int code = 0; int password = 0; while ((node = list_iterator_next(it))) { if (strcaseequal(node->val, FLOW_VALUE_PASSWORD) && !code) { list_rpush(wanted, list_node_new(OIDC_GRANTTYPE_PASSWORD)); password = 1; } if (strcaseequal(node->val, FLOW_VALUE_CODE) && !password) { list_rpush(wanted, list_node_new(OIDC_GRANTTYPE_AUTHCODE)); code = 1; } if (strcaseequal(node->val, FLOW_VALUE_DEVICE)) { list_rpush(wanted, list_node_new(OIDC_GRANTTYPE_DEVICE)); } } list_iterator_destroy(it); char* wanted_str = listToJSONArrayString(wanted); logger(DEBUG, "wanted grant types are: %s", wanted_str); secFree(wanted_str); logger(DEBUG, "daeSetByUser is: %d", issuer_getDeviceAuthorizationEndpointIsSetByUser( account_getIssuer(account))); list_t* usable = intersectLists(wanted, supp); secFreeList(supp); secFreeList(wanted); if (account_getIssuer(account) ? issuer_getDeviceAuthorizationEndpointIsSetByUser( account_getIssuer(account)) : 0 && findInList(usable, OIDC_GRANTTYPE_DEVICE) == NULL) { // Force device grant type when device // authorization endpoint set by user logger(DEBUG, "Forcing device grant type"); list_rpush(usable, list_node_new(oidc_strcopy(OIDC_GRANTTYPE_DEVICE))); } char* str = listToJSONArrayString(usable); secFreeList(usable); logger(DEBUG, "usable grant types are: %s", str); return str; } char* getUsableResponseTypes(const struct oidc_account* account, list_t* flows) { list_t* supp = JSONArrayStringToList(account_getResponseTypesSupported(account)); list_t* wanted = list_new(); wanted->match = (matchFunction)strequal; list_node_t* node; if (flows) { list_iterator_t* it = list_iterator_new(flows, LIST_HEAD); int code = 0; int token = 0; while ((node = list_iterator_next(it))) { if (strcaseequal(node->val, FLOW_VALUE_PASSWORD) && !token) { list_rpush(wanted, list_node_new(OIDC_RESPONSETYPE_TOKEN)); token = 1; } if (strcaseequal(node->val, FLOW_VALUE_CODE) && !token && !code) { list_rpush(wanted, list_node_new(OIDC_RESPONSETYPE_CODE)); code = 1; } if (strcaseequal(node->val, FLOW_VALUE_DEVICE) && !token) { list_rpush(wanted, list_node_new(OIDC_RESPONSETYPE_TOKEN)); token = 1; } } list_iterator_destroy(it); } list_t* usable = intersectLists(wanted, supp); secFreeList(supp); secFreeList(wanted); char* str = listToJSONArrayString(usable); secFreeList(usable); logger(DEBUG, "usable response types are: %s", str); return str; } /** * Compares two issuer urls. * Two issuer urls are defined equal if they are: * - completely equal (strcmp==0) or * - one url misses the trailing / * @return 1 if equal, 0 if not */ int compIssuerUrls(const char* a, const char* b) { if (a == NULL || b == NULL) { oidc_setArgNullFuncError(__func__); return 0; } size_t a_len = strlen(a); size_t b_len = strlen(b); if (a_len == b_len) { return strequal(a, b); } if (b_len == a_len - 1) { const char* t = a; size_t t_len = a_len; a = b; a_len = b_len; b = t; b_len = t_len; } if (a_len == b_len - 1) { if (b[b_len - 1] == '/') { return strncmp(a, b, a_len) == 0 ? 1 : 0; } return 0; } return 0; } void printIssuerHelp(const char* url) { char* fileContent = NULL; if (fileDoesExist(ETC_ISSUER_CONFIG_FILE)) { // Read the etc version by default, we have put some additional info there, // usually this won't be the case for the user space one. fileContent = readFile(ETC_ISSUER_CONFIG_FILE); } else { // Read the user space issuer.config only if there is no etc version. This // might be the case when a user installed the agent completly in the suer // space. fileContent = readOidcFile(ISSUER_CONFIG_FILENAME); } if (fileContent == NULL) { return; } char* elem = strtok(fileContent, "\n"); while (elem != NULL) { char* space = strchr(elem, ' '); if (space) { *space = '\0'; } if (compIssuerUrls(url, elem)) { if (space) { char* reg_uri = space + 1; space = strchr(reg_uri, ' '); char* contact = NULL; if (space) { *space = '\0'; contact = space + 1; } if (strValid(reg_uri)) { printStdout("You can try to register a client manually at '%s'\n", reg_uri); } if (strValid(contact)) { printStdout("You can contact the OpenID Provider at '%s'\n", contact); } } else { printStdout("Unfortunately no contact information were found for " "issuer '%s'\n", url); } break; } elem = strtok(NULL, "\n"); } secFree(fileContent); } list_t* getSuggestableIssuers() { list_t* issuers = list_new(); issuers->free = (void(*)(void*)) & _secFree; issuers->match = (matchFunction)compIssuerUrls; char* fileContent = readOidcFile(ISSUER_CONFIG_FILENAME); if (fileContent) { char* elem = strtok(fileContent, "\n"); while (elem != NULL) { char* space = strchr(elem, ' '); if (space) { *space = '\0'; } if (findInList(issuers, elem) == NULL) { list_rpush(issuers, list_node_new(oidc_strcopy(elem))); } elem = strtok(NULL, "\n"); } secFree(fileContent); } fileContent = readFile(ETC_ISSUER_CONFIG_FILE); if (fileContent) { char* elem = strtok(fileContent, "\n"); while (elem != NULL) { char* space = strchr(elem, ' '); if (space) { *space = '\0'; } if (findInList(issuers, elem) == NULL) { list_rpush(issuers, list_node_new(oidc_strcopy(elem))); } elem = strtok(NULL, "\n"); } secFree(fileContent); } return issuers; } size_t getFavIssuer(const struct oidc_account* account, list_t* suggestable) { if (strValid(account_getIssuerUrl( account))) { // if issuer already set suggest this one for (size_t i = 0; i < suggestable->len; i++) { list_node_t* node = list_at(suggestable, i); if (compIssuerUrls( node->val, account_getIssuerUrl(account))) { // if the short name is a // substring of the issuer // it's likely that this is the fav issuer return i; } } list_rpush(suggestable, list_node_new(suggestable->free == _secFree ? oidc_strcopy(account_getIssuerUrl(account)) : account_getIssuerUrl(account))); return suggestable->len - 1; } for (size_t i = 0; i < suggestable->len; i++) { list_node_t* node = list_at(suggestable, i); if (strSubStringCase( node->val, account_getName(account))) { // if the short name is a substring // of the issuer it's likely that // this is the fav issuer return i; } } return 0; } void printSuggestIssuer(list_t* suggastable) { if (suggastable == NULL) { return; } size_t i; for (i = 0; i < suggastable->len; i++) { // printed indices starts at 1 for non nerd printPrompt("[%lu] %s\n", i + 1, (char*)list_at(suggastable, i)->val); } } oidc-agent-4.2.6/src/account/account.h0000644000175000017500000000517414167074355017156 0ustar marcusmarcus#ifndef ACCOUNT_H #define ACCOUNT_H #include #include #include "issuer.h" #include "utils/file_io/promptCryptFileUtils.h" #include "wrapper/cjson.h" #include "wrapper/list.h" struct token { char* access_token; unsigned long token_expires_at; }; struct oidc_account { struct oidc_issuer* issuer; char* shortname; char* clientname; char* client_id; char* client_secret; char* scope; char* audience; char* username; char* password; char* refresh_token; struct token token; char* cert_path; list_t* redirect_uris; char* usedState; unsigned char usedStateChecked; time_t death; char* code_challenge_method; unsigned char mode; }; #define ACCOUNT_MODE_CONFIRM 0x01 #define ACCOUNT_MODE_NO_WEBSERVER 0x02 #define ACCOUNT_MODE_NO_SCHEME 0x04 #define ACCOUNT_MODE_ALWAYSALLOWID 0x08 char* defineUsableScopes(const struct oidc_account* account); struct oidc_account* getAccountFromJSON(const char* json); cJSON* accountToJSON(const struct oidc_account* p); char* accountToJSONString(const struct oidc_account* p); cJSON* accountToJSONWithoutCredentials(const struct oidc_account* p); char* accountToJSONStringWithoutCredentials(const struct oidc_account* p); void _secFreeAccount(struct oidc_account* p); void secFreeAccountContent(struct oidc_account* p); struct oidc_account* updateAccountWithPublicClientInfo(struct oidc_account*); char* getScopesForPublicClient(const struct oidc_account*); int accountConfigExists(const char* accountname); char* getAccountNameList(list_t* accounts); int hasRedirectUris(const struct oidc_account* account); int account_matchByState(const struct oidc_account* p1, const struct oidc_account* p2); int account_matchByName(const struct oidc_account* p1, const struct oidc_account* p2); int account_matchByIssuerUrl(const struct oidc_account* p1, const struct oidc_account* p2); void stringifyIssuerUrl(struct oidc_account* account); void account_setOSDefaultCertPath(struct oidc_account* account); // make setters and getters avialable #include "account/setandget.h" #ifndef secFreeAccount #define secFreeAccount(ptr) \ do { \ _secFreeAccount((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeAccount #endif // ACCOUNT_H oidc-agent-4.2.6/src/account/account.c0000644000175000017500000002576414167074355017160 0ustar marcusmarcus#include "account.h" #include "defines/agent_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "issuer_helper.h" #include "utils/file_io/fileUtils.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/matcher.h" #include "utils/pubClientInfos.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" /** * @brief compares two accounts by their name. * @param v1 pointer to the first element * @param v2 pointer to the second element * @return @c 1 if the names match, @c 0 otherwise */ int account_matchByName(const struct oidc_account* p1, const struct oidc_account* p2) { return matchStrings(account_getName(p1), account_getName(p2)); } /** * @brief compares two accounts by their name. * @param v1 pointer to the first element * @param v2 pointer to the second element * @return @c 1 if the states match, @c 0 otherwise */ int account_matchByState(const struct oidc_account* p1, const struct oidc_account* p2) { return matchStrings(account_getUsedState(p1), account_getUsedState(p2)); } int account_matchByIssuerUrl(const struct oidc_account* p1, const struct oidc_account* p2) { return matchUrls(account_getIssuerUrl(p1), account_getIssuerUrl(p2)); } /** * reads the pubclient.conf file and updates the account struct if a public * client is found for that issuer, also setting the redirect uris * @param account the account struct to be updated * @return the updated account struct, or @c NULL */ struct oidc_account* updateAccountWithPublicClientInfo( struct oidc_account* account) { if (account == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } struct pubClientInfos* pub = getPubClientInfos(account_getIssuerUrl(account)); if (pub == NULL) { return account; } account_setClientId(account, oidc_strcopy(pub->client_id)); account_setClientSecret(account, oidc_strcopy(pub->client_secret)); logger(DEBUG, "Using public client with id '%s' and secret '%s'", pub->client_id, pub->client_secret); secFreePubClientInfos(pub); account_setRedirectUris(account, defaultRedirectURIs()); return account; } char* getScopesForPublicClient(const struct oidc_account* p) { struct pubClientInfos* pub = getPubClientInfos(account_getIssuerUrl(p)); char* scope = pub ? oidc_strcopy(pub->scope) : NULL; secFreePubClientInfos(pub); return scope; } /** * @brief parses a json encoded account * @param json the json string * @return a pointer a the oidc_account. Has to be freed after usage. On * failure NULL is returned. */ struct oidc_account* getAccountFromJSON(const char* json) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return NULL; } INIT_KEY_VALUE(AGENT_KEY_ISSUERURL, OIDC_KEY_ISSUER, AGENT_KEY_SHORTNAME, OIDC_KEY_CLIENTID, OIDC_KEY_CLIENTSECRET, OIDC_KEY_USERNAME, OIDC_KEY_PASSWORD, OIDC_KEY_REFRESHTOKEN, AGENT_KEY_CERTPATH, OIDC_KEY_REDIRECTURIS, OIDC_KEY_SCOPE, OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT, OIDC_KEY_CLIENTNAME, AGENT_KEY_DAESETBYUSER, OIDC_KEY_AUDIENCE); GET_JSON_VALUES_RETURN_NULL_ONERROR(json); KEY_VALUE_VARS(issuer_url, issuer, shortname, client_id, client_secret, username, password, refresh_token, cert_path, redirect_uris, scope, device_authorization_endpoint, clientname, daeSetByUser, audience); struct oidc_account* p = secAlloc(sizeof(struct oidc_account)); struct oidc_issuer* iss = secAlloc(sizeof(struct oidc_issuer)); if (_issuer_url) { issuer_setIssuerUrl(iss, _issuer_url); secFree(_issuer); } else { issuer_setIssuerUrl(iss, _issuer); } issuer_setDeviceAuthorizationEndpoint(iss, _device_authorization_endpoint, strToInt(_daeSetByUser)); secFree(_daeSetByUser); account_setIssuer(p, iss); account_setName(p, _shortname, NULL); account_setClientName(p, _clientname); account_setClientId(p, _client_id); account_setClientSecret(p, _client_secret); account_setUsername(p, _username); account_setPassword(p, _password); account_setRefreshToken(p, _refresh_token); account_setCertPath(p, _cert_path); account_setScopeExact(p, _scope); account_setAudience(p, _audience); list_t* redirect_uris = JSONArrayStringToList(_redirect_uris); checkRedirectUrisForErrors(redirect_uris); account_setRedirectUris(p, redirect_uris); secFree(_redirect_uris); return p; } char* accountToJSONString(const struct oidc_account* p) { cJSON* json = accountToJSON(p); char* str = jsonToString(json); secFreeJson(json); return str; } char* accountToJSONStringWithoutCredentials(const struct oidc_account* p) { cJSON* json = accountToJSONWithoutCredentials(p); char* str = jsonToString(json); secFreeJson(json); return str; } cJSON* _accountToJSON(const struct oidc_account* p, int useCredentials) { cJSON* redirect_uris = listToJSONArray(account_getRedirectUris(p)); cJSON* json = generateJSONObject( AGENT_KEY_SHORTNAME, cJSON_String, strValid(account_getName(p)) ? account_getName(p) : "", OIDC_KEY_CLIENTNAME, cJSON_String, strValid(account_getClientName(p)) ? account_getClientName(p) : "", AGENT_KEY_ISSUERURL, cJSON_String, strValid(account_getIssuerUrl(p)) ? account_getIssuerUrl(p) : "", OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT, cJSON_String, strValid(account_getDeviceAuthorizationEndpoint(p)) ? account_getDeviceAuthorizationEndpoint(p) : "", AGENT_KEY_DAESETBYUSER, cJSON_Number, account_getIssuer(p) ? issuer_getDeviceAuthorizationEndpointIsSetByUser( account_getIssuer(p)) : 0, OIDC_KEY_CLIENTID, cJSON_String, strValid(account_getClientId(p)) ? account_getClientId(p) : "", OIDC_KEY_CLIENTSECRET, cJSON_String, strValid(account_getClientSecret(p)) ? account_getClientSecret(p) : "", OIDC_KEY_REFRESHTOKEN, cJSON_String, strValid(account_getRefreshToken(p)) ? account_getRefreshToken(p) : "", AGENT_KEY_CERTPATH, cJSON_String, strValid(account_getCertPath(p)) ? account_getCertPath(p) : "", OIDC_KEY_SCOPE, cJSON_String, strValid(account_getScope(p)) ? account_getScope(p) : "", OIDC_KEY_AUDIENCE, cJSON_String, strValid(account_getAudience(p)) ? account_getAudience(p) : "", NULL); jsonAddJSON(json, OIDC_KEY_REDIRECTURIS, redirect_uris); if (useCredentials) { jsonAddStringValue( json, OIDC_KEY_USERNAME, strValid(account_getUsername(p)) ? account_getUsername(p) : ""); jsonAddStringValue( json, OIDC_KEY_PASSWORD, strValid(account_getPassword(p)) ? account_getPassword(p) : ""); } return json; } /** * @brief converts an account into a json string * @param p a pointer to the oidc_account to be converted * @return a poitner to a json string representing the account. Has to be freed * after usage. */ cJSON* accountToJSON(const struct oidc_account* p) { return _accountToJSON(p, 1); } cJSON* accountToJSONWithoutCredentials(const struct oidc_account* a) { return _accountToJSON(a, 0); } /** void freeAccount(struct oidc_account* p) * @brief frees an account completly including all fields. * @param p a pointer to the account to be freed */ void _secFreeAccount(struct oidc_account* p) { if (p == NULL) { return; } secFreeAccountContent(p); secFree(p); } /** void freeAccountContent(struct oidc_account* p) * @brief frees a all fields of an account. Does not free the pointer it self * @param p a pointer to the account to be freed */ void secFreeAccountContent(struct oidc_account* p) { if (p == NULL) { return; } account_setName(p, NULL, NULL); // account_setClientName(p, NULL); //Included in account_setName account_setIssuer(p, NULL); account_setClientId(p, NULL); account_setClientSecret(p, NULL); account_setScopeExact(p, NULL); account_setAudience(p, NULL); account_setUsername(p, NULL); account_setPassword(p, NULL); account_setRefreshToken(p, NULL); account_setAccessToken(p, NULL); account_setCertPath(p, NULL); account_setRedirectUris(p, NULL); account_setUsedState(p, NULL); } /** int accountconfigExists(const char* accountname) * @brief checks if a configuration for a given account exists * @param accountname the short name that should be checked * @return 1 if the configuration exists, 0 if not */ int accountConfigExists(const char* accountname) { return oidcFileDoesExist(accountname); } /** @fn char* getAccountNameList(struct oidc_account* p, size_t size) * @brief gets the account short names from an array of accounts * @param p a pointer to the first account * @param size the nubmer of accounts * @return a pointer to a JSON Array String containing all the account names. * Has to be freed after usage. */ char* getAccountNameList(list_t* accounts) { list_t* stringList = list_new(); list_node_t* node; list_iterator_t* it = list_iterator_new(accounts, LIST_HEAD); while ((node = list_iterator_next(it))) { list_rpush(stringList, list_node_new(account_getName((struct oidc_account*)node->val))); } list_iterator_destroy(it); char* str = listToJSONArrayString(stringList); secFreeList(stringList); return str; } int hasRedirectUris(const struct oidc_account* account) { char* str = listToDelimitedString(account_getRedirectUris(account), " "); int ret = str != NULL ? 1 : 0; secFree(str); return ret; } list_t* defineUsableScopeList(const struct oidc_account* account) { char* wanted_str = account_getScope(account); list_t* wanted = delimitedStringToList(wanted_str, ' '); if (wanted == NULL) { wanted = createList(1, NULL); } list_addStringIfNotFound(wanted, OIDC_SCOPE_OPENID); list_addStringIfNotFound(wanted, OIDC_SCOPE_OFFLINE_ACCESS); if (compIssuerUrls(account_getIssuerUrl(account), GOOGLE_ISSUER_URL)) { list_removeIfFound(wanted, OIDC_SCOPE_OFFLINE_ACCESS); } return wanted; } char* defineUsableScopes(const struct oidc_account* account) { list_t* scopes = defineUsableScopeList(account); if (scopes == NULL) { return NULL; } char* usable = listToDelimitedString(scopes, " "); secFreeList(scopes); logger(DEBUG, "usable scope is '%s'", usable); return usable; } void stringifyIssuerUrl(struct oidc_account* account) { account_setIssuerUrl(account, withTrailingSlash(account_getIssuerUrl(account))); } void account_setOSDefaultCertPath(struct oidc_account* account) { for (unsigned int i = 0; i < sizeof(possibleCertFiles) / sizeof(*possibleCertFiles); i++) { if (fileDoesExist(possibleCertFiles[i])) { account_setCertPath(account, oidc_strcopy(possibleCertFiles[i])); return; } } } oidc-agent-4.2.6/src/oidc-agent/0000755000175000017500000000000014167074355015720 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidc-agent_options.h0000644000175000017500000000133214167074355021655 0ustar marcusmarcus#ifndef OIDC_AGENT_OPTIONS_H #define OIDC_AGENT_OPTIONS_H #include #include "utils/lifetimeArg.h" struct arguments { unsigned char kill_flag; unsigned char debug; unsigned char console; unsigned char seccomp; unsigned char no_autoload; unsigned char confirm; unsigned char no_webserver; unsigned char no_scheme; unsigned char always_allow_idtoken; unsigned char log_console; unsigned char status; unsigned char json; unsigned char quiet; unsigned char no_autoreauthenticate; time_t lifetime; struct lifetimeArg pw_lifetime; char* group; char* socket_path; }; void initArguments(struct arguments* arguments); extern struct argp argp; #endif // OIDC_AGENT_OPTIONS_H oidc-agent-4.2.6/src/oidc-agent/oidc-agent_options.c0000644000175000017500000001511714167074355021656 0ustar marcusmarcus#include "oidc-agent_options.h" #include "utils/agentLogger.h" #include "utils/string/stringUtils.h" #define OPT_SECCOMP 1 #define OPT_NOAUTOLOAD 2 #define OPT_NO_WEBSERVER 3 #define OPT_PW_STORE 4 #define OPT_GROUP 5 #define OPT_NO_SCHEME 6 #define OPT_LOG_CONSOLE 7 #define OPT_ALWAYS_ALLOW_IDTOKEN 8 #define OPT_STATUS 9 #define OPT_JSON 10 #define OPT_QUIET 11 #define OPT_NO_AUTOREAUTHENTICATE 12 void initArguments(struct arguments* arguments) { arguments->kill_flag = 0; arguments->console = 0; arguments->debug = 0; arguments->lifetime = 0; arguments->seccomp = 0; arguments->no_autoload = 0; arguments->confirm = 0; arguments->no_webserver = 0; arguments->pw_lifetime.lifetime = 0; arguments->pw_lifetime.argProvided = 0; arguments->group = NULL; arguments->socket_path = NULL; arguments->no_scheme = 0; arguments->always_allow_idtoken = 0; arguments->log_console = 0; arguments->status = 0; arguments->json = 0; arguments->quiet = 0; arguments->no_autoreauthenticate = 0; } static struct argp_option options[] = { {0, 0, 0, 0, "General:", 1}, {"kill", 'k', 0, 0, "Kill the current agent (given by the OIDCD_PID environment variable)", 1}, {"lifetime", 't', "TIME", 0, "Sets a default value in seconds for the maximum lifetime of account " "configurations added to the agent. A lifetime specified for an account " "configuration with oidc-add overwrites this default value. Without this " "option the default maximum lifetime is forever.", 1}, #ifndef __APPLE__ {"seccomp", OPT_SECCOMP, 0, 0, "Enables seccomp system call filtering; allowing only predefined system " "calls.", 1}, #endif {"no-autoload", OPT_NOAUTOLOAD, 0, 0, "Disables the autoload feature: A token request cannot load the needed " "configuration. You have to do it with oidc-add.", 1}, {"no-autoreauthenticate", OPT_NO_AUTOREAUTHENTICATE, 0, 0, "Disables the automatic re-authentication feature: If a refresh token " "expired the re-atuhentiacte is not started automatically; you have to do " "it manually.", 1}, {"no-auto-reauthenticate", OPT_NO_AUTOREAUTHENTICATE, 0, OPTION_ALIAS, NULL, 1}, {"confirm", 'c', 0, 0, "Requires user confirmation when an application requests an access token " "for any loaded configuration", 1}, {"no-webserver", OPT_NO_WEBSERVER, 0, 0, "This option applies only when the " "authorization code flow is used. oidc-agent will not start a webserver. " "Redirection to oidc-gen through a custom uri scheme redirect uri and " "'manual' redirect is possible.", 1}, {"no-scheme", OPT_NO_SCHEME, 0, 0, "This option applies only when the " "authorization code flow is used. oidc-agent will not use a custom uri " "scheme redirect.", 1}, {"pw-store", OPT_PW_STORE, "TIME", OPTION_ARG_OPTIONAL, "Keeps the encryption passwords for all loaded account configurations " "encrypted in memory for TIME seconds. Can be overwritten for a specific " "configuration with oidc-add. Default value for TIME: Forever", 1}, {"with-group", OPT_GROUP, "GROUP_NAME", OPTION_ARG_OPTIONAL, "This option allows that applications running under another user can " "access the agent. The user running the other application and the user " "running the agent have to be in the specified group. If no GROUP_NAME is " "specified the default is 'oidc-agent'.", 1}, {"socket-path", 'a', "PATH", 0, "Create the UNIX-domain used for communicating with the agent at this " "PATH. The default is '$TMPDIR/oidc-XXXXXX/oidc-agent.'. Use " "'XXXXXX' as the last six characters of a directory in the path to " "substitute them with random characters.", 1}, {"bind_address", 'a', "PATH", OPTION_ALIAS, NULL, 1}, {"always-allow-idtoken", OPT_ALWAYS_ALLOW_IDTOKEN, 0, 0, "Always allow id-token requests without manual approval by the user.", 1}, {"json", OPT_JSON, 0, 0, "Print agent socket and pid as JSON instead of bash.", 1}, {"quiet", OPT_QUIET, 0, 0, "Disable informational messages to stdout.", 1}, {0, 0, 0, 0, "Verbosity:", 2}, {"debug", 'g', 0, 0, "Sets the log level to DEBUG.", 2}, {"console", 'd', 0, 0, "Runs oidc-agent on the console, without daemonizing.", 2}, {"log-stderr", OPT_LOG_CONSOLE, 0, 0, "Additionally prints log messages to stderr.", 2}, {"status", OPT_STATUS, 0, 0, "Connects to the currently running agent and prints status information " "about it.", 2}, {0, 0, 0, 0, "Help:", -1}, {0, 'h', 0, OPTION_HIDDEN, 0, -1}, {0, 0, 0, 0, 0, 0}}; static char args_doc[] = ""; static char doc[] = "oidc-agent -- An agent to manage oidc token"; static error_t parse_opt(int key, char* arg __attribute__((unused)), struct argp_state* state) { struct arguments* arguments = state->input; switch (key) { case 'k': arguments->kill_flag = 1; break; case 'g': arguments->debug = 1; break; case 'd': arguments->console = 1; break; case 'c': arguments->confirm = 1; break; case OPT_SECCOMP: arguments->seccomp = 1; break; case OPT_NOAUTOLOAD: arguments->no_autoload = 1; break; case OPT_NO_WEBSERVER: arguments->no_webserver = 1; break; case OPT_NO_SCHEME: arguments->no_scheme = 1; break; case OPT_GROUP: arguments->group = arg ?: "oidc-agent"; break; case 'a': arguments->socket_path = arg; break; case OPT_LOG_CONSOLE: arguments->log_console = 1; setLogWithTerminal(); break; case OPT_ALWAYS_ALLOW_IDTOKEN: arguments->always_allow_idtoken = 1; break; case OPT_STATUS: arguments->status = 1; break; case 't': if (!isdigit(*arg)) { return ARGP_ERR_UNKNOWN; } arguments->lifetime = strToInt(arg); break; case OPT_PW_STORE: arguments->pw_lifetime.argProvided = 1; arguments->pw_lifetime.lifetime = strToULong(arg); break; case OPT_JSON: arguments->json = 1; break; case OPT_QUIET: arguments->quiet = 1; break; case OPT_NO_AUTOREAUTHENTICATE: arguments->no_autoreauthenticate = 1; break; case 'h': argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); break; case ARGP_KEY_ARG: argp_usage(state); break; default: return ARGP_ERR_UNKNOWN; } return 0; } struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; oidc-agent-4.2.6/src/oidc-agent/oidcp/0000755000175000017500000000000014167074355017016 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidcp/oidcp.h0000644000175000017500000000112014167074355020257 0ustar marcusmarcus#ifndef OIDC_PROXY_DAEMON_H #define OIDC_PROXY_DAEMON_H #include "defines/version.h" #include "ipc/pipe.h" #include "ipc/serveripc.h" #include "oidc-agent/oidc-agent_options.h" const char* argp_program_version = AGENT_VERSION; const char* argp_program_bug_address = BUG_ADDRESS; void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, const struct arguments* argument); _Noreturn void handleClientComm(struct ipcPipe pipes, const struct arguments* arguments); #endif // OIDC_PROXY_DAEMON_H oidc-agent-4.2.6/src/oidc-agent/oidcp/proxy_handler.h0000644000175000017500000000174014167074355022047 0ustar marcusmarcus#ifndef OIDC_PROXY_HANDLER_H #define OIDC_PROXY_HANDLER_H #include "utils/oidc_error.h" oidc_error_t updateRefreshToken(const char* shortname, const char* refresh_token); oidc_error_t updateRefreshTokenUsingPassword(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* password); oidc_error_t updateRefreshTokenUsingGPG(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* gpg_key); char* getAutoloadConfig(const char* shortname, const char* issuer, const char* application_hint); char* getDefaultAccountConfigForIssuer(const char* issuer_url); #endif // OIDC_PROXY_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/oidcp/config_updater.c0000644000175000017500000000470014167074355022154 0ustar marcusmarcus#include "config_updater.h" #include "defines/oidc_values.h" #include "oidc-agent/oidcp/passwords/password_store.h" #include "proxy_handler.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/cryptFileUtils.h" #include "utils/json.h" oidc_error_t _updateRT(char* file_content, const char* shortname, const char* refresh_token, const char* password, const char* gpg_key) { if (file_content == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } cJSON* cjson = stringToJson(file_content); secFree(file_content); setJSONValue(cjson, OIDC_KEY_REFRESHTOKEN, refresh_token); char* updated_content = jsonToString(cjson); secFreeJson(cjson); oidc_error_t e = encryptAndWriteToOidcFile(updated_content, shortname, password, gpg_key); secFree(updated_content); return e; } oidc_error_t updateRefreshTokenUsingPassword(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* password) { if (shortname == NULL || refresh_token == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } char* file_content = decryptFileContent(encrypted_content, password); return _updateRT(file_content, shortname, refresh_token, password, NULL); } oidc_error_t updateRefreshTokenUsingGPG(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* gpg_key) { if (shortname == NULL || refresh_token == NULL || gpg_key == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } char* file_content = decryptPGPFileContent(encrypted_content); if (file_content == NULL) { return oidc_errno; } return _updateRT(file_content, shortname, refresh_token, NULL, gpg_key); } oidc_error_t writeOIDCFile(const char* content, const char* shortname) { if (content == NULL || shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } char* gpg_key = getGPGKeyFor(shortname) ?: extractPGPKeyIDFromOIDCFile(shortname); char* password = gpg_key ? NULL : getPasswordFor(shortname); return encryptAndWriteToOidcFile(content, shortname, password, gpg_key); }oidc-agent-4.2.6/src/oidc-agent/oidcp/config_updater.h0000644000175000017500000000137314167074355022164 0ustar marcusmarcus#ifndef OIDC_AGENT_CONFIG_UPDATER_H #define OIDC_AGENT_CONFIG_UPDATER_H #include "utils/oidc_error.h" oidc_error_t updateRefreshTokenUsingPassword(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* password); oidc_error_t updateRefreshTokenUsingGPG(const char* shortname, const char* encrypted_content, const char* refresh_token, const char* gpg_key); oidc_error_t writeOIDCFile(const char* content, const char* shortname); #endif // OIDC_AGENT_CONFIG_UPDATER_H oidc-agent-4.2.6/src/oidc-agent/oidcp/oidcp.c0000644000175000017500000004063414167074355020267 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "oidcp.h" #include #include #include #include #include #include #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "ipc/cryptCommunicator.h" #include "ipc/pipe.h" #include "ipc/serveripc.h" #include "oidc-agent/agent_state.h" #include "oidc-agent/daemonize.h" #include "oidc-agent/oidc/device_code.h" #include "oidc-agent/oidcd/parse_internal.h" #include "oidc-agent/oidcp/passwords/agent_prompt.h" #include "oidc-agent/oidcp/passwords/askpass.h" #include "oidc-agent/oidcp/passwords/password_handler.h" #include "oidc-agent/oidcp/passwords/password_store.h" #include "oidc-agent/oidcp/proxy_handler.h" #include "oidc-agent/oidcp/start_oidcd.h" #include "utils/oidc/device.h" #ifndef __APPLE__ #include "privileges/agent_privileges.h" #endif #include "config_updater.h" #include "utils/agentLogger.h" #include "utils/crypt/crypt.h" #include "utils/db/connection_db.h" #include "utils/disableTracing.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/printer.h" #include "utils/printerUtils.h" #include "utils/string/stringUtils.h" struct connection* unix_listencon; int main(int argc, char** argv) { platform_disable_tracing(); agent_openlog("oidc-agent.p"); logger_setloglevel(NOTICE); struct arguments arguments; /* Set argument defaults */ initArguments(&arguments); srandom(time(NULL)); argp_parse(&argp, argc, argv, 0, 0, &arguments); if (arguments.debug) { logger_setloglevel(DEBUG); } #ifndef __APPLE__ if (arguments.seccomp) { initOidcAgentPrivileges(&arguments); } #endif initCrypt(); if (arguments.kill_flag) { char* pidstr = getenv(OIDC_PID_ENV_NAME); if (pidstr == NULL) { printError("%s not set, cannot kill Agent\n", OIDC_PID_ENV_NAME); exit(EXIT_FAILURE); } pid_t pid = strToInt(pidstr); if (0 == pid) { printError("%s not set to a valid pid: %s\n", OIDC_PID_ENV_NAME, pidstr); exit(EXIT_FAILURE); } if (kill(pid, SIGTERM) == -1) { perror("kill"); exit(EXIT_FAILURE); } else { unlink(getenv(OIDC_SOCK_ENV_NAME)); rmdir(dirname(getenv(OIDC_SOCK_ENV_NAME))); printStdout("unset %s;\n", OIDC_SOCK_ENV_NAME); printStdout("unset %s;\n", OIDC_PID_ENV_NAME); printStdout("echo Agent pid %d killed;\n", pid); exit(EXIT_SUCCESS); } } if (arguments.status) { char* res = ipc_cryptCommunicate( 0, arguments.json ? REQUEST_STATUS_JSON : REQUEST_STATUS); if (res == NULL) { oidc_perror(); exit(EXIT_FAILURE); } char* info = parseForInfo(res); if (info == NULL) { oidc_perror(); exit(EXIT_FAILURE); } printStdout(info); secFree(info); exit(EXIT_SUCCESS); } unix_listencon = secAlloc(sizeof(struct connection)); signal(SIGPIPE, SIG_IGN); if (ipc_server_init(unix_listencon, arguments.group, arguments.socket_path) != OIDC_SUCCESS) { printError("%s\n", oidc_serror()); exit(EXIT_FAILURE); } if (!arguments.console) { pid_t daemon_pid = daemonize(); if (daemon_pid > 0) { // Export PID of new daemon printEnvs(unix_listencon->server->sun_path, daemon_pid, arguments.quiet, arguments.json); exit(EXIT_SUCCESS); } } else { printEnvs(unix_listencon->server->sun_path, getpid(), arguments.quiet, arguments.json); } agent_state.defaultTimeout = arguments.lifetime; struct ipcPipe pipes = startOidcd(&arguments); if (ipc_bindAndListen(unix_listencon) != 0) { exit(EXIT_FAILURE); } handleClientComm(pipes, &arguments); } _Noreturn void handleClientComm(struct ipcPipe pipes, const struct arguments* arguments) { connectionDB_new(); connectionDB_setFreeFunction((void(*)(void*)) & _secFreeConnection); connectionDB_setMatchFunction((matchFunction)connection_comparator); time_t minDeath = 0; while (1) { minDeath = getMinPasswordDeath(); struct connection* con = ipc_readAsyncFromMultipleConnectionsWithTimeout( *unix_listencon, minDeath); if (con == NULL) { // timeout reached removeDeathPasswords(); continue; } char* client_req = server_ipc_read(*(con->msgsock)); if (client_req == NULL) { server_ipc_writeOidcErrnoPlain(*(con->msgsock)); } else { // NULL != q INIT_KEY_VALUE(IPC_KEY_REQUEST, IPC_KEY_PASSWORDENTRY, IPC_KEY_SHORTNAME); if (CALL_GETJSONVALUES(client_req) < 0) { server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, oidc_serror()); } else { KEY_VALUE_VARS(request, passwordentry, shortname); if (_request) { if (strequal(_request, REQUEST_VALUE_ADD) || strequal(_request, REQUEST_VALUE_GEN)) { pw_handleSave(_passwordentry, arguments->pw_lifetime); } else if (strequal(_request, REQUEST_VALUE_REMOVE)) { removePasswordFor(_shortname); } else if (strequal(_request, REQUEST_VALUE_REMOVEALL)) { removeAllPasswords(); } handleOidcdComm(pipes, *(con->msgsock), client_req, arguments); } else { // no request type server_ipc_write(*(con->msgsock), RESPONSE_BADREQUEST, "No request type."); } } SEC_FREE_KEY_VALUES(); secFree(client_req); } agent_log(DEBUG, "Remove con from pool"); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); } } char* _extractShortnameFromReauthenticateInfo(const char* info) { const char* const oidcgen = "oidc-gen "; const char* const reauth = " --reauthenticate"; char* begin = strstr(info, oidcgen); if (begin == NULL) { return NULL; } begin += strlen(oidcgen); char* end = strstr(info, reauth); if (end == NULL) { return NULL; } return oidc_strncopy(begin, end - begin); } #define SHUTDOWN_IF_D_DIED(res) \ if (res == NULL) { \ if (oidc_errno == OIDC_EIPCDIS || oidc_errno == OIDC_EWRITE) { \ agent_log(ERROR, "oidcd died"); \ server_ipc_write(sock, RESPONSE_ERROR, "oidcd died"); \ exit(EXIT_FAILURE); \ } \ agent_log(ERROR, "no response from oidcd"); \ server_ipc_writeOidcErrno(sock); \ return; \ } int _waitForCodeExchangeRequest(time_t expiration, const char* expected_state, struct ipcPipe pipes) { while (1) { struct connection* con = ipc_readAsyncFromMultipleConnectionsWithTimeout( *unix_listencon, expiration); if (con == NULL) { // timeout reached removeDeathPasswords(); return -1; } char* client_req = server_ipc_read(*(con->msgsock)); if (client_req == NULL) { connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); continue; } INIT_KEY_VALUE(IPC_KEY_REQUEST, OIDC_KEY_REDIRECTURI); if (CALL_GETJSONVALUES(client_req) < 0) { server_ipc_writeOidcErrno(*(con->msgsock)); secFree(client_req); SEC_FREE_KEY_VALUES(); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); continue; } KEY_VALUE_VARS(request, uri); if (!strcaseequal(_request, REQUEST_VALUE_CODEEXCHANGE)) { secFree(client_req); SEC_FREE_KEY_VALUES(); server_ipc_write( *(con->msgsock), RESPONSE_ERROR, "request currently not acceptable; please try again later"); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); continue; } char* forwarded_res = ipc_communicateThroughPipe(pipes, "%s", client_req); secFree(client_req); if (forwarded_res == NULL) { if (oidc_errno == OIDC_EIPCDIS || oidc_errno == OIDC_EWRITE) { agent_log(ERROR, "oidcd died"); server_ipc_write(*(con->msgsock), RESPONSE_ERROR, "oidcd died"); exit(EXIT_FAILURE); } agent_log(ERROR, "no response from oidcd"); server_ipc_writeOidcErrno(*(con->msgsock)); SEC_FREE_KEY_VALUES(); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); continue; } server_ipc_write(*(con->msgsock), "%s", forwarded_res); secFree(forwarded_res); char* state = extractParameterValueFromUri(_uri, "state"); if (strequal(expected_state, state)) { secFree(state); SEC_FREE_KEY_VALUES(); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); agent_log(DEBUG, "Reaturning"); return 0; } secFree(state); agent_log(DEBUG, "Once again"); SEC_FREE_KEY_VALUES(); connectionDB_removeIfFound(con); agent_log(DEBUG, "Currently there are %lu connections", connectionDB_getSize()); } } void doReauthenticate(struct ipcPipe pipes, int sock, const char* original_client_req, const char* oidcd_res, const char* info) { logger(DEBUG, "Doing automatic reauthentication"); char* shortname = _extractShortnameFromReauthenticateInfo(info); if (shortname == NULL) { server_ipc_write(sock, "%s", oidcd_res); // Forward oidcd response to client return; } logger(DEBUG, "Extracted shortname '%s'", shortname); char* reauth_res = ipc_communicateThroughPipe(pipes, REQUEST_REAUTHENTICATE, shortname); SHUTDOWN_IF_D_DIED(reauth_res); INIT_KEY_VALUE(IPC_KEY_DEVICE, IPC_KEY_URI, OIDC_KEY_STATE); if (CALL_GETJSONVALUES(reauth_res) < 0) { server_ipc_write(sock, "%s", oidcd_res); secFree(reauth_res); SEC_FREE_KEY_VALUES(); return; } secFree(reauth_res); KEY_VALUE_VARS(device, url, state); if (_url) { agent_displayAuthCodeURL(_url, shortname); time_t timeout = time(NULL) + AGENT_PROMPT_TIMEOUT; if (_waitForCodeExchangeRequest(timeout, _state, pipes)) { server_ipc_write(sock, "%s", oidcd_res); SEC_FREE_KEY_VALUES(); return; } char* lookup_res = ipc_communicateThroughPipe(pipes, REQUEST_STATELOOKUP, _state); SHUTDOWN_IF_D_DIED(lookup_res); char* config = parseStateLookupRes(lookup_res); if (config == NULL) { server_ipc_write(sock, "%s", oidcd_res); SEC_FREE_KEY_VALUES(); secFree(shortname); return; } SEC_FREE_KEY_VALUES(); if (writeOIDCFile(config, shortname) != OIDC_SUCCESS) { server_ipc_write(sock, "%s", oidcd_res); secFree(config); secFree(shortname); return; } secFree(shortname); secFree(config); char* final_res = ipc_communicateThroughPipe(pipes, "%s", original_client_req); server_ipc_write(sock, "%s", final_res); secFree(final_res); return; } if (_device) { struct oidc_device_code* dc = getDeviceCodeFromJSON(_device); if (dc == NULL) { SEC_FREE_KEY_VALUES(); server_ipc_write(sock, "%s", oidcd_res); secFree(shortname); return; } agent_displayDeviceCode(dc, shortname); if (dc->expires_in > AGENT_PROMPT_TIMEOUT) { dc->expires_in = AGENT_PROMPT_TIMEOUT; } char* config = agent_pollDeviceCode( _device, dc->interval, dc->expires_in ? time(NULL) + dc->expires_in : 0, 0, &pipes); SEC_FREE_KEY_VALUES(); if (config == NULL) { server_ipc_write(sock, "%s", oidcd_res); secFree(shortname); return; } if (writeOIDCFile(config, shortname) != OIDC_SUCCESS) { server_ipc_write(sock, "%s", oidcd_res); secFree(config); secFree(shortname); return; } secFree(config); secFree(shortname); char* final_res = ipc_communicateThroughPipe(pipes, "%s", original_client_req); server_ipc_write(sock, "%s", final_res); secFree(final_res); return; } SEC_FREE_KEY_VALUES(); server_ipc_write(sock, "%s", oidcd_res); secFree(shortname); return; } void handleOidcdComm(struct ipcPipe pipes, int sock, const char* msg, const struct arguments* arguments) { char* send = oidc_strcopy(msg); INIT_KEY_VALUE(IPC_KEY_REQUEST, OIDC_KEY_REFRESHTOKEN, IPC_KEY_SHORTNAME, IPC_KEY_APPLICATIONHINT, IPC_KEY_ISSUERURL, OIDC_KEY_ERROR, IPC_KEY_INFO); while (1) { // RESET_KEY_VALUE_VALUES_TO_NULL(); char* oidcd_res = ipc_communicateThroughPipe(pipes, "%s", send); secFree(send); SHUTDOWN_IF_D_DIED(oidcd_res); // check response, it might be an internal request if (CALL_GETJSONVALUES(oidcd_res) < 0) { server_ipc_write(sock, RESPONSE_BADREQUEST, oidc_serror()); secFree(oidcd_res); SEC_FREE_KEY_VALUES(); return; } KEY_VALUE_VARS(request, refresh_token, shortname, application_hint, issuer, error, info); if (_request == NULL) { // if the response is the final response, forward // it to the client if (_error != NULL && _info != NULL && (strstarts(_error, "invalid_grant:") || strstarts(_error, "invalid_token:") || errorMessageIsForError(_error, OIDC_ENOREFRSH)) && strSubString(_info, "--reauthenticate") && !arguments->no_autoreauthenticate) { doReauthenticate(pipes, sock, msg, oidcd_res, _info); SEC_FREE_KEY_VALUES(); secFree(oidcd_res); return; } server_ipc_write(sock, "%s", oidcd_res); // Forward oidcd response to client secFree(oidcd_res); SEC_FREE_KEY_VALUES(); return; } secFree(oidcd_res); if (strequal(_request, INT_REQUEST_VALUE_UPD_REFRESH)) { oidc_error_t e = updateRefreshToken(_shortname, _refresh_token); send = e == OIDC_SUCCESS ? oidc_strcopy(RESPONSE_SUCCESS) : oidc_sprintf(RESPONSE_ERROR, oidc_serror()); SEC_FREE_KEY_VALUES(); continue; } else if (strequal(_request, INT_REQUEST_VALUE_AUTOLOAD)) { char* config = getAutoloadConfig(_shortname, _issuer, _application_hint); send = config ? oidc_sprintf(RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, config) : oidc_sprintf(INT_RESPONSE_ERROR, oidc_errno); secFree(config); SEC_FREE_KEY_VALUES(); continue; } else if (strequal(_request, INT_REQUEST_VALUE_CONFIRM)) { oidc_error_t e = _issuer ? askpass_getConfirmationWithIssuer(_issuer, _shortname, _application_hint) : askpass_getConfirmation(_shortname, _application_hint); send = e == OIDC_SUCCESS ? oidc_strcopy(RESPONSE_SUCCESS) : oidc_sprintf(INT_RESPONSE_ERROR, oidc_errno); SEC_FREE_KEY_VALUES(); continue; } else if (strequal(_request, INT_REQUEST_VALUE_CONFIRMIDTOKEN)) { oidc_error_t e = _issuer ? askpass_getIdTokenConfirmationWithIssuer( _issuer, _shortname, _application_hint) : askpass_getIdTokenConfirmation( _shortname, _application_hint); send = e == OIDC_SUCCESS ? oidc_strcopy(RESPONSE_SUCCESS) : oidc_sprintf(INT_RESPONSE_ERROR, oidc_errno); SEC_FREE_KEY_VALUES(); continue; } else if (strequal(_request, INT_REQUEST_VALUE_QUERY_ACCDEFAULT)) { char* account = NULL; if (strValid(_issuer)) { // default for this issuer account = getDefaultAccountConfigForIssuer(_issuer); } else { // global default oidc_errno = OIDC_NOTIMPL; // TODO } send = oidc_sprintf(INT_RESPONSE_ACCDEFAULT, account ?: ""); secFree(account); SEC_FREE_KEY_VALUES(); continue; } else { server_ipc_write( sock, "Internal communication error: unknown internal request"); SEC_FREE_KEY_VALUES(); return; } } } oidc-agent-4.2.6/src/oidc-agent/oidcp/start_oidcd.h0000644000175000017500000000030514120404223021440 0ustar marcusmarcus#ifndef OIDCP_START_OIDCD_H #define OIDCP_START_OIDCD_H #include "oidc-agent/oidc-agent_options.h" struct ipcPipe startOidcd(const struct arguments* arguments); #endif /* OIDCP_START_OIDCD_H */ oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/0000755000175000017500000000000014167074355021043 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/password_store.h0000644000175000017500000000076214167074355024277 0ustar marcusmarcus#ifndef OIDC_PASSWORD_STORE_H #define OIDC_PASSWORD_STORE_H #include #include "utils/oidc_error.h" #include "utils/password_entry.h" oidc_error_t savePassword(struct password_entry* pw); char* getGPGKeyFor(const char* shortname); char* getPasswordFor(const char* shortname); oidc_error_t removePasswordFor(const char* shortname); oidc_error_t removeAllPasswords(); void removeDeathPasswords(); time_t getMinPasswordDeath(); #endif // OIDC_PASSWORD_STORE_H oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/agent_prompt.h0000644000175000017500000000112014167074355023705 0ustar marcusmarcus#ifndef OIDCP_AGENT_PROMPT_H #define OIDCP_AGENT_PROMPT_H #include "oidc-agent/oidc/device_code.h" #include "utils/prompt.h" #define AGENT_PROMPT_TIMEOUT PROMPT_DEFAULT_TIMEOUT char* agent_promptPassword(const char* text, const char* label, const char* init); int agent_promptConsentDefaultYes(const char* text); void agent_displayAuthCodeURL(const char* url, const char* shortname); void agent_displayDeviceCode(const struct oidc_device_code* device, const char* shortname); #endif /* OIDCP_AGENT_PROMPT_H */ oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/password_handler.h0000644000175000017500000000044614120404223024533 0ustar marcusmarcus#ifndef OIDC_PASSWORD_HANDLER_H #define OIDC_PASSWORD_HANDLER_H #include "utils/lifetimeArg.h" #include "utils/oidc_error.h" oidc_error_t pw_handleSave(const char* pw_entry_str, const struct lifetimeArg pw_lifetime); #endif // OIDC_PASSWORD_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/agent_prompt.c0000644000175000017500000000404514167074355023711 0ustar marcusmarcus#include "agent_prompt.h" #include #include "oidc-agent/oidc/device_code.h" #include "oidc-gen/qr.h" #include "utils/prompt.h" #include "utils/string/stringUtils.h" typedef void (*sighandler_t)(int); char* agent_promptPassword(const char* text, const char* label, const char* init) { // _promptPasswordGUI might raise SIGINT (if user cancels), oidcp should not // crash then sighandler_t old = signal(SIGINT, SIG_IGN); char* ret = _promptPasswordGUI(text, label, init, AGENT_PROMPT_TIMEOUT); signal(SIGINT, old); return ret; } int agent_promptConsentDefaultYes(const char* text) { return _promptConsentGUIDefaultYes(text, AGENT_PROMPT_TIMEOUT); } static const char* const intro_fmt = "An error occurred while using the '%s' account configuration.\n" "Most likely the refresh token expired. To solve the problem you have to " "re-authenticate.\n"; void agent_displayDeviceCode(const struct oidc_device_code* device, const char* shortname) { char* intro = oidc_sprintf(intro_fmt, shortname); char* text = oidc_sprintf( "%sTo continue please open the following URL in a browser on any device " "(or use the QR code):\n\n%s\n\nEnter the following code:\n\n%s\n", intro, oidc_device_getVerificationUri(*device), oidc_device_getUserCode(*device)); const char* qr = "/tmp/oidc-qr"; if (getIMGQRCode(strValid(oidc_device_getVerificationUriComplete(*device)) ? oidc_device_getVerificationUriComplete(*device) : oidc_device_getVerificationUri(*device), qr)) { qr = NULL; } secFree(intro); displayDeviceLinkGUI(text, qr); secFree(text); } void agent_displayAuthCodeURL(const char* url, const char* shortname) { char* intro = oidc_sprintf(intro_fmt, shortname); char* text = oidc_sprintf( "%sTo continue please open the following URL in your browser:\n\n%s\n", intro, url); secFree(intro); displayAuthCodeLinkGUI(text); secFree(text); } oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/password_store.c0000644000175000017500000001606714167074355024277 0ustar marcusmarcus#include "password_store.h" #include "oidc-agent/oidcp/passwords/askpass.h" #ifndef __APPLE__ #include "oidc-agent/oidcp/passwords/keyring.h" #endif #include #include "utils/agentLogger.h" #include "utils/crypt/passwordCrypt.h" #include "utils/db/password_db.h" #include "utils/file_io/file_io.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/password_entry.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" int matchPasswordEntryByShortname(struct password_entry* a, struct password_entry* b) { if (a == NULL && b == NULL) { return 1; } if (a == NULL || b == NULL) { return 0; } if (a->shortname == NULL && b->shortname == NULL) { return 1; } if (a->shortname == NULL || b->shortname == NULL) { return 0; } return strequal(a->shortname, b->shortname); } char* memory_getPasswordFor(const struct password_entry* pwe) { if (pwe == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (pwe->expires_at && pwe->expires_at < time(NULL)) { // Actually expired entries should already be gone from the list oidc_errno = OIDC_EPWNOTFOUND; agent_log(NOTICE, "Found an expired entry for '%s'", pwe->shortname); return NULL; } return oidc_strcopy(pwe->password); } void initPasswordStore() { passwordDB_new(); passwordDB_setMatchFunction((matchFunction)matchPasswordEntryByShortname); passwordDB_setFreeFunction((void(*)(void*))_secFreePasswordEntry); } oidc_error_t savePassword(struct password_entry* pw) { if (pw == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "Saving password for '%s'", pw->shortname); initPasswordStore(); if (pw->password) { // For prompt and command password won't be set char* tmp = encryptPassword(pw->password, pw->shortname); if (tmp == NULL) { return oidc_errno; } pwe_setPassword(pw, tmp); } if (pw->command) { char* tmp = encryptPassword(pw->command, pw->shortname); if (tmp == NULL) { return oidc_errno; } pwe_setCommand(pw, tmp); } if (pw->filepath) { char* tmp = encryptPassword(pw->filepath, pw->shortname); if (tmp == NULL) { return oidc_errno; } pwe_setFile(pw, tmp); } if (pw->gpg_key) { char* tmp = encryptPassword(pw->gpg_key, pw->shortname); if (tmp == NULL) { return oidc_errno; } pwe_setGPGKey(pw, tmp); } if (pw->type & PW_TYPE_MNG) { #ifndef __APPLE__ keyring_savePasswordFor(pw->shortname, pw->password); #else agent_log(WARNING, "keyring currently not supported for MACOS"); #endif } passwordDB_removeIfFound( pw); // Removing an existing (old) entry for the same shortname -> update passwordDB_addValue(pw); agent_log(DEBUG, "Now there are %lu passwords saved", passwordDB_getSize()); return OIDC_SUCCESS; } oidc_error_t removeOrExpirePasswordFor(const char* shortname, int remove) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "%s password for '%s'", remove ? "Removing" : "Expiring", shortname); struct password_entry key = {.shortname = oidc_strcopy(shortname)}; struct password_entry* pw = passwordDB_findValue(&key); secFree(key.shortname); if (pw == NULL) { agent_log(DEBUG, "No password found for '%s'", shortname); return OIDC_SUCCESS; } unsigned char type = pw->type; if (type & PW_TYPE_MNG) { #ifndef __APPLE__ keyring_removePasswordFor(shortname); #else agent_log(WARNING, "keyring currently not supported for MACOS"); #endif } if (remove) { passwordDB_removeIfFound(pw); } else { pwe_setPassword(pw, NULL); pwe_setExpiresAt(pw, 0); } agent_log(DEBUG, "Now there are %lu passwords saved", passwordDB_getSize()); return OIDC_SUCCESS; } oidc_error_t removePasswordFor(const char* shortname) { return removeOrExpirePasswordFor(shortname, 1); } oidc_error_t expirePasswordFor(const char* shortname) { return removeOrExpirePasswordFor(shortname, 0); } oidc_error_t removeAllPasswords() { agent_log(DEBUG, "Removing all passwords"); passwordDB_reset(); return OIDC_SUCCESS; } char* getGPGKeyFor(const char* shortname) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log(DEBUG, "Getting gpg key id for '%s'", shortname); struct password_entry key = {.shortname = oidc_strcopy(shortname)}; struct password_entry* pw = passwordDB_findValue(&key); secFree(key.shortname); if (pw == NULL) { agent_log(DEBUG, "No password found for '%s'", shortname); return NULL; } if (pw->type & PW_TYPE_MEM) { return decryptPassword(pw->gpg_key, shortname); } return NULL; } char* getPasswordFor(const char* shortname) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log(DEBUG, "Getting password for '%s'", shortname); struct password_entry key = {.shortname = oidc_strcopy(shortname)}; struct password_entry* pw = passwordDB_findValue(&key); secFree(key.shortname); if (pw == NULL) { agent_log(DEBUG, "No password found for '%s'", shortname); agent_log(DEBUG, "Try getting password from user prompt"); return askpass_getPasswordForUpdate(shortname); } unsigned char type = pw->type; agent_log(DEBUG, "Password type is %hhu", type); char* res = NULL; if (!res && type & PW_TYPE_MEM) { agent_log(DEBUG, "Try getting password from memory"); char* crypt = memory_getPasswordFor(pw); res = decryptPassword(crypt, shortname); secFree(crypt); } if (!res && type & PW_TYPE_MNG) { #ifndef __APPLE__ agent_log(DEBUG, "Try getting password from keyring"); char* crypt = keyring_getPasswordFor(shortname); res = decryptPassword(crypt, shortname); secFree(crypt); #else agent_log(WARNING, "keyring currently not supported for MACOS"); #endif } if (!res && type & PW_TYPE_CMD) { agent_log(DEBUG, "Try getting password from command"); char* cmd = decryptPassword(pw->command, shortname); res = getOutputFromCommand(cmd); secFree(cmd); } if (!res && type & PW_TYPE_FILE) { agent_log(DEBUG, "Try getting password from file"); char* file = decryptPassword(pw->filepath, shortname); res = getLineFromFile(file); secFree(file); } if (!res && type & PW_TYPE_PRMT) { agent_log(DEBUG, "Try getting password from user prompt"); res = askpass_getPasswordForUpdate(shortname); if (res && type & PW_TYPE_MEM) { pwe_setPassword(pw, encryptPassword(res, shortname)); } } return res; } time_t getMinPasswordDeath() { agent_log(DEBUG, "Getting min death time for passwords"); return passwordDB_getMinDeath((time_t(*)(void*))pwe_getExpiresAt); } struct password_entry* getDeathPasswordEntry() { agent_log(DEBUG, "Searching for death passwords"); return passwordDB_getDeathEntry((time_t(*)(void*))pwe_getExpiresAt); } void removeDeathPasswords() { struct password_entry* death_pwe = NULL; while ((death_pwe = getDeathPasswordEntry()) != NULL) { expirePasswordFor(death_pwe->shortname); } } oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/keyring.c0000644000175000017500000000572614167074355022671 0ustar marcusmarcus#ifndef __APPLE__ #include "keyring.h" #include #include "utils/agentLogger.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" const SecretSchema* agent_get_schema(void) G_GNUC_CONST; #define AGENT_SCHEMA agent_get_schema() const SecretSchema* agent_get_schema(void) { static const SecretSchema the_schema = { "edu.kit.oidc-agent.Password", SECRET_SCHEMA_NONE, { {"shortname", SECRET_SCHEMA_ATTRIBUTE_STRING}, {"NULL", 0}, }, // These are just reserved variables 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; return &the_schema; } void oidc_setGerror(GError* error) { if (error == NULL) { return; } agent_log(ERROR, "%s", error->message); oidc_errno = OIDC_EGERROR; oidc_seterror(error->message); } char* keyring_getPasswordFor(const char* shortname) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log(DEBUG, "Looking up password for '%s' in keyring", shortname); GError* error = NULL; gchar* pw = secret_password_lookup_sync(AGENT_SCHEMA, NULL, &error, "shortname", shortname, NULL); if (error != NULL) { oidc_setGerror(error); g_error_free(error); return NULL; } if (pw == NULL) { agent_log(DEBUG, "No password found for '%s' in keyring", shortname); oidc_errno = OIDC_EPWNOTFOUND; return NULL; } char* ret = oidc_strcopy(pw); secret_password_free(pw); return ret; } oidc_error_t keyring_savePasswordFor(const char* shortname, const char* password) { if (shortname == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "Saving password for '%s' in keyring", shortname); GError* error = NULL; secret_password_store_sync(AGENT_SCHEMA, SECRET_COLLECTION_DEFAULT, shortname, password, NULL, &error, "shortname", shortname, NULL); if (error == NULL) { agent_log(DEBUG, "Password for '%s' saved in keyring", shortname); return OIDC_SUCCESS; } oidc_setGerror(error); g_error_free(error); return oidc_errno; } oidc_error_t keyring_removePasswordFor(const char* shortname) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "Removing password for '%s' from keyring", shortname); GError* error = NULL; gboolean removed = secret_password_clear_sync(AGENT_SCHEMA, NULL, &error, "shortname", shortname, NULL); if (error != NULL) { oidc_setGerror(error); g_error_free(error); return oidc_errno; } if (removed) { agent_log(DEBUG, "Password for '%s' removed from keyring", shortname); } else { agent_log(DEBUG, "No password to remove for '%s' in keyring", shortname); } return OIDC_SUCCESS; } #endif oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/askpass.h0000644000175000017500000000224414120404223022637 0ustar marcusmarcus#ifndef OIDC_ASKPASS_RUNNER_H #define OIDC_ASKPASS_RUNNER_H #include "utils/oidc_error.h" char* askpass_getPasswordForUpdate(const char* shortname); char* askpass_getPasswordForAutoload(const char* shortname, const char* application_hint); char* askpass_getPasswordForAutoloadWithIssuer(const char* issuer, const char* shortname, const char* application_hint); oidc_error_t askpass_getConfirmation(const char* shortname, const char* application_hint); oidc_error_t askpass_getConfirmationWithIssuer(const char* issuer, const char* shortname, const char* application_hint); oidc_error_t askpass_getIdTokenConfirmation(const char* shortname, const char* application_hint); oidc_error_t askpass_getIdTokenConfirmationWithIssuer( const char* issuer, const char* shortname, const char* application_hint); #endif // OIDC_ASKPASS_RUNNER_H oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/keyring.h0000644000175000017500000000061714120404223022644 0ustar marcusmarcus#ifndef OIDCAGENT_KEYRING_INTEGRATION_H #define OIDCAGENT_KEYRING_INTEGRATION_H #include "utils/oidc_error.h" oidc_error_t keyring_savePasswordFor(const char* shortname, const char* password); char* keyring_getPasswordFor(const char* shortname); oidc_error_t keyring_removePasswordFor(const char* shortname); #endif // OIDCAGENT_KEYRING_INTEGRATION_H oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/password_handler.c0000644000175000017500000000214014167074355024543 0ustar marcusmarcus#include "password_handler.h" #include "oidc-agent/oidcp/passwords/password_store.h" #include "utils/agentLogger.h" #include "utils/oidc_error.h" #include "utils/password_entry.h" oidc_error_t pw_handleSave(const char* pw_entry_str, const struct lifetimeArg pw_lifetime) { if (pw_entry_str == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } struct password_entry* pw = JSONStringToPasswordEntry(pw_entry_str); if (pwe_getExpiresAt(pw) == 0 && pw_lifetime.argProvided) { pwe_setExpiresIn(pw, pw_lifetime.lifetime); } if (!pw->shortname) { oidc_setInternalError("shortname not set in pw_entry"); agent_log(ERROR, "%s", oidc_serror()); return oidc_errno; } if (!pw->type) { oidc_setInternalError("type not set in pw_entry"); agent_log(ERROR, "%s", oidc_serror()); return oidc_errno; } if (!pw->password && (pw->type & (PW_TYPE_MNG | PW_TYPE_MEM))) { oidc_setInternalError("password not set in pw_entry"); agent_log(ERROR, "%s", oidc_serror()); return oidc_errno; } return savePassword(pw); } oidc-agent-4.2.6/src/oidc-agent/oidcp/passwords/askpass.c0000644000175000017500000001516114167074355022660 0ustar marcusmarcus#include "askpass.h" #include "oidc-agent/oidcp/passwords/agent_prompt.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" char* askpass_getPasswordForUpdate(const char* shortname) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log( DEBUG, "Prompting user for encryption password for updating account config '%s'", shortname); const char* const fmt = "oidc-agent needs to update the account config for '%s'.\nPlease enter " "the encryption password for '%s':"; char* msg = oidc_sprintf(fmt, shortname, shortname); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { oidc_errno = OIDC_EUSRPWCNCL; } return ret; } char* askpass_getPasswordForAutoload(const char* shortname, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log(DEBUG, "Prompting user for encryption password for autoload config '%s'", shortname); const char* const fmt = "An application %srequests an access token for '%s'.\nThis configuration " "is currently not loaded.\nTo load '%s' into oidc-agent please enter " "the encryption password for '%s':"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", shortname, shortname, shortname); secFree(application_str); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { oidc_errno = OIDC_EUSRPWCNCL; } return ret; } char* askpass_getPasswordForAutoloadWithIssuer(const char* issuer, const char* shortname, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } agent_log( DEBUG, "Prompting user for encryption password for autoload config '%s' for " "issuer '%s'", shortname, issuer); const char* const fmt = "An application %srequests an access token for '%s',\nbut there's " "currently no account configuration loaded for this provider.\nThe " "default account configuration for this provider is '%s'.\nTo load '%s' " "into oidc-agent please enter the encryption password for '%s':"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", issuer, shortname, shortname, shortname); secFree(application_str); char* ret = agent_promptPassword(msg, "Encryption password", NULL); secFree(msg); if (ret == NULL) { oidc_errno = OIDC_EUSRPWCNCL; } return ret; } oidc_error_t askpass_getConfirmation(const char* shortname, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "Prompting user for confirmation of using config '%s'", shortname); const char* const fmt = "An application %srequests an access token for '%s'.\n" "Do you want to allow this usage?"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", shortname); secFree(application_str); oidc_errno = agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; secFree(msg); return oidc_errno; } oidc_error_t askpass_getConfirmationWithIssuer(const char* issuer, const char* shortname, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log( DEBUG, "Prompting user for confirmation of using config '%s' for issuer '%s'", shortname, issuer); const char* const fmt = "An application %srequests an access token for '%s'.\n" "Do you want to allow the usage of '%s'?"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", issuer, shortname); secFree(application_str); oidc_errno = agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; secFree(msg); return oidc_errno; } oidc_error_t askpass_getIdTokenConfirmation(const char* shortname, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } agent_log(DEBUG, "Prompting user for id-token confirmation for config '%s'", shortname); const char* const fmt = "An application %srequests an id token for '%s'.\n" "id tokens should not be passed to other applications as authorization.\n" "Do you want to allow this usage?"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", shortname); secFree(application_str); oidc_errno = agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; secFree(msg); return oidc_errno; } oidc_error_t askpass_getIdTokenConfirmationWithIssuer( const char* issuer, const char* shortname, const char* application_hint) { agent_log(DEBUG, "Prompting user for id-token confirmation for " "issuer '%s'", issuer); const char* const fmt = "An application %srequests an id token for '%s'.\n" "id tokens should not be passed to other applications as authorization.\n" "Do you want to allow the usage of '%s'?"; char* application_str = strValid(application_hint) ? oidc_sprintf("(%s) ", application_hint) : NULL; char* msg = oidc_sprintf(fmt, application_str ?: "", issuer, shortname ?: issuer); secFree(application_str); oidc_errno = agent_promptConsentDefaultYes(msg) ? OIDC_SUCCESS : OIDC_EFORBIDDEN; secFree(msg); return oidc_errno; } oidc-agent-4.2.6/src/oidc-agent/oidcp/start_oidcd.c0000644000175000017500000000242014167074355021457 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "start_oidcd.h" #include #include #ifndef __APPLE__ #include #endif #include #include "ipc/pipe.h" #include "oidc-agent/oidcd/oidcd.h" #include "utils/agentLogger.h" struct ipcPipe startOidcd(const struct arguments* arguments) { struct pipeSet pipes = ipc_pipe_init(); if (pipes.pipe1.rx == -1) { agent_log(ERROR, "could not create pipes"); exit(EXIT_FAILURE); } pid_t ppid_before_fork = getpid(); pid_t pid = fork(); if (pid == -1) { agent_log(ERROR, "fork %m"); exit(EXIT_FAILURE); } if (pid == 0) { // child #ifndef __APPLE__ // init child so that it exists if parent (oidcp) is killed. int r = prctl(PR_SET_PDEATHSIG, SIGTERM); if (r == -1) { agent_log(ERROR, "prctl %m"); exit(EXIT_FAILURE); } #endif // test in case the original parent exited just before the prctl() call if (getppid() != ppid_before_fork) { agent_log(ERROR, "Parent died shortly after fork"); exit(EXIT_FAILURE); } struct ipcPipe childPipes = toClientPipes(pipes); oidcd_main(childPipes, arguments); exit(EXIT_FAILURE); } else { // parent struct ipcPipe parentPipes = toServerPipes(pipes); return parentPipes; } } oidc-agent-4.2.6/src/oidc-agent/oidcp/proxy_handler.c0000644000175000017500000000626114167074355022045 0ustar marcusmarcus#include "proxy_handler.h" #include #include "account/issuer_helper.h" #include "defines/settings.h" #include "oidc-agent/oidcp/passwords/askpass.h" #include "oidc-agent/oidcp/passwords/password_store.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/oidc_file_io.h" #include "utils/listUtils.h" #include "utils/string/stringUtils.h" oidc_error_t updateRefreshToken(const char* shortname, const char* refresh_token) { if (shortname == NULL || refresh_token == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } char* encrypted_content = readOidcFile(shortname); if (!isPGPMessage(encrypted_content)) { char* password = getPasswordFor(shortname); oidc_error_t e = updateRefreshTokenUsingPassword( shortname, encrypted_content, refresh_token, password); secFree(encrypted_content); secFree(password); return e; } else { char* gpg_key = getGPGKeyFor(shortname); if (gpg_key == NULL) { gpg_key = extractPGPKeyID(encrypted_content); } oidc_error_t e = updateRefreshTokenUsingGPG(shortname, encrypted_content, refresh_token, gpg_key); secFree(encrypted_content); secFree(gpg_key); return e; } } char* getAutoloadConfig(const char* shortname, const char* issuer, const char* application_hint) { if (shortname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (!oidcFileDoesExist(shortname)) { oidc_errno = OIDC_ENOACCOUNT; return NULL; } char* crypt_content = readOidcFile(shortname); if (crypt_content == NULL) { return NULL; } if (isPGPMessage(crypt_content)) { char* config = decryptPGPFileContent(crypt_content); secFree(crypt_content); return config; } for (size_t i = 0; i < MAX_PASS_TRIES; i++) { char* password = issuer ? askpass_getPasswordForAutoloadWithIssuer(issuer, shortname, application_hint) : askpass_getPasswordForAutoload(shortname, application_hint); if (password == NULL) { secFree(crypt_content); return NULL; } char* config = decryptFileContent(crypt_content, password); secFree(password); if (config != NULL) { secFree(crypt_content); return config; } } secFree(crypt_content); return NULL; } char* getDefaultAccountConfigForIssuer(const char* issuer_url) { if (issuer_url == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_t* issuers = getLinesFromOidcFile(ISSUER_CONFIG_FILENAME); if (issuers == NULL) { return NULL; } char* shortname = NULL; list_node_t* node; list_iterator_t* it = list_iterator_new(issuers, LIST_HEAD); while ((node = list_iterator_next(it))) { char* line = node->val; char* iss = strtok(line, " "); char* acc = strtok(NULL, " "); if (compIssuerUrls(issuer_url, iss)) { if (strValid(acc)) { shortname = oidc_strcopy(acc); } break; } } list_iterator_destroy(it); secFreeList(issuers); return shortname; } oidc-agent-4.2.6/src/oidc-agent/lock_state.h0000644000175000017500000000035514120404223020200 0ustar marcusmarcus#ifndef LOCK_STATE_H #define LOCK_STATE_H #include "utils/oidc_error.h" struct lock_state { short locked; char* hash; }; oidc_error_t unlock(const char* password); oidc_error_t lock(const char* password); #endif // LOCK_STATE_H oidc-agent-4.2.6/src/oidc-agent/httpserver/0000755000175000017500000000000014167074355020126 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/httpserver/termHttpserver.c0000644000175000017500000000114014167074355023324 0ustar marcusmarcus#define _POSIX_C_SOURCE 200809L #include "termHttpserver.h" #include #include #include #include "running_server.h" #include "utils/agentLogger.h" #include "utils/memory.h" void stopHttpServer(struct MHD_Daemon** d_ptr) { agent_log(DEBUG, "HttpServer: Stopping HttpServer"); MHD_stop_daemon(*d_ptr); secFree(d_ptr); } void termHttpServer(const char* state) { if (state == NULL) { return; } pid_t pid = removeServer(state); if (pid > 0) { kill(pid, SIGTERM); agent_log(DEBUG, "killed webserver for state %s with pid %d", state, pid); } } oidc-agent-4.2.6/src/oidc-agent/httpserver/startHttpserver.c0000644000175000017500000001271114167074355023520 0ustar marcusmarcus#define _GNU_SOURCE #include "startHttpserver.h" #include #include #include #ifdef __APPLE__ #include #include #include #else #include #endif #include #include "ipc/ipc.h" #include "requestHandler.h" #include "running_server.h" #include "termHttpserver.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/portUtils.h" #include "utils/string/stringUtils.h" #include "wrapper/list.h" /** * @param config a pointer to a json account config. * */ struct MHD_Daemon** startHttpServer(const char* redirect_uri, const char* state) { logger_open("oidc-agent.httpserver"); unsigned short port = getPortFromUri(redirect_uri); if (port == 0) { agent_log(NOTICE, "Could not get port from uri"); return NULL; } size_t cls_size = 2; struct MHD_Daemon** d_ptr = secAlloc(sizeof(struct MHD_Daemon*)); char** cls = secAlloc(sizeof(char*) * cls_size); cls[0] = oidc_strcopy(redirect_uri); cls[1] = oidc_sprintf("%hhu:%s", strEnds(redirect_uri, "/"), state); *d_ptr = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, port, NULL, NULL, &request_echo, cls, MHD_OPTION_END); if (*d_ptr == NULL) { agent_log(ERROR, "Error starting the HttpServer on port %d", port); oidc_errno = OIDC_EHTTPD; secFree(d_ptr); secFreeArray(cls, cls_size); return NULL; } agent_log(DEBUG, "HttpServer: Started HttpServer on port %d", port); return d_ptr; } struct MHD_Daemon** oidc_mhd_daemon_ptr = NULL; #ifdef __APPLE__ // void noteProcDeath(CFFileDescriptorRef fdref, CFOptionFlags callBackTypes, // void* info) { // struct kevent kev; // int fd = CFFileDescriptorGetNativeDescriptor(fdref); // kevent(fd, NULL, 0, &kev, 1, NULL); // // take action on death of process here // unsigned int dead_pid = (unsigned int)kev.ident; // // CFFileDescriptorInvalidate(fdref); // CFRelease(fdref); // // int our_pid = getpid(); // exit(EXIT_FAILURE); // } // // void suicide_if_we_become_a_zombie() { // int parent_pid = getppid(); // int fd = kqueue(); // struct kevent kev; // EV_SET(&kev, parent_pid, EVFILT_PROC, EV_ADD | EV_ENABLE, NOTE_EXIT, 0, // NULL); kevent(fd, &kev, 1, NULL, 0, NULL); CFFileDescriptorRef fdref = // CFFileDescriptorCreate(kCFAllocatorDefault, fd, // true, noteProcDeath, // NULL); // CFFileDescriptorEnableCallBacks(fdref, kCFFileDescriptorReadCallBack); // CFRunLoopSourceRef source = // CFFileDescriptorCreateRunLoopSource(kCFAllocatorDefault, fdref, 0); // CFRunLoopAddSource(CFRunLoopGetMain(), source, kCFRunLoopDefaultMode); // CFRelease(source); // } #endif void http_sig_handler(int signo) { switch (signo) { case SIGTERM: sleep(5); stopHttpServer(oidc_mhd_daemon_ptr); break; default: agent_log(EMERGENCY, "HttpServer caught Signal %d", signo); } exit(signo); } oidc_error_t fireHttpServer(list_t* redirect_uris, size_t size, char** state_ptr) { int fd[2]; #ifdef __APPLE__ if (pipe(fd) != 0) { #else if (pipe2(fd, O_DIRECT) != 0) { #endif oidc_setErrnoError(); return oidc_errno; } pid_t pid = fork(); if (pid == -1) { agent_log(ALERT, "fork %m"); oidc_setErrnoError(); return oidc_errno; } if (pid == 0) { // child #ifdef __APPLE__ // suicide_if_we_become_a_zombie(); #else prctl(PR_SET_PDEATHSIG, SIGTERM); #endif close(fd[0]); size_t i; for (i = 0; i < size && oidc_mhd_daemon_ptr == NULL; i++) { oidc_mhd_daemon_ptr = startHttpServer(list_at(redirect_uris, i)->val, *state_ptr); } if (oidc_mhd_daemon_ptr == NULL) { ipc_write(fd[1], "%d", OIDC_EHTTPPORTS); close(fd[1]); exit(EXIT_FAILURE); } const char* used_uri = list_at(redirect_uris, i - 1)->val; ipc_write(fd[1], "%hu", getPortFromUri(used_uri)); close(fd[1]); signal(SIGTERM, http_sig_handler); sigset_t sigset; sigemptyset(&sigset); sigaddset(&sigset, SIGTERM); sigsuspend(&sigset); return OIDC_ENOPE; } else { // parent close(fd[1]); char* e = ipc_read(fd[0]); close(fd[0]); if (e == NULL) { return oidc_errno; } char** endptr = secAlloc(sizeof(char*)); long int port = strtol(e, endptr, 10); if (**endptr != '\0') { secFree(endptr); secFree(e); oidc_errno = OIDC_EERROR; oidc_seterror("Internal error. Could not convert pipe communication."); return oidc_errno; } secFree(endptr); secFree(e); if (port < 0) { oidc_errno = port; agent_log(ERROR, "HttpServer Start Error: %s", oidc_serror()); return oidc_errno; } char* used_uri = NULL; for (size_t i = 0; i < size; i++) { unsigned short p = getPortFromUri(list_at(redirect_uris, i)->val); if (p == port) { used_uri = list_at(redirect_uris, i)->val; } } char* tmp = oidc_sprintf("%hhu:%s", strEnds(used_uri, "/"), *state_ptr); secFree(*state_ptr); *state_ptr = tmp; struct running_server* running_server = secAlloc(sizeof(struct running_server)); running_server->pid = pid; running_server->state = oidc_strcopy(*state_ptr); addServer(running_server); return port; } } oidc-agent-4.2.6/src/oidc-agent/httpserver/requestHandler.c0000644000175000017500000001201514167074355023257 0ustar marcusmarcus#define _XOPEN_SOURCE #include "requestHandler.h" #include #include #include "defines/ipc_values.h" #include "ipc/serveripc.h" #include "utils/agentLogger.h" #include "utils/errorUtils.h" #include "utils/memory.h" #include "utils/parseJson.h" #include "utils/string/stringUtils.h" const char* const HTML_SUCCESS = #include "static/success.html" ; const char* const HTML_NO_CODE = #include "static/no_code.html" ; const char* const HTML_WRONG_STATE = #include "static/wrong_state.html" ; const char* const HTML_CODE_EXCHANGE_FAILED = #include "static/code_exchange_failed.html" ; const char* const HTML_CODE_EXCHANGE_FAILED_WITH_ERROR = #include "static/code_exchange_failed_with_error.html" ; const char* const HTML_ERROR = #include "static/error.html" ; static int makeResponseCodeExchangeFailed(struct MHD_Connection* connection, const char* url) { char* res = oidc_sprintf(HTML_CODE_EXCHANGE_FAILED, url); struct MHD_Response* response = MHD_create_response_from_buffer( strlen(res), (void*)res, MHD_RESPMEM_MUST_COPY); secFree(res); int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); return ret; } static int makeResponseFromIPCResponse(struct MHD_Connection* connection, char* res, const char* url, const char* state) { char* error = parseForError(res); if (error) { res = oidc_sprintf(HTML_CODE_EXCHANGE_FAILED_WITH_ERROR, error, url); secFree(error); } else { res = oidc_sprintf(HTML_SUCCESS, state); } struct MHD_Response* response = MHD_create_response_from_buffer( strlen(res), (void*)res, MHD_RESPMEM_MUST_COPY); secFree(res); int ret = MHD_queue_response(connection, MHD_HTTP_OK, response); MHD_destroy_response(response); return ret; } static int makeResponseWrongState(struct MHD_Connection* connection) { struct MHD_Response* response = MHD_create_response_from_buffer( strlen(HTML_WRONG_STATE), (void*)HTML_WRONG_STATE, MHD_RESPMEM_PERSISTENT); int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); MHD_destroy_response(response); return ret; } static int makeResponseError(struct MHD_Connection* connection) { const char* error = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "error"); const char* error_description = MHD_lookup_connection_value( connection, MHD_GET_ARGUMENT_KIND, "error_description"); struct MHD_Response* response; if (error) { char* err = combineError(error, error_description); agent_log(ERROR, "HttpServer Error: %s", err); char* res = oidc_sprintf(HTML_ERROR, err); secFree(err); response = MHD_create_response_from_buffer(strlen(res), (void*)res, MHD_RESPMEM_MUST_COPY); secFree(res); kill(getpid(), SIGTERM); } else { response = MHD_create_response_from_buffer( strlen(HTML_NO_CODE), (void*)HTML_NO_CODE, MHD_RESPMEM_PERSISTENT); } int ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, response); MHD_destroy_response(response); return ret; } static int handleRequest(void* cls, struct MHD_Connection* connection) { const char* code = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "code"); const char* state = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "state"); if (code == NULL) { return makeResponseError(connection); } agent_log(DEBUG, "HttpServer: Code is %s", code); char** cr = (char**)cls; if (!strequal(cr[1], state)) { return makeResponseWrongState(connection); } char* url = oidc_sprintf("%s?code=%s&state=%s", cr[0], code, state); char* res = ipc_cryptCommunicateWithServerPath(REQUEST_CODEEXCHANGE, url); int ret; if (res == NULL) { ret = makeResponseCodeExchangeFailed(connection, url); } else { agent_log(DEBUG, "Httpserver ipc response is: %s", res); ret = makeResponseFromIPCResponse(connection, res, url, state); } secFree(url); secFreeArray(cr, 2); return ret; } #ifdef MHD_YES int request_echo(void* cls, struct MHD_Connection* connection, const char* url, #else enum MHD_Result request_echo(void* cls, struct MHD_Connection* connection, const char* url, #endif const char* method, const char* version, const char* upload_data __attribute__((unused)), size_t* upload_data_size, void** ptr) { static int dummy; agent_log(DEBUG, "HttpServer: New connection: %s %s %s", version, method, url); if (!strequal(method, "GET")) { return MHD_NO; /* unexpected method */ } if (&dummy != *ptr) { /* The first time only the headers are valid, do not respond in the first round... */ *ptr = &dummy; return MHD_YES; } if (0 != *upload_data_size) { return MHD_NO; /* upload data in a GET!? */ } *ptr = NULL; /* clear context pointer */ return handleRequest(cls, connection); } oidc-agent-4.2.6/src/oidc-agent/httpserver/termHttpserver.h0000644000175000017500000000030414120404223023306 0ustar marcusmarcus#ifndef TERM_HTTPSERVER_H #define TERM_HTTPSERVER_H #include void termHttpServer(const char* state); void stopHttpServer(struct MHD_Daemon** d_ptr); #endif // TERM_HTTPSERVER_H oidc-agent-4.2.6/src/oidc-agent/httpserver/static/0000755000175000017500000000000014120404223021371 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/httpserver/static/no_code.html0000644000175000017500000000024614120404223023667 0ustar marcusmarcus"\ \ \ \ \ No Code Received\ \ \ \ Error: Response did not contain the code param.\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/static/wrong_state.html0000644000175000017500000000022114120404223024606 0ustar marcusmarcus"\ \ \ \ \ Wrong State\ \ \ \ Error: State comparison failed!\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/static/code_exchange_failed.html0000644000175000017500000000041714120404223026341 0ustar marcusmarcus"\ \ \ \ \ Code Exchange Failed\ \ \ \

An error occurred during code exchange.

\ Please try calling oidc-gen with the following command:\

\ oidc-gen --codeExchange='%s'\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/static/code_exchange_failed_with_error.html0000644000175000017500000000044514120404223030606 0ustar marcusmarcus"\ \ \ \ \ Code Exchange Failed\ \ \ \

An error occurred during code exchange.

\

Error: %s

\ Please try calling oidc-gen with the following command:\

\ oidc-gen --codeExchange='%s'\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/static/error.html0000644000175000017500000000017614120404223023414 0ustar marcusmarcus"\ \ \ \ \ Error\ \ \ \

Error: %s

\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/static/success.html0000644000175000017500000000057414120404223023735 0ustar marcusmarcus"\ \ \ \ \ Success\ \ \ \

Success

\ Successfully performed code exchange. \

\ oidc-gen should have received the generated account config.
\ Please check back with oidc-gen. \ In case of an error call oidc-gen with the following command:\

\ oidc-gen --state='%s'\ \ \ \ " oidc-agent-4.2.6/src/oidc-agent/httpserver/running_server.c0000644000175000017500000000225114167074355023340 0ustar marcusmarcus#include "running_server.h" #include "utils/agentLogger.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" static list_t* servers = NULL; void _secFreeRunningServer(struct running_server* s) { secFree(s->state); secFree(s); } int matchRunningServer(char* state, struct running_server* s) { return strequal(s->state, state); } void addServer(struct running_server* running_server) { if (servers == NULL) { servers = list_new(); servers->free = (void(*)(void*)) & _secFreeRunningServer; servers->match = (matchFunction)matchRunningServer; } list_rpush(servers, list_node_new(running_server)); agent_log(DEBUG, "Added Server. Now %d server run", servers->len); } pid_t removeServer(const char* state) { if (servers == NULL) { agent_log(DEBUG, "No servers running"); return -1; } list_node_t* n = findInList(servers, (char*)state); if (n == NULL) { agent_log(DEBUG, "No server found for state %s", state); return -1; } pid_t pid = ((struct running_server*)n->val)->pid; list_remove(servers, n); agent_log(DEBUG, "Removed Server. Now %d server run", servers->len); return pid; } oidc-agent-4.2.6/src/oidc-agent/httpserver/running_server.h0000644000175000017500000000112214120404223023315 0ustar marcusmarcus#ifndef RUNNING_SERVER_H #define RUNNING_SERVER_H #include struct running_server { pid_t pid; char* state; }; void _secFreeRunningServer(struct running_server* s); int matchRunningServer(char* state, struct running_server* s); void addServer(struct running_server* running_server); pid_t removeServer(const char* state); #ifndef secFreeRunningServer #define secFreeRunningServer(ptr) \ do { \ _secFreeRunningServer((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeRunningServer #endif // RUNNING_SERVER_H oidc-agent-4.2.6/src/oidc-agent/httpserver/requestHandler.h0000644000175000017500000000071614167074355023271 0ustar marcusmarcus#ifndef HTTP_REQUEST_HANDLER_H #define HTTP_REQUEST_HANDLER_H #include #ifdef MHD_YES int request_echo(void* cls, struct MHD_Connection* connection, const char* url, #else enum MHD_Result request_echo(void* cls, struct MHD_Connection* connection, const char* url, #endif const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** ptr); #endif // HTTP_REQUEST_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/httpserver/startHttpserver.h0000644000175000017500000000043414120404223023500 0ustar marcusmarcus#ifndef START_HTTPSERVER_H #define START_HTTPSERVER_H #include "defines/settings.h" #include "utils/oidc_error.h" #include "wrapper/list.h" oidc_error_t fireHttpServer(list_t* redirect_uris, size_t size, char** state_ptr); #endif // START_HTTPSERVER_H oidc-agent-4.2.6/src/oidc-agent/daemonize.c0000644000175000017500000000247014167074355020042 0ustar marcusmarcus#include "daemonize.h" #include #include #include #include #include #include #include #include "utils/agentLogger.h" void sig_handler(int signo) { switch (signo) { case SIGSEGV: agent_log(EMERGENCY, "Caught Signal SIGSEGV"); break; default: agent_log(EMERGENCY, "Caught Signal %d", signo); } exit(signo); } pid_t daemonize() { fflush(stdout); // flush before forking, otherwise the buffered content is // printed multiple times. (Only relevant when stdout is // redirected, tty is line-buffered.) fflush(stderr); pid_t pid; if ((pid = fork()) == -1) { agent_log(ALERT, "fork %m"); exit(EXIT_FAILURE); } else if (pid > 0) { exit(EXIT_SUCCESS); } if (setsid() < 0) { exit(EXIT_FAILURE); } signal(SIGHUP, SIG_IGN); if ((pid = fork()) == -1) { agent_log(ALERT, "fork %m"); exit(EXIT_FAILURE); } else if (pid > 0) { return pid; } if (chdir("/") != 0) { agent_log(ERROR, "chdir %m"); } umask(0); close(STDIN_FILENO); close(STDOUT_FILENO); close(STDERR_FILENO); assert(open("/dev/null", O_RDONLY) == STDIN_FILENO); assert(open("/dev/null", O_RDWR) == STDOUT_FILENO); assert(open("/dev/null", O_RDWR) == STDERR_FILENO); return pid; } oidc-agent-4.2.6/src/oidc-agent/daemonize.h0000644000175000017500000000020014167074355020034 0ustar marcusmarcus#ifndef AGENT_DAEMONIZING_H #define AGENT_DAEMONIZING_H #include pid_t daemonize(); #endif // AGENT_DAEMONIZING_H oidc-agent-4.2.6/src/oidc-agent/lock_state.c0000644000175000017500000000323414167074355020216 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "lock_state.h" #include "agent_state.h" #include "utils/agentLogger.h" #include "utils/crypt/crypt.h" #include "utils/crypt/dbCryptUtils.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/sleeper.h" #include "utils/string/stringUtils.h" oidc_error_t unlock(const char* password) { static unsigned char fail_count = 0; agent_log(DEBUG, "Unlocking agent"); if (agent_state.lock_state.locked == 0) { agent_log(DEBUG, "Agent not locked"); oidc_errno = OIDC_ENOTLOCKED; return oidc_errno; } char* hash = s256(password); if (!strequal(agent_state.lock_state.hash, hash)) { secFree(hash); oidc_errno = OIDC_EPASS; return oidc_errno; } secFree(hash); if (lockDecrypt(password) == OIDC_SUCCESS) { agent_state.lock_state.locked = 0; fail_count = 0; secFree(agent_state.lock_state.hash); agent_log(DEBUG, "Agent unlocked"); return OIDC_SUCCESS; } /* delay in 0.1s increments up to 10s */ if (fail_count < 100) { fail_count++; } unsigned int delay = 100 * fail_count; agent_log(DEBUG, "unlock failed, delaying %0.1lf seconds", (double)delay / 1000); msleep(delay); return oidc_errno; } oidc_error_t lock(const char* password) { agent_log(DEBUG, "Locking agent"); if (agent_state.lock_state.locked) { agent_log(DEBUG, "Agent already locked"); oidc_errno = OIDC_ELOCKED; return oidc_errno; } agent_state.lock_state.hash = s256(password); if (lockEncrypt(password) != OIDC_SUCCESS) { return oidc_errno; } agent_state.lock_state.locked = 1; agent_log(DEBUG, "Agent locked"); return OIDC_SUCCESS; } oidc-agent-4.2.6/src/oidc-agent/oidc/0000755000175000017500000000000014167074355016636 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidc/device_code.c0000644000175000017500000000512714167074355021240 0ustar marcusmarcus#include "device_code.h" #include "defines/oidc_values.h" #include "utils/agentLogger.h" #include "utils/json.h" #include "utils/string/stringUtils.h" struct oidc_device_code* getDeviceCodeFromJSON(const char* json) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return NULL; } INIT_KEY_VALUE(OIDC_KEY_DEVICECODE, OIDC_KEY_USERCODE, OIDC_KEY_VERIFICATIONURI, OIDC_KEY_VERIFICATIONURI_COMPLETE, OIDC_KEY_EXPIRESIN, OIDC_KEY_INTERVAL); cJSON* cjson = stringToJson(json); if (CALL_GETJSONVALUES_FROM_CJSON(cjson) < 0) { agent_log(ERROR, "Error while parsing json\n"); secFreeJson(cjson); SEC_FREE_KEY_VALUES(); return NULL; } KEY_VALUE_VARS(device_code, usercode, verification_uri, verification_uri_complete, expires_in, interval); size_t expires_in = strToInt(_expires_in); size_t interval = strValid(_interval) ? strToInt(_interval) : 5; // Default of strToInt would be 0 secFree(_expires_in); secFree(_interval); if (_verification_uri == NULL) { _verification_uri = getJSONValue(cjson, GOOGLE_KEY_VERIFICATIONURI); } // needed for the google device flow that is not conforming to the spec // draft if (_verification_uri_complete == NULL) { _verification_uri_complete = getJSONValue(cjson, GOOGLE_KEY_VERIFICATIONURI_COMPLETE); } // needed for the google device flow that is not conforming to the spec // draft secFreeJson(cjson); struct oidc_device_code* code = oidc_device_new(_device_code, _usercode, _verification_uri, _verification_uri_complete, expires_in, interval); return code; } char* deviceCodeToJSON(struct oidc_device_code c) { cJSON* cjson = generateJSONObject( OIDC_KEY_DEVICECODE, cJSON_String, strValid(oidc_device_getDeviceCode(c)) ? oidc_device_getDeviceCode(c) : "", OIDC_KEY_USERCODE, cJSON_String, strValid(oidc_device_getUserCode(c)) ? oidc_device_getUserCode(c) : "", OIDC_KEY_VERIFICATIONURI, cJSON_String, strValid(oidc_device_getVerificationUri(c)) ? oidc_device_getVerificationUri(c) : "", OIDC_KEY_VERIFICATIONURI_COMPLETE, cJSON_String, strValid(oidc_device_getVerificationUriComplete(c)) ? oidc_device_getVerificationUriComplete(c) : "", OIDC_KEY_EXPIRESIN, cJSON_Number, oidc_device_getExpiresIn(c), OIDC_KEY_INTERVAL, cJSON_Number, oidc_device_getInterval(c), NULL); char* json = jsonToString(cjson); secFreeJson(cjson); return json; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/0000755000175000017500000000000014167074355017770 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidc/flows/access_token_handler.h0000644000175000017500000000321514167074355024300 0ustar marcusmarcus#ifndef FLOW_HANDLER_H #define FLOW_HANDLER_H #include #include "account/account.h" #include "ipc/pipe.h" #include "utils/oidc_error.h" #include "wrapper/list.h" char* getAccessTokenUsingRefreshFlow(struct oidc_account* account, time_t min_valid_period, const char* scope, const char* audience, struct ipcPipe pipes); char* getIdToken(struct oidc_account* p, const char* scope, struct ipcPipe pipes); oidc_error_t getAccessTokenUsingPasswordFlow(struct oidc_account* account, time_t min_valid_period, const char* scope, struct ipcPipe pipes); oidc_error_t getAccessTokenUsingAuthCodeFlow(struct oidc_account* account, const char* code, const char* used_redirect_uri, char* code_verifier, time_t min_valid_period, struct ipcPipe pipes); oidc_error_t getAccessTokenUsingDeviceFlow(struct oidc_account* account, const char* device_code, time_t min_valid_period, struct ipcPipe pipes); list_t* parseFlow(const char* flow); #endif // FLOW_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/registration.h0000644000175000017500000000036514120404223022633 0ustar marcusmarcus#ifndef OIDC_REGISTRATION_H #define OIDC_REGISTRATION_H #include "account/account.h" #include "wrapper/list.h" char* dynamicRegistration(struct oidc_account* account, list_t* flows, const char* access_token); #endif oidc-agent-4.2.6/src/oidc-agent/oidc/flows/oidc.h0000644000175000017500000000136514167074355021064 0ustar marcusmarcus#ifndef OIDC_H #define OIDC_H #include "account/account.h" #include "ipc/pipe.h" #define TOKENPARSEMODE_SAVE_AT 0x01 #define TOKENPARSEMODE_SAVE_AT_IF(X) ((X) ? 0x01 : 0) #define TOKENPARSEMODE_RETURN_AT 0x02 #define TOKENPARSEMODE_RETURN_ID 0x04 char* generatePostData(char* k1, char* v1, ...); char* generatePostDataFromList(list_t* list); char* parseTokenResponse(unsigned char mode, const char* res, struct oidc_account* a, struct ipcPipe pipes, unsigned char refreshFlow); char* parseTokenResponseCallbacks( unsigned char mode, const char* res, struct oidc_account* a, void (*errorHandling)(const char*, const char*), struct ipcPipe pipes, unsigned char refreshFlow); #endif // OIDC_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/code.c0000644000175000017500000001164314167074355021053 0ustar marcusmarcus#include "code.h" #include "defines/agent_values.h" #include "defines/oidc_values.h" #include "oidc-agent/http/http_ipc.h" #include "oidc-agent/httpserver/startHttpserver.h" #include "oidc.h" #include "utils/agentLogger.h" #include "utils/crypt/crypt.h" #include "utils/listUtils.h" #include "utils/portUtils.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" oidc_error_t codeExchange(struct oidc_account* account, const char* code, const char* used_redirect_uri, char* code_verifier, struct ipcPipe pipes) { agent_log(DEBUG, "Doing Authorization Code Flow\n"); list_t* postData = createList(LIST_CREATE_DONT_COPY_VALUES, // OIDC_KEY_CLIENTID, account_getClientId(account), // OIDC_KEY_CLIENTSECRET, account_getClientSecret(account), OIDC_KEY_GRANTTYPE, OIDC_GRANTTYPE_AUTHCODE, OIDC_KEY_CODE, code, OIDC_KEY_REDIRECTURI, used_redirect_uri, OIDC_KEY_RESPONSETYPE, OIDC_RESPONSETYPE_TOKEN, NULL); if (code_verifier) { list_rpush(postData, list_node_new(OIDC_KEY_CODEVERIFIER)); list_rpush(postData, list_node_new(code_verifier)); } char* data = generatePostDataFromList(postData); list_destroy(postData); if (data == NULL) { return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth( account_getTokenEndpoint(account), data, account_getCertPath(account), account_getClientId(account), account_getClientSecret(account)); secFree(data); if (res == NULL) { return oidc_errno; } char* access_token = parseTokenResponse(TOKENPARSEMODE_RETURN_AT | TOKENPARSEMODE_SAVE_AT, res, account, pipes, 0); secFree(res); return access_token == NULL ? oidc_errno : OIDC_SUCCESS; } char* createCodeChallenge(const char* code_verifier, const char* code_challenge_method) { char* code_challenge = NULL; if (strValid(code_challenge_method)) { if (strequal(code_challenge_method, CODE_CHALLENGE_METHOD_PLAIN)) { code_challenge = oidc_strcopy(code_verifier); } else if (strequal(code_challenge_method, CODE_CHALLENGE_METHOD_S256)) { code_challenge = s256(code_verifier); } } return code_challenge; } char* buildCodeFlowUri(const struct oidc_account* account, char** state_ptr, char** code_verifier_ptr) { const char* auth_endpoint = account_getAuthorizationEndpoint(account); list_t* redirect_uris = account_getRedirectUris(account); size_t uri_count = account_getRedirectUrisCount(account); if (redirect_uris == NULL || uri_count <= 0) { oidc_errno = OIDC_ENOREURI; return NULL; } char* redirect = account_getNoScheme(account) ? NULL : findCustomSchemeUri(redirect_uris); if (!account_getNoWebServer(account)) { int port = fireHttpServer(account_getRedirectUris(account), account_getRedirectUrisCount(account), state_ptr); if (port <= 0) { if (redirect == NULL) { return NULL; } } redirect = findRedirectUriByPort(account, port); } else { // no web server if (redirect == NULL) { // no custom scheme uri found redirect = list_at(redirect_uris, 0)->val; if (strstarts(redirect, AGENT_CUSTOM_SCHEME) && account_getNoScheme(account)) { char* tmp = list_at(redirect_uris, 1)->val; if (tmp != NULL) { redirect = tmp; } } } char* tmp = oidc_sprintf("%hhu:%s", strEnds(redirect, "/"), *state_ptr); secFree(*state_ptr); *state_ptr = tmp; } list_t* postData = createList( LIST_CREATE_DONT_COPY_VALUES, OIDC_KEY_RESPONSETYPE, OIDC_RESPONSETYPE_CODE, OIDC_KEY_CLIENTID, account_getClientId(account), OIDC_KEY_REDIRECTURI, redirect, OIDC_KEY_SCOPE, account_getScope(account), GOOGLE_KEY_ACCESSTYPE, GOOGLE_ACCESSTYPE_OFFLINE, OIDC_KEY_PROMPT, OIDC_PROMPT_CONSENT, OIDC_KEY_STATE, *state_ptr, NULL); char* code_challenge_method = account_getCodeChallengeMethod(account); char* code_challenge = createCodeChallenge(*code_verifier_ptr, code_challenge_method); if (code_challenge) { list_rpush(postData, list_node_new(OIDC_KEY_CODECHALLENGE_METHOD)); list_rpush(postData, list_node_new(code_challenge_method)); list_rpush(postData, list_node_new(OIDC_KEY_CODECHALLENGE)); list_rpush(postData, list_node_new(code_challenge)); } else { secFree(*code_verifier_ptr); code_verifier_ptr = NULL; } if (strValid(account_getAudience(account))) { list_rpush(postData, list_node_new(OIDC_KEY_AUDIENCE)); list_rpush(postData, list_node_new(account_getAudience(account))); } char* uri_parameters = generatePostDataFromList(postData); secFree(code_challenge); list_destroy(postData); char* uri = oidc_sprintf("%s?%s", auth_endpoint, uri_parameters); secFree(uri_parameters); return uri; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/revoke.c0000644000175000017500000000432414167074355021432 0ustar marcusmarcus#include "revoke.h" #include "account/account.h" #include "defines/oidc_values.h" #include "oidc-agent/http/http_ipc.h" #include "oidc.h" #include "utils/agentLogger.h" #include "utils/parseJson.h" #include "utils/string/stringUtils.h" oidc_error_t _revokeToken(struct oidc_account* account, unsigned char withClientId) { agent_log(DEBUG, "Performing Token revocation flow"); if (!strValid(account_getRevocationEndpoint(account))) { oidc_errno = OIDC_ENOSUPREV; agent_log(NOTICE, "%s", oidc_serror()); return oidc_errno; } char* refresh_token = account_getRefreshToken(account); char* data = generatePostData( OIDC_KEY_TOKENTYPE_HINT, OIDC_TOKENTYPE_REFRESH, OIDC_KEY_TOKEN, refresh_token, withClientId ? OIDC_KEY_CLIENTID : NULL, withClientId ? account_getClientId(account) : NULL, NULL); if (data == NULL) { return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth(account_getRevocationEndpoint(account), data, account_getCertPath(account), account_getClientId(account), account_getClientSecret(account)); secFree(data); if (res == NULL) { if (oidc_errno == OIDC_EHTTP0) { account_setRefreshToken(account, NULL); oidc_errno = OIDC_SUCCESS; agent_log( INFO, "Ignored http0 error - empty response allowed for token revocation"); } return oidc_errno; } char* error = parseForError(res); if (error) { oidc_errno = OIDC_EOIDC; oidc_seterror(error); secFree(error); return oidc_errno; } account_setRefreshToken(account, NULL); oidc_errno = OIDC_SUCCESS; return oidc_errno; } oidc_error_t revokeToken(struct oidc_account* account) { oidc_error_t err = _revokeToken(account, 0); if (err == OIDC_SUCCESS) { return err; } struct oidc_error_state* errorState = saveErrorState(); err = _revokeToken(account, 1); if (err == OIDC_SUCCESS) { secFreeErrorState(errorState); return err; } restoreAndFreeErrorState(errorState); return oidc_errno; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/registration.c0000644000175000017500000000772414167074355022660 0ustar marcusmarcus#include "registration.h" #include "account/account.h" #include "account/issuer_helper.h" #include "defines/agent_values.h" #include "defines/oidc_values.h" #include "defines/settings.h" #include "oidc-agent/http/http_ipc.h" #include "utils/agentLogger.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/portUtils.h" #include "utils/string/stringUtils.h" char* generateRedirectUris() { char* redirect_uri0 = portToUri(HTTP_DEFAULT_PORT); char* redirect_uri1 = portToUri(getRandomPort()); char* redirect_uri2 = portToUri(HTTP_FALLBACK_PORT); char* redirect_uri3 = oidc_strcat(AGENT_CUSTOM_SCHEME, "redirect"); cJSON* json = generateJSONArray(redirect_uri0, redirect_uri1, redirect_uri2, redirect_uri3, NULL); secFree(redirect_uri0); secFree(redirect_uri1); secFree(redirect_uri2); secFree(redirect_uri3); char* uris = jsonToStringUnformatted(json); secFreeJson(json); return uris; } char* getRegistrationPostData(const struct oidc_account* account, list_t* flows, const char* application_type) { char* client_name = account_getClientName(account); char* response_types = getUsableResponseTypes(account, flows); char* grant_types = getUsableGrantTypes(account, flows); char* redirect_uris_json = account_getRedirectUris(account) ? listToJSONArrayString(account_getRedirectUris(account)) : generateRedirectUris(); cJSON* json = generateJSONObject( OIDC_KEY_APPLICATIONTYPE, cJSON_String, application_type, OIDC_KEY_CLIENTNAME, cJSON_String, client_name, OIDC_KEY_RESPONSETYPES, cJSON_Array, response_types, OIDC_KEY_GRANTTYPES, cJSON_Array, grant_types, OIDC_KEY_SCOPE, cJSON_String, account_getScope(account), OIDC_KEY_REDIRECTURIS, cJSON_Array, redirect_uris_json, NULL); secFree(response_types); secFree(grant_types); secFree(redirect_uris_json); char* json_str = jsonToStringUnformatted(json); secFreeJson(json); return json_str; } char* _dynamicRegistration(struct oidc_account* account, list_t* flows, const char* access_token, const char* application_type) { agent_log(DEBUG, "Performing dynamic Registration flow"); if (!strValid(account_getRegistrationEndpoint(account))) { oidc_errno = OIDC_ENOSUPREG; return NULL; } char* body = getRegistrationPostData(account, flows, application_type); if (body == NULL) { return NULL; } agent_log(DEBUG, "Data to send: %s", body); struct curl_slist* headers = curl_slist_append(NULL, HTTP_HEADER_CONTENTTYPE_JSON); if (strValid(access_token)) { char* auth_header = oidc_sprintf(HTTP_HEADER_AUTHORIZATION_BEARER_FMT, access_token); headers = curl_slist_append(headers, auth_header); secFree(auth_header); } char* res = httpsPOST(account_getRegistrationEndpoint(account), body, headers, account_getCertPath(account), account_getClientId(account), account_getClientSecret(account)); curl_slist_free_all(headers); secFree(body); if (res == NULL) { return NULL; } return res; } char* dynamicRegistration(struct oidc_account* account, list_t* flows, const char* access_token) { char* res = _dynamicRegistration(account, flows, access_token, OIDC_APPLICATIONTYPES_WEB); if (res == NULL) { return NULL; } if (res == NULL || !isJSONObject(res)) { return res; } char* error = getJSONValueFromString(res, OIDC_KEY_ERROR); char* error_description = getJSONValueFromString(res, OIDC_KEY_ERROR_DESCRIPTION); if (error == NULL) { return res; } if (strequal(error, "invalid redirect_uri") && error_description && strequal(error_description, "Custom redirect_uri not allowed for web client")) { return _dynamicRegistration(account, flows, access_token, OIDC_APPLICATIONTYPES_NATIVE); } return res; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/refresh.h0000644000175000017500000000044714120404223021560 0ustar marcusmarcus#ifndef OIDC_REFRESH_H #define OIDC_REFRESH_H #include "account/account.h" #include "ipc/pipe.h" char* refreshFlow(unsigned char return_mode, struct oidc_account* p, const char* scope, const char* audience, struct ipcPipe pipes); #endif // OIDC_REFRESH_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/deleteClient.h0000644000175000017500000000045414120404223022521 0ustar marcusmarcus#ifndef OIDC_DELETE_CLIENT_H #define OIDC_DELETE_CLIENT_H #include "utils/oidc_error.h" oidc_error_t deleteClient(const char* configuration_endpoint, const char* registration_access_token, const char* cert_path); #endif // OIDC_DELETE_CLIENT_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/code.h0000644000175000017500000000073314120404223021032 0ustar marcusmarcus#ifndef OIDC_CODE_H #define OIDC_CODE_H #include "account/account.h" #include "ipc/pipe.h" #include "utils/oidc_error.h" char* buildCodeFlowUri(const struct oidc_account* account, char** state_ptr, char** code_verifier_ptr); oidc_error_t codeExchange(struct oidc_account* account, const char* code, const char* used_redirect_uri, char* code_verifier, struct ipcPipe pipes); #endif // OIDC_CODE_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/device.h0000644000175000017500000000061514167074355021402 0ustar marcusmarcus#ifndef OIDC_DEVICE_H #define OIDC_DEVICE_H #include "account/account.h" #include "ipc/pipe.h" #include "oidc-agent/oidc/device_code.h" struct oidc_device_code* initDeviceFlow(struct oidc_account* account); oidc_error_t lookUpDeviceCode(struct oidc_account* account, const char* device_code, struct ipcPipe pipes); #endif // OIDC_DEVICE_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/openid_config.h0000644000175000017500000000037514120404223022725 0ustar marcusmarcus#ifndef OPENID_CONFIG_H #define OPENID_CONFIG_H #include "account/account.h" #include "utils/oidc_error.h" oidc_error_t getIssuerConfig(struct oidc_account* account); char* getScopesSupportedFor(const char* issuer_url, const char* cert_path); #endif oidc-agent-4.2.6/src/oidc-agent/oidc/flows/device.c0000644000175000017500000000740214167074355021376 0ustar marcusmarcus#include "device.h" #include "defines/oidc_values.h" #include "oidc-agent/http/http_ipc.h" #include "oidc-agent/oidc/parse_oidp.h" #include "oidc-agent/oidcd/deviceCodeEntry.h" #include "oidc.h" #include "utils/agentLogger.h" #include "utils/db/deviceCode_db.h" #include "utils/errorUtils.h" #include "utils/string/stringUtils.h" char* generateDeviceCodePostData(const struct oidc_account* a) { return generatePostData(OIDC_KEY_CLIENTID, account_getClientId(a), OIDC_KEY_SCOPE, account_getScope(a), NULL); } char* generateDeviceCodeLookupPostData(const struct oidc_account* a, const char* device_code) { char* tmp_devicecode = oidc_strcopy(device_code); list_t* postDataList = list_new(); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); // list_rpush(postDataList, list_node_new(account_getClientId(a))); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTSECRET)); // list_rpush(postDataList, list_node_new(account_getClientSecret(a))); list_rpush(postDataList, list_node_new(OIDC_KEY_GRANTTYPE)); list_rpush(postDataList, list_node_new(OIDC_GRANTTYPE_DEVICE)); list_rpush(postDataList, list_node_new(OIDC_KEY_DEVICECODE)); list_rpush(postDataList, list_node_new(tmp_devicecode)); if (strValid(account_getAudience(a))) { list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); list_rpush(postDataList, list_node_new(account_getAudience(a))); } char* str = generatePostDataFromList(postDataList); list_destroy(postDataList); secFree(tmp_devicecode); return str; } struct oidc_device_code* initDeviceFlow(struct oidc_account* account) { agent_log(DEBUG, "Init device flow"); const char* device_authorization_endpoint = account_getDeviceAuthorizationEndpoint(account); if (!strValid(device_authorization_endpoint)) { oidc_errno = OIDC_ENODEVICE; return NULL; } char* data = generateDeviceCodePostData(account); if (data == NULL) { return NULL; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth( device_authorization_endpoint, data, account_getCertPath(account), account_getClientId(account), account_getClientSecret(account)); secFree(data); if (res == NULL) { return NULL; } struct oidc_device_code* deviceCode = parseDeviceCode(res); secFree(res); if (deviceCode != NULL) { deviceCodeDB_addValue( createDeviceCodeEntry(deviceCode->device_code, account)); } return deviceCode; } void handleDeviceLookupError(const char* error, const char* error_description) { if (strequal(error, OIDC_SLOW_DOWN) || strequal(error, OIDC_AUTHORIZATION_PENDING)) { oidc_seterror((char*)error); } else { if (error_description) { char* err = combineError(error, error_description); oidc_seterror(err); secFree(err); } else { oidc_seterror((char*)error); } } oidc_errno = OIDC_EOIDC; } oidc_error_t lookUpDeviceCode(struct oidc_account* account, const char* device_code, struct ipcPipe pipes) { agent_log(DEBUG, "Doing Device Code Lookup\n"); char* data = generateDeviceCodeLookupPostData(account, device_code); if (data == NULL) { return oidc_errno; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth( account_getTokenEndpoint(account), data, account_getCertPath(account), account_getClientId(account), account_getClientSecret(account)); secFree(data); if (res == NULL) { return oidc_errno; } char* access_token = parseTokenResponseCallbacks( TOKENPARSEMODE_RETURN_AT | TOKENPARSEMODE_SAVE_AT, res, account, &handleDeviceLookupError, pipes, 0); secFree(res); return access_token == NULL ? oidc_errno : OIDC_SUCCESS; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/refresh.c0000644000175000017500000000624014167074355021574 0ustar marcusmarcus#include "refresh.h" #include #include "account/account.h" #include "defines/oidc_values.h" #include "oidc-agent/http/http_ipc.h" #include "oidc.h" #include "utils/agentLogger.h" #include "utils/string/stringUtils.h" char* generateRefreshPostData(const struct oidc_account* a, const char* scope, const char* audience) { char* refresh_token = account_getRefreshToken(a); char* scope_tmp = oidc_strcopy( strValid(scope) ? scope : account_getScope( a)); // if scopes are explicitly set use these, if // not we use the same as for the used refresh // token. Usually this parameter can be // omitted. For unity we have to include this. char* aud_tmp = oidc_strcopy(strValid(audience) ? audience : NULL); // account_getAudience(a)); // if audience is // explicitly set use it, if not we use the // default audience for this account. This is // only needed if including audience changes // not only the audience of the new AT, but // also of the RT and therefore of future ATs. list_t* postDataList = list_new(); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); // list_rpush(postDataList, list_node_new(account_getClientId(a))); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTSECRET)); // list_rpush(postDataList, list_node_new(account_getClientSecret(a))); list_rpush(postDataList, list_node_new(OIDC_KEY_GRANTTYPE)); list_rpush(postDataList, list_node_new(OIDC_GRANTTYPE_REFRESH)); list_rpush(postDataList, list_node_new(OIDC_KEY_REFRESHTOKEN)); list_rpush(postDataList, list_node_new(refresh_token)); if (strValid(scope_tmp)) { list_rpush(postDataList, list_node_new(OIDC_KEY_SCOPE)); list_rpush(postDataList, list_node_new(scope_tmp)); } if (strValid(aud_tmp)) { list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); list_rpush(postDataList, list_node_new(aud_tmp)); } char* str = generatePostDataFromList(postDataList); list_destroy(postDataList); secFree(aud_tmp); secFree(scope_tmp); return str; } char* refreshFlow(unsigned char return_mode, struct oidc_account* p, const char* scope, const char* audience, struct ipcPipe pipes) { agent_log(DEBUG, "Doing RefreshFlow\n"); char* data = generateRefreshPostData(p, scope, audience); if (data == NULL) { return NULL; ; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth( account_getTokenEndpoint(p), data, account_getCertPath(p), account_getClientId(p), account_getClientSecret(p)); secFree(data); if (NULL == res) { return NULL; ; } char* access_token = parseTokenResponse( return_mode | TOKENPARSEMODE_SAVE_AT_IF(!strValid(scope) && !strValid(audience)), res, p, pipes, 1); secFree(res); return access_token; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/deleteClient.c0000644000175000017500000000127614120404223022517 0ustar marcusmarcus#include "deleteClient.h" #include "oidc-agent/http/http_ipc.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/parseJson.h" oidc_error_t deleteClient(const char* configuration_endpoint, const char* registration_access_token, const char* cert_path) { char* res = httpsDELETE(configuration_endpoint, NULL, cert_path, registration_access_token); if (res == NULL) { return oidc_errno; } char* error = parseForError(res); if (error != NULL) { oidc_seterror(error); oidc_errno = OIDC_EOIDC; secFree(error); return oidc_errno; } secFree(error); return OIDC_SUCCESS; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/password.h0000644000175000017500000000042514120404223021760 0ustar marcusmarcus#ifndef OIDC_PASSWORD_H #define OIDC_PASSWORD_H #include "account/account.h" #include "ipc/pipe.h" #include "utils/oidc_error.h" oidc_error_t passwordFlow(struct oidc_account* p, struct ipcPipe pipes, const char* scope); #endif // OIDC_PASSWORD_H oidc-agent-4.2.6/src/oidc-agent/oidc/flows/access_token_handler.c0000644000175000017500000001272714167074355024303 0ustar marcusmarcus#include "access_token_handler.h" #include "code.h" #include "defines/agent_values.h" #include "device.h" #include "oidc-agent/oidc/flows/oidc.h" #include "password.h" #include "refresh.h" #include "utils/agentLogger.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/string/stringUtils.h" char* tryRefreshFlow(struct oidc_account* p, const char* scope, const char* audience, struct ipcPipe pipes) { agent_log(DEBUG, "Trying Refresh Flow"); if (!account_refreshTokenIsValid(p)) { agent_log(ERROR, "No refresh token found"); oidc_errno = OIDC_ENOREFRSH; return NULL; } return refreshFlow(TOKENPARSEMODE_RETURN_AT, p, scope, audience, pipes); } char* getIdToken(struct oidc_account* p, const char* scope, struct ipcPipe pipes) { if (!account_refreshTokenIsValid(p)) { agent_log(ERROR, "No refresh token found"); oidc_errno = OIDC_ENOREFRSH; return NULL; } return refreshFlow(TOKENPARSEMODE_RETURN_ID, p, scope, NULL, pipes); } /** @fn oidc_error_t tryPasswordFlow(struct oidc_account* p) * @brief tries to issue an access token by using the password flow. The user * might be prompted for his username and password * @param p a pointer to the account for whom an access token should be issued * @return 0 on success; 1 otherwise */ oidc_error_t tryPasswordFlow(struct oidc_account* p, struct ipcPipe pipes, const char* scope) { agent_log(DEBUG, "Trying Password Flow"); if (!strValid(account_getUsername(p)) || !strValid(account_getPassword(p))) { oidc_errno = OIDC_ECRED; agent_log(DEBUG, "No credentials found"); return oidc_errno; } return passwordFlow(p, pipes, scope); } /** @fn int tokenIsValidforSeconds(struct oidc_account p, time_t * min_valid_period) * @brief checks if the access token for an account is at least valid for the * given period of time * @param p the account whose access token should be checked * @param min_valid_period the period of time the access token should be valid * (at least) * @return 1 if the access_token is valid for the given time; 0 if not. */ int tokenIsValidForSeconds(const struct oidc_account* p, time_t min_valid_period) { time_t now = time(NULL); time_t expires_at = account_getTokenExpiresAt(p); return expires_at - now > 0 && expires_at - now > min_valid_period; } char* getAccessTokenUsingRefreshFlow(struct oidc_account* account, time_t min_valid_period, const char* scope, const char* audience, struct ipcPipe pipes) { if (scope == NULL && audience == NULL && min_valid_period != FORCE_NEW_TOKEN && strValid(account_getAccessToken(account)) && tokenIsValidForSeconds(account, min_valid_period)) { return account_getAccessToken(account); } agent_log(DEBUG, "No access token found that is valid long enough"); return tryRefreshFlow(account, scope, audience, pipes); } oidc_error_t getAccessTokenUsingPasswordFlow(struct oidc_account* account, time_t min_valid_period, const char* scope, struct ipcPipe pipes) { if (scope == NULL && min_valid_period != FORCE_NEW_TOKEN && strValid(account_getAccessToken(account)) && tokenIsValidForSeconds(account, min_valid_period)) { return OIDC_SUCCESS; } oidc_errno = tryPasswordFlow(account, pipes, scope); return oidc_errno; } oidc_error_t getAccessTokenUsingAuthCodeFlow(struct oidc_account* account, const char* code, const char* used_redirect_uri, char* code_verifier, time_t min_valid_period, struct ipcPipe pipes) { if (min_valid_period != FORCE_NEW_TOKEN && strValid(account_getAccessToken(account)) && tokenIsValidForSeconds(account, min_valid_period)) { return OIDC_SUCCESS; } oidc_errno = codeExchange(account, code, used_redirect_uri, code_verifier, pipes); return oidc_errno; } oidc_error_t getAccessTokenUsingDeviceFlow(struct oidc_account* account, const char* device_code, time_t min_valid_period, struct ipcPipe pipes) { if (min_valid_period != FORCE_NEW_TOKEN && strValid(account_getAccessToken(account)) && tokenIsValidForSeconds(account, min_valid_period)) { return OIDC_SUCCESS; } oidc_errno = lookUpDeviceCode(account, device_code, pipes); return oidc_errno; } list_t* parseFlow(const char* flow) { list_t* flows = list_new(); flows->match = (matchFunction)strequal; if (flow == NULL) { // Using default order list_rpush(flows, list_node_new(FLOW_VALUE_REFRESH)); list_rpush(flows, list_node_new(FLOW_VALUE_PASSWORD)); list_rpush(flows, list_node_new(FLOW_VALUE_CODE)); list_rpush(flows, list_node_new(FLOW_VALUE_DEVICE)); return flows; } flows->free = (void(*)(void*)) & _secFree; if (flow[0] != '[') { list_rpush(flows, list_node_new(oidc_sprintf("%s", flow))); return flows; } secFreeList(flows); return JSONArrayStringToList(flow); } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/password.c0000644000175000017500000000500714167074355022000 0ustar marcusmarcus#include "password.h" #include #include "account/account.h" #include "defines/oidc_values.h" #include "oidc-agent/http/http_ipc.h" #include "oidc.h" #include "utils/agentLogger.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" char* generatePasswordPostData(const struct oidc_account* a, const char* scope) { list_t* postDataList = list_new(); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTID)); // list_rpush(postDataList, list_node_new(account_getClientId(a))); // list_rpush(postDataList, list_node_new(OIDC_KEY_CLIENTSECRET)); // list_rpush(postDataList, list_node_new(account_getClientSecret(a))); list_rpush(postDataList, list_node_new(OIDC_KEY_GRANTTYPE)); list_rpush(postDataList, list_node_new(OIDC_GRANTTYPE_PASSWORD)); list_rpush(postDataList, list_node_new(OIDC_KEY_USERNAME)); list_rpush(postDataList, list_node_new(account_getUsername(a))); list_rpush(postDataList, list_node_new(OIDC_KEY_PASSWORD)); list_rpush(postDataList, list_node_new(account_getPassword(a))); if (scope || strValid(account_getScope(a))) { list_rpush(postDataList, list_node_new(OIDC_KEY_SCOPE)); list_rpush(postDataList, list_node_new((char*)scope ?: account_getScope(a))); } if (strValid(account_getAudience(a))) { list_rpush(postDataList, list_node_new(OIDC_KEY_AUDIENCE)); list_rpush(postDataList, list_node_new(account_getAudience(a))); } char* str = generatePostDataFromList(postDataList); list_destroy(postDataList); return str; } /** @fn oidc_error_t passwordFlow(struct oidc_account* p) * @brief issues an access token using the password flow * @param p a pointer to the account for whom an access token should be issued * @return 0 on success; 1 otherwise */ oidc_error_t passwordFlow(struct oidc_account* p, struct ipcPipe pipes, const char* scope) { agent_log(DEBUG, "Doing PasswordFlow\n"); char* data = generatePasswordPostData(p, scope); if (data == NULL) { return oidc_errno; ; } agent_log(DEBUG, "Data to send: %s", data); char* res = sendPostDataWithBasicAuth( account_getTokenEndpoint(p), data, account_getCertPath(p), account_getClientId(p), account_getClientSecret(p)); secFree(data); if (NULL == res) { return oidc_errno; ; } char* access_token = parseTokenResponse( TOKENPARSEMODE_RETURN_AT | TOKENPARSEMODE_SAVE_AT, res, p, pipes, 0); secFree(res); return access_token == NULL ? oidc_errno : OIDC_SUCCESS; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/openid_config.c0000644000175000017500000000326214167074355022742 0ustar marcusmarcus#include "openid_config.h" #include "account/account.h" #include "defines/settings.h" #include "oidc-agent/http/http_ipc.h" #include "oidc-agent/oidc/parse_oidp.h" #include "utils/agentLogger.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" /** @fn oidc_error_t getIssuerConfig(struct oidc_account* account) * @brief retrieves issuer config from the configuration_endpoint * @note the issuer url has to be set prior * @param account the account struct, will be updated with the retrieved * config * @return a oidc_error status code */ oidc_error_t getIssuerConfig(struct oidc_account* account) { char* configuration_endpoint = oidc_strcat(account_getIssuerUrl(account), CONF_ENDPOINT_SUFFIX); issuer_setConfigurationEndpoint(account_getIssuer(account), configuration_endpoint); agent_log(DEBUG, "Configuration endpoint is: %s", account_getConfigEndpoint(account)); char* res = httpsGET(account_getConfigEndpoint(account), NULL, account_getCertPath(account)); if (NULL == res) { return oidc_errno; } return parseOpenidConfiguration(res, account); } char* getScopesSupportedFor(const char* issuer_url, const char* cert_path) { struct oidc_account account = {}; account_setIssuerUrl(&account, oidc_strcopy(issuer_url)); if (strValid(cert_path)) { account_setCertPath(&account, oidc_strcopy(cert_path)); } else { account_setOSDefaultCertPath(&account); } oidc_error_t err = getIssuerConfig(&account); if (err != OIDC_SUCCESS) { return NULL; } char* scopes = oidc_strcopy(account_getScopesSupported(&account)); secFreeAccountContent(&account); return scopes; } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/oidc.c0000644000175000017500000001147114167074355021056 0ustar marcusmarcus#include "oidc.h" #include #include #include #include "account/account.h" #include "defines/oidc_values.h" #include "oidc-agent/oidcd/internal_request_handler.h" #include "utils/agentLogger.h" #include "utils/errorUtils.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" /** * last argument has to be NULL */ char* generatePostData(char* k1, char* v1, ...) { va_list args; va_start(args, v1); list_t* list = list_new(); list_rpush(list, list_node_new(k1)); list_rpush(list, list_node_new(v1)); char* s; while ((s = va_arg(args, char*)) != NULL) { list_rpush(list, list_node_new(s)); } va_end(args); char* data = generatePostDataFromList(list); list_destroy(list); return data; } char* generatePostDataFromList(list_t* list) { if (list == NULL || list->len < 2) { oidc_setArgNullFuncError(__func__); return NULL; } char* data = oidc_sprintf("%s=%s", (char*)list_at(list, 0)->val, (char*)list_at(list, 1)->val); for (size_t i = 2; i < list->len - 1; i += 2) { char* tmp = oidc_sprintf("%s&%s=%s", data, (char*)list_at(list, i)->val, (char*)list_at(list, i + 1)->val); if (tmp == NULL) { return NULL; } secFree(data); data = tmp; } return data; } void defaultErrorHandling(const char* error, const char* error_description) { char* error_str = combineError(error, error_description); oidc_seterror(error_str); oidc_errno = OIDC_EOIDC; agent_log(ERROR, "%s", error); secFree(error_str); } #define TOKENPARSEMODE_DONTFREE_AT \ (TOKENPARSEMODE_SAVE_AT | TOKENPARSEMODE_RETURN_AT) #define TOKENPARSEMODE_DONTFREE_ID TOKENPARSEMODE_RETURN_ID char* parseTokenResponseCallbacks( const unsigned char mode, const char* res, struct oidc_account* a, void (*errorHandling)(const char*, const char*), struct ipcPipe pipes, const unsigned char refreshFlow) { if (mode & TOKENPARSEMODE_RETURN_ID && mode & TOKENPARSEMODE_RETURN_AT) { oidc_setInternalError("cannot return AT and ID token"); return NULL; } INIT_KEY_VALUE(OIDC_KEY_ACCESSTOKEN, OIDC_KEY_REFRESHTOKEN, OIDC_KEY_IDTOKEN, OIDC_KEY_EXPIRESIN, OIDC_KEY_ERROR, OIDC_KEY_ERROR_DESCRIPTION); if (CALL_GETJSONVALUES(res) < 0) { agent_log(ERROR, "Error while parsing json\n"); SEC_FREE_KEY_VALUES(); return NULL; } KEY_VALUE_VARS(access_token, refresh_token, id_token, expires_in, error, error_description); if (_error || _error_description) { errorHandling(_error, _error_description); SEC_FREE_KEY_VALUES(); return NULL; } if (NULL != _expires_in) { if (mode & TOKENPARSEMODE_SAVE_AT) { account_setTokenExpiresAt(a, time(NULL) + strToInt(_expires_in)); agent_log(DEBUG, "expires_at is: %lu\n", account_getTokenExpiresAt(a)); } secFree(_expires_in); } char* refresh_token = account_getRefreshToken(a); if (strValid(_refresh_token) && !strequal(refresh_token, _refresh_token)) { if (strValid(refresh_token) && refreshFlow) { // only update, if the refresh token // changes, not when // it is initially obtained // Also only update when doing the // refresh flow, on other flows the refresh // token did not change in the same sense. // It's possible that a RT "changes" if // perfomring the auth code flow or another // flow but the user provided a RT (e.g. in // a file), but it wasn't used. (Unlikely, // but possible) agent_log(DEBUG, "Updating refreshtoken for %s from '%s' to '%s'", account_getName(a), refresh_token, _refresh_token); oidcd_handleUpdateRefreshToken(pipes, account_getName(a), _refresh_token); } account_setRefreshToken(a, _refresh_token); } else { secFree(_refresh_token); } if (mode & TOKENPARSEMODE_SAVE_AT) { account_setAccessToken(a, _access_token); } if (!(mode & TOKENPARSEMODE_DONTFREE_AT)) { secFree(_access_token); } if (!(mode & TOKENPARSEMODE_DONTFREE_ID)) { secFree(_id_token); } if (mode & TOKENPARSEMODE_RETURN_AT) { return _access_token; } else if (mode & TOKENPARSEMODE_RETURN_ID) { return _id_token; } oidc_errno = OIDC_SUCCESS; return NULL; } char* parseTokenResponse(const unsigned char mode, const char* res, struct oidc_account* a, struct ipcPipe pipes, const unsigned char refreshFlow) { return parseTokenResponseCallbacks(mode, res, a, &defaultErrorHandling, pipes, refreshFlow); } oidc-agent-4.2.6/src/oidc-agent/oidc/flows/revoke.h0000644000175000017500000000027314120404223021412 0ustar marcusmarcus#ifndef OIDC_REVOKE_H #define OIDC_REVOKE_H #include "account/account.h" #include "utils/oidc_error.h" oidc_error_t revokeToken(struct oidc_account* account); #endif // OIDC_REVOKE_H oidc-agent-4.2.6/src/oidc-agent/oidc/device_code.h0000644000175000017500000000711514167074355021244 0ustar marcusmarcus#ifndef DEVICE_CODE_H #define DEVICE_CODE_H #include #include #include "utils/memory.h" struct oidc_device_code { char* device_code; char* user_code; char* verification_uri; char* verification_uri_complete; size_t expires_in; size_t interval; }; static inline char* oidc_device_getDeviceCode(struct oidc_device_code c) { return c.device_code; } static inline char* oidc_device_getUserCode(struct oidc_device_code c) { return c.user_code; } static inline char* oidc_device_getVerificationUri(struct oidc_device_code c) { return c.verification_uri; } static inline char* oidc_device_getVerificationUriComplete( struct oidc_device_code c) { return c.verification_uri_complete; } static inline size_t oidc_device_getExpiresIn(struct oidc_device_code c) { return c.expires_in; } static inline size_t oidc_device_getInterval(struct oidc_device_code c) { return c.interval; } static inline void oidc_device_setDeviceCode(struct oidc_device_code* c, char* device_code) { if (c->device_code == device_code) { return; } secFree(c->device_code); c->device_code = device_code; } static inline void oidc_device_setUserCode(struct oidc_device_code* c, char* user_code) { if (c->user_code == user_code) { return; } secFree(c->user_code); c->user_code = user_code; } static inline void oidc_device_setVerificationUrl(struct oidc_device_code* c, char* verification_uri) { if (c->verification_uri == verification_uri) { return; } secFree(c->verification_uri); c->verification_uri = verification_uri; } static inline void oidc_device_setVerificationUrlComplete( struct oidc_device_code* c, char* verification_uri_complete) { if (c->verification_uri_complete == verification_uri_complete) { return; } secFree(c->verification_uri_complete); c->verification_uri_complete = verification_uri_complete; } static inline void oidc_device_setExpiresIn(struct oidc_device_code* c, size_t expires_in) { c->expires_in = expires_in; } static inline void oidc_device_setInterval(struct oidc_device_code* c, size_t interval) { c->interval = interval; } static inline struct oidc_device_code* oidc_device_new( char* device_code, char* user_code, char* verification_uri, char* verification_uri_complete, size_t expires_in, size_t interval) { struct oidc_device_code* c = secAlloc(sizeof(struct oidc_device_code)); oidc_device_setDeviceCode(c, device_code); oidc_device_setUserCode(c, user_code); oidc_device_setVerificationUrl(c, verification_uri); oidc_device_setVerificationUrlComplete(c, verification_uri_complete); oidc_device_setExpiresIn(c, expires_in); oidc_device_setInterval(c, interval); return c; } static inline void _secFreeDeviceCode(struct oidc_device_code* c) { oidc_device_setDeviceCode(c, NULL); oidc_device_setUserCode(c, NULL); oidc_device_setVerificationUrl(c, NULL); oidc_device_setVerificationUrlComplete(c, NULL); oidc_device_setExpiresIn(c, 0); oidc_device_setInterval(c, 0); secFree(c); } struct oidc_device_code* getDeviceCodeFromJSON(const char* json); char* deviceCodeToJSON(struct oidc_device_code c); #ifndef secFreeDeviceCode #define secFreeDeviceCode(ptr) \ do { \ _secFreeDeviceCode((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeDeviceCode #endif // DEVICE_CODE_H oidc-agent-4.2.6/src/oidc-agent/oidc/oidc_agent_help.c0000644000175000017500000000403614167074355022111 0ustar marcusmarcus#include "oidc_agent_help.h" #include "account/account.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" const char* getHelp() { if (oidc_errno != OIDC_EOIDC && oidc_errno != OIDC_ENOREFRSH) { return NULL; } // We escape \n \t as \\n \\t etc. because these strings are sent in the json // ipc to clients const char* err = oidc_serror(); if (oidc_errno == OIDC_ENOREFRSH || strstarts(err, "invalid_grant:") || strstarts(err, "invalid_token:")) { return "Most likely the refresh token expired. To create a new one, just " "run:\\n" "\\t$ oidc-gen --reauthenticate\\n"; } if (strstarts(err, "invalid_scope:")) { return "We cannot get these scopes with the current configuration. To get " "these scopes you might need to adapt the account configuration " "with\\n" "\\t$ oidc-gen -m \\n" "but it also might be necessary to change the client configuration " "with the OpenID provider.\\n"; } if (strstarts(err, "invalid_request")) { return "This is most likely a bug. Please hand in a bug report: " "https://github.com/indigo-dc/oidc-agent\\n"; } if (strstarts(err, "invalid_client")) { return "Probably the OIDC client has been deleted. You must register a new " "client with the OpenID Provider manually and then use \\n" "\\t$ oidc-gen -m \\n" "to update this account configuration, or you delete this account " "configuration and create a new one using dynamic client " "configuration (if supported):\\n" "\\t $ oidc-gen -d \\n" "\\t $ oidc-gen --iss=\\n"; } return NULL; } char* getHelpWithAccountInfo(struct oidc_account* account) { const char* helpFmt = getHelp(); char* help = strreplace(helpFmt, "", account_getName(account)); char* tmp = strreplace(help, "", account_getIssuerUrl(account)); secFree(help); help = tmp; return help; } oidc-agent-4.2.6/src/oidc-agent/oidc/oidc_agent_help.h0000644000175000017500000000035314167074355022114 0ustar marcusmarcus#ifndef OIDC_AGENT_OIDC_AGENT_HELP_H #define OIDC_AGENT_OIDC_AGENT_HELP_H #include "account/account.h" char* getHelpWithAccountInfo(struct oidc_account* account); const char* getHelp(); #endif // OIDC_AGENT_OIDC_AGENT_HELP_H oidc-agent-4.2.6/src/oidc-agent/oidc/parse_oidp.h0000644000175000017500000000041314120404223021106 0ustar marcusmarcus#ifndef PARSE_OIDP_H #define PARSE_OIDP_H #include "account/account.h" #include "utils/oidc_error.h" struct oidc_device_code* parseDeviceCode(const char* res); oidc_error_t parseOpenidConfiguration(char* res, struct oidc_account* account); #endif // PARSE_OIDP_H oidc-agent-4.2.6/src/oidc-agent/oidc/parse_oidp.c0000644000175000017500000000734714167074355021142 0ustar marcusmarcus#include "parse_oidp.h" #include "account/account.h" #include "defines/oidc_values.h" #include "device_code.h" #include "utils/agentLogger.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/parseJson.h" #include "utils/string/stringUtils.h" struct oidc_device_code* parseDeviceCode(const char* res) { if (!isJSONObject(res)) { return NULL; } char* error = parseForError(oidc_strcopy(res)); if (error) { oidc_seterror(error); oidc_errno = OIDC_EOIDC; secFree(error); return NULL; } return getDeviceCodeFromJSON(res); } oidc_error_t parseOpenidConfiguration(char* res, struct oidc_account* account) { INIT_KEY_VALUE(OIDC_KEY_TOKEN_ENDPOINT, OIDC_KEY_AUTHORIZATION_ENDPOINT, OIDC_KEY_REGISTRATION_ENDPOINT, OIDC_KEY_REVOCATION_ENDPOINT, OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT, OIDC_KEY_SCOPES_SUPPORTED, OIDC_KEY_GRANT_TYPES_SUPPORTED, OIDC_KEY_RESPONSE_TYPES_SUPPORTED, OIDC_KEY_CODE_CHALLENGE_METHODS_SUPPORTED); if (CALL_GETJSONVALUES(res) < 0) { secFree(res); return oidc_errno; } secFree(res); KEY_VALUE_VARS(token_endpoint, authorization_endpoint, registration_endpoint, revocation_endpoint, device_authorization_endpoint, scopes_supported, grant_types_supported, response_types_supported, code_challenge_method_supported); if (_token_endpoint == NULL) { agent_log(ERROR, "Could not get token endpoint"); SEC_FREE_KEY_VALUES(); oidc_seterror( "Could not get token endpoint from the configuration endpoint. This " "could be because of a network issue. But it's more likely that your " "issuer is not correct."); oidc_errno = OIDC_EERROR; return oidc_errno; } struct oidc_issuer* issuer = account_getIssuer(account); if (_token_endpoint) { issuer_setTokenEndpoint(issuer, _token_endpoint); } if (_authorization_endpoint) { issuer_setAuthorizationEndpoint(issuer, _authorization_endpoint); } if (_registration_endpoint) { issuer_setRegistrationEndpoint(issuer, _registration_endpoint); } if (_revocation_endpoint) { issuer_setRevocationEndpoint(issuer, _revocation_endpoint); } if (_device_authorization_endpoint) { issuer_setDeviceAuthorizationEndpoint(issuer, _device_authorization_endpoint, 0); } if (_grant_types_supported == NULL) { const char* defaultValue = OIDC_PROVIDER_DEFAULT_GRANTTYPES; _grant_types_supported = oidc_sprintf("%s", defaultValue); } char* scopes_supported = JSONArrayStringToDelimitedString(_scopes_supported, " "); if (scopes_supported == NULL) { secFree(_scopes_supported); secFree(_grant_types_supported); secFree(_response_types_supported); secFree(_code_challenge_method_supported); return oidc_errno; } account_setScopesSupported(account, scopes_supported); secFree(_scopes_supported); issuer_setGrantTypesSupported(account_getIssuer(account), _grant_types_supported); issuer_setResponseTypesSupported(account_getIssuer(account), _response_types_supported); if (_code_challenge_method_supported) { if (strSubString(_code_challenge_method_supported, CODE_CHALLENGE_METHOD_S256)) { account_setCodeChallengeMethod(account, CODE_CHALLENGE_METHOD_S256); } else if (strSubString(_code_challenge_method_supported, CODE_CHALLENGE_METHOD_PLAIN)) { account_setCodeChallengeMethod(account, CODE_CHALLENGE_METHOD_PLAIN); } secFree(_code_challenge_method_supported); } agent_log(DEBUG, "Successfully retrieved endpoints."); return OIDC_SUCCESS; } oidc-agent-4.2.6/src/oidc-agent/http/0000755000175000017500000000000014167074355016677 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/http/http_errorHandler.c0000644000175000017500000000327614167074355022541 0ustar marcusmarcus#include "http_errorHandler.h" #include "utils/agentLogger.h" #include "utils/oidc_error.h" oidc_error_t handleCURLE_OK(CURL* curl) { long http_code = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code); agent_log(DEBUG, "Received status code %ld", http_code); if (http_code >= 400) { oidc_errno = http_code; } else { oidc_errno = OIDC_SUCCESS; } return oidc_errno; } oidc_error_t handleSSL(int res, CURL* curl) { agent_log(ERROR, "%s (%s:%d) HTTPS Request failed: %s Please check the provided " "certh_path.\n", __func__, __FILE__, __LINE__, curl_easy_strerror(res)); curl_easy_cleanup(curl); oidc_errno = OIDC_ESSL; return oidc_errno; } oidc_error_t handleHost(int res, CURL* curl) { agent_log( ERROR, "%s (%s:%d) HTTPS Request failed: %s Please check the provided URLs.\n", __func__, __FILE__, __LINE__, curl_easy_strerror(res)); curl_easy_cleanup(curl); oidc_errno = OIDC_EURL; return oidc_errno; } oidc_error_t CURLErrorHandling(int res, CURL* curl) { switch (res) { case CURLE_OK: return handleCURLE_OK(curl); case CURLE_URL_MALFORMAT: case CURLE_COULDNT_RESOLVE_HOST: return handleHost(res, curl); case CURLE_SSL_CONNECT_ERROR: case CURLE_SSL_CERTPROBLEM: case CURLE_SSL_CIPHER: case CURLE_SSL_CACERT: case CURLE_SSL_CACERT_BADFILE: case CURLE_SSL_CRL_BADFILE: case CURLE_SSL_ISSUER_ERROR: return handleSSL(res, curl); default: agent_log(ERROR, "%s (%s:%d) curl_easy_perform() failed: %s\n", __func__, __FILE__, __LINE__, curl_easy_strerror(res)); curl_easy_cleanup(curl); oidc_errno = OIDC_EERROR; return OIDC_EERROR; } } oidc-agent-4.2.6/src/oidc-agent/http/http.h0000644000175000017500000000073314120404223020006 0ustar marcusmarcus#ifndef HTTP_H #define HTTP_H #include char* _httpsGET(const char* url, struct curl_slist* list, const char* cert_path); char* _httpsPOST(const char* url, const char* data, struct curl_slist* headers, const char* cert_path, const char* username, const char* password); char* _httpsDELETE(const char* url, struct curl_slist* headers, const char* cert_path, const char* bearer_token); #endif oidc-agent-4.2.6/src/oidc-agent/http/http.c0000644000175000017500000000704314167074355020026 0ustar marcusmarcus#include "http.h" #include #include "http_handler.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/pass.h" #include "utils/string/stringUtils.h" /** @fn char* httpsGET(const char* url, const char* cert_path) * @brief does a https GET request * @param url the request url * @param cert_path the path to the SSL certs * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* _httpsGET(const char* url, struct curl_slist* headers, const char* cert_path) { agent_log(DEBUG, "Https GET to: %s", url); CURL* curl = init(); setUrl(curl, url); struct string s; if (setWriteFunction(curl, &s) != OIDC_SUCCESS) { return NULL; } setSSLOpts(curl, cert_path); setHeaders(curl, headers); oidc_error_t err = perform(curl); if (err != OIDC_SUCCESS) { if (err >= 200 && err < 600 && strValid(s.ptr)) { pass; } else { secFree(s.ptr); return NULL; } } cleanup(curl); agent_log(DEBUG, "Response: %s\n", s.ptr); return s.ptr; } /** @fn char* httpsDELETE(const char* url, const char* cert_path) * @brief does a https DELETE request * @param url the request url * @param cert_path the path to the SSL certs * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* _httpsDELETE(const char* url, struct curl_slist* headers, const char* cert_path, const char* bearer_token) { agent_log(DEBUG, "Https GET to: %s", url); CURL* curl = init(); setUrl(curl, url); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE"); struct string s; if (setWriteFunction(curl, &s) != OIDC_SUCCESS) { return NULL; } setSSLOpts(curl, cert_path); char* bearer_header = oidc_sprintf("Authorization: Bearer %s", bearer_token); headers = curl_slist_append(headers, bearer_header); setHeaders(curl, headers); // if (bearer_token) { // setTokenAuth(curl, bearer_token); // } oidc_error_t err = perform(curl); secFree(bearer_header); if (err != OIDC_SUCCESS) { if (err >= 200 && err < 600 && strValid(s.ptr)) { pass; } else { secFree(s.ptr); return NULL; } } cleanup(curl); agent_log(DEBUG, "Response: %s\n", s.ptr); return s.ptr; } /** @fn char* httpsPOST(const char* url, const char* data, const char* * cert_path) * @brief does a https POST request * @param url the request url * @param cert_path the path to the SSL certs * @param data the data to be posted * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* _httpsPOST(const char* url, const char* data, struct curl_slist* headers, const char* cert_path, const char* username, const char* password) { agent_log(DEBUG, "Https POST to: %s", url); CURL* curl = init(); setUrl(curl, url); curl_easy_setopt(curl, CURLOPT_POST, 1L); struct string s; if (setWriteFunction(curl, &s) != OIDC_SUCCESS) { return NULL; } setPostData(curl, data); setSSLOpts(curl, cert_path); setHeaders(curl, headers); if (username) { setBasicAuth(curl, username, password ?: ""); } oidc_error_t err = perform(curl); if (err != OIDC_SUCCESS) { if (err >= 200 && err < 600 && strValid(s.ptr)) { pass; } else { secFree(s.ptr); cleanup(curl); return NULL; } } cleanup(curl); agent_log(DEBUG, "Response: %s\n", s.ptr ? s.ptr : "(null)"); return s.ptr; } oidc-agent-4.2.6/src/oidc-agent/http/http_handler.h0000644000175000017500000000115714167074355021530 0ustar marcusmarcus#ifndef HTTP_HANDLER_H #define HTTP_HANDLER_H #include #include "utils/oidc_error.h" #include "utils/string/oidc_string.h" CURL* init(); void setSSLOpts(CURL* curl, const char* cert_file); oidc_error_t setWriteFunction(CURL* curl, struct string* s); void setUrl(CURL* curl, const char* url); void setHeaders(CURL* curl, struct curl_slist* headers); void setPostData(CURL* curl, const char* data); void setBasicAuth(CURL* curl, const char* username, const char* password); oidc_error_t perform(CURL* curl); void cleanup(CURL* curl); #endif // HTTP_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/http/http_errorHandler.h0000644000175000017500000000030414120404223022507 0ustar marcusmarcus#ifndef HTTP_ERRORHANDLER_H #define HTTP_ERRORHANDLER_H #include #include "utils/oidc_error.h" oidc_error_t CURLErrorHandling(int res, CURL* curl); #endif // HTTP_ERRORHANDLER_H oidc-agent-4.2.6/src/oidc-agent/http/http_ipc.c0000644000175000017500000001102414167074355020653 0ustar marcusmarcus#include "http_ipc.h" #include #include #include #include "ipc/pipe.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/oidc_error.h" char* _handleParent(struct ipcPipe pipes) { char* e = ipc_readFromPipe(pipes); ipc_closePipes(pipes); if (e == NULL) { return NULL; } char* end = NULL; long int error = strtol(e, &end, 10); if (error) { secFree(e); oidc_errno = error; agent_log(ERROR, "Error from http request: %s", oidc_serror()); return NULL; } if (*end != '\0') { char* res = e; agent_log(DEBUG, "Received response: %s", res); return res; } secFree(e); agent_log(ERROR, "Internal error: Http sent 0"); oidc_errno = OIDC_EHTTP0; return NULL; } void handleChild(char* res, struct ipcPipe pipes) { if (res == NULL) { ipc_writeOidcErrnoToPipe(pipes); exit(EXIT_FAILURE); } ipc_writeToPipe(pipes, res); secFree(res); exit(EXIT_SUCCESS); } /** @fn char* httpsGET(const char* url, const char* cert_path) * @brief forks and does a https GET request * @param url the request url * @param cert_path the path to the SSL certs * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* httpsGET(const char* url, struct curl_slist* headers, const char* cert_path) { struct pipeSet pipes = ipc_pipe_init(); if (pipes.pipe1.rx == -1) { return NULL; } pid_t pid = fork(); if (pid == -1) { agent_log(ALERT, "fork %m"); oidc_setErrnoError(); return NULL; } if (pid == 0) { // child struct ipcPipe childPipes = toClientPipes(pipes); logger_open("oidc-agent.http"); char* res = _httpsGET(url, headers, cert_path); handleChild(res, childPipes); return NULL; } else { // parent signal(SIGCHLD, SIG_IGN); struct ipcPipe parentPipes = toServerPipes(pipes); return _handleParent(parentPipes); } } /** @fn char* httpsDELETE(const char* url, const char* cert_path) * @brief forks and does a https DELETE request * @param url the request url * @param cert_path the path to the SSL certs * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* httpsDELETE(const char* url, struct curl_slist* headers, const char* cert_path, const char* bearer_token) { struct pipeSet pipes = ipc_pipe_init(); if (pipes.pipe1.rx == -1) { return NULL; } pid_t pid = fork(); if (pid == -1) { agent_log(ALERT, "fork %m"); oidc_setErrnoError(); return NULL; } if (pid == 0) { // child struct ipcPipe childPipes = toClientPipes(pipes); logger_open("oidc-agent.http"); char* res = _httpsDELETE(url, headers, cert_path, bearer_token); handleChild(res, childPipes); return NULL; } else { // parent signal(SIGCHLD, SIG_IGN); struct ipcPipe parentPipes = toServerPipes(pipes); return _handleParent(parentPipes); } } /** @fn char* httpsPOST(const char* url, const char* data, const char* * cert_path) * @brief forks and does a https POST request * @param url the request url * @param cert_path the path to the SSL certs * @param data the data to be posted * @return a pointer to the response. Has to be freed after usage. If the Https * call failed, NULL is returned. */ char* httpsPOST(const char* url, const char* data, struct curl_slist* headers, const char* cert_path, const char* username, const char* password) { struct pipeSet pipes = ipc_pipe_init(); if (pipes.pipe1.rx == -1) { return NULL; } pid_t pid = fork(); if (pid == -1) { agent_log(ALERT, "fork %m"); oidc_setErrnoError(); return NULL; } if (pid == 0) { // child struct ipcPipe childPipes = toClientPipes(pipes); logger_open("oidc-agent.http"); char* res = _httpsPOST(url, data, headers, cert_path, username, password); handleChild(res, childPipes); return NULL; } else { // parent struct ipcPipe parentPipes = toServerPipes(pipes); return _handleParent(parentPipes); } } char* sendPostDataWithBasicAuth(const char* endpoint, const char* data, const char* cert_path, const char* username, const char* password) { return httpsPOST(endpoint, data, NULL, cert_path, username, password); } char* sendPostDataWithoutBasicAuth(const char* endpoint, const char* data, const char* cert_path) { return httpsPOST(endpoint, data, NULL, cert_path, NULL, NULL); } oidc-agent-4.2.6/src/oidc-agent/http/http_ipc.h0000644000175000017500000000167214120404223020644 0ustar marcusmarcus#ifndef HTTP_IPC_H #define HTTP_IPC_H #include "http.h" #define HTTP_HEADER_CONTENTTYPE_JSON "Content-Type: application/json" #define HTTP_HEADER_AUTHORIZATION_BEARER_FMT "Authorization: Bearer %s" char* httpsGET(const char* url, struct curl_slist* list, const char* cert_path); char* httpsPOST(const char* url, const char* data, struct curl_slist* headers, const char* cert_path, const char* username, const char* password); char* httpsDELETE(const char* url, struct curl_slist* headers, const char* cert_path, const char* bearer_token); char* sendPostDataWithBasicAuth(const char* endpoint, const char* data, const char* cert_path, const char* username, const char* password); char* sendPostDataWithoutBasicAuth(const char* endpoint, const char* data, const char* cert_path); #endif // HTTP_IPC_H oidc-agent-4.2.6/src/oidc-agent/http/http_handler.c0000644000175000017500000000767214167074355021533 0ustar marcusmarcus#include "http_handler.h" #include #include #include "http_errorHandler.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/string/oidc_string.h" #include "utils/string/stringUtils.h" static size_t write_callback(void* ptr, size_t size, size_t nmemb, struct string* s) { size_t new_len = s->len + size * nmemb; void* tmp = secRealloc(s->ptr, new_len + 1); if (tmp == NULL) { exit(EXIT_FAILURE); } s->ptr = tmp; memcpy(s->ptr + s->len, ptr, size * nmemb); // s->ptr[new_len] = '\0'; s->len = new_len; return size * nmemb; } /** @fn CURL* init() * @brief initializes curl * @return a CURL pointer */ CURL* init() { CURLcode res = curl_global_init_mem(CURL_GLOBAL_ALL, secAlloc, _secFree, secRealloc, oidc_strcopy, secCalloc); if (CURLErrorHandling(res, NULL) != OIDC_SUCCESS) { return NULL; } CURL* curl = curl_easy_init(); if (!curl) { curl_global_cleanup(); agent_log(ALERT, "%s (%s:%d) Couldn't init curl. %s\n", __func__, __FILE__, __LINE__, curl_easy_strerror(res)); oidc_errno = OIDC_ECURLI; return NULL; } return curl; } /** @fn void setSSLOpts(CURL* curl) * @brief sets SSL options * @param curl the curl instance */ void setSSLOpts(CURL* curl, const char* cert_file) { curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); if (cert_file) { curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file); } } /** @fn void setWritefunction(CURL* curl, struct string* s) * @brief sets the function and buffer for writing the response * @param curl the curl instance * @param s the string struct where the response will be stored */ oidc_error_t setWriteFunction(CURL* curl, struct string* s) { oidc_error_t e; if ((e = init_string(s)) != OIDC_SUCCESS) { return e; } curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, s); return OIDC_SUCCESS; } /** @fn void setUrl(CURL* curl, const char* url) * @brief sets the url * @param curl the curl instance * @param url the url */ void setUrl(CURL* curl, const char* url) { curl_easy_setopt(curl, CURLOPT_URL, url); } /** @fn void setPostData(CURL* curl, const char* data) * @brief sets the data to be posted * @param curl the curl instance * @param data the data to be posted */ void setPostData(CURL* curl, const char* data) { long data_len = (long)strlen(data); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, data); curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, data_len); } void setHeaders(CURL* curl, struct curl_slist* headers) { if (headers) { curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); } } void setBasicAuth(CURL* curl, const char* username, const char* password) { curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_easy_setopt(curl, CURLOPT_USERNAME, username); curl_easy_setopt(curl, CURLOPT_PASSWORD, password); // agent_log(DEBUG, "Http Set Client credentials: %s - %s", // username ?: "NULL", password ?: "NULL"); } // void setTokenAuth(CURL* curl, const char* token) { // curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BEARER); // This is only // available since curl 7.61 curl_easy_setopt(curl, CURLOPT_XOAUTH2_BEARER, // token); // } /** @fn int perform(CURL* curl) * @brief performs the https request and checks for errors * @param curl the curl instance * @return 0 on success, for error values see \f CURLErrorHandling */ oidc_error_t perform(CURL* curl) { // curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); CURLcode res = curl_easy_perform(curl); return CURLErrorHandling(res, curl); } /** @fn void cleanup(CURL* curl) * @brief does a easy and global cleanup * @param curl the curl instance */ void cleanup(CURL* curl) { curl_easy_cleanup(curl); curl_global_cleanup(); } oidc-agent-4.2.6/src/oidc-agent/agent_state.c0000644000175000017500000000007214120404223020335 0ustar marcusmarcus#include "agent_state.h" struct agent_state agent_state; oidc-agent-4.2.6/src/oidc-agent/agent_state.h0000644000175000017500000000036714167074355020375 0ustar marcusmarcus#ifndef AGENT_STATE_H #define AGENT_STATE_H #include #include "lock_state.h" struct agent_state { time_t defaultTimeout; struct lock_state lock_state; }; extern struct agent_state agent_state; #endif // AGENT_STATE_H oidc-agent-4.2.6/src/oidc-agent/oidcd/0000755000175000017500000000000014167074355017002 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent/oidcd/oidcd_handler.c0000644000175000017500000013077714167074355021744 0ustar marcusmarcus#include "oidcd_handler.h" #include #include #include #include #include "defines/agent_values.h" #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "defines/version.h" #include "deviceCodeEntry.h" #include "ipc/pipe.h" #include "ipc/serveripc.h" #include "oidc-agent/agent_state.h" #include "oidc-agent/httpserver/startHttpserver.h" #include "oidc-agent/httpserver/termHttpserver.h" #include "oidc-agent/oidc/device_code.h" #include "oidc-agent/oidc/flows/access_token_handler.h" #include "oidc-agent/oidc/flows/code.h" #include "oidc-agent/oidc/flows/deleteClient.h" #include "oidc-agent/oidc/flows/device.h" #include "oidc-agent/oidc/flows/openid_config.h" #include "oidc-agent/oidc/flows/registration.h" #include "oidc-agent/oidc/flows/revoke.h" #include "oidc-agent/oidc/oidc_agent_help.h" #include "oidc-agent/oidcd/codeExchangeEntry.h" #include "oidc-agent/oidcd/parse_internal.h" #include "utils/accountUtils.h" #include "utils/agentLogger.h" #include "utils/crypt/crypt.h" #include "utils/crypt/dbCryptUtils.h" #include "utils/db/account_db.h" #include "utils/db/codeVerifier_db.h" #include "utils/db/deviceCode_db.h" #include "utils/db/file_db.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/parseJson.h" #include "utils/string/stringUtils.h" #include "utils/uriUtils.h" void initAuthCodeFlow(struct oidc_account* account, struct ipcPipe pipes, const char* info, const char* nowebserver_str, const char* noscheme_str, const int only_at, const struct arguments* arguments) { if (arguments->no_webserver || strToInt(nowebserver_str)) { account_setNoWebServer(account); } if (arguments->no_scheme || strToInt(noscheme_str)) { account_setNoScheme(account); } size_t state_len = 24; size_t socket_path_len = oidc_strlen(getServerSocketPath()); char* socket_path_base64 = toBase64UrlSafe(getServerSocketPath(), socket_path_len); // agent_log(DEBUG, "Base64 socket path is '%s'", // socket_path_base64); char random[state_len + 1]; randomFillBase64UrlSafe(random, state_len); random[state_len] = '\0'; char* state = oidc_sprintf("%d%s:%lu:%s", only_at ? 1 : 0, random, socket_path_len, socket_path_base64); char** state_ptr = &state; secFree(socket_path_base64); char* code_verifier = secAlloc(CODE_VERIFIER_LEN + 1); randomFillBase64UrlSafe(code_verifier, CODE_VERIFIER_LEN); char* uri = buildCodeFlowUri(account, state_ptr, &code_verifier); if (uri == NULL) { ipc_writeOidcErrnoToPipe(pipes); secFree(code_verifier); secFree(*state_ptr); secFreeAccount(account); return; } // agent_log(DEBUG, "code_verifier for state '%s' is '%s'", // state, code_verifier); codeVerifierDB_addValue( createCodeExchangeEntry(*state_ptr, account, code_verifier)); if (info) { ipc_writeToPipe(pipes, RESPONSE_STATUS_CODEURI_INFO, STATUS_ACCEPTED, uri, *state_ptr, info); } else { ipc_writeToPipe(pipes, RESPONSE_STATUS_CODEURI, STATUS_ACCEPTED, uri, *state_ptr); } secFree(uri); } char* removeScope(char* scopes, char* rem) { scopes = strremove(scopes, rem); scopes = strelimIfAfter( scopes, ' ', ' '); // strremove leaves a doubled space; this call removes one of it. return scopes; } void _handleGenFlows(struct ipcPipe pipes, struct oidc_account* account, const char* flow, const char* scope, const unsigned char only_at, const char* nowebserver_str, const char* noscheme_str, const struct arguments* arguments) { int success = 0; list_t* flows = parseFlow(flow); list_node_t* current_flow; list_iterator_t* it = list_iterator_new(flows, LIST_HEAD); while ((current_flow = list_iterator_next(it))) { if (strcaseequal(current_flow->val, FLOW_VALUE_REFRESH)) { char* at = NULL; if ((at = getAccessTokenUsingRefreshFlow(account, FORCE_NEW_TOKEN, scope, account_getAudience(account), pipes)) != NULL) { success = 1; if (only_at) { account_setAccessToken( account, at); // if only_at store the AT so we can send it back, we have // to store it manually because we provided manual scopes // (because offline_access removed). The token will be // correctly freed at the end when the account is freed. } break; } else if (flows->len == 1) { ipc_writeOidcErrnoToPipe(pipes); list_iterator_destroy(it); secFreeList(flows); secFreeAccount(account); return; } } else if (strcaseequal(current_flow->val, FLOW_VALUE_PASSWORD)) { if (getAccessTokenUsingPasswordFlow(account, FORCE_NEW_TOKEN, scope, pipes) == OIDC_SUCCESS) { success = 1; break; } else if (flows->len == 1) { ipc_writeOidcErrnoToPipe(pipes); list_iterator_destroy(it); secFreeList(flows); secFreeAccount(account); return; } } else if (strcaseequal(current_flow->val, FLOW_VALUE_CODE) && hasRedirectUris(account)) { initAuthCodeFlow(account, pipes, NULL, nowebserver_str, noscheme_str, only_at, arguments); list_iterator_destroy(it); secFreeList(flows); // secFreeAccount(account); //don't free it -> it is stored return; } else if (strcaseequal(current_flow->val, FLOW_VALUE_DEVICE)) { if (scope) { account_setScopeExact(account, oidc_strcopy(scope)); } struct oidc_device_code* dc = initDeviceFlow(account); if (dc == NULL) { if (flows->len != 1) { continue; } ipc_writeOidcErrnoToPipe(pipes); list_iterator_destroy(it); secFreeList(flows); secFreeAccount(account); return; } char* json = deviceCodeToJSON(*dc); ipc_writeToPipe(pipes, RESPONSE_ACCEPTED_DEVICE, json); secFree(json); secFreeDeviceCode(dc); list_iterator_destroy(it); secFreeList(flows); // secFreeAccount(account); // Don't free account, it is stored return; } else { // UNKNOWN FLOW char* msg; if (strcaseequal(current_flow->val, FLOW_VALUE_CODE) && !hasRedirectUris(account)) { msg = oidc_sprintf("Only '%s' flow specified, but no redirect uris", FLOW_VALUE_CODE); } else { msg = oidc_sprintf("Unknown flow '%s'", (char*)current_flow->val); } ipc_writeToPipe(pipes, RESPONSE_ERROR, msg); secFree(msg); list_iterator_destroy(it); secFreeList(flows); secFreeAccount(account); return; } } list_iterator_destroy(it); secFreeList(flows); account_setUsername(account, NULL); account_setPassword(account, NULL); if (success && account_refreshTokenIsValid(account) && !only_at) { char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); db_addAccountEncrypted(account); } else if (success && only_at && strValid(account_getAccessToken(account))) { ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, account_getAccessToken(account), account_getIssuerUrl(account), account_getTokenExpiresAt(account)); secFreeAccount(account); } else { ipc_writeToPipe(pipes, RESPONSE_ERROR, success && !only_at ? "OIDP response does not contain a refresh token" : "No flow was successful."); secFreeAccount(account); } } void oidcd_handleGen(struct ipcPipe pipes, const char* account_json, const char* flow, const char* nowebserver_str, const char* noscheme_str, const char* only_at_str, const struct arguments* arguments) { agent_log(DEBUG, "Handle Gen request"); struct oidc_account* account = getAccountFromJSON(account_json); if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } if (getIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; } if (!strValid(account_getTokenEndpoint(account))) { ipc_writeOidcErrnoToPipe(pipes); secFreeAccount(account); return; } const int only_at = strToInt(only_at_str); char* scope = only_at ? removeScope(oidc_strcopy(account_getScope(account)), OIDC_SCOPE_OFFLINE_ACCESS) : NULL; _handleGenFlows(pipes, account, flow, scope, only_at, nowebserver_str, noscheme_str, arguments); secFree(scope); } /** * checks if an account is feasible (issuer config / AT retrievable) and adds it * to the loaded list; does not check if account already loaded. */ oidc_error_t addAccount(struct ipcPipe pipes, struct oidc_account* account) { if (account == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } if (getIssuerConfig(account) != OIDC_SUCCESS) { return oidc_errno; } if (!strValid(account_getTokenEndpoint(account))) { return oidc_errno; } if (getAccessTokenUsingRefreshFlow(account, FORCE_NEW_TOKEN, NULL, NULL, pipes) == NULL) { account_setDeath(account, time(NULL) + 10); // with short timeout so no password // required for re-authentication db_addAccountEncrypted(account); return oidc_errno; } db_addAccountEncrypted(account); return OIDC_SUCCESS; } void oidcd_handleAdd(struct ipcPipe pipes, const char* account_json, const char* timeout_str, const char* confirm_str, const char* alwaysallowid) { agent_log(DEBUG, "Handle Add request"); struct oidc_account* account = getAccountFromJSON(account_json); if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } time_t timeout = strValid(timeout_str) ? atol(timeout_str) : agent_state.defaultTimeout; account_setDeath(account, timeout ? time(NULL) + timeout : 0); if (strToInt(confirm_str)) { account_setConfirmationRequired(account); } if (strToInt(alwaysallowid)) { account_setAlwaysAllowId(account); } struct oidc_account* found = NULL; if ((found = db_getAccountDecrypted(account)) != NULL) { if (account_getDeath(found) != account_getDeath(account)) { account_setDeath(found, account_getDeath(account)); char* msg = oidc_sprintf( "account already loaded. Lifetime set to %lu seconds.", timeout ?: 0); ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, msg); secFree(msg); } else { ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, "account already loaded."); } db_addAccountEncrypted(found); // reencrypting sensitive data secFreeAccount(account); return; } if (addAccount(pipes, account) != OIDC_SUCCESS) { char* help = getHelpWithAccountInfo(account); if (help != NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, oidc_serror(), help); secFree(help); return; } ipc_writeOidcErrnoToPipe(pipes); return; } agent_log(DEBUG, "Loaded Account. Used timeout of %lu", timeout); if (timeout > 0) { char* msg = oidc_sprintf("Lifetime set to %lu seconds", timeout); ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, msg); secFree(msg); } else { ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } } void oidcd_handleDeleteClient(struct ipcPipe pipes, const char* client_uri, const char* registration_access_token, const char* cert_path) { agent_log(DEBUG, "Handle DeleteClient request"); if (deleteClient(client_uri, registration_access_token, cert_path) != OIDC_SUCCESS) { char* error = oidc_sprintf("Could not delete client: %s", oidc_serror()); ipc_writeToPipe(pipes, RESPONSE_ERROR, error); secFree(error); return; } ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } void oidcd_handleDelete(struct ipcPipe pipes, const char* account_json) { agent_log(DEBUG, "Handle Delete request"); struct oidc_account* account = getAccountFromJSON(account_json); if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } if (getIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; } if (revokeToken(account) != OIDC_SUCCESS) { secFreeAccount(account); char* error = oidc_sprintf("Could not revoke token: %s", oidc_serror()); ipc_writeToPipe(pipes, RESPONSE_ERROR, error); secFree(error); return; } accountDB_removeIfFound(account); secFreeAccount(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } void oidcd_handleRm(struct ipcPipe pipes, char* account_name) { if (account_name == NULL) { ipc_writeToPipe( pipes, RESPONSE_BADREQUEST, "Have to provide shortname of the account config that should be " "removed."); return; } agent_log(DEBUG, "Handle Remove request for config '%s'", account_name); struct oidc_account key = {.shortname = account_name}; if (accountDB_findValue(&key) == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); return; } accountDB_removeIfFound(&key); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } void oidcd_handleRemoveAll(struct ipcPipe pipes) { accountDB_reset(); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } oidc_error_t oidcd_autoload(struct ipcPipe pipes, const char* short_name, const char* issuer, const char* application_hint) { agent_log(DEBUG, "Send autoload request for '%s'", short_name); char* res = issuer ? ipc_communicateThroughPipe( pipes, INT_REQUEST_AUTOLOAD_WITH_ISSUER, short_name, issuer, application_hint ?: "") : ipc_communicateThroughPipe(pipes, INT_REQUEST_AUTOLOAD, short_name, application_hint ?: ""); if (res == NULL) { return oidc_errno; } char* config = parseForConfig(res); if (config == NULL) { return oidc_errno; } struct oidc_account* account = getAccountFromJSON(config); secFree(config); account_setDeath(account, agent_state.defaultTimeout ? time(NULL) + agent_state.defaultTimeout : 0); return addAccount(pipes, account); } #define CONFIRMATION_MODE_AT 0 #define CONFIRMATION_MODE_ID 1 oidc_error_t _oidcd_getConfirmation(unsigned char mode, struct ipcPipe pipes, const char* short_name, const char* issuer, const char* application_hint) { agent_log(DEBUG, "Send confirm request for '%s'", short_name); const char* request_type = NULL; switch (mode) { case CONFIRMATION_MODE_AT: request_type = INT_REQUEST_VALUE_CONFIRM; break; case CONFIRMATION_MODE_ID: request_type = INT_REQUEST_VALUE_CONFIRMIDTOKEN; break; } char* res = issuer ? ipc_communicateThroughPipe( pipes, INT_REQUEST_CONFIRM_WITH_ISSUER, request_type, issuer, short_name, application_hint ?: "") : ipc_communicateThroughPipe(pipes, INT_REQUEST_CONFIRM, request_type, short_name, application_hint ?: ""); if (res == NULL) { return oidc_errno; } oidc_errno = parseForErrorCode(res); return oidc_errno; } oidc_error_t oidcd_getConfirmation(struct ipcPipe pipes, const char* short_name, const char* issuer, const char* application_hint) { return _oidcd_getConfirmation(CONFIRMATION_MODE_AT, pipes, short_name, issuer, application_hint); } oidc_error_t oidcd_getIdTokenConfirmation(struct ipcPipe pipes, const char* short_name, const char* issuer, const char* application_hint) { return _oidcd_getConfirmation(CONFIRMATION_MODE_ID, pipes, short_name, issuer, application_hint); } char* oidcd_queryDefaultAccountIssuer(struct ipcPipe pipes, const char* issuer) { agent_log(DEBUG, "Send default account config query request for issuer '%s'", issuer); char* res = ipc_communicateThroughPipe( pipes, INT_REQUEST_QUERY_ACCDEFAULT_ISSUER, issuer); if (res == NULL) { return NULL; } char* shortname = getJSONValueFromString(res, IPC_KEY_SHORTNAME); secFree(res); if (!strValid(shortname)) { secFree(shortname); return NULL; } return shortname; } struct oidc_account* _getLoadedUnencryptedAccount( struct ipcPipe pipes, const char* short_name, const char* application_hint, const struct arguments* arguments) { struct oidc_account* account = db_getAccountDecryptedByShortname(short_name); if (account) { return account; } if (arguments->no_autoload) { ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); return NULL; } oidc_error_t autoload_error = oidcd_autoload(pipes, short_name, NULL, application_hint); switch (autoload_error) { case OIDC_SUCCESS: account = db_getAccountDecryptedByShortname(short_name); if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); } return account; case OIDC_EUSRPWCNCL: ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); return NULL; default: { const char* help = getHelp(); if (help != NULL) { char* h = strreplace(help, "", short_name); ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, oidc_serror(), h); secFree(h); return NULL; } ipc_writeOidcErrnoToPipe(pipes); return NULL; } } } struct oidc_account* _getLoadedUnencryptedAccountForIssuer( struct ipcPipe pipes, const char* issuer, const char* application_hint, const struct arguments* arguments) { struct oidc_account* account = NULL; list_t* accounts = db_findAccountsByIssuerUrl(issuer); if (accounts == NULL) { // no accounts loaded for this issuer if (arguments->no_autoload) { ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); return NULL; } char* defaultAccount = oidcd_queryDefaultAccountIssuer(pipes, issuer); if (defaultAccount == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); return NULL; } oidc_error_t autoload_error = oidcd_autoload(pipes, defaultAccount, issuer, application_hint); switch (autoload_error) { case OIDC_SUCCESS: account = db_getAccountDecryptedByShortname(defaultAccount); secFree(defaultAccount); return account; case OIDC_EUSRPWCNCL: ipc_writeToPipe(pipes, RESPONSE_ERROR, ACCOUNT_NOT_LOADED); secFree(defaultAccount); return NULL; default: { const char* help = getHelp(); if (help != NULL) { char* h = strreplace(help, "", defaultAccount); char* tmp = strreplace(h, "", issuer); secFree(h); h = tmp; ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, oidc_serror(), h); secFree(h); return NULL; } ipc_writeOidcErrnoToPipe(pipes); return NULL; } } } else if (accounts->len == 1) { // only one account loaded for this issuer -> use this one account = _db_decryptFoundAccount(list_at(accounts, 0)->val); secFreeList(accounts); } else { // more than 1 account loaded for this issuer char* defaultAccount = oidcd_queryDefaultAccountIssuer(pipes, issuer); account = db_getAccountDecryptedByShortname(defaultAccount); if (account == NULL) { account = _db_decryptFoundAccount( list_at(accounts, accounts->len - 1) ->val); // use the account that was loaded last } secFreeList(accounts); } if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); return NULL; } if (arguments->confirm || account_getConfirmationRequired(account)) { if (oidcd_getConfirmation(pipes, account_getName(account), issuer, application_hint) != OIDC_SUCCESS) { db_addAccountEncrypted(account); // reencrypting ipc_writeOidcErrnoToPipe(pipes); return NULL; } } return account; } void oidcd_handleTokenIssuer(struct ipcPipe pipes, char* issuer, const char* min_valid_period_str, const char* scope, const char* application_hint, const char* audience, const struct arguments* arguments) { agent_log(DEBUG, "Handle Token request from '%s' for issuer '%s'", application_hint, issuer); time_t min_valid_period = min_valid_period_str != NULL ? strToInt(min_valid_period_str) : 0; struct oidc_account* account = _getLoadedUnencryptedAccountForIssuer( pipes, issuer, application_hint, arguments); if (account == NULL) { return; } char* access_token = getAccessTokenUsingRefreshFlow(account, min_valid_period, scope, audience, pipes); if (access_token == NULL) { char* help = getHelpWithAccountInfo(account); db_addAccountEncrypted(account); // reencrypting if (help != NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, oidc_serror(), help); secFree(help); return; } ipc_writeOidcErrnoToPipe(pipes); return; } db_addAccountEncrypted(account); // reencrypting ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, access_token, account_getIssuerUrl(account), account_getTokenExpiresAt(account)); if (strValid(scope)) { secFree(access_token); } } void oidcd_handleReauthenticate(struct ipcPipe pipes, char* short_name, const struct arguments* arguments) { agent_log(DEBUG, "Handle Reauthentication request"); if (short_name == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Bad request. Required field '" IPC_KEY_SHORTNAME "' not present."); return; } struct oidc_account* account = _getLoadedUnencryptedAccount(pipes, short_name, NULL, arguments); if (account == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Could not get account config"); return; } // The account we just retrieved has a very short timeout and will be freed // soon if we don't increase it account_setDeath(account, arguments->lifetime ? arguments->lifetime + time(NULL) : 0); _handleGenFlows(pipes, account, "[\"" FLOW_VALUE_PASSWORD "\",\"" FLOW_VALUE_DEVICE "\",\"" FLOW_VALUE_CODE "\"]", NULL, 0, NULL, "1", arguments); } void oidcd_handleToken(struct ipcPipe pipes, char* short_name, const char* min_valid_period_str, const char* scope, const char* application_hint, const char* audience, const struct arguments* arguments) { agent_log(DEBUG, "Handle Token request from %s", application_hint); if (short_name == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Bad request. Required field '" IPC_KEY_SHORTNAME "' not present."); return; } time_t min_valid_period = min_valid_period_str != NULL ? strToInt(min_valid_period_str) : 0; struct oidc_account* account = _getLoadedUnencryptedAccount( pipes, short_name, application_hint, arguments); if (account == NULL) { return; } if (arguments->confirm || account_getConfirmationRequired(account)) { if (oidcd_getConfirmation(pipes, short_name, NULL, application_hint) != OIDC_SUCCESS) { db_addAccountEncrypted(account); // reencrypting ipc_writeOidcErrnoToPipe(pipes); return; } } char* access_token = getAccessTokenUsingRefreshFlow(account, min_valid_period, scope, audience, pipes); if (access_token == NULL) { char* help = getHelpWithAccountInfo(account); db_addAccountEncrypted(account); // reencrypting if (help != NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, oidc_serror(), help); secFree(help); return; } ipc_writeOidcErrnoToPipe(pipes); return; } db_addAccountEncrypted(account); // reencrypting ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, access_token, account_getIssuerUrl(account), account_getTokenExpiresAt(account)); if (strValid(scope)) { secFree(access_token); } } void oidcd_handleIdToken(struct ipcPipe pipes, const char* short_name, const char* issuer, const char* scope, const char* application_hint, const struct arguments* arguments) { agent_log(DEBUG, "Handle ID-Token request from %s", application_hint); struct oidc_account* account = short_name != NULL ? _getLoadedUnencryptedAccount( pipes, short_name, application_hint, arguments) : _getLoadedUnencryptedAccountForIssuer( pipes, issuer, application_hint, arguments); if (account == NULL) { return; } if (!(arguments->always_allow_idtoken || account_getAlwaysAllowId( account))) { // TODO account based does not work yet if (oidcd_getIdTokenConfirmation(pipes, short_name, issuer, application_hint) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); return; } } char* id_token = getIdToken(account, scope, pipes); db_addAccountEncrypted(account); // reencrypting if (id_token == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } ipc_writeToPipe(pipes, RESPONSE_STATUS_IDTOKEN, STATUS_SUCCESS, id_token, account_getIssuerUrl(account)); secFree(id_token); } void oidcd_handleRegister(struct ipcPipe pipes, const char* account_json, const char* flows_json_str, const char* access_token) { agent_log(DEBUG, "Handle Register request for flows: '%s'", flows_json_str); struct oidc_account* account = getAccountFromJSON(account_json); if (account == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } agent_log(DEBUG, "daeSetByUser is: %d", issuer_getDeviceAuthorizationEndpointIsSetByUser( account_getIssuer(account))); if (NULL != accountDB_findValue(account)) { secFreeAccount(account); ipc_writeToPipe( pipes, RESPONSE_ERROR, "An account with this shortname is already loaded. I will not " "register a new one."); return; } if (getIssuerConfig(account) != OIDC_SUCCESS) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; } agent_log(DEBUG, "daeSetByUser is: %d", issuer_getDeviceAuthorizationEndpointIsSetByUser( account_getIssuer(account))); list_t* flows = JSONArrayStringToList(flows_json_str); if (flows == NULL) { secFreeAccount(account); ipc_writeOidcErrnoToPipe(pipes); return; } char* res = dynamicRegistration(account, flows, access_token); secFreeList(flows); if (res == NULL) { ipc_writeOidcErrnoToPipe(pipes); secFreeAccount(account); return; } if (!isJSONObject(res)) { char* escaped = escapeCharInStr(res, '"'); ipc_writeToPipe(pipes, RESPONSE_ERROR_INFO, "Received no JSON formatted response.", escaped); secFree(escaped); secFreeAccount(account); return; } cJSON* json_res = stringToJson(res); if (jsonHasKey(json_res, OIDC_KEY_ERROR)) { char* error = parseForError(res); // frees res ipc_writeToPipe(pipes, RESPONSE_ERROR, error); secFree(error); secFreeAccount(account); return; } char* scopes = getJSONValue(json_res, OIDC_KEY_SCOPE); secFreeJson(json_res); if (!strSubStringCase(scopes, OIDC_SCOPE_OPENID) || !strSubStringCase(scopes, OIDC_SCOPE_OFFLINE_ACCESS)) { // did not get all scopes necessary for oidc-agent oidc_errno = OIDC_EUNSCOPE; ipc_writeToPipe(pipes, RESPONSE_ERROR_CLIENT, oidc_serror(), res); } else { ipc_writeToPipe(pipes, RESPONSE_SUCCESS_CLIENT_MAXSCOPES, res, account_getScopesSupported(account)); } secFree(scopes); secFree(res); secFreeAccount(account); return; } void oidcd_handleCodeExchange(struct ipcPipe pipes, const char* redirected_uri, const char* fromString) { if (redirected_uri == NULL) { oidc_setArgNullFuncError(__func__); ipc_writeOidcErrnoToPipe(pipes); return; } int fromGen = strToInt(fromString); agent_log(DEBUG, "Handle codeExchange request for redirect_uri '%s' from %s", redirected_uri, fromGen ? "oidc-gen" : "other (httpserver)"); struct codeState codeState = codeStateFromURI(redirected_uri); char* redirect_uri = codeState.uri; char* state = codeState.state; char* code = codeState.code; if (state == NULL || code == NULL || redirect_uri == NULL) { ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); return; } if (strlen(state) < 3) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "state malformed"); secFreeCodeState(codeState); return; } const unsigned char only_at = state[2] == '1' ? 1 : 0; struct codeExchangeEntry key = {.state = state}; agent_log(DEBUG, "Getting code_verifier and account info for state '%s'", state); struct codeExchangeEntry* cee = codeVerifierDB_findValue(&key); if (cee == NULL) { oidc_errno = OIDC_EWRONGSTATE; ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); return; } struct oidc_account* account = cee->account; if (account == NULL) { oidc_setInternalError("account found for state is NULL"); ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); secFreeCodeExchangeContent(cee); codeVerifierDB_removeIfFound(cee); return; } if (getIssuerConfig(account) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); secFreeCodeExchangeContent(cee); codeVerifierDB_removeIfFound(cee); return; } // agent_log(DEBUG, "code_verifier for state '%s' is '%s'", // state, cee->code_verifier); if (getAccessTokenUsingAuthCodeFlow(account, code, redirect_uri, cee->code_verifier, FORCE_NEW_TOKEN, pipes) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); secFreeCodeState(codeState); return; } if (account_refreshTokenIsValid(account) && (!fromGen || !only_at)) { char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); secFreeCodeState(codeState); db_addAccountEncrypted(account); secFree(cee->code_verifier); if (fromGen) { termHttpServer(cee->state); account->usedStateChecked = 1; } account_setUsedState(account, cee->state); codeVerifierDB_removeIfFound(cee); } else if (only_at && strValid(account_getAccessToken(account))) { ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, account_getAccessToken(account), account_getIssuerUrl(account), account_getTokenExpiresAt(account)); if (fromGen) { termHttpServer(cee->state); account->usedStateChecked = 1; } secFreeCodeState(codeState); secFreeCodeExchangeContent(cee); codeVerifierDB_removeIfFound(cee); } else { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Could not get a refresh token"); secFreeCodeState(codeState); secFreeCodeExchangeContent(cee); codeVerifierDB_removeIfFound(cee); } } void oidcd_handleDeviceLookup(struct ipcPipe pipes, const char* device_json, const char* only_at_str) { agent_log(DEBUG, "Handle deviceLookup request"); struct oidc_device_code* dc = getDeviceCodeFromJSON(device_json); if (dc == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } struct deviceCodeEntry key = {.device_code = dc->device_code}; agent_log(DEBUG, "Getting account for device_code '%s'", dc->device_code); struct deviceCodeEntry* dce = deviceCodeDB_findValue(&key); if (dce == NULL) { oidc_errno = OIDC_EWRONGDEVICECODE; ipc_writeOidcErrnoToPipe(pipes); return; } struct oidc_account* account = dce->account; if (account == NULL) { oidc_setInternalError("account found for device code is NULL"); ipc_writeOidcErrnoToPipe(pipes); secFreeDeviceCodeEntryContent(dce); deviceCodeDB_removeIfFound(dce); return; } if (getIssuerConfig(account) != OIDC_SUCCESS) { ipc_writeOidcErrnoToPipe(pipes); secFreeDeviceCode(dc); secFreeDeviceCodeEntryContent(dce); deviceCodeDB_removeIfFound(dce); return; } if (getAccessTokenUsingDeviceFlow(account, oidc_device_getDeviceCode(*dc), FORCE_NEW_TOKEN, pipes) != OIDC_SUCCESS) { secFreeDeviceCode(dc); if (oidc_errno == OIDC_EOIDC && (strequal(oidc_serror(), OIDC_SLOW_DOWN) || strequal(oidc_serror(), OIDC_AUTHORIZATION_PENDING))) { pass; } else { secFreeDeviceCodeEntryContent(dce); deviceCodeDB_removeIfFound(dce); } ipc_writeOidcErrnoToPipe(pipes); return; } secFreeDeviceCode(dc); const int only_at = strToInt(only_at_str); if (account_refreshTokenIsValid(account) && !only_at) { char* json = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, json); secFree(json); db_addAccountEncrypted(account); secFree(dce->device_code); } else if (only_at && strValid(account_getAccessToken(account))) { ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, account_getAccessToken(account), account_getIssuerUrl(account), account_getTokenExpiresAt(account)); secFreeDeviceCodeEntryContent(dce); } else { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Could not get a refresh token"); secFreeDeviceCodeEntryContent(dce); } deviceCodeDB_removeIfFound(dce); } void oidcd_handleStateLookUp(struct ipcPipe pipes, char* state) { agent_log(DEBUG, "Handle stateLookUp request"); if (state == NULL || strlen(state) < 3) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "state malformed"); return; } const unsigned char only_at = state[2] == '1' ? 1 : 0; struct oidc_account key = {.usedState = state}; matchFunction oldMatch = accountDB_setMatchFunction((matchFunction)account_matchByState); struct oidc_account* account = db_getAccountDecrypted(&key); accountDB_setMatchFunction(oldMatch); if (account == NULL) { char* info = oidc_sprintf("No loaded account info found for state=%s", state); ipc_writeToPipe(pipes, RESPONSE_STATUS_INFO, STATUS_NOTFOUND, info); secFree(info); return; } if (account->usedStateChecked) { ipc_writeToPipe(pipes, RESPONSE_STATUS_INFO, STATUS_FOUNDBUTDONE, "Account config already retrieved from another oidc-gen"); db_addAccountEncrypted(account); // reencrypting return; } account->usedStateChecked = 1; if (only_at) { ipc_writeToPipe(pipes, RESPONSE_STATUS_ACCESS, STATUS_SUCCESS, account_getAccessToken(account), account_getIssuerUrl(account), account_getTokenExpiresAt(account)); accountDB_removeIfFound(account); } else { char* config = accountToJSONString(account); ipc_writeToPipe(pipes, RESPONSE_STATUS_CONFIG, STATUS_SUCCESS, config); secFree(config); db_addAccountEncrypted(account); // reencrypting } termHttpServer(state); } void oidcd_handleTermHttp(struct ipcPipe pipes, const char* state) { termHttpServer(state); ipc_writeToPipe(pipes, RESPONSE_SUCCESS); } void oidcd_handleLock(struct ipcPipe pipes, const char* password, int _lock) { if (_lock) { if (lock(password) == OIDC_SUCCESS) { ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, "Agent locked"); return; } } else { if (unlock(password) == OIDC_SUCCESS) { ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, "Agent unlocked"); return; } } ipc_writeOidcErrnoToPipe(pipes); } void oidcd_handleScopes(struct ipcPipe pipes, const char* issuer_url, const char* cert_path) { if (issuer_url == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "Bad Request: issuer url not given"); return; } agent_log(DEBUG, "Handle scope lookup request for %s", issuer_url); char* scopes = getScopesSupportedFor(issuer_url, cert_path); if (scopes == NULL) { ipc_writeOidcErrnoToPipe(pipes); return; } ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, scopes); secFree(scopes); } list_t* _getNameListLoadedAccounts() { list_t* accounts = accountDB_getList(); list_t* names = list_new(); list_node_t* node; list_iterator_t* it = list_iterator_new(accounts, LIST_HEAD); while ((node = list_iterator_next(it))) { char* name = account_getName(node->val); list_rpush(names, list_node_new(name)); } list_iterator_destroy(it); return names; } void oidcd_handleListLoadedAccounts(struct ipcPipe pipes) { list_t* names = _getNameListLoadedAccounts(); char* jsonList = listToJSONArrayString(names); ipc_writeToPipe(pipes, RESPONSE_SUCCESS_LOADEDACCOUNTS, jsonList); secFree(jsonList); secFreeList(names); } char* _argumentsToOptionsText(const struct arguments* arguments) { const char* const fmt = "Lifetime:\t\t%s\n" "Confirm:\t\t%s\n" "Autoload:\t\t%s\n" "Auto Re-authenticate:\t%s\n" "Use custom URI scheme:\t%s\n" "Webserver:\t\t%s\n" "Store password:\t\t%s\n" "Allow ID-Token:\t\t%s\n" "Group:\t\t\t%s\n" "Seccomp:\t\t%s\n" "Daemon:\t\t\t%s\n" "Log Debug:\t\t%s\n" "Log to stderr:\t\t%s\n"; char* lifetime = arguments->lifetime ? oidc_sprintf("%lu seconds", arguments->lifetime) : oidc_strcopy("Forever"); char* pw_lifetime = arguments->pw_lifetime.argProvided ? arguments->pw_lifetime.lifetime ? oidc_sprintf("%lu seconds", arguments->pw_lifetime.lifetime) : oidc_strcopy("Forever") : NULL; char* store_pw = arguments->pw_lifetime.argProvided ? oidc_sprintf("true - %s", pw_lifetime) : oidc_strcopy("false"); secFree(pw_lifetime); char* options = oidc_sprintf(fmt, lifetime, arguments->confirm ? "true" : "false", arguments->no_autoload ? "false" : "true", arguments->no_autoreauthenticate ? "false" : "true", arguments->no_scheme ? "false" : "true", arguments->no_webserver ? "false" : "true", store_pw, arguments->always_allow_idtoken ? "true" : "false", arguments->group ? arguments->group : "false", arguments->seccomp ? "true" : "false", arguments->console ? "false" : "true", arguments->debug ? "true" : "false", arguments->log_console ? "true" : "false"); secFree(lifetime); secFree(store_pw); return options; } char* _argumentsToCommandLineOptions(const struct arguments* arguments) { list_t* options = list_new(); options->match = (matchFunction)strequal; options->free = (void(*)(void*))_secFree; if (arguments->lifetime) { list_rpush(options, list_node_new(oidc_sprintf("--lifetime=%ld", arguments->lifetime))); } if (arguments->pw_lifetime.argProvided) { if (arguments->pw_lifetime.lifetime) { list_rpush(options, list_node_new(oidc_sprintf("--pw-store=%ld", arguments->pw_lifetime.lifetime))); } else { list_rpush(options, list_node_new(oidc_strcopy("--pw-store"))); } } if (arguments->confirm) { list_rpush(options, list_node_new(oidc_strcopy("--confirm"))); } if (arguments->no_autoload) { list_rpush(options, list_node_new(oidc_strcopy("--no-autoload"))); } if (arguments->no_autoreauthenticate) { list_rpush(options, list_node_new(oidc_strcopy("--no-autoreauthenticate"))); } if (arguments->no_scheme) { list_rpush(options, list_node_new(oidc_strcopy("--no-scheme"))); } if (arguments->no_webserver) { list_rpush(options, list_node_new(oidc_strcopy("--no-webserver"))); } if (arguments->always_allow_idtoken) { list_rpush(options, list_node_new(oidc_strcopy("--always-allow-idtoken"))); } if (arguments->always_allow_idtoken) { list_rpush(options, list_node_new(oidc_strcopy("--always-allow-idtoken"))); } if (arguments->group) { list_rpush(options, list_node_new(oidc_sprintf("--with-group", arguments->group))); } if (arguments->seccomp) { list_rpush(options, list_node_new(oidc_strcopy("--seccomp"))); } if (arguments->console) { list_rpush(options, list_node_new(oidc_strcopy("--console"))); } if (arguments->debug) { list_rpush(options, list_node_new(oidc_strcopy("--debug"))); } if (arguments->log_console) { list_rpush(options, list_node_new(oidc_strcopy("--log-stderr"))); } char* opts = listToDelimitedString(options, " "); secFreeList(options); return opts; } void oidcd_handleAgentStatus(struct ipcPipe pipes, const struct arguments* arguments) { const char* fmt = "####################################\n" "## oidc-agent status ##\n" "####################################\n" "\nThis agent is running version %s.\n\nThis agent was started with the " "following options:\n%s\nCurrently there are %d accounts loaded: %s\n\n"; list_t* names = _getNameListLoadedAccounts(); int num_loaded = 0; char* names_str = NULL; if (names != NULL) { num_loaded = names->len; names_str = listToDelimitedString(names, ", "); } char* options = _argumentsToOptionsText(arguments); char* status = oidc_sprintf(fmt, VERSION, options, num_loaded, names_str ?: ""); secFree(options); secFree(names_str); ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO, status); secFreeList(names); secFree(status); } void oidcd_handleAgentStatusJSON(struct ipcPipe pipes, const struct arguments* arguments) { list_t* names = _getNameListLoadedAccounts(); cJSON* names_j = listToJSONArray(names); secFreeList(names); char* options = _argumentsToCommandLineOptions(arguments); cJSON* json = generateJSONObject("version", cJSON_String, VERSION, "command_line_options", cJSON_String, options, NULL); secFree(options); cJSON_AddItemToObject(json, "loaded_accounts", names_j); // names_j will freed with json char* info = jsonToString(json); secFreeJson(json); ipc_writeToPipe(pipes, RESPONSE_SUCCESS_INFO_OBJECT, info); secFree(info); } void oidcd_handleFileWrite(struct ipcPipe pipes, const char* filename, const char* data) { fileDB_addValue(filename, data); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } void oidcd_handleFileRead(struct ipcPipe pipes, const char* filename) { char* data = fileDB_findValue(filename); if (data == NULL) { ipc_writeToPipe(pipes, RESPONSE_ERROR, "File not found"); return; } ipc_writeToPipe(pipes, RESPONSE_SUCCESS_FILE, data); secFree(data); } void oidcd_handleFileRemove(struct ipcPipe pipes, const char* filename) { fileDB_removeIfFound(filename); ipc_writeToPipe(pipes, RESPONSE_STATUS_SUCCESS); } oidc-agent-4.2.6/src/oidc-agent/oidcd/oidcd.h0000644000175000017500000000030214120404223020204 0ustar marcusmarcus#ifndef OIDC_DAEMON_H #define OIDC_DAEMON_H #include "ipc/pipe.h" #include "oidc-agent/oidc-agent_options.h" int oidcd_main(struct ipcPipe, const struct arguments*); #endif // OIDC_DAEMON_H oidc-agent-4.2.6/src/oidc-agent/oidcd/deviceCodeEntry.h0000644000175000017500000000104514167074355022227 0ustar marcusmarcus#ifndef OIDC_AGENT_DEVICECODEENTRY_H #define OIDC_AGENT_DEVICECODEENTRY_H #include "account/account.h" struct deviceCodeEntry { char* device_code; struct oidc_account* account; }; int dce_match(struct deviceCodeEntry* a, struct deviceCodeEntry* b); struct deviceCodeEntry* createDeviceCodeEntry(const char* device_code, struct oidc_account* account); void secFreeDeviceCodeEntryContent(struct deviceCodeEntry*); #endif // OIDC_AGENT_DEVICECODEENTRY_H oidc-agent-4.2.6/src/oidc-agent/oidcd/internal_request_handler.h0000644000175000017500000000042014120404223024204 0ustar marcusmarcus#ifndef OIDCD_INTERNAL_REQUEST_HANDLER_H #define OIDCD_INTERNAL_REQUEST_HANDLER_H #include "ipc/pipe.h" void oidcd_handleUpdateRefreshToken(const struct ipcPipe, const char*, const char*); #endif // OIDCD_INTERNAL_REQUEST_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/oidcd/oidcd_handler.h0000644000175000017500000000631014167074355021732 0ustar marcusmarcus#ifndef OIDCD_HANDLER_H #define OIDCD_HANDLER_H #include "account/account.h" #include "ipc/pipe.h" #include "oidc-agent/oidc-agent_options.h" void oidcd_handleGen(struct ipcPipe pipes, const char* account_json, const char* flow, const char* nowebserver_str, const char* noscheme_str, const char* only_at, const struct arguments* arguments); void oidcd_handleReauthenticate(struct ipcPipe pipes, char* short_name, const struct arguments* arguments); void oidcd_handleAdd(struct ipcPipe, const char* account_json, const char* timeout_str, const char* confirm_str, const char* alwaysallowid); void oidcd_handleDelete(struct ipcPipe, const char* account_json); void oidcd_handleDeleteClient(struct ipcPipe pipes, const char* client_uri, const char* registration_access_token, const char* cert_path); void oidcd_handleRm(struct ipcPipe, char* account_name); void oidcd_handleRemoveAll(struct ipcPipe); void oidcd_handleToken(struct ipcPipe, char* short_name, const char* min_valid_period_str, const char* scope, const char* application_hint, const char* audience, const struct arguments*); void oidcd_handleTokenIssuer(struct ipcPipe pipes, char* issuer, const char* min_valid_period_str, const char* scope, const char* application_hint, const char* audience, const struct arguments* arguments); void oidcd_handleIdToken(struct ipcPipe pipes, const char* short_name, const char* issuer, const char* scope, const char* application_hint, const struct arguments* arguments); void oidcd_handleRegister(struct ipcPipe, const char* account_json, const char* json_str, const char* access_token); void oidcd_handleCodeExchange(struct ipcPipe pipes, const char* redirected_uri, const char* fromString); void oidcd_handleStateLookUp(struct ipcPipe, char* state); void oidcd_handleDeviceLookup(struct ipcPipe, const char* device_json, const char* only_at_str); void oidcd_handleScopes(struct ipcPipe pipes, const char* issuer_url, const char* cert_path); void oidcd_handleListLoadedAccounts(struct ipcPipe pipes); void oidcd_handleTermHttp(struct ipcPipe, const char* state); void oidcd_handleLock(struct ipcPipe, const char* password, int _lock); void oidcd_handleAgentStatus(struct ipcPipe pipes, const struct arguments* arguments); void oidcd_handleAgentStatusJSON(struct ipcPipe pipes, const struct arguments* arguments); void oidcd_handleFileRemove(struct ipcPipe pipes, const char* filename); void oidcd_handleFileRead(struct ipcPipe pipes, const char* filename); void oidcd_handleFileWrite(struct ipcPipe pipes, const char* filename, const char* data); #endif // OIDCD_HANDLER_H oidc-agent-4.2.6/src/oidc-agent/oidcd/deviceCodeEntry.c0000644000175000017500000000134614167074355022226 0ustar marcusmarcus#include "deviceCodeEntry.h" #include "utils/matcher.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" void secFreeDeviceCodeEntryContent(struct deviceCodeEntry* entry) { secFreeAccount(entry->account); secFree(entry->device_code); } struct deviceCodeEntry* createDeviceCodeEntry(const char* device_code, struct oidc_account* account) { struct deviceCodeEntry* entry = secAlloc(sizeof(struct deviceCodeEntry)); entry->account = account; entry->device_code = oidc_strcopy(device_code); return entry; } int dce_match(struct deviceCodeEntry* a, struct deviceCodeEntry* b) { return matchStrings(a->device_code, b->device_code); } oidc-agent-4.2.6/src/oidc-agent/oidcd/codeExchangeEntry.h0000644000175000017500000000117714120404223022534 0ustar marcusmarcus#ifndef OIDC_CODEEXCHANGEENTRY_H #define OIDC_CODEEXCHANGEENTRY_H #include "account/account.h" struct codeExchangeEntry { char* state; struct oidc_account* account; char* code_verifier; }; int cee_matchByState(struct codeExchangeEntry* a, struct codeExchangeEntry* b); struct codeExchangeEntry* createCodeExchangeEntry(char* state, struct oidc_account* account, char* code_verifier); void secFreeCodeExchangeContent(struct codeExchangeEntry* cee); #endif // OIDC_CODEEXCHANGEENTRY_H oidc-agent-4.2.6/src/oidc-agent/oidcd/oidcd.c0000644000175000017500000001727314167074355020242 0ustar marcusmarcus#include "oidcd.h" #include "account/account.h" #include "defines/ipc_values.h" #include "deviceCodeEntry.h" #include "oidc-agent/agent_state.h" #include "oidc-agent/oidc/device_code.h" #include "oidc-agent/oidcd/codeExchangeEntry.h" #include "oidc-agent/oidcd/oidcd_handler.h" #include "utils/accountUtils.h" #include "utils/agentLogger.h" #include "utils/crypt/crypt.h" #include "utils/crypt/memoryCrypt.h" #include "utils/db/account_db.h" #include "utils/db/codeVerifier_db.h" #include "utils/db/deviceCode_db.h" #include "utils/db/file_db.h" #include "utils/json.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" int oidcd_main(struct ipcPipe pipes, const struct arguments* arguments) { logger_open("oidc-agent.d"); initCrypt(); initMemoryCrypt(); codeVerifierDB_new(); codeVerifierDB_setFreeFunction((freeFunction)_secFree); codeVerifierDB_setMatchFunction((matchFunction)cee_matchByState); deviceCodeDB_new(); deviceCodeDB_setFreeFunction((freeFunction)_secFree); deviceCodeDB_setMatchFunction((matchFunction)dce_match); accountDB_new(); accountDB_setFreeFunction((freeFunction)_secFreeAccount); accountDB_setMatchFunction((matchFunction)account_matchByName); fileDB_new(); time_t minDeath = 0; while (1) { minDeath = getMinAccountDeath(); char* q = ipc_readFromPipeWithTimeout(pipes, minDeath); if (q == NULL) { if (oidc_errno == OIDC_ETIMEOUT) { struct oidc_account* death = NULL; while ((death = getDeathAccount()) != NULL) { accountDB_removeIfFound(death); } continue; } // A real error and no timeout agent_log(ERROR, "%s", oidc_serror()); if (oidc_errno == OIDC_EIPCDIS) { exit(EXIT_FAILURE); } if (ipc_writeOidcErrnoToPipe(pipes) == OIDC_SUCCESS) { // Try to communicate the error back continue; } exit(EXIT_FAILURE); } INIT_KEY_VALUE(IPC_KEY_REQUEST, IPC_KEY_SHORTNAME, IPC_KEY_MINVALID, IPC_KEY_CONFIG, IPC_KEY_FLOW, IPC_KEY_USECUSTOMSCHEMEURL, IPC_KEY_REDIRECTEDURI, OIDC_KEY_STATE, IPC_KEY_AUTHORIZATION, OIDC_KEY_SCOPE, IPC_KEY_DEVICE, IPC_KEY_FROMGEN, IPC_KEY_LIFETIME, IPC_KEY_PASSWORD, IPC_KEY_APPLICATIONHINT, IPC_KEY_CONFIRM, IPC_KEY_ISSUERURL, IPC_KEY_NOSCHEME, IPC_KEY_CERTPATH, IPC_KEY_AUDIENCE, IPC_KEY_ALWAYSALLOWID, IPC_KEY_FILENAME, IPC_KEY_DATA, OIDC_KEY_REGISTRATION_CLIENT_URI, OIDC_KEY_REGISTRATION_ACCESS_TOKEN, IPC_KEY_ONLYAT); if (getJSONValuesFromString(q, pairs, sizeof(pairs) / sizeof(*pairs)) < 0) { ipc_writeToPipe(pipes, RESPONSE_BADREQUEST, oidc_serror()); secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); secFree(q); continue; } secFree(q); KEY_VALUE_VARS(request, shortname, minvalid, config, flow, nowebserver, redirectedUri, state, authorization, scope, device, fromGen, lifetime, password, applicationHint, confirm, issuer, noscheme, cert_path, audience, alwaysallowid, filename, data, registration_client_uri, registration_access_token, only_at); // Gives variables for key_value values; // e.g. _request=pairs[0].value if (_request == NULL) { ipc_writeToPipe(pipes, RESPONSE_BADREQUEST, "No request type."); secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); continue; } if (strequal(_request, REQUEST_VALUE_CHECK)) { // Allow check in all cases ipc_writeToPipe(pipes, RESPONSE_SUCCESS); secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); continue; } if (agent_state.lock_state.locked) { // If locked allow only unlock if (strequal(_request, REQUEST_VALUE_UNLOCK)) { oidcd_handleLock(pipes, _password, 0); } else { oidc_errno = OIDC_ELOCKED; ipc_writeOidcErrnoToPipe(pipes); } secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); continue; } if (strequal(_request, REQUEST_VALUE_GEN)) { oidcd_handleGen(pipes, _config, _flow, _nowebserver, _noscheme, _only_at, arguments); } else if (strequal(_request, REQUEST_VALUE_REAUTHENTICATE)) { oidcd_handleReauthenticate(pipes, _shortname, arguments); } else if (strequal(_request, REQUEST_VALUE_CODEEXCHANGE)) { oidcd_handleCodeExchange(pipes, _redirectedUri, _fromGen); } else if (strequal(_request, REQUEST_VALUE_STATELOOKUP)) { oidcd_handleStateLookUp(pipes, _state); } else if (strequal(_request, REQUEST_VALUE_DEVICELOOKUP)) { oidcd_handleDeviceLookup(pipes, _device, _only_at); } else if (strequal(_request, REQUEST_VALUE_ADD)) { oidcd_handleAdd(pipes, _config, _lifetime, _confirm, _alwaysallowid); } else if (strequal(_request, REQUEST_VALUE_REMOVE)) { oidcd_handleRm(pipes, _shortname); } else if (strequal(_request, REQUEST_VALUE_REMOVEALL)) { oidcd_handleRemoveAll(pipes); } else if (strequal(_request, REQUEST_VALUE_DELETE)) { oidcd_handleDelete(pipes, _config); } else if (strequal(_request, REQUEST_VALUE_DELETECLIENT)) { oidcd_handleDeleteClient(pipes, _registration_client_uri, _registration_access_token, _cert_path); } else if (strequal(_request, REQUEST_VALUE_STATUS)) { oidcd_handleAgentStatus(pipes, arguments); } else if (strequal(_request, REQUEST_VALUE_STATUS_JSON)) { oidcd_handleAgentStatusJSON(pipes, arguments); } else if (strequal(_request, REQUEST_VALUE_ACCESSTOKEN)) { if (_shortname) { oidcd_handleToken(pipes, _shortname, _minvalid, _scope, _applicationHint, _audience, arguments); } else if (_issuer) { oidcd_handleTokenIssuer(pipes, _issuer, _minvalid, _scope, _applicationHint, _audience, arguments); } else { // global default oidc_errno = OIDC_NOTIMPL; // TODO ipc_writeOidcErrnoToPipe(pipes); } } else if (strequal(_request, REQUEST_VALUE_IDTOKEN)) { if (_shortname || _issuer) { oidcd_handleIdToken(pipes, _shortname, _issuer, _scope, _applicationHint, arguments); } else { // global default oidc_errno = OIDC_NOTIMPL; // TODO ipc_writeOidcErrnoToPipe(pipes); } } else if (strequal(_request, REQUEST_VALUE_REGISTER)) { oidcd_handleRegister(pipes, _config, _flow, _authorization); } else if (strequal(_request, REQUEST_VALUE_TERMHTTP)) { oidcd_handleTermHttp(pipes, _state); } else if (strequal(_request, REQUEST_VALUE_FILEWRITE)) { oidcd_handleFileWrite(pipes, _filename, _data); } else if (strequal(_request, REQUEST_VALUE_FILEREAD)) { oidcd_handleFileRead(pipes, _filename); } else if (strequal(_request, REQUEST_VALUE_FILEREMOVE)) { oidcd_handleFileRemove(pipes, _filename); } else if (strequal(_request, REQUEST_VALUE_SCOPES)) { oidcd_handleScopes(pipes, _issuer, _cert_path); } else if (strequal(_request, REQUEST_VALUE_LOADEDACCOUNTS)) { oidcd_handleListLoadedAccounts(pipes); } else if (strequal(_request, REQUEST_VALUE_LOCK)) { oidcd_handleLock(pipes, _password, 1); } else if (strequal(_request, REQUEST_VALUE_UNLOCK)) { oidc_errno = OIDC_ENOTLOCKED; ipc_writeOidcErrnoToPipe(pipes); } else { // Unknown request type ipc_writeToPipe(pipes, RESPONSE_BADREQUEST, "Unknown request type."); } secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); } return EXIT_FAILURE; } oidc-agent-4.2.6/src/oidc-agent/oidcd/parse_internal.h0000644000175000017500000000046614167074355022167 0ustar marcusmarcus#ifndef OIDCAGENT_INTERNAL_PARSER_H #define OIDCAGENT_INTERNAL_PARSER_H #include "utils/oidc_error.h" char* parseForConfig(char* res); char* parseForInfo(char* res); oidc_error_t parseForErrorCode(char* res); char* parseStateLookupRes(char* res); #endif // OIDCAGENT_INTERNAL_PARSER_H oidc-agent-4.2.6/src/oidc-agent/oidcd/internal_request_handler.c0000644000175000017500000000174114167074355024232 0ustar marcusmarcus#include "internal_request_handler.h" #include "defines/ipc_values.h" #include "ipc/pipe.h" #include "utils/agentLogger.h" #include "utils/memory.h" #include "utils/parseJson.h" void oidcd_handleUpdateRefreshToken(const struct ipcPipe pipes, const char* short_name, const char* refresh_token) { char* res = ipc_communicateThroughPipe(pipes, INT_REQUEST_UPD_REFRESH, short_name, refresh_token); char* error = parseForError(res); if (error == NULL) { agent_log(DEBUG, "Successfully updated refresh token for '%s'", short_name); return; } secFree(error); agent_log( WARNING, "WARNING: Received new refresh token from OIDC Provider. It's most " "likely that the old one was therefore revoked. Updating the config " "file failed. You may want to revoke the new refresh token or pass it " "to oidc-gen --rt"); } oidc-agent-4.2.6/src/oidc-agent/oidcd/parse_internal.c0000644000175000017500000000443714167074355022164 0ustar marcusmarcus#include "parse_internal.h" #include "defines/ipc_values.h" #include "utils/agentLogger.h" #include "utils/json.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" char* parseForConfig(char* res) { INIT_KEY_VALUE(INT_IPC_KEY_OIDCERRNO, IPC_KEY_CONFIG); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); return NULL; } secFree(res); KEY_VALUE_VARS(oidc_errno, config); if (_oidc_errno) { oidc_errno = strToInt(_oidc_errno); secFree(_oidc_errno); } return _config; } char* parseForInfo(char* res) { INIT_KEY_VALUE(IPC_KEY_INFO, OIDC_KEY_ERROR); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); return NULL; } secFree(res); KEY_VALUE_VARS(info, error); if (_error) { oidc_errno = OIDC_EERROR; oidc_seterror(_error); secFree(_info); secFree(_error); return NULL; } return _info; } oidc_error_t parseForErrorCode(char* res) { INIT_KEY_VALUE(INT_IPC_KEY_OIDCERRNO); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); return oidc_errno; } secFree(res); KEY_VALUE_VARS(oidc_errno); if (_oidc_errno) { oidc_errno = strToInt(_oidc_errno); secFree(_oidc_errno); return oidc_errno; } return OIDC_SUCCESS; } char* parseStateLookupRes(char* res) { INIT_KEY_VALUE(IPC_KEY_STATUS, IPC_KEY_CONFIG, OIDC_KEY_ERROR); if (CALL_GETJSONVALUES(res) < 0) { secFree(res); return NULL; } KEY_VALUE_VARS(status, config, error); secFree(res); if (_error != NULL) { agent_log(ERROR, _error); SEC_FREE_KEY_VALUES(); return NULL; } if (_config) { char* config = oidc_strcopy(_config); SEC_FREE_KEY_VALUES(); return config; } if (strcaseequal(_status, STATUS_NOTFOUND)) { SEC_FREE_KEY_VALUES(); oidc_errno = OIDC_EWRONGSTATE; return NULL; } SEC_FREE_KEY_VALUES(); return NULL; } oidc-agent-4.2.6/src/oidc-agent/oidcd/codeExchangeEntry.c0000644000175000017500000000162314167074355022547 0ustar marcusmarcus#include "codeExchangeEntry.h" #include "utils/matcher.h" void secFreeCodeExchangeContent(struct codeExchangeEntry* cee) { secFreeAccount(cee->account); secFree(cee->code_verifier); secFree(cee->state); } void secFreeCodeExchangeEntry(struct codeExchangeEntry* cee) { secFreeCodeExchangeContent(cee); secFree(cee); } struct codeExchangeEntry* createCodeExchangeEntry(char* state, struct oidc_account* account, char* code_verifier) { struct codeExchangeEntry* cee = secAlloc(sizeof(struct codeExchangeEntry)); cee->account = account; cee->state = state; cee->code_verifier = code_verifier; return cee; } int cee_matchByState(struct codeExchangeEntry* a, struct codeExchangeEntry* b) { return matchStrings(a->state, b->state); } oidc-agent-4.2.6/src/oidc-add/0000755000175000017500000000000014167074355015352 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-add/oidc-add_options.c0000644000175000017500000001557614167074355020753 0ustar marcusmarcus#include "oidc-add_options.h" #include "defines/settings.h" #include "utils/commonFeatures.h" #include "utils/prompt_mode.h" #include "utils/string/stringUtils.h" #define OPT_SECCOMP 1 #define OPT_PW_STORE 2 #define OPT_PW_KEYRING 3 #define OPT_PW_CMD 4 #define OPT_ALWAYS_ALLOW_IDTOKEN 5 #define OPT_PW_PROMPT 6 #define OPT_PW_FILE 7 #define OPT_REMOTE 8 #define OPT_PW_ENV 9 #define OPT_PW_GPG 10 static struct argp_option options[] = { {0, 0, 0, 0, "General:", 1}, {"remove", 'r', 0, 0, "The account configuration is removed, not added", 1}, {"remove-all", 'R', 0, 0, "Removes all account configurations currently loaded", 1}, {"list", 'l', 0, 0, "Lists all configured account configurations", 1}, {"loaded", 'a', 0, 0, "Lists the currently loaded account configurations", 1}, {"print", 'p', 0, 0, "Prints the encrypted account configuration and exits", 1}, {"lifetime", 't', "TIME", 0, "Set a maximum lifetime in seconds when adding the account configuration", 1}, {"lock", 'x', 0, 0, "Lock agent", 1}, {"unlock", 'X', 0, 0, "Unlock agent", 1}, {"pw-store", OPT_PW_STORE, "TIME", 1, "Keeps the encryption password encrypted in memory for TIME seconds. " "Default value for TIME: Forever", 1}, #ifndef __APPLE__ {"pw-keyring", OPT_PW_KEYRING, 0, 0, "Stores the used encryption password in the systems' keyring", 1}, #endif {"pw-env", OPT_PW_ENV, OIDC_PASSWORD_ENV_NAME, OPTION_ARG_OPTIONAL, "Reads the encryption password from the passed environment variable " "(default: " OIDC_PASSWORD_ENV_NAME "), instead of prompting the user", 1}, {"pw-cmd", OPT_PW_CMD, "CMD", 0, "Command from which the agent can read the encryption password", 1}, {"pw-file", OPT_PW_FILE, "FILE", 0, "Uses the first line of FILE as the encryption password.", 1}, {"pw-gpg", OPT_PW_GPG, "KEY_ID", 0, "Uses the passed GPG KEY for encryption", 1}, {"pw-pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, {"gpg", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, {"pgp", OPT_PW_GPG, "KEY_ID", OPTION_ALIAS, NULL, 1}, {"confirm", 'c', 0, 0, "Require user confirmation when an application requests an access token " "for this configuration", 1}, {"pw-prompt", OPT_PW_PROMPT, "cli|gui", 0, "Change the mode how oidc-add should prompt for passwords. The default is " "'cli'.", 1}, #ifndef __APPLE__ {"seccomp", OPT_SECCOMP, 0, 0, "Enables seccomp system call filtering; allowing only predefined system " "calls.", 1}, #endif {"always-allow-idtoken", OPT_ALWAYS_ALLOW_IDTOKEN, 0, 0, "Always allow id-token requests without manual approval by the user for " "this account configuration.", 1}, {"remote", OPT_REMOTE, 0, 0, "Use a remote central oidc-agent, instead of a local one.", 1}, {0, 0, 0, 0, "Verbosity:", 2}, {"debug", 'g', 0, 0, "Sets the log level to DEBUG", 2}, {"verbose", 'v', 0, 0, "Enables verbose mode", 2}, {0, 0, 0, 0, "Help:", -1}, {0, 'h', 0, OPTION_HIDDEN, 0, -1}, {0, 0, 0, 0, 0, 0}}; static error_t parse_opt(int key, char* arg, struct argp_state* state) { struct arguments* arguments = state->input; switch (key) { case 'r': arguments->remove = 1; break; case 'R': arguments->removeAll = 1; break; case 'g': arguments->debug = 1; break; case 'v': arguments->verbose = 1; break; case 'p': arguments->print = 1; break; case 'l': arguments->listConfigured = 1; break; case 'a': arguments->listLoaded = 1; break; case 'x': arguments->lock = 1; break; case 'X': arguments->unlock = 1; break; case 'c': arguments->confirm = 1; break; case OPT_PW_ENV: arguments->pw_env = arg ?: OIDC_PASSWORD_ENV_NAME; break; case OPT_PW_CMD: arguments->pw_cmd = arg; break; case OPT_PW_FILE: arguments->pw_file = arg; break; case OPT_PW_GPG: arguments->pw_gpg = arg; break; case OPT_PW_KEYRING: arguments->pw_keyring = 1; break; case OPT_PW_PROMPT: if (strequal(arg, "cli")) { arguments->pw_prompt_mode = PROMPT_MODE_CLI; } else if (strequal(arg, "gui")) { arguments->pw_prompt_mode = PROMPT_MODE_GUI; common_assertOidcPrompt(); } else { return ARGP_ERR_UNKNOWN; } set_pw_prompt_mode(arguments->pw_prompt_mode); break; case OPT_PW_STORE: if (arg == NULL) { arguments->pw_lifetime.argProvided = ARG_PROVIDED_BUT_USES_DEFAULT; break; } if (!isdigit(*arg)) { return ARGP_ERR_UNKNOWN; } arguments->pw_lifetime.lifetime = strToULong(arg); arguments->pw_lifetime.argProvided = 1; break; case OPT_ALWAYS_ALLOW_IDTOKEN: arguments->always_allow_idtoken = 1; break; case 't': if (!isdigit(*arg)) { return ARGP_ERR_UNKNOWN; } arguments->lifetime.lifetime = strToInt(arg); arguments->lifetime.argProvided = 1; break; case OPT_SECCOMP: arguments->seccomp = 1; break; case OPT_REMOTE: arguments->remote = 1; break; case 'h': argp_state_help(state, state->out_stream, ARGP_HELP_STD_HELP); break; case ARGP_KEY_ARG: if (state->arg_num >= 1) { argp_usage(state); } arguments->args[state->arg_num] = arg; break; case ARGP_KEY_END: if (arguments->listConfigured || arguments->listLoaded || arguments->lock || arguments->unlock || arguments->removeAll) { break; } if (state->arg_num < 1) { argp_usage(state); } break; default: return ARGP_ERR_UNKNOWN; } return 0; } static char args_doc[] = "ACCOUNT_SHORTNAME | -a | -l | -x | -X | -R"; static char doc[] = "oidc-add -- A client for adding and removing accounts to the oidc-agent"; struct argp argp = {options, parse_opt, args_doc, doc, 0, 0, 0}; void initArguments(struct arguments* arguments) { arguments->remove = 0; arguments->removeAll = 0; arguments->debug = 0; arguments->verbose = 0; arguments->listConfigured = 0; arguments->listLoaded = 0; arguments->print = 0; arguments->lifetime.argProvided = 0; arguments->lifetime.lifetime = 0; arguments->lock = 0; arguments->unlock = 0; arguments->args[0] = NULL; arguments->seccomp = 0; arguments->pw_lifetime.argProvided = 0; arguments->pw_lifetime.lifetime = 0; arguments->pw_keyring = 0; arguments->pw_cmd = NULL; arguments->pw_file = NULL; arguments->pw_gpg = NULL; arguments->pw_env = NULL; arguments->confirm = 0; arguments->always_allow_idtoken = 0; arguments->remote = 0; arguments->pw_prompt_mode = PROMPT_MODE_CLI; set_pw_prompt_mode(arguments->pw_prompt_mode); } oidc-agent-4.2.6/src/oidc-add/parse_ipc.h0000644000175000017500000000024114120404223017441 0ustar marcusmarcus#ifndef PARSE_IPC_ADD_H #define PARSE_IPC_ADD_H void add_parseResponse(char* res); void add_parseLoadedAccountsResponse(char* res); #endif // PARSE_IPC_ADD_H oidc-agent-4.2.6/src/oidc-add/add_handler.h0000644000175000017500000000075514120404223017733 0ustar marcusmarcus#ifndef ADD_HANDLER_H #define ADD_HANDLER_H #include "oidc-add/oidc-add_options.h" void add_handleAdd(char* account, struct arguments* arguments); void add_handleRemove(const char* account, struct arguments* arguments); void add_handleRemoveAll(struct arguments* arguments); void add_handlePrint(char* account, struct arguments* arguments); void add_handleLock(int lock, struct arguments* arguments); void add_handleListLoadedAccounts(struct arguments* arguments); #endif // ADD_HANDLER_H oidc-agent-4.2.6/src/oidc-add/parse_ipc.c0000644000175000017500000000353014167074355017464 0ustar marcusmarcus#include "parse_ipc.h" #include #include "defines/ipc_values.h" #include "utils/json.h" #include "utils/key_value.h" #include "utils/memory.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" struct statusInfo { char* status; char* info; }; void secFreeStatusInfo(struct statusInfo a) { secFree(a.status); secFree(a.info); } struct statusInfo _add_parseResponse(char* res) { if (NULL == res) { printError("Error: %s\n", oidc_serror()); exit(EXIT_FAILURE); } INIT_KEY_VALUE(IPC_KEY_STATUS, IPC_KEY_INFO, OIDC_KEY_ERROR); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(res); KEY_VALUE_VARS(status, info, error); if (_error != NULL) { printError("Error: %s\n", _error); if (_info != NULL) { printImportant(_info); } SEC_FREE_KEY_VALUES(); exit(EXIT_FAILURE); } secFree(_error); return (struct statusInfo){_status, _info}; } void add_parseResponse(char* res) { struct statusInfo tmp = _add_parseResponse(res); printStdout("%s\n", tmp.status); if (strValid(tmp.info)) { printNormal("%s\n", tmp.info); } secFreeStatusInfo(tmp); } void add_parseLoadedAccountsResponse(char* res) { struct statusInfo tmp = _add_parseResponse(res); if (strequal("[]", tmp.info)) { printStdout("No account configurations are currently loaded.\n"); secFreeStatusInfo(tmp); return; } char* printable = JSONArrayStringToDelimitedString(tmp.info, "\n"); secFreeStatusInfo(tmp); if (printable == NULL) { oidc_perror(); exit(EXIT_FAILURE); } printStdout( "The following account configurations are currently loaded: \n%s\n", printable); secFree(printable); } oidc-agent-4.2.6/src/oidc-add/add_handler.c0000644000175000017500000001064314167074355017747 0ustar marcusmarcus#include "add_handler.h" #include #include "account/account.h" #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "oidc-add/parse_ipc.h" #include "utils/accountUtils.h" #include "utils/file_io/oidc_file_io.h" #include "utils/password_entry.h" #include "utils/printer.h" #include "utils/prompt.h" #include "utils/promptUtils.h" #include "utils/string/stringUtils.h" time_t getPWExpiresInDependingOn(struct arguments* arguments) { if (arguments->pw_lifetime.argProvided == ARG_PROVIDED_BUT_USES_DEFAULT && arguments->lifetime.argProvided) { return arguments->lifetime.lifetime; } return arguments->pw_lifetime.lifetime; } void add_handleAdd(char* account, struct arguments* arguments) { struct resultWithEncryptionPassword result = getDecryptedAccountAsStringAndPasswordFromFilePrompt( account, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); char* json_p = result.result; if (json_p == NULL) { secFree(result.password); oidc_perror(); exit(EXIT_FAILURE); } char* password = result.password; struct password_entry pw = {.shortname = account}; unsigned char type = PW_TYPE_PRMT; if (arguments->pw_cmd) { pwe_setCommand(&pw, arguments->pw_cmd); type |= PW_TYPE_CMD; } if (arguments->pw_lifetime.argProvided && password) { pwe_setPassword(&pw, password); pwe_setExpiresIn(&pw, getPWExpiresInDependingOn(arguments)); type |= PW_TYPE_MEM; } if (arguments->pw_keyring) { if (pw.password == NULL) { // Only set password if not already done pwe_setPassword(&pw, password); } type |= PW_TYPE_MNG; } if (arguments->pw_env) { if (pw.password == NULL) { pwe_setPassword(&pw, password); type |= PW_TYPE_MEM; } } if (arguments->pw_file) { pwe_setFile(&pw, arguments->pw_file); type |= PW_TYPE_FILE; } if (arguments->pw_gpg) { pwe_setGPGKey(&pw, arguments->pw_gpg); type |= PW_TYPE_GPG; } pwe_setType(&pw, type); char* pw_str = passwordEntryToJSONString(&pw); secFree(password); char* res = NULL; if (arguments->lifetime.argProvided) { res = ipc_cryptCommunicate(arguments->remote, REQUEST_ADD_LIFETIME, json_p, arguments->lifetime.lifetime, pw_str, arguments->confirm, arguments->always_allow_idtoken); } else { res = ipc_cryptCommunicate(arguments->remote, REQUEST_ADD, json_p, pw_str, arguments->confirm, arguments->always_allow_idtoken); } secFree(pw_str); secFree(json_p); add_parseResponse(res); } void add_handleRemove(const char* account, struct arguments* arguments) { char* res = ipc_cryptCommunicate(arguments->remote, REQUEST_REMOVE, account); add_parseResponse(res); } void add_handleRemoveAll(struct arguments* arguments) { char* res = ipc_cryptCommunicate(arguments->remote, REQUEST_REMOVEALL); add_parseResponse(res); } void add_handleLock(int lock, struct arguments* arguments) { char* password = promptPassword("Enter lock password: ", "Password", NULL, CLI_PROMPT_VERBOSE); if (password == NULL) { oidc_perror(); exit(EXIT_FAILURE); } char* res = NULL; if (!lock) { // unlocking agent res = ipc_cryptCommunicate(arguments->remote, REQUEST_LOCK, REQUEST_VALUE_UNLOCK, password); } else { // locking agent char* passwordConfirm = promptPassword( "Confirm lock password: ", "Password", NULL, CLI_PROMPT_VERBOSE); if (!strequal(password, passwordConfirm)) { printError("Passwords do not match.\n"); secFree(password); secFree(passwordConfirm); exit(EXIT_FAILURE); } secFree(passwordConfirm); res = ipc_cryptCommunicate(arguments->remote, REQUEST_LOCK, REQUEST_VALUE_LOCK, password); } secFree(password); add_parseResponse(res); } void add_handlePrint(char* account, struct arguments* arguments) { char* json_p = getDecryptedAccountAsStringFromFilePrompt( account, arguments->pw_cmd, arguments->pw_file, arguments->pw_env); if (json_p == NULL) { exit(EXIT_FAILURE); } printStdout("%s\n", json_p); secFree(json_p); } void add_handleListLoadedAccounts(struct arguments* arguments) { char* res = ipc_cryptCommunicate(arguments->remote, REQUEST_LOADEDACCOUNTS); add_parseLoadedAccountsResponse(res); } oidc-agent-4.2.6/src/oidc-add/oidc-add.h0000644000175000017500000000033714167074355017172 0ustar marcusmarcus#ifndef OIDC_ADD_H #define OIDC_ADD_H #include "defines/version.h" #include "oidc-add_options.h" const char* argp_program_version = ADD_VERSION; const char* argp_program_bug_address = BUG_ADDRESS; #endif // OIDC_ADD_H oidc-agent-4.2.6/src/oidc-add/oidc-add_options.h0000644000175000017500000000150714167074355020745 0ustar marcusmarcus#ifndef OIDC_ADD_OPTIONS_H #define OIDC_ADD_OPTIONS_H #include #include "utils/lifetimeArg.h" #define ARG_PROVIDED_BUT_USES_DEFAULT 2 struct arguments { char* args[1]; /* account */ char* pw_cmd; char* pw_file; char* pw_env; char* pw_gpg; unsigned char remove; unsigned char removeAll; unsigned char debug; unsigned char verbose; unsigned char listConfigured; unsigned char listLoaded; unsigned char print; unsigned char lock; unsigned char unlock; unsigned char seccomp; unsigned char pw_keyring; unsigned char confirm; unsigned char always_allow_idtoken; unsigned char pw_prompt_mode; unsigned char remote; struct lifetimeArg pw_lifetime; struct lifetimeArg lifetime; }; void initArguments(struct arguments* arguments); extern struct argp argp; #endif // OIDC_ADD_OPTIONS_H oidc-agent-4.2.6/src/oidc-add/oidc-add.c0000644000175000017500000000336414167074355017170 0ustar marcusmarcus#include "oidc-add.h" #include "account/account.h" #include "add_handler.h" #ifndef __APPLE__ #include "privileges/add_privileges.h" #endif #include "utils/commonFeatures.h" #include "utils/disableTracing.h" #include "utils/file_io/fileUtils.h" #include "utils/logger.h" int main(int argc, char** argv) { platform_disable_tracing(); logger_open("oidc-add"); logger_setloglevel(NOTICE); struct arguments arguments; initArguments(&arguments); argp_parse(&argp, argc, argv, 0, 0, &arguments); if (arguments.debug) { logger_setloglevel(DEBUG); } #ifndef __APPLE__ if (arguments.seccomp) { initOidcAddPrivileges(&arguments); } #endif if (arguments.listConfigured) { common_handleListConfiguredAccountConfigs(); return EXIT_SUCCESS; } if (arguments.listLoaded) { add_handleListLoadedAccounts(&arguments); return EXIT_SUCCESS; } if (arguments.removeAll) { add_handleRemoveAll(&arguments); return EXIT_SUCCESS; } common_assertAgent(arguments.remote); if (arguments.lock || arguments.unlock) { add_handleLock(arguments.lock, &arguments); return EXIT_SUCCESS; } checkOidcDirExists(); char* account = arguments.args[0]; if (!accountConfigExists(account)) { if (!(arguments.remove && arguments.remote)) { // If connected with // remote agent a remove // uses a shortname that does not exist locally oidc_errno = OIDC_ENOACCOUNT; oidc_perror(); exit(EXIT_FAILURE); } } if (arguments.print) { add_handlePrint(account, &arguments); return EXIT_SUCCESS; } if (arguments.remove) { add_handleRemove(account, &arguments); } else { add_handleAdd(account, &arguments); } return EXIT_SUCCESS; } oidc-agent-4.2.6/src/oidc-prompt/0000755000175000017500000000000014167074355016143 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-prompt/oidc-prompt_yad0000755000175000017500000000555114167074355021171 0ustar marcusmarcus function password { yad --no-markup --title "$title" --center --skip-taskbar --splash --text "$text" --button gtk-cancel:1 --button gtk-ok:0 --image dialog-password --entry --entry-label "$label" --entry-text "$init" --hide-text --completion --timeout $timeout --timeout-indicator bottom exit $? } function input { yad --no-markup --title "$title" --center --skip-taskbar --splash --text "$text" --button gtk-cancel:1 --button gtk-ok:0 --image dialog-question --entry --entry-label "$label" --entry-text "$init" --completion --timeout $timeout --timeout-indicator bottom exit $? } function confirm_default_no { yad --no-markup --title "$title" --center --skip-taskbar --splash --text "$text" --button gtk-no:1 --button gtk-yes:0 --image dialog-question --timeout $timeout --timeout-indicator bottom ret=$? if [ $ret == 0 ]; then echo "yes" fi exit $ret } function confirm_default_yes { yad --no-markup --title "$title" --center --skip-taskbar --splash --text "$text" --button gtk-yes:0 --button gtk-no:1 --image dialog-question --timeout $timeout --timeout-indicator bottom ret=$? if [ $ret == 0 ]; then echo "yes" fi exit $ret } function _select { h=$((125 + 22 * $#)) yad --no-markup --title "$title" --width 400 --height $h --center --skip-taskbar --splash --text "$text" --button gtk-cancel:1 --button gtk-ok:0 --timeout $timeout --timeout-indicator bottom --image dialog-question --list --separator "" --search-column 1 --regex-search --column "$label" "$init" "${@}" } function select2 { if [ "$type" == "select-other" ]; then out=$(_select ${@} "Other") else out=$(_select ${@}) fi ret=$? out=${out//\\n/} if [ $ret != 0 ]; then exit $ret fi if [ "$out" != "Other" ]; then echo $out exit $ret else text=${text//Select/Enter} text=${text//select/enter} input fi } function multiple { printf '%s\n' "${@}" | head -c -1 | yad --no-markup --title "$title" --width 400 --height 222 --center --skip-taskbar --splash --text "$text" --button gtk-cancel:1 --button gtk-ok:0 --timeout $timeout --timeout-indicator bottom --image dialog-question --label "$label" --text-info --editable | grep -v '^$' } function simple_link_QR { echo "$text" | yad --no-markup --title "$title" --width 600 --height 350 --center --skip-taskbar --splash --button gtk-ok:0 --image "$init" --wrap --show-uri --text-info --timeout $timeout --timeout-indicator bottom } function complex_link { # The option --wrap would be nice, but it wraps the long auth url really ugly # The option --show-uri would be nice (it makes uris clickable), but it doesn't work with query parameters. echo "$text" | yad --no-markup --title "$title" --width 800 --height 222 --center --skip-taskbar --splash --button gtk-ok:0 --text-info --timeout $timeout --timeout-indicator bottom } oidc-agent-4.2.6/src/oidc-prompt/oidc-prompt0000755000175000017500000000252514167074355020332 0ustar marcusmarcus#!/bin/bash VERSION="1.1.0" type=$1 title=$2 text=$3 label=${4//_/__} # "Escape" underscores} timeout=$5 init=$6 ADDITIONAL_ARGS=7 function help { ( echo "Usage: oidc-prompt TITLE TEXT LABEL [INIT] [LIST_ELEMENTS ...]" echo "oidc-prompt -- An interface for prompting the user." echo echo "This tool is intended as a internal tool of oidc-agent. Different oidc-agent components use it to prompt the user for information. The user should not call oidc-prompt." )>&2 } case "$1" in "-?"|-h|--help) usage help exit ;; --usage) help exit ;; -V|--version) echo "oidc-prompt $VERSION" exit ;; esac if [ $# -le 2 ]; then help exit fi # To use another dialog creation tool, create a shell script that provides the # needed functions and include it here. OIDC_INCLUDE case $type in "password") password ;; "input") input ;; "confirm-default-no") confirm_default_no ;; "confirm-default-yes") confirm_default_yes ;; "confirm") confirm_default_yes ;; select*) select2 "${@:$ADDITIONAL_ARGS}" ;; "multiple") multiple "$init" "${@:$ADDITIONAL_ARGS}" ;; "complex-link") complex_link ;; "simple-link") simple_link_QR ;; *) >&2 echo "unknown type" exit 1 ;; esac oidc-agent-4.2.6/src/oidc-prompt/oidc-prompt_pashua0000755000175000017500000001076014167074355021673 0ustar marcusmarcus # Tries to find the Pashua executable in one of a few default search locations or in # a custom path passed as optional argument. When it can be found, the filesystem # path will be in $pashuapath, otherwise $pashuapath will be empty. The return value # is 0 if it can be found, 1 otherwise. # # Argument 1: Path to a folder containing Pashua.app (optional) locate_pashua() { local bundlepath="Pashua.app/Contents/MacOS/Pashua" local mypath=`dirname "$0"` pashuapath="" if [ ! "$1" = "" ] then searchpaths[0]="$1/$bundlepath" fi searchpaths[1]="$mypath/Pashua" searchpaths[2]="$mypath/$bundlepath" searchpaths[3]="./$bundlepath" searchpaths[4]="/Applications/$bundlepath" searchpaths[5]="$HOME/Applications/$bundlepath" for searchpath in "${searchpaths[@]}" do if [ -f "$searchpath" -a -x "$searchpath" ] then pashuapath=$searchpath return 0 fi done return 1 } # Function for communicating with Pashua # # Argument 1: Configuration string # Argument 2: Path to a folder containing Pashua.app (optional) pashua_run() { # Write config file local pashua_configfile=`/usr/bin/mktemp /tmp/pashua_XXXXXXXXX` echo "$1" > "$pashua_configfile" locate_pashua "$2" if [ "" = "$pashuapath" ] then >&2 echo "Error: Pashua could not be found" exit 1 fi # Get result local result=$("$pashuapath" "$pashua_configfile") # Remove config file rm "$pashua_configfile" oldIFS="$IFS" IFS=$'\n' # Parse result for line in $result do local name=$(echo $line | sed 's/^\([^=]*\)=.*$/\1/') local value=$(echo $line | sed 's/^[^=]*=\(.*\)$/\1/') eval $name='$value' done IFS="$oldIFS" } function pash { location='' locate_pashua "$location" pashua_run "$conf" "$location" 2>/dev/null } function _escape_newline { text=${text//$'\n'/[return]} } _escape_newline function password { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" tx.type=text tx.default="$text" pw.type=password pw.label="$label" pw.default="$init" cb.type=cancelbutton " pash if [ ! -z "$pw" ]; then echo "$pw" fi exit $cb } function input { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" tx.type=textfield tx.label="$label" tx.default="$init" cb.type=cancelbutton " pash if [ ! -z "$tx" ]; then echo "$tx" fi exit $cb } function confirm_default_no { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" ok.type=defaultbutton ok.label=No cb.type=cancelbutton cb.label=Yes " pash if [ $cb != 0 ]; then echo "yes" exit 0 else exit 1 fi } function confirm_default_yes { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" ok.type=defaultbutton ok.label=Yes cb.type=cancelbutton cb.label=No " pash if [ $cb == 0 ]; then echo "yes" fi exit $cb } function _select { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" tx.type=popup tx.label="$label" tx.default="$init" tx.option="$init" cb.type=cancelbutton " for opt in "${@}"; do conf+="tx.option="$opt" " done pash if [ ! -z "$tx" ]; then echo "$tx" fi exit $cb } function _select_other { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" tx.type=combobox tx.completion=2 tx.label="$label" tx.default="$init" tx.option="$init" cb.type=cancelbutton " for opt in "${@}"; do conf+="tx.option="$opt" " done pash if [ ! -z "$tx" ]; then echo "$tx" fi exit $cb } function select2 { if [ "$type" == "select-other" ]; then _select_other ${@} else _select ${@} fi } function multiple { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" tx.type=textbox tx.height=104 tx.label="$label" tx.default="$(printf '%s[return]' "${@}")" cb.type=cancelbutton " pash if [ ! -z "$tx" ]; then echo "$(echo "$tx" | sed 's/\[return\]/\ /g' | grep -v '^$')" fi exit $cb } function simple_link_QR { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" img.type=image img.path="$init" " pash } function complex_link { conf=" *.title="$title" *.floating=1 *.autoclosetime="$timeout" txt.type=text txt.default="$text" " pash } oidc-agent-4.2.6/src/utils/0000755000175000017500000000000014167074355015046 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/logger.h0000644000175000017500000000117614167074355016503 0ustar marcusmarcus#ifndef OIDC_LOGGER_H #define OIDC_LOGGER_H #ifdef __linux__ #include #define DEBUG LOG_DEBUG #define INFO LOG_INFO #define NOTICE LOG_NOTICE #define WARNING LOG_WARNING #define ERROR LOG_ERR #define ALERT LOG_ALERT #define EMERGENCY LOG_EMERG #elif __APPLE__ #define DEBUG 1 #define INFO 2 #define NOTICE 3 #define WARNING 4 #define ERROR 5 #define ALERT 6 #define EMERGENCY 7 #endif void logger_open(const char* logger_name); void logger(int log_level, const char* msg, ...); void loggerTerminal(int log_level, const char* msg, ...); int logger_setlogmask(int); int logger_setloglevel(int); #endif // OIDC_LOGGER_H oidc-agent-4.2.6/src/utils/prompt.c0000644000175000017500000002466514167074355016550 0ustar marcusmarcus#define _XOPEN_SOURCE 700 #include "prompt.h" #include #include #include #include #include #include #include "memory.h" #include "oidc_error.h" #include "printer.h" #include "utils/file_io/file_io.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/prompt_mode.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" void displayAuthCodeLinkGUI(const char* text) { char* cmd = oidc_sprintf("oidc-prompt complex-link \"oidc-agent - Reauthentication " "required\" \"%s\" \"\" \"%d\" \"\"", text, PROMPT_DEFAULT_TIMEOUT); fireCommand(cmd); secFree(cmd); } void displayDeviceLinkGUI(const char* text, const char* qr_path) { char* cmd = oidc_sprintf("oidc-prompt simple-link \"oidc-agent - Reauthentication " "required\" \"%s\" \"\" \"%d\" \"%s\"", text, PROMPT_DEFAULT_TIMEOUT, qr_path ?: ""); fireCommand(cmd); secFree(cmd); } char* _promptPasswordGUI(const char* text, const char* label, const char* init, const int timeout) { char* cmd = oidc_sprintf("oidc-prompt password \"oidc-agent prompt\" \"%s\" " "\"%s\" %d \"%s\"", text, label, timeout, init ?: ""); char* ret = getOutputFromCommand(cmd); secFree(cmd); if (!strValid(ret)) { // Cancel raise(SIGINT); // Cancel should have the same behaviour as interrupt } return ret; } char* _promptGUI(const char* text, const char* label, const char* init, const int timeout) { char* cmd = oidc_sprintf( "oidc-prompt input \"oidc-agent prompt\" \"%s\" \"%s\" %d \"%s\"", text, label, timeout, init ?: ""); char* ret = getOutputFromCommand(cmd); secFree(cmd); if (!strValid(ret)) { // Cancel raise(SIGINT); // Cancel should have the same behaviour as interrupt } return ret; } char* _promptSelectGUI(const char* text, const char* label, list_t* init, size_t initPos, const int timeout) { list_t* copy = list_new(); copy->free = _secFree; for (size_t i = 0; i < init->len; i++) { if (i != initPos) { list_rpush(copy, list_node_new(oidc_sprintf( "\"%s\"", (char*)list_at(init, i)->val))); } } char* options = listToDelimitedString(copy, " "); secFreeList(copy); char* cmd = oidc_sprintf("oidc-prompt select-other \"oidc-agent prompt\" " "\"%s\" \"%s\" %d \"%s\" %s", text, label, timeout, (char*)list_at(init, initPos)->val, options); secFree(options); char* ret = getOutputFromCommand(cmd); secFree(cmd); if (!strValid(ret)) { // Cancel raise(SIGINT); // Cancel should have the same behaviour as interrupt } return ret; } list_t* _promptMultipleGUI(const char* text, const char* label, list_t* init, const int timeout) { char* inits = listToDelimitedString(init, " "); char* text_p = oidc_sprintf("%s (One value per line)", text); char* cmd = oidc_sprintf( "oidc-prompt multiple \"oidc-agent prompt\" \"%s\" \"%s\" %d %s", text_p, label, timeout, inits); secFree(text_p); secFree(inits); char* input = getOutputFromCommand(cmd); secFree(cmd); if (!strValid(input)) { // Cancel raise(SIGINT); // Cancel should have the same behaviour as interrupt } list_t* out = delimitedStringToList(input, '\n'); secFree(input); return out; } char* _promptCLI(const char* text, const char* init) { printPrompt("%s", text); if (init) { printPrompt(" [%s]", init); } printPrompt(": "); char* input = getLineFromFILE(stdin); if (input && strequal(input, "") && init) { secFree(input); return oidc_strcopy(init); } return input; } char* _promptPasswordCLI(const char* text, const char* init) { struct termios oflags, nflags; /* disabling echo */ tcgetattr(STDIN_FILENO, &oflags); nflags = oflags; nflags.c_lflag &= ~ECHO; nflags.c_lflag |= ECHONL; if (tcsetattr(STDIN_FILENO, TCSANOW, &nflags) != 0) { logger(ERROR, "tcsetattr: %m"); oidc_errno = OIDC_ETCS; return NULL; } char* password = _promptCLI(text, init ? "***" : NULL); /* restore terminal */ if (tcsetattr(STDIN_FILENO, TCSANOW, &oflags) != 0) { logger(ERROR, "tcsetattr: %m"); oidc_errno = OIDC_ETCS; secFree(password); return NULL; } if (password && strequal(password, "***") && init) { secFree(password); password = oidc_strcopy(init); } return password; } void _printSelectOptions(list_t* suggastable) { if (suggastable == NULL) { return; } size_t i; for (i = 0; i < suggastable->len; i++) { // printed indices starts at 1 for non nerd printPrompt("[%lu] %s\n", i + 1, (char*)list_at(suggastable, i)->val); } } char* _promptSelectCLI(const char* text, list_t* init, size_t initPos) { char* fav = NULL; if (init == NULL || list_at(init, initPos) == NULL) { logger(ERROR, "In %s initPos not valid", __func__); } else { fav = list_at(init, initPos)->val; } char* out = NULL; _printSelectOptions(init); while (out == NULL) { char* input = _promptCLI(text, fav); if (!strValid(input)) { out = oidc_strcopy(fav); secFree(input); return out; } else if (isdigit(*input)) { size_t i = strToULong(input); secFree(input); if (i == 0 || i > init->len) { printError("Input out of bound\n"); continue; } i--; // printed indices start at 1 for non nerds out = oidc_strcopy(list_at(init, i)->val); } else { out = input; } } return out; } list_t* _promptMultipleCLI(const char* text, list_t* init) { char* arr_str = listToDelimitedString(init, " "); char* text_p = oidc_sprintf("%s (space separated)", text); char* input = _promptCLI(text_p, arr_str); secFree(arr_str); secFree(text_p); list_t* ret = delimitedStringToList(input, ' '); secFree(input); return ret; } /** @fn char* promptPassword(char* prompt_str, ...) * @brief prompts the user and disables terminal echo for the userinput, so it * is useable for password prompts * @param prompt_mode the mode how the user should be prompted * @return a pointer to the user input. Has to be freed after usage. */ char* promptPassword(const char* text, const char* label, const char* init, unsigned char cliVerbose) { char* password = NULL; switch (pw_prompt_mode()) { case PROMPT_MODE_CLI: password = _promptPasswordCLI(cliVerbose ? text : label, init); break; case PROMPT_MODE_GUI: password = _promptPasswordGUI(text, label, init, PROMPT_NO_TIMEOUT); break; default: logger(ERROR, "Invalid prompt mode"); oidc_setInternalError("Programming error: Prompt Mode must be set!"); oidc_perror(); return NULL; } return password; } /** @fn char* prompt(char* prompt_str, ...) * @brief prompts the user for userinput, should not be used for password * prompts, user \f promptPassword instead * @param prompt_mode the mode how the user should be prompted * @return a pointer to the user input. Has to freed after usage. */ char* prompt(const char* text, const char* label, const char* init, unsigned char cliVerbose) { char* input = NULL; switch (prompt_mode()) { case PROMPT_MODE_CLI: input = _promptCLI(cliVerbose ? text : label, init); break; case PROMPT_MODE_GUI: input = _promptGUI(text, label, init, PROMPT_NO_TIMEOUT); break; default: logger(ERROR, "Invalid prompt mode"); oidc_setInternalError("Programming error: Prompt Mode must be set!"); oidc_perror(); return NULL; } return input; } char* promptSelect(const char* text, const char* label, list_t* options, size_t initPos, unsigned char cliVerbose) { char* input = NULL; switch (prompt_mode()) { case PROMPT_MODE_CLI: input = _promptSelectCLI(cliVerbose ? text : label, options, initPos); break; case PROMPT_MODE_GUI: input = _promptSelectGUI(text, label, options, initPos, PROMPT_NO_TIMEOUT); break; default: logger(ERROR, "Invalid prompt mode"); oidc_setInternalError("Programming error: Prompt Mode must be set!"); oidc_perror(); return NULL; } return input; } list_t* promptMultiple(const char* text, const char* label, list_t* init, unsigned char cliVerbose) { list_t* out = NULL; switch (prompt_mode()) { case PROMPT_MODE_CLI: out = _promptMultipleCLI(cliVerbose ? text : label, init); break; case PROMPT_MODE_GUI: out = _promptMultipleGUI(text, label, init, PROMPT_NO_TIMEOUT); break; default: logger(ERROR, "Invalid prompt mode"); oidc_setInternalError("Programming error: Prompt Mode must be set!"); oidc_perror(); return NULL; } return out; } int _promptConsentGUIDefaultYes(const char* text, const int timeout) { char* cmd = oidc_sprintf("oidc-prompt confirm-default-yes " "\"oidc-agent prompt confirm\" \"%s\" \"\" %d", text, timeout); char* out = getOutputFromCommand(cmd); secFree(cmd); int ret = out != NULL && strcaseequal(out, "yes") ? 1 : 0; secFree(out); return ret; } int _promptConsentGUIDefaultNo(const char* text, const int timeout) { char* cmd = oidc_sprintf("oidc-prompt confirm-default-no \"oidc-agent prompt " "confirm\" \"%s\" \"\" %d", text, timeout); char* out = getOutputFromCommand(cmd); secFree(cmd); int ret = out != NULL && strcaseequal(out, "yes") ? 1 : 0; secFree(out); return ret; } int promptConsentDefaultNo(const char* text) { if (prompt_mode() == PROMPT_MODE_GUI) { return _promptConsentGUIDefaultNo(text, 0); } char* res = prompt(text, NULL, "No/yes/quit", CLI_PROMPT_VERBOSE); if (strequal(res, "yes")) { secFree(res); return 1; } else if (strequal(res, "quit")) { exit(EXIT_SUCCESS); } else { secFree(res); return 0; } } int promptConsentDefaultYes(const char* text) { if (prompt_mode() == PROMPT_MODE_GUI) { return _promptConsentGUIDefaultNo(text, 0); } char* res = prompt(text, NULL, "Yes/no/quit", CLI_PROMPT_VERBOSE); if (strequal(res, "no")) { secFree(res); return 0; } else if (strequal(res, "quit")) { exit(EXIT_SUCCESS); } else { secFree(res); return 1; } } oidc-agent-4.2.6/src/utils/logger.c0000644000175000017500000000633314167074355016476 0ustar marcusmarcus#define _POSIX_C_SOURCE 200809L #ifdef __linux__ #define _BSD_SOURCE #define _DEFAULT_SOURCE #endif #include "logger.h" #include #include #include #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" static const char* logger_name; char* format_time() { char* s = secAlloc(sizeof(char) * (19 + 1)); if (s == NULL) { return NULL; } time_t now = time(NULL); struct tm* t = secAlloc(sizeof(struct tm)); if (localtime_r(&now, t) == NULL) { oidc_perror(); secFree(t); return NULL; } strftime(s, 19 + 1, "%F %H:%M:%S", t); secFree(t); return s; } char* create_log_message(int _log_level, const char* msg, va_list args) { char* logmsg = oidc_vsprintf(msg, args); char* time_str = format_time(); const char* const fmt = "%s %s %s: %s"; const char* level; switch (_log_level) { case DEBUG: level = "DEBUG"; break; case INFO: level = "INFO"; break; case NOTICE: level = "NOTICE"; break; case WARNING: level = "WARNING"; break; case ERROR: level = "ERROR"; break; case ALERT: level = "ALERT"; break; case EMERGENCY: level = "EMERG"; break; default: level = ""; break; } char* log = oidc_sprintf(fmt, time_str, logger_name, level, logmsg); secFree(time_str); secFree(logmsg); return log; } #ifdef __linux__ #include static int _mask; void logger_open(const char* _logger_name) { openlog(_logger_name, LOG_CONS | LOG_PID, LOG_AUTHPRIV); logger_name = _logger_name; } void logger(int log_level, const char* msg, ...) { va_list args; va_start(args, msg); vsyslog(LOG_AUTHPRIV | log_level, msg, args); } void loggerTerminal(int log_level, const char* msg, ...) { va_list args, copy; va_start(args, msg); va_copy(copy, args); vsyslog(LOG_AUTHPRIV | log_level, msg, args); if (_mask & LOG_MASK(log_level)) { char* logmsg = create_log_message(log_level, msg, copy); fprintf(stderr, "%s\n", logmsg); secFree(logmsg); } } int logger_setlogmask(int mask) { _mask = mask; return setlogmask(mask); } int logger_setloglevel(int level) { return logger_setlogmask(LOG_UPTO(level)); } #elif __APPLE__ #include "utils/file_io/oidc_file_io.h" static int log_level = NOTICE; void own_log(int terminal, int _log_level, const char* msg, va_list args) { char* log = create_log_message(_log_level, msg, args); appendOidcFile("oidc-agent.log", log); if (terminal) { fprintf(stderr, "%s\n", log); } secFree(log); } void logger_open(const char* _logger_name) { logger_name = oidc_strcopy(_logger_name); } void _logger(int terminal, int _log_level, const char* msg, va_list args) { if (_log_level >= log_level) { own_log(terminal, _log_level, msg, args); } } void loggerTerminal(int _log_level, const char* msg, ...) { va_list args; va_start(args, msg); _logger(1, _log_level, msg, args); va_end(args); } void logger(int _log_level, const char* msg, ...) { va_list args; va_start(args, msg); _logger(0, _log_level, msg, args); va_end(args); } int logger_setlogmask(int mask) { return logger_setloglevel(mask); } int logger_setloglevel(int level) { int old = log_level; log_level = level; return old; } #endif oidc-agent-4.2.6/src/utils/prompt_mode.c0000644000175000017500000000075714167074355017550 0ustar marcusmarcus#include "prompt_mode.h" unsigned char _prompt_mode; unsigned char _pw_prompt_mode; void set_prompt_mode(unsigned char mode) { _prompt_mode = mode; } void set_pw_prompt_mode(unsigned char mode) { _pw_prompt_mode = mode; } unsigned char prompt_mode() { return _prompt_mode; } unsigned char pw_prompt_mode() { if (_pw_prompt_mode) { return _pw_prompt_mode; } if (_prompt_mode) { return _prompt_mode; } return PROMPT_MODE_CLI; // Default, so PW prompts are always possible. } oidc-agent-4.2.6/src/utils/listUtils.h0000644000175000017500000000227614167074355017222 0ustar marcusmarcus#ifndef LIST_UTILS_H #define LIST_UTILS_H #include "wrapper/list.h" #define LIST_CREATE_COPY_VALUES 1 #define LIST_CREATE_DONT_COPY_VALUES 0 typedef int (*matchFunction)(const void*, const void*); typedef void (*freeFunction)(void*); char* delimitedStringToJSONArray(char* str, char delimiter); list_t* delimitedStringToList(const char* str, char delimiter); char* listToDelimitedString(list_t* list, char* delimiter); list_t* copyList(list_t* a); list_t* mergeLists(list_t* a, list_t* b); list_t* intersectLists(list_t* a, list_t* b); list_t* subtractLists(list_t* a, list_t* b); char* subtractListStrings(const char* a, const char* b, const char del); char* listToJSONArrayString(list_t* list); list_node_t* findInList(list_t* l, const void* v); list_t* findAllInList(list_t* l, const void* v); void list_removeIfFound(list_t* l, const void* v); void list_mergeSort(list_t* l, int (*comp)(const void*, const void*)); void secFreeList(list_t* l); list_t* createList(int copyValues, char* s, ...); list_t* list_addStringIfNotFound(list_t* l, char* v); int listValid(const list_t* l); #endif // LIST_UTILS_H oidc-agent-4.2.6/src/utils/file_io/0000755000175000017500000000000014167074355016454 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/file_io/promptCryptFileUtils.h0000644000175000017500000000233614167074355023015 0ustar marcusmarcus#ifndef PROMPT_CRYPT_FILE_UTILS_H #define PROMPT_CRYPT_FILE_UTILS_H #include "utils/oidc_error.h" #include "utils/resultWithEncryptionPassword.h" oidc_error_t promptEncryptAndWriteToFile( const char* text, const char* filepath, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env, const char* gpg_key); oidc_error_t promptEncryptAndWriteToOidcFile( const char* text, const char* filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env, const char* gpg_key); struct resultWithEncryptionPassword getDecryptedFileAndPasswordFor( const char* filepath, const char* pw_cmd, const char* pw_file, const char* pw_env); struct resultWithEncryptionPassword getDecryptedOidcFileAndPasswordFor( const char* filename, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getDecryptedFileFor(const char* filepath, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getDecryptedOidcFileFor(const char* filename, const char* pw_cmd, const char* pw_file, const char* pw_env); #endif // PROMPT_CRYPT_FILE_UTILS_H oidc-agent-4.2.6/src/utils/file_io/cryptFileUtils.h0000644000175000017500000000115014167074355021604 0ustar marcusmarcus#ifndef CRYPT_FILE_UTILS_H #define CRYPT_FILE_UTILS_H #include "utils/oidc_error.h" oidc_error_t encryptAndWriteToFile(const char* text, const char* filepath, const char* password, const char* gpg_key); oidc_error_t encryptAndWriteToOidcFile(const char* text, const char* filename, const char* password, const char* gpg_key); char* decryptFile(const char* filepath, const char* password); char* decryptOidcFile(const char* filename, const char* password); #endif // CRYPT_FILE_UTILS_H oidc-agent-4.2.6/src/utils/file_io/oidc_file_io.h0000644000175000017500000000134414120404223021207 0ustar marcusmarcus#ifndef OIDC_FILE_IO_H #define OIDC_FILE_IO_H #include "utils/oidc_error.h" #include "wrapper/list.h" char* getOidcDir(); oidc_error_t createOidcDir(); oidc_error_t writeOidcFile(const char* filename, const char* text); oidc_error_t appendOidcFile(const char* filename, const char* text); char* readOidcFile(const char* filename); int oidcFileDoesExist(const char* filename); int removeOidcFile(const char* filename); char* concatToOidcDir(const char* filename); void updateIssuerConfig(const char* issuer_url, const char* shortname); list_t* getLinesFromOidcFile(const char* filename); list_t* getLinesFromOidcFileWithoutComments(const char* filename); #endif // OIDC_FILE_IO_H oidc-agent-4.2.6/src/utils/file_io/fileUtils.h0000644000175000017500000000135114120404223020541 0ustar marcusmarcus#ifndef FILE_UTILS_H #define FILE_UTILS_H #include "utils/oidc_error.h" #include "wrapper/list.h" void assertOidcDirExists(); void checkOidcDirExists(); list_t* getAccountConfigFileList(); list_t* getClientConfigFileList(); int compareFilesByName(const char* filename1, const char* filename2); int compareOidcFilesByDateModified(const char* filename1, const char* filename2); int compareOidcFilesByDateAccessed(const char* filename1, const char* filename2); char* generateClientConfigFileName(const char* issuer_url, const char* client_id); oidc_error_t changeGroup(const char* path, const char* group_name); #endif // FILE_UTILS_H oidc-agent-4.2.6/src/utils/file_io/file_io.h0000644000175000017500000000160514167074355020235 0ustar marcusmarcus#ifndef FILE_IO_H #define FILE_IO_H #include #include #include "utils/oidc_error.h" #include "wrapper/list.h" #define OIDC_DIREXIST_OK 1 #define OIDC_DIREXIST_NO 0 #define OIDC_DIREXIST_ERROR -1 #define DEFAULT_COMMENT_CHAR '#' oidc_error_t writeFile(const char* filepath, const char* text); oidc_error_t appendFile(const char* path, const char* text); char* readFile(const char* path); char* readFILE(FILE* fp); char* getLineFromFILE(FILE* fp); char* getLineFromFile(const char* path); int fileDoesExist(const char* path); int dirExists(const char* path); oidc_error_t createDir(const char* path); int removeFile(const char* path); list_t* getLinesFromFile(const char* path); list_t* getLinesFromFileWithoutComments(const char* path); oidc_error_t mkpath(const char* p, mode_t mode); #endif // FILE_IO_H oidc-agent-4.2.6/src/utils/file_io/file_io.c0000644000175000017500000001705214167074355020233 0ustar marcusmarcus#define _XOPEN_SOURCE 700 #include "file_io.h" #include #include #include #include #include #include #include #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" char* readFILE2(FILE* fp) { logger(DEBUG, "I'm reading a file step by step"); size_t bsize = 8; char* buffer = secAlloc(bsize + 1); size_t written = 0; while (1) { if (fread(buffer + written, bsize, 1, fp) != 1) { if (feof(fp)) { if (buffer[strlen(buffer) - 1] == '\n') { buffer[strlen(buffer) - 1] = '\0'; } if (buffer[0] == '\0') { secFree(buffer); return NULL; } return buffer; } if (ferror(fp)) { oidc_setErrnoError(); secFree(buffer); return NULL; } } written += bsize; buffer = secRealloc(buffer, written + bsize + 1); } } char* readFILE(FILE* fp) { if (fp == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (fseek(fp, 0L, SEEK_END) != 0) { return readFILE2(fp); } long lSize = ftell(fp); rewind(fp); if (lSize < 0) { oidc_setErrnoError(); logger(ERROR, "%s", oidc_serror()); return NULL; } char* buffer = secAlloc(lSize + 1); if (!buffer) { logger(ERROR, "memory alloc failed in function %s for %ld bytes", __func__, lSize); oidc_errno = OIDC_EALLOC; return NULL; } if (1 != fread(buffer, lSize, 1, fp)) { if (feof(fp)) { oidc_errno = OIDC_EEOF; } else { oidc_errno = OIDC_EFREAD; } secFree(buffer); logger(ERROR, "entire read failed in function %s", __func__); return NULL; } return buffer; } /** @fn char* readFile(const char* path) * @brief reads a file and returns a pointer to the content * @param path the file to be read * @return a pointer to the file content. Has to be freed after usage. On * failure NULL is returned and oidc_errno is set. */ char* readFile(const char* path) { logger(DEBUG, "Reading file: %s", path); FILE* fp = fopen(path, "rb"); if (!fp) { logger(NOTICE, "%m\n"); oidc_errno = OIDC_EFOPEN; return NULL; } char* ret = readFILE(fp); fclose(fp); return ret; } char* getLineFromFILE(FILE* fp) { char* buf = NULL; size_t len = 0; int n; if ((n = getline(&buf, &len, fp)) < 0) { logger(NOTICE, "getline: %s", strerror(errno)); oidc_errno = OIDC_EIN; return NULL; } if (buf[n - 1] == '\n') { buf[n - 1] = 0; // removing '\n' at the end } char* secFreeAblePointer = oidc_strcopy(buf); // Because getline allocates memory using malloc and // not secAlloc, we cannot free buf with secFree. To // be able to do so we copy the buf to memory // allocated with secAlloc and free buf using secFreeN secFreeN(buf, n); return secFreeAblePointer; } char* getLineFromFile(const char* path) { FILE* fp = fopen(path, "rb"); if (!fp) { logger(NOTICE, "%m\n"); oidc_errno = OIDC_EFOPEN; return NULL; } char* ret = getLineFromFILE(fp); fclose(fp); return ret; } /** @fn void writeFile(const char* path, const char* text) * @brief writes text to a file * @note \p text has to be nullterminated and must not contain nullbytes. * @param path the file to be written * @param text the nullterminated text to be written * @return OIDC_OK on success, OID_EFILE if an error occurred. The system sets * errno. */ oidc_error_t writeFile(const char* path, const char* text) { if (path == NULL || text == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } FILE* f = fopen(path, "w"); if (f == NULL) { logger(ALERT, "Error opening file '%s' in function writeToFile().\n", path); return OIDC_EFOPEN; } fprintf(f, "%s", text); fclose(f); return OIDC_SUCCESS; } oidc_error_t appendFile(const char* path, const char* text) { if (path == NULL || text == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } FILE* f = fopen(path, "a"); if (f == NULL) { #ifndef __APPLE__ // logger on MAC uses this function so don't use logger if // something goes wrong logger(ALERT, "Error opening file '%s' in function appendFile().\n", path); #endif return OIDC_EFOPEN; } fprintf(f, "%s\n", text); fclose(f); return OIDC_SUCCESS; } /** @fn int fileDoesExist(const char* path) * @brief checks if a file exists * @param path the path to the file to be checked * @return 1 if the file does exist, 0 if not */ int fileDoesExist(const char* path) { return path ? access(path, F_OK) == 0 ? 1 : 0 : 0; } /** @fn int dirExists(const char* path) * @brief checks if a directory exists * @param path the path to the directory to be checked * @return @c OIDC_DIREXIST_OK if the directory does exist, @c OIDC_DIREXIST_NO * if not, @c OIDC_DIREXIST_ERROR if the directory if an error occurred */ int dirExists(const char* path) { DIR* dir = opendir(path); if (dir) { /* Directory exists. */ closedir(dir); return OIDC_DIREXIST_OK; } else if (ENOENT == errno) { /* Directory does not exist. */ return OIDC_DIREXIST_NO; } else if (EACCES == errno) { logger(NOTICE, "opendir: %m"); oidc_setErrnoError(); return OIDC_DIREXIST_NO; } else { /* opendir() failed for some other reason. */ logger(ALERT, "opendir: %m"); oidc_setErrnoError(); return OIDC_DIREXIST_ERROR; } } oidc_error_t createDir(const char* path) { if (mkdir(path, 0777) != 0) { oidc_setErrnoError(); return oidc_errno; } return OIDC_SUCCESS; } /** @fn int removeFile(const char* path) * @brief removes a file * @param path the path to the file to be removed * @return On success, 0 is returned. On error, -1 is returned, and errno is * set appropriately. */ int removeFile(const char* path) { return unlink(path); } list_t* _getLinesFromFile(const char* path, const unsigned char ignoreComments, const char commentChar) { if (path == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Getting Lines from file: %s", path); FILE* fp = fopen(path, "r"); if (fp == NULL) { oidc_setErrnoError(); return NULL; } list_t* lines = list_new(); lines->free = _secFree; lines->match = (matchFunction)strequal; char* line = NULL; size_t len = 0; ssize_t read = 0; while ((read = getline(&line, &len, fp)) != -1) { if (line[strlen(line) - 1] == '\n') { line[strlen(line) - 1] = '\0'; } if (!ignoreComments || commentChar != firstNonWhiteSpaceChar(line)) { list_rpush(lines, list_node_new(oidc_strcopy(line))); } secFreeN(line, len); } secFreeN(line, len); fclose(fp); return lines; } list_t* getLinesFromFile(const char* path) { return _getLinesFromFile(path, 0, 0); } list_t* getLinesFromFileWithoutComments(const char* path) { return _getLinesFromFile(path, 1, DEFAULT_COMMENT_CHAR); } oidc_error_t mkpath(const char* p, const mode_t mode) { if (p == NULL) { return OIDC_SUCCESS; } char* path = oidc_strcopy(p); if (lastChar(path) == '/') { lastChar(path) = '\0'; } char* pos = path; while ((pos = strchr(pos + 1, '/')) != NULL) { *pos = '\0'; if (mkdir(path, mode) && errno != EEXIST) { secFree(path); oidc_setErrnoError(); return oidc_errno; } *pos = '/'; } if (mkdir(path, mode) && errno != EEXIST) { secFree(path); oidc_setErrnoError(); return oidc_errno; } secFree(path); return OIDC_SUCCESS; } oidc-agent-4.2.6/src/utils/file_io/fileUtils.c0000644000175000017500000001507614167074355020571 0ustar marcusmarcus#define _XOPEN_SOURCE 700 // Because we specify _XOPEN_SOURCE, _DEFAULT_SOURCE is not enabled by default anymore. #define _DEFAULT_SOURCE 1 #include "fileUtils.h" #include #include #include #include #include #include #include #include #include #include #include "oidc_file_io.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" /** * @brief checks if the oidc directory exists */ void checkOidcDirExists() { char* dir = NULL; if ((dir = getOidcDir()) == NULL) { printError("Error: oidc-dir does not exist. Run oidc-gen to create it.\n"); exit(EXIT_FAILURE); } secFree(dir); } /** * @brief asserts that the oidc directory exists */ void assertOidcDirExists() { char* dir = getOidcDir(); if (dir != NULL) { secFree(dir); return; } logger(DEBUG, "oidcdir does not exist, creating oidcdir"); if (createOidcDir() != OIDC_SUCCESS) { oidc_perror(); exit(EXIT_FAILURE); } } list_t* getFileListForDirIf(const char* dirname, int(match(const char*, const char*)), const char* arg) { DIR* dir; struct dirent* ent; if ((dir = opendir(dirname)) != NULL) { list_t* list = list_new(); list->free = (void(*)(void*)) & _secFree; list->match = (matchFunction)strequal; while ((ent = readdir(dir)) != NULL) { #ifdef _DIRENT_HAVE_D_TYPE if (ent->d_type != DT_REG) { continue; } #endif if (!strstarts(ent->d_name, ".") && match(ent->d_name, arg)) { list_rpush(list, list_node_new(oidc_strcopy(ent->d_name))); } } closedir(dir); return list; } else { oidc_seterror(strerror(errno)); oidc_errno = OIDC_EERROR; return NULL; } } int alwaysOne(const char* a __attribute__((unused)), const char* b __attribute__((unused))) { return 1; } list_t* getFileListForDir(const char* dirname) { return getFileListForDirIf(dirname, &alwaysOne, NULL); } int isClientConfigFile(const char* filename, const char* a __attribute__((unused))) { const char* const suffix = ".clientconfig"; if (strEnds(filename, suffix)) { return 1; } char* pos = NULL; if ((pos = strstr(filename, suffix))) { pos += strlen(suffix); while (*pos != '\0') { if (!isdigit(*pos)) { return 0; } pos++; } return 1; } return 0; } int isAccountConfigFile(const char* filename, const char* a __attribute__((unused))) { if (isClientConfigFile(filename, a)) { return 0; } if (strEnds(filename, ".config")) { return 0; } if (strEnds(filename, ".log")) { return 0; } return 1; } list_t* getAccountConfigFileList() { char* oidc_dir = getOidcDir(); if (oidc_dir == NULL) { return NULL; } list_t* list = getFileListForDirIf(oidc_dir, &isAccountConfigFile, NULL); secFree(oidc_dir); return list; } list_t* getClientConfigFileList() { char* oidc_dir = getOidcDir(); if (oidc_dir == NULL) { return NULL; } list_t* list = getFileListForDirIf(oidc_dir, &isClientConfigFile, NULL); list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); while ((node = list_iterator_next(it))) { char* old = node->val; node->val = oidc_strcat(oidc_dir, old); secFree(old); } secFree(oidc_dir); return list; } int compareFilesByName(const char* filename1, const char* filename2) { return strcmp(filename1, filename2); } int compareOidcFilesByDateModified(const char* filename1, const char* filename2) { struct stat* stat1 = secAlloc(sizeof(struct stat)); struct stat* stat2 = secAlloc(sizeof(struct stat)); char* path1 = concatToOidcDir(filename1); char* path2 = concatToOidcDir(filename2); stat(path1, stat1); stat(path2, stat2); int ret = 0; if (stat1->st_mtime < stat2->st_mtime) { ret = -1; } if (stat1->st_mtime > stat2->st_mtime) { ret = 1; } secFree(stat1); secFree(stat2); return ret; } int compareOidcFilesByDateAccessed(const char* filename1, const char* filename2) { struct stat* stat1 = secAlloc(sizeof(struct stat)); struct stat* stat2 = secAlloc(sizeof(struct stat)); char* path1 = concatToOidcDir(filename1); char* path2 = concatToOidcDir(filename2); stat(path1, stat1); stat(path2, stat2); int ret = 0; if (stat1->st_atime < stat2->st_atime) { ret = -1; } if (stat1->st_atime > stat2->st_atime) { ret = 1; } secFree(stat1); secFree(stat2); return ret; } char* generateClientConfigFileName(const char* issuer_url, const char* client_id) { char* filename_fmt = "%s_%s_%s.clientconfig"; char* iss = oidc_strcopy(issuer_url + 8); char* iss_new_end = strchr(iss, '/'); // cut after the first '/' *iss_new_end = 0; char* today = getDateString(); char* filename = oidc_sprintf(filename_fmt, iss, today, client_id); secFree(today); secFree(iss); if (oidcFileDoesExist(filename)) { logger(DEBUG, "The clientconfig file already exists. Changing path."); int i = 0; char* newName = NULL; do { secFree(newName); newName = oidc_sprintf("%s%d", filename, i); i++; } while (oidcFileDoesExist(newName)); secFree(filename); filename = newName; } return filename; } oidc_error_t changeGroup(const char* path, const char* group_name) { if (path == NULL || group_name == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } errno = 0; struct group* grp = getgrnam(group_name); if (grp == NULL) { if (errno == 0) { oidc_errno = OIDC_EGROUPNF; } else { oidc_setErrnoError(); } return oidc_errno; } int flags = O_RDONLY; #ifdef O_DIRECTORY flags |= O_DIRECTORY; #endif int fd = open(path, flags); if (fd == -1) { oidc_setErrnoError(); return oidc_errno; } gid_t gid = grp->gr_gid; if (fchown(fd, -1, gid) != 0) { oidc_setErrnoError(); close(fd); return oidc_errno; } struct stat* buf = secAlloc(sizeof(struct stat)); if (fstat(fd, buf) != 0) { oidc_setErrnoError(); close(fd); secFree(buf); return oidc_errno; } int err = fchmod(fd, buf->st_mode | S_ISGID | S_IRWXG); close(fd); secFree(buf); if (err != 0) { oidc_setErrnoError(); return oidc_errno; } return OIDC_SUCCESS; } oidc-agent-4.2.6/src/utils/file_io/oidc_file_io.c0000644000175000017500000001604214167074355021227 0ustar marcusmarcus#include "oidc_file_io.h" #include #include #include #include #include #include "defines/settings.h" #include "file_io.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" /** @fn char* readOidcFile(const char* filename) * @brief reads a file located in the oidc dir and returns a pointer to the * content * @param filename the filename of the file * @return a pointer to the file content. Has to be freed after usage. */ char* readOidcFile(const char* filename) { char* path = concatToOidcDir(filename); char* c = readFile(path); secFree(path); return c; } /** @fn void writeOidcFile(const char* filename, const char* text) * @brief writes text to a file located in the oidc directory * @note \p text has to be nullterminated and must not contain nullbytes. * @param filename the file to be written * @param text the nullterminated text to be written * @return OIDC_OK on success, OID_EFILE if an error occurred. The system sets * errno. */ oidc_error_t writeOidcFile(const char* filename, const char* text) { char* path = concatToOidcDir(filename); oidc_error_t er = writeFile(path, text); secFree(path); return er; } oidc_error_t appendOidcFile(const char* filename, const char* text) { char* path = concatToOidcDir(filename); oidc_error_t er = appendFile(path, text); secFree(path); return er; } /** @fn int oidcFileDoesExist(const char* filename) * @brief checks if a file exists in the oidc dir * @param filename the file to be checked * @return 1 if the file does exist, 0 if not */ int oidcFileDoesExist(const char* filename) { char* path = concatToOidcDir(filename); int b = fileDoesExist(path); secFree(path); return b; } char* getNonTildePath(const char* path_in) { if (path_in == NULL) { return NULL; } if (path_in[0] == '~') { char* home = getenv("HOME"); if (home == NULL) { oidc_errno = OIDC_EERROR; oidc_seterror("Environment variable HOME is not set, cannot resolve ~."); return NULL; } if (strlen(path_in) == 1) { return oidc_strcopy(home); } return oidc_strcat(home, path_in + 1); } else { return oidc_strcopy(path_in); } } list_t* getPossibleOidcDirLocations() { char* pathDotConfig = getNonTildePath(AGENTDIR_LOCATION_CONFIG); char* pathDot = getNonTildePath(AGENTDIR_LOCATION_DOT); if (pathDotConfig == NULL && pathDot == NULL) { return NULL; } list_t* possibleLocations = createList(0, pathDotConfig, pathDot, NULL); possibleLocations->free = _secFree; char* locFromEnv = getenv(OIDC_CONFIG_DIR_ENV_NAME); if (locFromEnv) { list_lpush(possibleLocations, list_node_new(oidc_strcopy(locFromEnv))); } return possibleLocations; } /** @fn char* getOidcDir() * @brief get the oidc directory path * @return a pointer to the oidc directory path. Has to be freed after usage. If * no oidc dir is found, NULL is returned */ char* getOidcDir() { list_t* possibleLocations = getPossibleOidcDirLocations(); if (possibleLocations == NULL) { return NULL; } list_node_t* node; list_iterator_t* it = list_iterator_new(possibleLocations, LIST_HEAD); while ((node = list_iterator_next(it))) { char* path = node->val; switch (dirExists(path)) { case OIDC_DIREXIST_ERROR: list_iterator_destroy(it); secFreeList(possibleLocations); return NULL; case OIDC_DIREXIST_OK: list_iterator_destroy(it); char* ret = withTrailingSlash(path); secFreeList(possibleLocations); return ret; } } list_iterator_destroy(it); secFreeList(possibleLocations); return NULL; } oidc_error_t createOidcDir() { list_t* possibleLocations = getPossibleOidcDirLocations(); if (possibleLocations == NULL) { return oidc_errno; } char* path = NULL; list_node_t* node; list_iterator_t* it = list_iterator_new(possibleLocations, LIST_HEAD); unsigned char foundParent = 0; while ((node = list_iterator_next(it))) { path = node->val; char* tmp = oidc_strcopy(path); char* parent = dirname(tmp); switch (dirExists(parent)) { case OIDC_DIREXIST_ERROR: secFree(tmp); list_iterator_destroy(it); secFreeList(possibleLocations); return oidc_errno; case OIDC_DIREXIST_OK: foundParent = 1; secFree(tmp); break; } if (foundParent) { break; } secFree(tmp); path = NULL; } list_iterator_destroy(it); if (path == NULL) { secFreeList(possibleLocations); return oidc_errno; } logger(DEBUG, "Using '%s' as oidcdir.", path); switch (dirExists(path)) { case OIDC_DIREXIST_ERROR: secFreeList(possibleLocations); return oidc_errno; case OIDC_DIREXIST_OK: secFreeList(possibleLocations); return OIDC_SUCCESS; } logger(DEBUG, "Creating '%s' as oidcdir.", path); oidc_error_t ret = createDir(path); char* issuerconfig_path = oidc_sprintf("%s/%s", path, ISSUER_CONFIG_FILENAME); secFreeList(possibleLocations); int fd = open(issuerconfig_path, O_WRONLY | O_CREAT | O_TRUNC, 0644); close(fd); secFree(issuerconfig_path); return ret; } /** @fn int removeOidcFile(const char* filename) * @brief removes a file located in the oidc dir * @param filename the filename of the file to be removed * @return On success, 0 is returned. On error, -1 is returned, and errno is * set appropriately. */ int removeOidcFile(const char* filename) { char* path = concatToOidcDir(filename); int r = removeFile(path); secFree(path); return r; } char* concatToOidcDir(const char* filename) { char* oidc_dir = getOidcDir(); char* path = oidc_strcat(oidc_dir, filename); secFree(oidc_dir); return path; } list_t* getLinesFromOidcFile(const char* filename) { char* path = concatToOidcDir(filename); list_t* ret = getLinesFromFile(path); secFree(path); return ret; } list_t* getLinesFromOidcFileWithoutComments(const char* filename) { char* path = concatToOidcDir(filename); list_t* ret = getLinesFromFileWithoutComments(path); secFree(path); return ret; } /** * @brief updates the issuer.config file. * If the issuer url is not already in the issuer.config file, it will be added. * @param issuer_url the issuer url to be added * @param shortname will be used as the default account config for this issuer */ void updateIssuerConfig(const char* issuer_url, const char* shortname) { if (issuer_url == NULL || shortname == NULL) { return; } char* issuers = readOidcFile(ISSUER_CONFIG_FILENAME); char* new_issuers; if (issuers) { if (strSubStringCase(issuers, issuer_url)) { secFree(issuers); return; } new_issuers = oidc_sprintf("%s\n%s %s", issuers, issuer_url, shortname); secFree(issuers); } else { new_issuers = oidc_sprintf("%s %s", issuer_url, shortname); } if (new_issuers == NULL) { logger(ERROR, "%s", oidc_serror()); } else { writeOidcFile(ISSUER_CONFIG_FILENAME, new_issuers); secFree(new_issuers); } } oidc-agent-4.2.6/src/utils/file_io/cryptFileUtils.c0000644000175000017500000000525414167074355021610 0ustar marcusmarcus#include "cryptFileUtils.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "wrapper/list.h" /** * @brief encrypts and writes a given text with the given password. * @param text the text to be encrypted * @param filepath an absolute path to the output file * @param password the encryption password * @return an oidc_error code. oidc_errno is set properly. */ oidc_error_t encryptAndWriteToFile(const char* text, const char* filepath, const char* password, const char* gpg_key) { if (text == NULL || filepath == NULL || (password == NULL && gpg_key == NULL)) { oidc_setArgNullFuncError(__func__); return oidc_errno; } char* toWrite = gpg_key ? encryptPGPWithVersionLine(text, gpg_key) : encryptWithVersionLine(text, password); if (toWrite == NULL) { return oidc_errno; } logger(DEBUG, "Write to file %s", filepath); writeFile(filepath, toWrite); secFree(toWrite); return OIDC_SUCCESS; } oidc_error_t encryptAndWriteToOidcFile(const char* text, const char* filename, const char* password, const char* gpg_key) { if (text == NULL || filename == NULL || (password == NULL && gpg_key == NULL)) { oidc_setArgNullFuncError(__func__); return oidc_errno; } logger(DEBUG, "Write to oidc file %s", filename); char* filepath = concatToOidcDir(filename); oidc_error_t ret = encryptAndWriteToFile(text, filepath, password, gpg_key); secFree(filepath); return ret; } /** * @brief decrypts a file in the oidcdir with the given password * @param filename the filename of the oidc-file * @param password th password used for decryption * @return a pointer to the decrypted filecontent. It has to be freed after * usage. */ char* decryptOidcFile(const char* filename, const char* password) { if (filename == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* filepath = concatToOidcDir(filename); char* ret = decryptFile(filepath, password); secFree(filepath); return ret; } char* decryptFile(const char* filepath, const char* password) { if (filepath == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (!fileDoesExist(filepath)) { return NULL; } list_t* lines = getLinesFromFile(filepath); if (lines == NULL) { return NULL; } char* ret = decryptLinesList(lines, password); secFreeList(lines); return ret; } oidc-agent-4.2.6/src/utils/file_io/promptCryptFileUtils.c0000644000175000017500000001026414167074355023007 0ustar marcusmarcus#include "promptCryptFileUtils.h" #include "utils/file_io/cryptFileUtils.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/memory.h" #include "utils/promptUtils.h" oidc_error_t _promptAndCryptAndWriteToAnyFile( const char* text, const char* filepath, const char* oidc_filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env, const char* gpg_key) { if (text == NULL || hint == NULL || (filepath == NULL && oidc_filename == NULL)) { oidc_setArgNullFuncError(__func__); return oidc_errno; } oidc_error_t (*encryptAndWriteFnc)(const char*, const char*, const char*, const char*) = oidc_filename != NULL ? encryptAndWriteToOidcFile : encryptAndWriteToFile; char* encryptionPassword = NULL; if (gpg_key == NULL) { encryptionPassword = getEncryptionPasswordFor(hint, suggestedPassword, pw_cmd, pw_file, pw_env); if (encryptionPassword == NULL) { return oidc_errno; } } oidc_error_t ret = encryptAndWriteFnc(text, oidc_filename ?: filepath, encryptionPassword, gpg_key); secFree(encryptionPassword); return ret; } oidc_error_t promptEncryptAndWriteToFile( const char* text, const char* filepath, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env, const char* gpg_key) { if (text == NULL || filepath == NULL || hint == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return _promptAndCryptAndWriteToAnyFile(text, filepath, NULL, hint, suggestedPassword, pw_cmd, pw_file, pw_env, gpg_key); } oidc_error_t promptEncryptAndWriteToOidcFile( const char* text, const char* filename, const char* hint, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env, const char* gpg_key) { if (text == NULL || filename == NULL || hint == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return _promptAndCryptAndWriteToAnyFile(text, NULL, filename, hint, suggestedPassword, pw_cmd, pw_file, pw_env, gpg_key); } struct resultWithEncryptionPassword getDecryptedFileAndPasswordFor( const char* filepath, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (filepath == NULL) { oidc_setArgNullFuncError(__func__); return RESULT_WITH_PASSWORD_NULL; } if (!fileDoesExist(filepath)) { oidc_errno = OIDC_EFNEX; return RESULT_WITH_PASSWORD_NULL; } return _getDecryptedTextAndPasswordWithPromptFor(filepath, 0, pw_cmd, pw_file, pw_env); } struct resultWithEncryptionPassword getDecryptedOidcFileAndPasswordFor( const char* filename, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (filename == NULL) { oidc_setArgNullFuncError(__func__); return RESULT_WITH_PASSWORD_NULL; } if (!oidcFileDoesExist(filename)) { oidc_errno = OIDC_EFNEX; return RESULT_WITH_PASSWORD_NULL; } return _getDecryptedTextAndPasswordWithPromptFor(filename, 1, pw_cmd, pw_file, pw_env); } char* getDecryptedFileFor(const char* filepath, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (filepath == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } struct resultWithEncryptionPassword res = getDecryptedFileAndPasswordFor(filepath, pw_cmd, pw_file, pw_env); secFree(res.password); return res.result; } char* getDecryptedOidcFileFor(const char* filename, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (filename == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } struct resultWithEncryptionPassword res = getDecryptedOidcFileAndPasswordFor(filename, pw_cmd, pw_file, pw_env); secFree(res.password); return res.result; } oidc-agent-4.2.6/src/utils/prompt.h0000644000175000017500000000231314167074355016537 0ustar marcusmarcus#ifndef PROMPT_H #define PROMPT_H #include "wrapper/list.h" #define CLI_PROMPT_NOT_VERBOSE 0 #define CLI_PROMPT_VERBOSE 1 #define PROMPT_NO_TIMEOUT 0 #define PROMPT_DEFAULT_TIMEOUT 300 typedef char* (*promptFnc)(const char*, const char*, const char*, unsigned char); char* _promptPasswordGUI(const char* text, const char* label, const char* init, const int timeout); int _promptConsentGUIDefaultYes(const char* text, const int timeout); char* promptPassword(const char* text, const char* label, const char* init, unsigned char cliVerbose); char* prompt(const char* text, const char* label, const char* init, unsigned char cliVerbose); int promptConsentDefaultNo(const char* text); int promptConsentDefaultYes(const char* text); list_t* promptMultiple(const char* text, const char* label, list_t* init, unsigned char cliVerbose); char* promptSelect(const char* text, const char* label, list_t* options, size_t favPos, unsigned char cliVerbose); void displayDeviceLinkGUI(const char* text, const char* qr_path); void displayAuthCodeLinkGUI(const char* text); #endif // PROMPT_H oidc-agent-4.2.6/src/utils/guiChecker.c0000644000175000017500000000014714167074355017265 0ustar marcusmarcus#include "guiChecker.h" #include int GUIAvailable() { return NULL != getenv("DISPLAY"); } oidc-agent-4.2.6/src/utils/macros.h0000644000175000017500000001463214120404223016465 0ustar marcusmarcus#ifndef OIDCAGENT_MARCOS_H #define OIDCAGENT_MARCOS_H #define _GET_NTH_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, \ _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, \ _25, _26, _27, _28, _29, _30, _31, N, ...) \ N #define COUNT_VARARGS(...) \ _GET_NTH_ARG("ignored", ##__VA_ARGS__, 30, 29, 28, 27, 26, 25, 24, 23, 22, \ 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, \ 4, 3, 2, 1, 0) // Define some macros to help us create overrides based on the // arity of a for-each-style macro. #define _fe_0(_call, ...) #define _fe_1(_call, x) _call(x) #define _fe_2(_call, x, ...) _call(x) _fe_1(_call, __VA_ARGS__) #define _fe_3(_call, x, ...) _call(x) _fe_2(_call, __VA_ARGS__) #define _fe_4(_call, x, ...) _call(x) _fe_3(_call, __VA_ARGS__) #define _fe_5(_call, x, ...) _call(x) _fe_4(_call, __VA_ARGS__) #define _fe_6(_call, x, ...) _call(x) _fe_5(_call, __VA_ARGS__) #define _fe_7(_call, x, ...) _call(x) _fe_6(_call, __VA_ARGS__) #define _fe_8(_call, x, ...) _call(x) _fe_7(_call, __VA_ARGS__) #define _fe_9(_call, x, ...) _call(x) _fe_8(_call, __VA_ARGS__) #define _fe_10(_call, x, ...) _call(x) _fe_9(_call, __VA_ARGS__) #define _fe_11(_call, x, ...) _call(x) _fe_10(_call, __VA_ARGS__) #define _fe_12(_call, x, ...) _call(x) _fe_11(_call, __VA_ARGS__) #define _fe_13(_call, x, ...) _call(x) _fe_12(_call, __VA_ARGS__) #define _fe_14(_call, x, ...) _call(x) _fe_13(_call, __VA_ARGS__) #define _fe_15(_call, x, ...) _call(x) _fe_14(_call, __VA_ARGS__) #define _fe_16(_call, x, ...) _call(x) _fe_15(_call, __VA_ARGS__) #define _fe_17(_call, x, ...) _call(x) _fe_16(_call, __VA_ARGS__) #define _fe_18(_call, x, ...) _call(x) _fe_17(_call, __VA_ARGS__) #define _fe_19(_call, x, ...) _call(x) _fe_18(_call, __VA_ARGS__) #define _fe_20(_call, x, ...) _call(x) _fe_19(_call, __VA_ARGS__) #define _fe_21(_call, x, ...) _call(x) _fe_20(_call, __VA_ARGS__) #define _fe_22(_call, x, ...) _call(x) _fe_21(_call, __VA_ARGS__) #define _fe_23(_call, x, ...) _call(x) _fe_22(_call, __VA_ARGS__) #define _fe_24(_call, x, ...) _call(x) _fe_23(_call, __VA_ARGS__) #define _fe_25(_call, x, ...) _call(x) _fe_24(_call, __VA_ARGS__) #define _fe_26(_call, x, ...) _call(x) _fe_25(_call, __VA_ARGS__) #define _fe_27(_call, x, ...) _call(x) _fe_26(_call, __VA_ARGS__) #define _fe_28(_call, x, ...) _call(x) _fe_27(_call, __VA_ARGS__) #define _fe_29(_call, x, ...) _call(x) _fe_28(_call, __VA_ARGS__) #define _fe_30(_call, x, ...) _call(x) _fe_29(_call, __VA_ARGS__) #define _fei_0(_call, ...) #define _fei_1(_call, n, x) _call(n, x) #define _fei_2(_call, n, x, ...) _call(n, x) _fei_1(_call, n + 1, __VA_ARGS__) #define _fei_3(_call, n, x, ...) _call(n, x) _fei_2(_call, n + 1, __VA_ARGS__) #define _fei_4(_call, n, x, ...) _call(n, x) _fei_3(_call, n + 1, __VA_ARGS__) #define _fei_5(_call, n, x, ...) _call(n, x) _fei_4(_call, n + 1, __VA_ARGS__) #define _fei_6(_call, n, x, ...) _call(n, x) _fei_5(_call, n + 1, __VA_ARGS__) #define _fei_7(_call, n, x, ...) _call(n, x) _fei_6(_call, n + 1, __VA_ARGS__) #define _fei_8(_call, n, x, ...) _call(n, x) _fei_7(_call, n + 1, __VA_ARGS__) #define _fei_9(_call, n, x, ...) _call(n, x) _fei_8(_call, n + 1, __VA_ARGS__) #define _fei_10(_call, n, x, ...) _call(n, x) _fei_9(_call, n + 1, __VA_ARGS__) #define _fei_11(_call, n, x, ...) _call(n, x) _fei_10(_call, n + 1, __VA_ARGS__) #define _fei_12(_call, n, x, ...) _call(n, x) _fei_11(_call, n + 1, __VA_ARGS__) #define _fei_13(_call, n, x, ...) _call(n, x) _fei_12(_call, n + 1, __VA_ARGS__) #define _fei_14(_call, n, x, ...) _call(n, x) _fei_13(_call, n + 1, __VA_ARGS__) #define _fei_15(_call, n, x, ...) _call(n, x) _fei_14(_call, n + 1, __VA_ARGS__) #define _fei_16(_call, n, x, ...) _call(n, x) _fei_15(_call, n + 1, __VA_ARGS__) #define _fei_17(_call, n, x, ...) _call(n, x) _fei_16(_call, n + 1, __VA_ARGS__) #define _fei_18(_call, n, x, ...) _call(n, x) _fei_17(_call, n + 1, __VA_ARGS__) #define _fei_19(_call, n, x, ...) _call(n, x) _fei_18(_call, n + 1, __VA_ARGS__) #define _fei_20(_call, n, x, ...) _call(n, x) _fei_19(_call, n + 1, __VA_ARGS__) #define _fei_21(_call, n, x, ...) _call(n, x) _fei_20(_call, n + 1, __VA_ARGS__) #define _fei_22(_call, n, x, ...) _call(n, x) _fei_21(_call, n + 1, __VA_ARGS__) #define _fei_23(_call, n, x, ...) _call(n, x) _fei_22(_call, n + 1, __VA_ARGS__) #define _fei_24(_call, n, x, ...) _call(n, x) _fei_23(_call, n + 1, __VA_ARGS__) #define _fei_25(_call, n, x, ...) _call(n, x) _fei_24(_call, n + 1, __VA_ARGS__) #define _fei_26(_call, n, x, ...) _call(n, x) _fei_25(_call, n + 1, __VA_ARGS__) #define _fei_27(_call, n, x, ...) _call(n, x) _fei_26(_call, n + 1, __VA_ARGS__) #define _fei_28(_call, n, x, ...) _call(n, x) _fei_27(_call, n + 1, __VA_ARGS__) #define _fei_29(_call, n, x, ...) _call(n, x) _fei_28(_call, n + 1, __VA_ARGS__) #define _fei_30(_call, n, x, ...) _call(n, x) _fei_29(_call, n + 1, __VA_ARGS__) /** * Provide a for-each construct for variadic macros. Supports up * to 30 args. * * Example usage1: * #define FWD_DECLARE_CLASS(cls) class cls; * CALL_MACRO_X_FOR_EACH(FWD_DECLARE_CLASS, Foo, Bar) * * Example usage 2: * #define START_NS(ns) namespace ns { * #define END_NS(ns) } * #define MY_NAMESPACES System, Net, Http * CALL_MACRO_X_FOR_EACH(START_NS, MY_NAMESPACES) * typedef foo int; * CALL_MACRO_X_FOR_EACH(END_NS, MY_NAMESPACES) */ #define CALL_MACRO_X_FOR_EACH(x, ...) \ _GET_NTH_ARG("ignored", ##__VA_ARGS__, _fe_30, _fe_29, _fe_28, _fe_27, \ _fe_26, _fe_25, _fe_24, _fe_23, _fe_22, _fe_21, _fe_20, _fe_19, \ _fe_18, _fe_17, _fe_16, _fe_15, _fe_14, _fe_13, _fe_12, _fe_11, \ _fe_10, _fe_9, _fe_8, _fe_7, _fe_6, _fe_5, _fe_4, _fe_3, _fe_2, \ _fe_1, _fe_0) \ (x, ##__VA_ARGS__) #define CALL_MACRO_X_FOR_EACH_WITH_N(x, ...) \ _GET_NTH_ARG("ignored", ##__VA_ARGS__, _fei_30, _fei_29, _fei_28, _fei_27, \ _fei_26, _fei_25, _fei_24, _fei_23, _fei_22, _fei_21, _fei_20, \ _fei_19, _fei_18, _fei_17, _fei_16, _fei_15, _fei_14, _fei_13, \ _fei_12, _fei_11, _fei_10, _fei_9, _fei_8, _fei_7, _fei_6, \ _fei_5, _fei_4, _fei_3, _fei_2, _fei_1, _fei_0) \ (x, 0, ##__VA_ARGS__) #endif // OIDCAGENT_MARCOS_H oidc-agent-4.2.6/src/utils/key_value.h0000644000175000017500000000360414167074355017206 0ustar marcusmarcus#ifndef KEY_VALUE_H #define KEY_VALUE_H #include #include "utils/macros.h" #include "utils/memory.h" struct key_value { const char* key; char* value; }; static inline void secFreeKeyValuePairs(struct key_value* pairs, size_t size) { size_t i; for (i = 0; i < size; i++) { _secFree(pairs[i].value); } } #define KEY_VALUE(i, keyname) \ pairs[(i)].key = (keyname); \ pairs[(i)].value = NULL; #define KEY_VALUE_VAR(i, valuename) char* _##valuename = pairs[(i)].value; #define KEY_VALUE_VARS(...) \ CALL_MACRO_X_FOR_EACH_WITH_N(KEY_VALUE_VAR, __VA_ARGS__) #define INIT_KEY_VALUE(...) \ struct key_value pairs[COUNT_VARARGS(__VA_ARGS__)]; \ CALL_MACRO_X_FOR_EACH_WITH_N(KEY_VALUE, __VA_ARGS__) #define GET_JSON_VALUES_RETURN_X_ONERROR(json, returnvalue) \ if (getJSONValuesFromString((json), pairs, sizeof(pairs) / sizeof(*pairs)) < \ 0) { \ secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)); \ return (returnvalue); \ } #define GET_JSON_VALUES_RETURN_NULL_ONERROR(json) \ GET_JSON_VALUES_RETURN_X_ONERROR((json), NULL) #define GET_JSON_VALUES_RETURN_OIDCERRNO_ONERROR(json) \ GET_JSON_VALUES_RETURN_X_ONERROR((json), oidc_errno) #define SEC_FREE_KEY_VALUES() \ secFreeKeyValuePairs(pairs, sizeof(pairs) / sizeof(*pairs)) #define CALL_GETJSONVALUES(json) \ getJSONValuesFromString((json), pairs, sizeof(pairs) / sizeof(*pairs)) #define CALL_GETJSONVALUES_FROM_CJSON(json) \ getJSONValues((json), pairs, sizeof(pairs) / sizeof(*pairs)) #define RESET_KEY_VALUE_VALUES_TO_NULL() \ for (size_t _i; _i < sizeof(pairs) / sizeof(*pairs); _i++) { \ pairs[_i].value = NULL; \ } #endif // KEY_VALUE_H oidc-agent-4.2.6/src/utils/portUtils.h0000644000175000017500000000065614120404223017207 0ustar marcusmarcus#ifndef PORT_UTILS_H #define PORT_UTILS_H #include "account/account.h" #include "utils/oidc_error.h" #define MAX_PORT 49151 #define MIN_PORT 1024 unsigned short getRandomPort(); char* portToUri(unsigned short port); unsigned int getPortFromUri(const char* uri); char* findRedirectUriByPort(const struct oidc_account* a, unsigned short port); oidc_error_t portIsInRange(unsigned short port); #endif // PORT_UTILS_H oidc-agent-4.2.6/src/utils/json.h0000644000175000017500000000413514167074355016173 0ustar marcusmarcus#ifndef OIDC_JSON_H #define OIDC_JSON_H #include "key_value.h" #include "oidc_error.h" #include "wrapper/cjson.h" #include "wrapper/list.h" void _secFreeJson(cJSON* cjson); char* getJSONValue(const cJSON* cjson, const char* key); char* getJSONValueFromString(const char* json, const char* key); oidc_error_t getJSONValues(const cJSON* cjson, struct key_value* pairs, size_t size); oidc_error_t getJSONValuesFromString(const char* json, struct key_value* pairs, size_t size); int jsonHasKey(const cJSON* cjson, const char* key); int jsonStringHasKey(const char* json, const char* key); int isJSONObject(const char* json); int jsonArrayIsEmpty(cJSON* json); char* jsonToString(cJSON* cjson); char* jsonToStringUnformatted(cJSON* cjson); cJSON* stringToJson(const char* json); list_t* JSONArrayToList(const cJSON* cjson); list_t* JSONArrayStringToList(const char* json); char* JSONArrayToDelimitedString(const cJSON* cjson, char* delim); char* JSONArrayStringToDelimitedString(const char* json, char* delim); cJSON* listToJSONArray(list_t* list); cJSON* generateJSONObject(const char* k1, int type1, const char* v1, ...); oidc_error_t setJSONValue(cJSON* cjson, const char* key, const char* value); cJSON* jsonAddJSON(cJSON* cjson, const char* key, cJSON* item); cJSON* jsonAddObjectValue(cJSON* cjson, const char* key, const char* json_object); cJSON* jsonAddArrayValue(cJSON* cjson, const char* key, const char* json_array); cJSON* jsonAddNumberValue(cJSON* cjson, const char* key, const double value); cJSON* jsonAddStringValue(cJSON* cjson, const char* key, const char* value); cJSON* jsonArrayAddStringValue(cJSON* cjson, const char* value); cJSON* generateJSONArray(char* v1, ...); cJSON* mergeJSONObjects(const cJSON* j1, const cJSON* j2); char* mergeJSONObjectStrings(const char* j1, const char* j2); #ifndef secFreeJson #define secFreeJson(ptr) \ do { \ _secFreeJson((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeJson #endif // OIDC_JSON_H oidc-agent-4.2.6/src/utils/printerUtils.c0000644000175000017500000000203714167074355017720 0ustar marcusmarcus#define _XOPEN_SOURCE #include #include "defines/settings.h" #include "printer.h" #include "utils/json.h" #include "utils/string/stringUtils.h" void printEnvs(const char* daemon_socket, pid_t daemon_pid, unsigned char quiet, unsigned char json) { if (!json) { if (daemon_socket != NULL) { printStdout("%s=%s; export %s;\n", OIDC_SOCK_ENV_NAME, daemon_socket, OIDC_SOCK_ENV_NAME); } if (daemon_pid != 0) { printStdout("%s=%d; export %s;\n", OIDC_PID_ENV_NAME, daemon_pid, OIDC_PID_ENV_NAME); if (!quiet) { printStdout("echo Agent pid $%s\n", OIDC_PID_ENV_NAME); } } } else { char* pid_str = oidc_sprintf("%d", daemon_pid); cJSON* jsonP = generateJSONObject("socket", cJSON_String, daemon_socket ?: "", "dpid", cJSON_String, pid_str, NULL); secFree(pid_str); char* jsonPrint = jsonToString(jsonP); secFreeJson(jsonP); printStdout("%s", jsonPrint); secFree(jsonPrint); } } oidc-agent-4.2.6/src/utils/pass.h0000644000175000017500000000013614120404223016141 0ustar marcusmarcus#ifndef PASS_H #define PASS_H #define pass \ do { \ } while (0) #endif // PASS_H oidc-agent-4.2.6/src/utils/password_entry.c0000644000175000017500000000757214167074355020310 0ustar marcusmarcus#include "password_entry.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/string/stringUtils.h" void _secFreePasswordEntry(struct password_entry* pw) { secFree(pw->shortname); secFree(pw->password); secFree(pw->command); secFree(pw->filepath); secFree(pw); } void pwe_setPassword(struct password_entry* pw, char* password) { if (pw->password == password) { return; } secFree(pw->password); pw->password = password; logger(DEBUG, "Setting password. Expires_at is %lu. Expires after is %lu", pw->expires_at, pw->expires_after); if (pw->expires_at == 0 && pw->expires_after != 0) { pw->expires_at = time(NULL) + pw->expires_after; } } void pwe_setCommand(struct password_entry* pw, char* command) { if (pw->command == command) { return; } secFree(pw->command); pw->command = command; } void pwe_setFile(struct password_entry* pw, char* filepath) { if (pw->filepath == filepath) { return; } secFree(pw->filepath); pw->filepath = filepath; } void pwe_setGPGKey(struct password_entry* pw, char* key_id) { if (pw->gpg_key == key_id) { return; } secFree(pw->gpg_key); pw->gpg_key = key_id; } void pwe_setShortname(struct password_entry* pw, char* shortname) { if (pw->shortname == shortname) { return; } secFree(pw->shortname); pw->shortname = shortname; } void pwe_setType(struct password_entry* pw, unsigned char type) { pw->type = type; } void pwe_setExpiresAt(struct password_entry* pw, time_t expires_at) { pw->expires_at = expires_at; } void pwe_setExpiresIn(struct password_entry* pw, time_t expires_in) { pwe_setExpiresAt(pw, expires_in ? time(NULL) + expires_in : 0); pwe_setExpiresAfter(pw, expires_in); } void pwe_setExpiresAfter(struct password_entry* pw, time_t expires_after) { pw->expires_after = expires_after; } cJSON* passwordEntryToJSON(const struct password_entry* pw) { if (pw == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } cJSON* json = stringToJson("{}"); if (pw->shortname) { jsonAddStringValue(json, PW_KEY_SHORTNAME, pw->shortname); } if (pw->type) { jsonAddNumberValue(json, PW_KEY_TYPE, pw->type); } if (pw->password) { jsonAddStringValue(json, PW_KEY_PASSWORD, pw->password); } if (pw->filepath) { jsonAddStringValue(json, PW_KEY_PWFILE, pw->filepath); } if (pw->gpg_key) { jsonAddStringValue(json, PW_KEY_GPG, pw->gpg_key); } if (pw->expires_at) { jsonAddNumberValue(json, PW_KEY_EXPIRESAT, pw->expires_at); } if (pw->expires_after) { jsonAddNumberValue(json, PW_KEY_EXPIRESAFTER, pw->expires_after); } if (pw->command) { jsonAddStringValue(json, PW_KEY_COMMAND, pw->command); } return json; } char* passwordEntryToJSONString(const struct password_entry* pw) { if (pw == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } cJSON* json = passwordEntryToJSON(pw); char* ret = jsonToString(json); secFreeJson(json); return ret; } struct password_entry* JSONStringToPasswordEntry(const char* json) { if (json == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } INIT_KEY_VALUE(PW_KEY_SHORTNAME, PW_KEY_TYPE, PW_KEY_PASSWORD, PW_KEY_PWFILE, PW_KEY_GPG, PW_KEY_EXPIRESAT, PW_KEY_EXPIRESAFTER, PW_KEY_COMMAND); GET_JSON_VALUES_RETURN_NULL_ONERROR(json); KEY_VALUE_VARS(shortname, type, password, filepath, gpg_key, expires_at, expires_after, command); struct password_entry* pw = secAlloc(sizeof(struct password_entry)); pwe_setShortname(pw, _shortname); pwe_setPassword(pw, _password); pwe_setCommand(pw, _command); pwe_setFile(pw, _filepath); pwe_setGPGKey(pw, _gpg_key); pwe_setType(pw, strToUChar(_type)); pwe_setExpiresAt(pw, strToULong(_expires_at)); pwe_setExpiresAfter(pw, strToULong(_expires_after)); secFree(_type); secFree(_expires_at); secFree(_expires_after); return pw; } oidc-agent-4.2.6/src/utils/commonFeatures.h0000644000175000017500000000032414120404223020161 0ustar marcusmarcus#ifndef COMMON_FEATURES_H #define COMMON_FEATURES_H void common_handleListConfiguredAccountConfigs(); void common_assertAgent(unsigned char remote); void common_assertOidcPrompt(); #endif // COMMON_FEATURES_H oidc-agent-4.2.6/src/utils/parseJson.c0000644000175000017500000000124714120404223017136 0ustar marcusmarcus#include "parseJson.h" #include "defines/oidc_values.h" #include "utils/errorUtils.h" #include "utils/json.h" #include "utils/printer.h" char* parseForError(char* res) { INIT_KEY_VALUE(OIDC_KEY_ERROR, OIDC_KEY_ERROR_DESCRIPTION); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); secFree(res); SEC_FREE_KEY_VALUES(); return NULL; } secFree(res); KEY_VALUE_VARS(error, error_description); if (_error_description) { char* error = combineError(_error, _error_description); SEC_FREE_KEY_VALUES(); return error; } return _error; } oidc-agent-4.2.6/src/utils/sleeper.h0000644000175000017500000000015614120404223016634 0ustar marcusmarcus#ifndef OIDC_UTILS_SLEEP_H #define OIDC_UTILS_SLEEP_H int msleep(const long); #endif // OIDC_UTILS_SLEEP_H oidc-agent-4.2.6/src/utils/prompt_mode.h0000644000175000017500000000110514120404223017515 0ustar marcusmarcus#ifndef OIDC_PROMPT_MODE_H #define OIDC_PROMPT_MODE_H #define PROMPT_MODE_CLI 1 #define PROMPT_MODE_GUI 2 /** set_prompt_mode sets the prompt mode and password prompt mode, if a * password prompt mode that is different from the normal prompt mode should * be set, the @c pw_prompt_mode function has be called after this function */ void set_prompt_mode(unsigned char mode); /** set_pw_prompt_mode sets only the pw_prompt_mode */ void set_pw_prompt_mode(unsigned char mode); unsigned char prompt_mode(); unsigned char pw_prompt_mode(); #endif // OIDC_PROMPT_MODE_H oidc-agent-4.2.6/src/utils/deathUtils.h0000644000175000017500000000034114167074355017323 0ustar marcusmarcus#ifndef DEATH_UTILS_H #define DEATH_UTILS_H #include #include "wrapper/list.h" time_t getMinDeathFrom(list_t*, time_t (*)(void*)); void* getDeathElementFrom(list_t*, time_t (*)(void*)); #endif // DEATH_UTILS_H oidc-agent-4.2.6/src/utils/promptUtils.h0000644000175000017500000000311214167074355017556 0ustar marcusmarcus#ifndef PROMPT_UTILS_H #define PROMPT_UTILS_H #include "utils/resultWithEncryptionPassword.h" char* getEncryptionPasswordFor(const char* forWhat, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getEncryptionPasswordForAccountConfig(const char* shortname, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getDecryptionPasswordFor(const char* forWhat, const char* pw_cmd, const char* pw_file, const char* pw_env, unsigned int max_pass_tries, unsigned int* number_try); char* getDecryptionPasswordForAccountConfig( const char* shortname, const char* pw_cmd, const char* pw_file, const char* pw_env, unsigned int max_pass_tries, unsigned int* number_try); struct resultWithEncryptionPassword _getDecryptedTextAndPasswordWithPromptFor( const char* file, unsigned char isAccountConfig, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getDecryptedTextWithPromptFor(const char* filet, unsigned char isAccountConfig, const char* pw_cmd, const char* pw_file, const char* pw_env); #endif // PROMPT_UTILS_H oidc-agent-4.2.6/src/utils/hostname.h0000644000175000017500000000014214120404223017006 0ustar marcusmarcus#ifndef OIDC_HOSTNAME_H #define OIDC_HOSTNAME_H char* getHostName(); #endif // OIDC_HOSTNAME_H oidc-agent-4.2.6/src/utils/password_entry.h0000644000175000017500000000410014167074355020275 0ustar marcusmarcus#ifndef OIDCAGENT_PASSWORD_ENTRY_H #define OIDCAGENT_PASSWORD_ENTRY_H #include #include "defines/agent_values.h" #include "defines/ipc_values.h" #include "wrapper/cjson.h" struct password_entry { char* shortname; unsigned char type; char* password; time_t expires_at; char* command; time_t expires_after; char* filepath; char* gpg_key; }; #define PW_TYPE_MEM 0x01 #define PW_TYPE_MNG 0x02 #define PW_TYPE_CMD 0x04 #define PW_TYPE_PRMT 0x08 #define PW_TYPE_FILE 0x10 #define PW_TYPE_GPG 0x20 #define PW_KEY_SHORTNAME IPC_KEY_SHORTNAME #define PW_KEY_TYPE "type" #define PW_KEY_PASSWORD IPC_KEY_PASSWORD #define PW_KEY_EXPIRESAT AGENT_KEY_EXPIRESAT #define PW_KEY_COMMAND "command" #define PW_KEY_EXPIRESAFTER "expires_after" #define PW_KEY_PWFILE "pw_file" #define PW_KEY_GPG "gpg_key" void _secFreePasswordEntry(struct password_entry*); cJSON* passwordEntryToJSON(const struct password_entry*); char* passwordEntryToJSONString(const struct password_entry*); struct password_entry* JSONStringToPasswordEntry(const char*); void pwe_setPassword(struct password_entry* pw, char* password); void pwe_setCommand(struct password_entry* pw, char* command); void pwe_setFile(struct password_entry* pw, char* file); void pwe_setShortname(struct password_entry* pw, char* shortname); void pwe_setType(struct password_entry* pw, unsigned char type); void pwe_setGPGKey(struct password_entry* pw, char* key_id); void pwe_setExpiresAt(struct password_entry* pw, time_t expires_at); void pwe_setExpiresIn(struct password_entry* pw, time_t expires_in); void pwe_setExpiresAfter(struct password_entry* pw, time_t expires_after); static inline time_t pwe_getExpiresAt(struct password_entry* pw) { return pw ? pw->expires_at : 0; } #ifndef secFreePasswordEntry #define secFreePasswordEntry(ptr) \ do { \ _secFreePasswordEntry((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreePasswordEntry #endif // OIDCAGENT_PASSWORD_ENTRY_H oidc-agent-4.2.6/src/utils/errorUtils.h0000644000175000017500000000021514120404223017343 0ustar marcusmarcus#ifndef ERROR_UTILS_H #define ERROR_UTILS_H char* combineError(const char* error, const char* error_description); #endif // ERROR_UTILS_H oidc-agent-4.2.6/src/utils/system_runner.c0000644000175000017500000000171614167074355020134 0ustar marcusmarcus#define _XOPEN_SOURCE #include "system_runner.h" #include #include #include #include "utils/file_io/file_io.h" #include "utils/logger.h" #include "utils/oidc_error.h" char* getOutputFromCommand(const char* cmd) { if (cmd == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Running command: %s", cmd); /* Open the command for reading. */ FILE* fp = popen(cmd, "r"); if (fp == NULL) { oidc_setErrnoError(); logger(ERROR, "Failed to execute command: %s", cmd); return NULL; } char* ret = readFILE(fp); pclose(fp); return ret; } void fireCommand(const char* cmd) { pid_t pid = fork(); if (pid == -1) { logger(ERROR, "fork %m"); return; } else if (pid > 0) { // parent return; } // child execlp("/bin/sh", "sh", "-c", cmd, (char*)NULL); /* exec functions only return on error */ logger(ERROR, "Error executing command: %m"); exit(EXIT_FAILURE); }oidc-agent-4.2.6/src/utils/accountUtils.h0000644000175000017500000000316714167074355017703 0ustar marcusmarcus#ifndef ACCOUNT_UTILS_H #define ACCOUNT_UTILS_H #include #include "account/account.h" time_t getMinAccountDeath(); struct oidc_account* getDeathAccount(); struct oidc_account* getAccountFromMaybeEncryptedFile(const char* filepath); struct resultWithEncryptionPassword getDecryptedAccountAndPasswordFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env); struct oidc_account* getDecryptedAccountFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env); char* getDecryptedAccountAsStringFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env); struct resultWithEncryptionPassword getDecryptedAccountAsStringAndPasswordFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env); struct oidc_account* db_findAccountByShortname(const char* shortname); list_t* db_findAccountsByIssuerUrl(const char* issuer_url); #endif // ACCOUNT_UTILS_H oidc-agent-4.2.6/src/utils/promptUtils.c0000644000175000017500000001473314167074355017564 0ustar marcusmarcus#include "promptUtils.h" #include #include "defines/settings.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/prompt.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" char* getEncryptionPasswordForAccountConfig(const char* shortname, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env) { char* forWhat = oidc_sprintf("account config '%s'", shortname); char* ret = getEncryptionPasswordFor(forWhat, suggestedPassword, pw_cmd, pw_file, pw_env); secFree(forWhat); return ret; } char* getDecryptionPasswordForAccountConfig( const char* shortname, const char* pw_cmd, const char* pw_file, const char* pw_env, unsigned int max_pass_tries, unsigned int* number_try) { char* forWhat = oidc_sprintf("account config '%s'", shortname); char* ret = getDecryptionPasswordFor(forWhat, pw_cmd, pw_file, pw_env, max_pass_tries, number_try); secFree(forWhat); return ret; } char* getEncryptionPasswordFor(const char* forWhat, const char* suggestedPassword, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (pw_env != NULL) { char* pass = oidc_strcopy(getenv(pw_env)); if (pass) { return pass; } } if (pw_cmd) { char* pass = getOutputFromCommand(pw_cmd); if (pass) { return pass; } } if (pw_file) { char* pass = getLineFromFile(pw_file); if (pass) { return pass; } } char* encryptionPassword = NULL; while (1) { char* prompt_text = oidc_sprintf("Enter encryption password for %s", forWhat); char* input = promptPassword(prompt_text, "Encryption password", suggestedPassword, CLI_PROMPT_VERBOSE); secFree(prompt_text); if (strValid(suggestedPassword) && input && strequal(suggestedPassword, input)) { // use same encryption password secFree(input); encryptionPassword = oidc_strcopy(suggestedPassword); return encryptionPassword; } else { encryptionPassword = input; char* confirm = promptPassword("Confirm encryption Password", "Encryption password", NULL, CLI_PROMPT_VERBOSE); if (!strequal(encryptionPassword, confirm)) { printError("Encryption passwords did not match.\n"); secFree(confirm); secFree(encryptionPassword); } else { secFree(confirm); return encryptionPassword; } } } } char* getDecryptionPasswordFor(const char* forWhat, const char* pw_cmd, const char* pw_file, const char* pw_env, unsigned int max_pass_tries, unsigned int* number_try) { unsigned int max_tries = max_pass_tries == 0 ? MAX_PASS_TRIES : max_pass_tries; if (pw_env && (number_try == NULL || *number_try == 0)) { char* pass = oidc_strcopy(getenv(pw_env)); if (pass) { if (number_try) { (*number_try)++; } return pass; } } if (pw_cmd && (number_try == NULL || *number_try == 0)) { char* pass = getOutputFromCommand(pw_cmd); if (pass) { if (number_try) { (*number_try)++; } return pass; } } if (pw_file && (number_try == NULL || *number_try == 0 || (*number_try == 1 && pw_cmd))) { char* pass = getLineFromFile(pw_file); if (pass) { if (number_try) { (*number_try)++; } return pass; } } if (number_try == NULL || *number_try < max_tries) { if (number_try) { (*number_try)++; } char* prompt_str = oidc_strcat("Enter decryption password for ", forWhat); char* input = promptPassword(prompt_str, "Encryption password", NULL, CLI_PROMPT_VERBOSE); secFree(prompt_str); return input; } oidc_errno = OIDC_EMAXTRIES; return NULL; } struct resultWithEncryptionPassword _getDecryptedTextAndPasswordWithPromptFor( const char* file, unsigned char isAccountConfig, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (file == NULL) { oidc_setArgNullFuncError(__func__); return RESULT_WITH_PASSWORD_NULL; } char* encrypted_content = isAccountConfig ? readOidcFile(file) : readFile(file); if (isPGPMessage(encrypted_content)) { char* plain = decryptPGPFileContent(encrypted_content); secFree(encrypted_content); return (struct resultWithEncryptionPassword){.result = plain, .password = NULL}; } char* (*getPasswordFnc)(const char*, const char*, const char*, const char*, unsigned int, unsigned int*) = isAccountConfig ? getDecryptionPasswordForAccountConfig : getDecryptionPasswordFor; char* password = NULL; char* fileContent = NULL; unsigned int i = 0; unsigned int* i_ptr = &i; while (NULL == fileContent) { secFree(password); password = getPasswordFnc(file, pw_cmd, pw_file, pw_env, 0 /*default MAX_PASS_TRY*/, i_ptr); if (password == NULL && oidc_errno == OIDC_EMAXTRIES) { oidc_perror(); return RESULT_WITH_PASSWORD_NULL; } fileContent = decryptFileContent(encrypted_content, password); } return (struct resultWithEncryptionPassword){.result = fileContent, .password = password}; } char* getDecryptedTextWithPromptFor(const char* file, unsigned char isAccountConfig, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (file == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } struct resultWithEncryptionPassword res = _getDecryptedTextAndPasswordWithPromptFor(file, isAccountConfig, pw_cmd, pw_file, pw_env); secFree(res.password); return res.result; } oidc-agent-4.2.6/src/utils/oidc/0000755000175000017500000000000014167074355015764 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/oidc/device.h0000644000175000017500000000077614167074355017406 0ustar marcusmarcus#ifndef OIDC_AGENT_DEVICE_H #define OIDC_AGENT_DEVICE_H #include #include #include "ipc/pipe.h" char* pollDeviceCode(const char* json_device, size_t interval, time_t expires_at, unsigned char remote, unsigned char only_at); char* agent_pollDeviceCode(const char* json_device, size_t interval, time_t expires_at, unsigned char only_at, struct ipcPipe* pipes); #endif // OIDC_AGENT_DEVICE_H oidc-agent-4.2.6/src/utils/oidc/device.c0000644000175000017500000000500314167074355017365 0ustar marcusmarcus#include "device.h" #include #include #include #include "defines/ipc_values.h" #include "defines/oidc_values.h" #include "ipc/cryptCommunicator.h" #include "ipc/pipe.h" #include "oidc-agent/oidc/device_code.h" #include "utils/json.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" char* _pollDeviceCode(const char* json_device, size_t interval, time_t expires_at, const unsigned char only_at, const unsigned char remote, struct ipcPipe* pipes) { while (expires_at ? expires_at > time(NULL) : 1) { sleep(interval); char* res = pipes ? ipc_communicateThroughPipe(*pipes, REQUEST_DEVICE, json_device, only_at) : ipc_cryptCommunicate(remote, REQUEST_DEVICE, json_device, only_at); INIT_KEY_VALUE(IPC_KEY_STATUS, OIDC_KEY_ERROR, IPC_KEY_CONFIG, OIDC_KEY_ACCESSTOKEN); if (CALL_GETJSONVALUES(res) < 0) { printError("Could not decode json: %s\n", res); printError("This seems to be a bug. Please hand in a bug report.\n"); SEC_FREE_KEY_VALUES(); secFree(res); return NULL; } secFree(res); KEY_VALUE_VARS(status, error, config, at); if (_error) { if (strequal(_error, OIDC_SLOW_DOWN)) { interval++; SEC_FREE_KEY_VALUES(); continue; } if (strequal(_error, OIDC_AUTHORIZATION_PENDING)) { SEC_FREE_KEY_VALUES(); continue; } oidc_seterror(_error); oidc_errno = OIDC_EERROR; SEC_FREE_KEY_VALUES(); return NULL; } secFree(_status); if (only_at) { secFree(_config); } else { secFree(_at); } return only_at ? _at : _config; } oidc_seterror("Device code is not valid any more!"); oidc_errno = OIDC_EERROR; return NULL; } char* pollDeviceCode(const char* json_device, size_t interval, time_t expires_at, const unsigned char remote, const unsigned char only_at) { return _pollDeviceCode(json_device, interval, expires_at, only_at, remote, NULL); } char* agent_pollDeviceCode(const char* json_device, size_t interval, time_t expires_at, const unsigned char only_at, struct ipcPipe* pipes) { return _pollDeviceCode(json_device, interval, expires_at ?: time(NULL) + 300, only_at, 0, pipes); } oidc-agent-4.2.6/src/utils/lifetimeArg.h0000644000175000017500000000023614120404223017424 0ustar marcusmarcus#ifndef LIFETIME_ARG_H #define LIFETIME_ARG_H #include struct lifetimeArg { time_t lifetime; short argProvided; }; #endif // LIFETIME_ARG_H oidc-agent-4.2.6/src/utils/memory.c0000644000175000017500000000342614167074355016527 0ustar marcusmarcus#include "memory.h" #include #include #include "memzero.h" #include "oidc_error.h" #include "utils/logger.h" void* secCalloc(size_t nmemb, size_t size) { return secAlloc(nmemb * size); } void* secAlloc(size_t size) { if (size == 0) { return NULL; } size_t sizesize = sizeof(size); void* p = calloc(size + sizesize, 1); if (p == NULL) { oidc_errno = OIDC_EALLOC; logger(ALERT, "Memory alloc failed when trying to allocate %lu bytes", size); return NULL; } *(size_t*)p = size; return p + sizeof(size); } void* secRealloc(void* p, size_t size) { if (p == NULL) { return secAlloc(size); } if (size == 0) { secFree(p); return NULL; } size_t oldsize = *(size_t*)(p - sizeof(size_t)); size_t movelen = oldsize < size ? oldsize : size; void* newp = secAlloc(size); if (newp == NULL) { return NULL; } memmove(newp, p, movelen); secFree(p); return newp; } void _secFreeArray(char** arr, size_t size) { size_t i; for (i = 0; i < size; i++) { secFree(arr[i]); } secFree(arr); } void _secFree(void* p) { if (p == NULL) { return; } void* fp = p - sizeof(size_t); size_t len = *(size_t*)fp; secFreeN(fp, len); } /** @fn void secFree(void* p, size_t len) * @brief clears and frees allocated memory. * @param p a pointer to the memory to be freed * @param len the length of the allocated memory */ void _secFreeN(void* p, size_t len) { if (p == NULL) { return; } moresecure_memzero(p, len); free(p); } void* oidc_memcopy(void* src, size_t size) { void* dest = secAlloc(size); memcpy(dest, src, size); return dest; } void oidc_memshiftr(void* src, size_t size) { char tmp = ((char*)src)[size - 1]; memmove(src + 1, src, size - 1); ((char*)src)[0] = tmp; } oidc-agent-4.2.6/src/utils/printer.c0000644000175000017500000000424514167074355016702 0ustar marcusmarcus#define _XOPEN_SOURCE #include "printer.h" #include #include #include #include "utils/colors.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" int getColorSupport() { char* agent_color = NULL; if ((agent_color = getenv("OIDC_AGENT_NOCOLOR")) != NULL) { if (*agent_color == '0') { // If NOCOLOR is false for OIDC-AGENT, color is desired return 1; } else { // if set to any other value, don't use color return 0; } } if (getenv("NO_COLOR")) { // If $NO_COLOR is set, don't use color return 0; } if (strequal(getenv("TERM"), "dumb")) { // If $TERM is 'dumb', don't use color return 0; } return 1; } int getColorSupportFor(FILE* out) { return getColorSupport() && isatty(fileno(out)); } int getColorSupportStderr() { return getColorSupportFor(stderr); } int getColorSupportStdout() { return getColorSupportFor(stdout); } int printNormal(char* fmt, ...) { va_list args; va_start(args, fmt); FILE* out = stderr; int ret = vfprintf(out, fmt, args); va_end(args); return ret; } int fprintNormal(FILE* out, char* fmt, ...) { va_list args; va_start(args, fmt); int ret = vfprintf(out, fmt, args); va_end(args); return ret; } int printStdout(char* fmt, ...) { va_list args; va_start(args, fmt); int ret = vprintf(fmt, args); va_end(args); return ret; } int printError(char* fmt, ...) { va_list args; va_start(args, fmt); FILE* out = stderr; int ret; if (getColorSupportFor(out)) { ret = printErrorColored(fmt, args); } else { ret = vfprintf(out, fmt, args); } va_end(args); return ret; } int printPrompt(char* fmt, ...) { va_list args; va_start(args, fmt); FILE* out = stderr; int ret; if (getColorSupportFor(out)) { ret = printPromptColored(fmt, args); } else { ret = vfprintf(out, fmt, args); } va_end(args); return ret; } int printImportant(char* fmt, ...) { va_list args; va_start(args, fmt); FILE* out = stderr; int ret; if (getColorSupportFor(out)) { ret = printImportantColored(fmt, args); } else { ret = vfprintf(out, fmt, args); } va_end(args); return ret; } oidc-agent-4.2.6/src/utils/matcher.h0000644000175000017500000000024714120404223016621 0ustar marcusmarcus#ifndef OIDC_MATCHER_H #define OIDC_MATCHER_H int matchStrings(const char* a, const char* b); int matchUrls(const char* a, const char* b); #endif // OIDC_MATCHER_H oidc-agent-4.2.6/src/utils/printerUtils.h0000644000175000017500000000033214120404223017675 0ustar marcusmarcus#ifndef PRINTER_UTILS_H #define PRINTER_UTILS_H #include void printEnvs(const char* daemon_socket, pid_t daemon_pid, unsigned char quiet, unsigned char json); #endif // PRINTER_UTILS_H oidc-agent-4.2.6/src/utils/hostname.c0000644000175000017500000000104214167074355017025 0ustar marcusmarcus#ifndef __APPLE__ #define _XOPEN_SOURCE 500 #else #define _XOPEN_SOURCE 600 #endif #include "hostname.h" #include #include #include "utils/memory.h" #include "utils/oidc_error.h" #ifndef HOST_NAME_MAX #include #ifdef MAXHOSTNAMELEN #define HOST_NAME_MAX MAXHOSTNAMELEN - 1 #else #define HOST_NAME_MAX 255 #endif #endif char* getHostName() { char* buf = secAlloc(HOST_NAME_MAX); if (gethostname(buf, HOST_NAME_MAX) != 0) { oidc_setErrnoError(); secFree(buf); return NULL; } return buf; } oidc-agent-4.2.6/src/utils/string/0000755000175000017500000000000014167074355016354 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/string/numberString.c0000644000175000017500000000260714167074355021204 0ustar marcusmarcus#include "numberString.h" #include #include "utils/memzero.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" static char table[] = " !\"#$%&'()*+,-./" "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" "abcdefghijklmnopqrstuvwxyz{|}~"; unsigned short charToNumber(char c) { for (size_t i = 0; i < strlen(table); i++) { if (table[i] == c) { return (unsigned short)(i + 1); } } return -1; } char numberToChar(unsigned short s) { return table[s - 1]; } unsigned long long lpow(unsigned long long base, unsigned long long exp) { unsigned long long result = 1ULL; while (exp) { if (exp & 1) { result *= base; } exp >>= 1; base *= base; } return result; } // Do not use more than 9 characters unsigned long long stringToNumber(char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return 0; } unsigned long long i = 0; while (*str != '\0') { i += lpow(strlen(table) + 1, (strlen(str) - 1)) * charToNumber(*str); str++; } return i; } char* numberToString(unsigned long long l) { char str[10]; short i = sizeof(str) - 1; str[i] = '\0'; while (l && i >= 0) { i--; str[i] = numberToChar(l % (strlen(table) + 1)); l /= strlen(table) + 1; } char* ret = oidc_strcopy(&str[i]); moresecure_memzero(str, 10); return ret; } oidc-agent-4.2.6/src/utils/string/numberString.h0000644000175000017500000000026214167074355021204 0ustar marcusmarcus#ifndef NUMBERSTRING_H #define NUMBERSTRING_H char* numberToString(unsigned long long l); unsigned long long stringToNumber(char* str); #endif // NUMBER_STRING_H oidc-agent-4.2.6/src/utils/string/stringUtils.c0000644000175000017500000002015114167074355021046 0ustar marcusmarcus#define _GNU_SOURCE #include "stringUtils.h" #include #include #include #include #include #include #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" /** @fn int strValid(const char* c) * @brief checks if a string contains a valid value, meaning it is not empty, * NULL or "(null)" * @param c the string to be checked * @return 1 if the string is valid; 0 if not */ int strValid(const char* c) { return c && !strequal("", c) && !strequal("(null)", c) && !strequal("null", c); } /** @fn strstarts(const char* str, const char* pre) * @brief checks if a string starts with a given string * @param str the string to be checked * @param pre the prefix \p str might start with * @return 1 if str starts with pre; 0 if not */ int strstarts(const char* str, const char* pre) { if (str == NULL || pre == NULL) { oidc_setArgNullFuncError(__func__); return 0; } return strncmp(pre, str, strlen(pre)) == 0; } int strEnds(const char* str, const char* suf) { if (str == NULL || suf == NULL) { oidc_setArgNullFuncError(__func__); return 0; } size_t lenstr = strlen(str); size_t lensuffix = strlen(suf); if (lensuffix > lenstr) { return 0; } return strncmp(str + lenstr - lensuffix, suf, lensuffix) == 0; } int strEndsNot(const char* str, const char* suf) { return strEnds(str, suf) == 0 ? 1 : 0; } char* oidc_sprintf(const char* fmt, ...) { if (fmt == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } va_list args; va_start(args, fmt); char* ret = oidc_vsprintf(fmt, args); va_end(args); return ret; } char* oidc_vsprintf(const char* fmt, va_list args) { if (fmt == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } va_list orig; va_copy(orig, args); size_t len = vsnprintf(NULL, 0, fmt, args); char* s = secAlloc(sizeof(char) * (len + 1)); if (s == NULL) { return NULL; } vsprintf(s, fmt, orig); va_end(orig); return s; } char* oidc_strcat(const char* str, const char* suf) { if (str == NULL || suf == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } return oidc_sprintf("%s%s", str, suf); } char* oidc_strcopy(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } return oidc_sprintf("%s", str); } char* oidc_strncopy(const char* str, int len) { if (str == NULL || len == 0) { oidc_setArgNullFuncError(__func__); return NULL; } int len_str = (int)strlen(str); return oidc_sprintf("%.*s", len_str < len ? len_str : len, str); } /** @fn char* getDateString() * @brief returns the current date in YYYY-mm-dd format * @returns a pointer to the formated date. Has to be freed after usage */ char* getDateString() { char* s = secAlloc(sizeof(char) * (10 + 1)); if (s == NULL) { return NULL; } time_t now = time(NULL); struct tm* t = secAlloc(sizeof(struct tm)); if (localtime_r(&now, t) == NULL) { oidc_setErrnoError(); secFree(t); return NULL; } strftime(s, 10 + 1, "%F", t); secFree(t); return s; } /** * eliminates a character c if it is followed by character f */ char* strelimIfFollowed(char* str, char c, char f) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return str; } size_t len = strlen(str); size_t i, j; for (i = 0; i < len - 1; i++) { if (str[i] == c && str[i + 1] == f) { for (j = i; j < len - 1; j++) { str[j] = str[j + 1]; } str[j] = '\0'; } } return str; } /** * eliminates a character c if it the previous character is f */ char* strelimIfAfter(char* str, char c, char f) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return str; } size_t len = strlen(str); size_t i, j; for (i = 1; i < len - 1; i++) { if (str[i] == c && str[i - 1] == f) { for (j = i; j < len - 1; j++) { str[j] = str[j + 1]; } str[j] = '\0'; } } return str; } char* strelim(char str[], char c) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } size_t len = strlen(str); size_t i, j; for (i = 0; i < len; i++) { if (str[i] == c) { for (j = i; j < len; j++) { str[j] = str[j + 1]; } } } return str; } char* strremove(char* str, const char* sub) { char *p, *q, *r; if ((q = r = strstr(str, sub)) != NULL) { size_t len = strlen(sub); while ((r = strstr(p = r + len, sub)) != NULL) { while (p < r) *q++ = *p++; } while ((*q++ = *p++) != '\0') continue; } return str; } size_t strCountChar(const char* s, char c) { if (s == NULL) { oidc_setArgNullFuncError(__func__); return 0; } int i; for (i = 0; s[i]; s[i] == c ? i++ : *s++) ; return i; } int strequal(const char* a, const char* b) { if (a == NULL && b == NULL) { return 1; } if (a == NULL || b == NULL) { return 0; } return strcmp(a, b) == 0 ? 1 : 0; } int strcaseequal(const char* a, const char* b) { if (a == NULL && b == NULL) { return 1; } if (a == NULL || b == NULL) { return 0; } return strcasecmp(a, b) == 0 ? 1 : 0; } char* escapeCharInStr(const char* str, char c) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* s = oidc_strcopy(str); const char* pos = s; unsigned int rel = pos - s; while (rel < strlen(s) && (pos = strchr(s + rel, c)) != NULL) { rel = pos - s; char* tmp = secRealloc(s, strlen(s) + 1 + 1); if (tmp == NULL) { secFree(s); return NULL; } s = tmp; memmove(s + rel + 1, s + rel, strlen(s + rel) + 1); s[rel] = '\\'; rel += 2; } return s; } int strSubStringCase(const char* h, const char* n) { if (h == NULL || n == NULL) { return 0; } return strcasestr(h, n) != NULL; } int strSubString(const char* h, const char* n) { if (h == NULL || n == NULL) { return 0; } return strstr(h, n) != NULL; } char* withTrailingSlash(const char* str) { if (str[strlen(str) - 1] == '/') { return oidc_strcopy(str); } return oidc_strcat(str, "/"); } size_t oidc_strlen(const char* str) { if (str == NULL) { return 0; } return strlen(str); } int strToInt(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return 0; } int i = 0; sscanf(str, "%d", &i); return i; } unsigned long strToULong(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return 0; } unsigned long l = 0; sscanf(str, "%lu", &l); return l; } unsigned char strToUChar(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return 0; } unsigned char c = 0; sscanf(str, "%hhu", &c); return c; } unsigned short strToUShort(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return 0; } unsigned short s = 0; sscanf(str, "%hu", &s); return s; } void debugPrintVaArg(const char* function, const char* fmt, va_list args) { va_list copy; va_copy(copy, args); printf("In function %s arguments are: ", function); vprintf(fmt, copy); va_end(copy); printf("\n"); } char firstNonWhiteSpaceChar(const char* str) { for (size_t i = 0; i < strlen(str); i++) { if (!isspace(str[i])) { return str[i]; } } return 0; } char* oidc_pathcat(const char* a, const char* b) { return lastChar(a) == '/' ? oidc_strcat(a, b) : oidc_sprintf("%s/%s", a, b); } char* repeatChar(char c, size_t n) { char* str = secAlloc(n + 1); memset(str, c, n); return str; } char* strreplace(const char* str, const char* old, const char* new) { if (str == NULL || old == NULL) { return NULL; } if (new == NULL) { new = ""; } size_t old_len = strlen(old); char* str_tmp = oidc_strcopy(str); char* result = oidc_strcopy(""); char* front = str_tmp; char* pos = strstr(front, old); while (pos != NULL) { *pos = '\0'; char* tmp = oidc_sprintf("%s%s%s", result, front, new); secFree(result); result = tmp; front = pos + old_len; pos = strstr(front, old); } char* tmp = oidc_strcat(result, front); secFree(result); result = tmp; secFree(str_tmp); return result; } oidc-agent-4.2.6/src/utils/string/stringbuilder.h0000644000175000017500000000236614167074355021411 0ustar marcusmarcus#ifndef OIDC_AGENT_STRINGBUILDER_H #define OIDC_AGENT_STRINGBUILDER_H #include struct str_builder; typedef struct str_builder str_builder_t; /** * @brief Create a str builder. * @return str builder. */ str_builder_t* str_builder_create(size_t alloc_interval); /** * @brief Frees a str builder. * @param sb Builder. */ void secFree_str_builder(str_builder_t* sb); /** * @brief Add a string to the builder. * @param sb Builder. * @param str String to add. */ void str_builder_add_str(str_builder_t* sb, const char* str); /** * @brief Add a character to the builder. * @param sb Builder. * @param c Character. */ void str_builder_add_char(str_builder_t* sb, char c); /** * @brief Add an integer as to the builder. * @param sb Builder. * @param val Int to add. */ void str_builder_add_int(str_builder_t* sb, int val); /** * @brief Returns the length of the string contained in the builder. * @param sb Builder. * @return Length. */ size_t str_builder_len(const str_builder_t* sb); /** * @brief Returns a copy of the string data. * @param sb Builder. * @return Copy of the internal string data, must be freed using @c secFree. */ char* str_builder_get_string(const str_builder_t* sb); #endif // OIDC_AGENT_STRINGBUILDER_H oidc-agent-4.2.6/src/utils/string/oidc_string.c0000644000175000017500000000057214167074355021030 0ustar marcusmarcus#include "oidc_string.h" #include "utils/logger.h" #include "utils/memory.h" oidc_error_t init_string(struct string* s) { s->len = 0; s->ptr = secAlloc(s->len + 1); if (s->ptr == NULL) { logger(EMERGENCY, "%s (%s:%d) alloc() failed: %m\n", __func__, __FILE__, __LINE__); oidc_errno = OIDC_EALLOC; return OIDC_EALLOC; } return OIDC_SUCCESS; } oidc-agent-4.2.6/src/utils/string/oidc_string.h0000644000175000017500000000032714167074355021033 0ustar marcusmarcus#ifndef OIDC_STRING_H #define OIDC_STRING_H #include #include "utils/oidc_error.h" struct string { char* ptr; size_t len; }; oidc_error_t init_string(struct string* s); #endif // OIDC_STRING_H oidc-agent-4.2.6/src/utils/string/stringbuilder.c0000644000175000017500000000466114167074355021404 0ustar marcusmarcus#include "stringbuilder.h" #include #include "utils/memory.h" #include "utils/string/stringUtils.h" struct str_builder { char* str; size_t alloced; size_t len; size_t alloc_interval; }; str_builder_t* str_builder_create(size_t alloc_interval) { str_builder_t* sb = secAlloc(sizeof(str_builder_t)); sb->alloc_interval = alloc_interval; sb->str = secAlloc(sb->alloc_interval + 1); sb->alloced = sb->alloc_interval; sb->len = 0; return sb; } void secFree_str_builder(str_builder_t* sb) { if (sb == NULL) { return; } secFree(sb->str); secFree(sb); } /** @brief Ensure there is enough space for data being added plus a NULL * terminator. * * @param sb The str_builder * @param add_len The length that needs to be added *not* including a * NULL terminator. */ static void str_builder_ensure_space(str_builder_t* sb, size_t add_len) { if (sb == NULL || add_len == 0) { return; } if (sb->alloced >= sb->len + add_len) { return; } while (sb->alloced < sb->len + add_len) { /* Doubling growth strategy. */ sb->alloced <<= 1; if (sb->alloced == 0) { /* Left shift of max bits will go to 0. An unsigned type set to * -1 will return the maximum possible size. However, we should * have run out of memory well before we need to do this. Since * this is the theoretical maximum total system memory we don't * have a flag saying we can't grow any more because it should * be impossible to get to this point. */ sb->alloced--; } } sb->str = secRealloc(sb->str, sb->alloced + 1); } void str_builder_add_str(str_builder_t* sb, const char* str) { if (sb == NULL || str == NULL || *str == '\0') { return; } size_t len = strlen(str); str_builder_ensure_space(sb, len); memmove(sb->str + sb->len, str, len); sb->len += len; } void str_builder_add_char(str_builder_t* sb, char c) { if (sb == NULL) { return; } str_builder_ensure_space(sb, 1); sb->str[sb->len] = c; sb->len++; } void str_builder_add_int(str_builder_t* sb, int val) { if (sb == NULL) { return; } char* v = oidc_sprintf("%d", val); str_builder_add_str(sb, v); secFree(v); } size_t str_builder_len(const str_builder_t* sb) { if (sb == NULL) { return 0; } return sb->len; } char* str_builder_get_string(const str_builder_t* sb) { if (sb == NULL) { return NULL; } return oidc_strcopy(sb->str); } oidc-agent-4.2.6/src/utils/string/stringUtils.h0000644000175000017500000000313014167074355021051 0ustar marcusmarcus#ifndef STRING_UTILS_H #define STRING_UTILS_H #include #include int strstarts(const char* str, const char* pre); int strEnds(const char* str, const char* suf); int strEndsNot(const char* str, const char* suf); int strValid(const char* c); size_t strCountChar(const char* s, char c); char* strelim(char* str, char c); char* strremove(char* str, const char* sub); int strequal(const char* a, const char* b); int strcaseequal(const char* a, const char* b); char* escapeCharInStr(const char* str, char c); int strSubStringCase(const char* h, const char* n); int strSubString(const char* h, const char* n); size_t oidc_strlen(const char* str); char* strelimIfFollowed(char str[], char c, char f); char* strelimIfAfter(char* str, char c, char f); char* oidc_sprintf(const char* fmt, ...); char* oidc_vsprintf(const char* fmt, va_list args); char* oidc_strcat(const char* str, const char* suf); char* oidc_pathcat(const char* a, const char* b); char* oidc_strcopy(const char* str); char* oidc_strncopy(const char* str, int len); char* withTrailingSlash(const char* str); char firstNonWhiteSpaceChar(const char* str); char* strreplace(const char* str, const char* old, const char* new); #define lastChar(str) str[strlen(str) - 1] char* getDateString(); unsigned long strToULong(const char* str); int strToInt(const char* str); unsigned char strToUChar(const char* str); unsigned short strToUShort(const char* str); char* repeatChar(char c, size_t n); void debugPrintVaArg(const char* function, const char* fmt, va_list args); #endif // STRING_UTILS_H oidc-agent-4.2.6/src/utils/deathUtils.c0000644000175000017500000000303714167074355017323 0ustar marcusmarcus#include "deathUtils.h" #include #include "utils/logger.h" #include "utils/oidc_error.h" /** * @brief returns the minimum death time in an list * @param list a list * @return the minimum time of death; might be @c 0 */ time_t getMinDeathFrom(list_t* list, time_t (*deathGetter)(void*)) { if (list == NULL) { oidc_setArgNullFuncError(__func__); return 0; } time_t min = 0; list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); while ((node = list_iterator_next(it))) { void* elem = node->val; time_t death = deathGetter(elem); logger(DEBUG, "this death is %lu", death); if (death > 0 && (death < min || min == 0) && death > time(NULL)) { logger(DEBUG, "updating min to %lu", death); min = death; } } list_iterator_destroy(it); logger(DEBUG, "Minimum death in list is %lu", min); return min; } void* getDeathElementFrom(list_t* list, time_t (*deathGetter)(void*)) { if (list == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); time_t now = time(NULL); while ((node = list_iterator_next(it))) { void* elem = node->val; time_t death = deathGetter(elem); if (death > 0 && death <= now) { list_iterator_destroy(it); logger(DEBUG, "Found element died at %lu (current time %lu)", death, now); return elem; } } list_iterator_destroy(it); logger(DEBUG, "Found no death element"); return NULL; } oidc-agent-4.2.6/src/utils/listUtils.c0000644000175000017500000002101314167074355017203 0ustar marcusmarcus#include "listUtils.h" #include #include #include "json.h" #include "memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" char* delimitedStringToJSONArray(char* str, char delimiter) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } size_t size = strCountChar(str, delimiter) + 1; char* copy = oidc_sprintf("%s", str); char* delim = oidc_sprintf("%c", delimiter); char* json = oidc_sprintf("\"%s\"", strtok(copy, delim)); size_t i; for (i = 1; i < size; i++) { char* tmp = oidc_sprintf("%s, \"%s\"", json, strtok(NULL, delim)); secFree(json); if (tmp == NULL) { secFree(delim); secFree(copy); return NULL; } json = tmp; } secFree(delim); secFree(copy); char* tmp = oidc_sprintf("[%s]", json); secFree(json); if (tmp == NULL) { return NULL; } return tmp; } char* listToJSONArrayString(list_t* list) { if (list == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } cJSON* json = listToJSONArray(list); if (json == NULL) { return NULL; } char* str = jsonToString(json); secFreeJson(json); return str; } list_t* delimitedStringToList(const char* str, char delimiter) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* copy = oidc_sprintf("%s", str); char* delim = oidc_sprintf("%c", delimiter); list_t* list = list_new(); list->free = (void(*)(void*)) & _secFree; list->match = (matchFunction)strequal; char* elem = strtok(copy, delim); while (elem != NULL) { list_rpush(list, list_node_new(oidc_sprintf(elem))); elem = strtok(NULL, delim); } secFree(delim); secFree(copy); return list; } char* listToDelimitedString(list_t* list, char* delimiter) { if (list == NULL) { return NULL; } if (list->len == 0) { return oidc_strcopy(""); } list_node_t* node = list_at(list, 0); char* tmp = NULL; char* str = oidc_sprintf("%s", (char*)node->val); unsigned int i; for (i = 1; i < list->len; i++) { tmp = oidc_sprintf("%s%s%s", str, delimiter, (char*)list_at(list, i)->val); secFree(str); if (tmp == NULL) { return NULL; } str = tmp; } return str; } void* passThrough(void* ptr) { return ptr; } list_t* createList(int copyValues, char* s, ...) { list_t* list = list_new(); list->match = (matchFunction)strequal; void* (*value_f_ptr)(void*) = passThrough; if (copyValues) { value_f_ptr = (void* (*)(void*))oidc_strcopy; list->free = (void(*)(void*))_secFree; } if (s == NULL) { return list; } va_list args; va_start(args, s); list_rpush(list, list_node_new(value_f_ptr(s))); char* a; while ((a = va_arg(args, char*)) != NULL) { list_rpush(list, list_node_new(value_f_ptr(a))); } va_end(args); return list; } list_t* intersectLists(list_t* a, list_t* b) { list_t* l = list_new(); l->free = _secFree; l->match = a->match; list_node_t* node; list_iterator_t* it = list_iterator_new(a, LIST_HEAD); while ((node = list_iterator_next(it))) { list_node_t* n = findInList(b, node->val); if (n) { list_rpush(l, list_node_new(oidc_strcopy(n->val))); } } list_iterator_destroy(it); return l; } list_t* copyList(list_t* a) { list_t* l = list_new(); l->free = _secFree; l->match = a->match; list_node_t* node; list_iterator_t* it = list_iterator_new(a, LIST_HEAD); while ((node = list_iterator_next(it))) { list_rpush(l, list_node_new(oidc_strcopy(node->val))); } list_iterator_destroy(it); return l; } list_t* mergeLists(list_t* a, list_t* b) { list_t* l = copyList(a); list_node_t* node; list_iterator_t* it = list_iterator_new(b, LIST_HEAD); while ((node = list_iterator_next(it))) { list_node_t* n = findInList(a, node->val); if (n == NULL) { list_rpush(l, list_node_new(oidc_strcopy(node->val))); } } list_iterator_destroy(it); return l; } /** * a-b */ list_t* subtractLists(list_t* a, list_t* b) { list_t* l = list_new(); l->free = a->free; l->match = a->match; list_node_t* node; list_iterator_t* it = list_iterator_new(a, LIST_HEAD); while ((node = list_iterator_next(it))) { list_node_t* n = findInList(b, node->val); if (n == NULL) { list_rpush(l, list_node_new(oidc_strcopy(node->val))); } } list_iterator_destroy(it); return l; } int listValid(const list_t* l) { if (l == NULL) { return 0; } if (l->len == 0) { return 0; } return 1; } char* subtractListStrings(const char* a, const char* b, const char del) { list_t* al = delimitedStringToList(a, del); list_t* bl = delimitedStringToList(b, del); list_t* l = subtractLists(al, bl); secFreeList(al); secFreeList(bl); if (!listValid(l)) { secFreeList(l); return NULL; } char* del_str = (char[2]){del, 0}; char* s = listToDelimitedString(l, del_str); secFreeList(l); return s; } list_node_t* findInList(list_t* l, const void* v) { if (l == NULL) { return NULL; } return list_find(l, v); } list_t* findAllInList(list_t* l, const void* v) { if (l == NULL || v == NULL) { return NULL; } list_t* founds = list_new(); founds->match = l->match; // Don't copy the free function over. We copy the same value pointer, the // values should not be freed, only the list list_iterator_t* it = list_iterator_new(l, LIST_HEAD); list_node_t* node; while ((node = list_iterator_next(it))) { if (l->match) { if (l->match(v, node->val)) { list_rpush(founds, list_node_new(node->val)); } } else { if (v == node->val) { list_rpush(founds, list_node_new(node->val)); } } } list_iterator_destroy(it); if (!listValid(founds)) { secFreeList(founds); founds = NULL; } return founds; } void list_removeIfFound(list_t* l, const void* v) { if (l == NULL || v == NULL) { return; } list_node_t* node = findInList(l, v); if (node == NULL) { return; } return list_remove(l, node); } // Merges two subarrays of arr[]. // First subarray is arr[l..m] // Second subarray is arr[m+1..r] void _merge(void* arr[], int l, int m, int r, int (*comp)(const void*, const void*)) { int i, j, k; int n1 = m - l + 1; int n2 = r - m; /* create temp arrays */ void* L[n1]; void* R[n2]; /* Copy data to temp arrays L[] and R[] */ for (i = 0; i < n1; i++) L[i] = arr[l + i]; for (j = 0; j < n2; j++) R[j] = arr[m + 1 + j]; /* Merge the temp arrays back into arr[l..r]*/ i = 0; // Initial index of first subarray j = 0; // Initial index of second subarray k = l; // Initial index of merged subarray while (i < n1 && j < n2) { if (comp(L[i], R[j]) <= 0) { arr[k] = L[i]; i++; } else { arr[k] = R[j]; j++; } k++; } /* Copy the remaining elements of L[], if there are any */ while (i < n1) { arr[k] = L[i]; i++; k++; } /* Copy the remaining elements of R[], if there are any */ while (j < n2) { arr[k] = R[j]; j++; k++; } } /* l is for left index and r is right index of the sub-array of arr to be sorted */ void mergeSort(void* arr[], int l, int r, int (*comp)(const void*, const void*)) { if (l < r) { // Same as (l+r)/2, but avoids overflow for // large l and h int m = l + (r - l) / 2; // Sort first and second halves mergeSort(arr, l, m, comp); mergeSort(arr, m + 1, r, comp); _merge(arr, l, m, r, comp); } } void list_mergeSort(list_t* l, int (*comp)(const void*, const void*)) { void* arr[l->len]; for (size_t i = 0; i < l->len; i++) { arr[i] = list_at(l, i)->val; } mergeSort(arr, 0, l->len - 1, comp); for (size_t i = 0; i < l->len; i++) { list_at(l, i)->val = arr[i]; } } void secFreeList(list_t* l) { if (l == NULL) { return; } list_destroy(l); } list_t* list_addStringIfNotFound(list_t* l, char* v) { if (v == NULL || l == NULL) { return l; } if (findInList(l, v)) { return l; } char* value = v; if (l->free == _secFree) { value = oidc_strcopy(v); } list_rpush(l, list_node_new(value)); return l; } void _printList(list_t* l) { if (l == NULL) { printStdout("NULL\n"); return; } printStdout("LIST START\n"); list_node_t* node; list_iterator_t* it = list_iterator_new(l, LIST_HEAD); while ((node = list_iterator_next(it))) { printStdout("\tnode value: %s\n", (char*)node->val ?: "NULL"); } list_iterator_destroy(it); printStdout("LIST END\n"); } oidc-agent-4.2.6/src/utils/uriUtils.c0000644000175000017500000001017714167074355017040 0ustar marcusmarcus#include "uriUtils.h" #include #include #include #include "defines/agent_values.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/portUtils.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" #include "wrapper/list.h" oidc_error_t urldecode(char* dst, const char* src) { if (dst == NULL || src == NULL) { return OIDC_EARGNULL; } char a, b; while (*src) { if ((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { if (a >= 'a') a -= 'a' - 'A'; if (a >= 'A') a -= ('A' - 10); else a -= '0'; if (b >= 'a') b -= 'a' - 'A'; if (b >= 'A') b -= ('A' - 10); else b -= '0'; *dst++ = 16 * a + b; src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst++ = '\0'; return OIDC_SUCCESS; } char* getBaseUri(const char* uri) { if (uri == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* tmp = oidc_strcopy(uri); char* tmp_uri = strtok(tmp, "?"); char* base = oidc_strcopy(tmp_uri); secFree(tmp); urldecode(base, base); return base; } struct codeState codeStateFromURI(const char* uri) { if (uri == NULL) { oidc_setArgNullFuncError(__func__); return (struct codeState){}; } char* state = extractParameterValueFromUri(uri, "state"); char* code = extractParameterValueFromUri(uri, "code"); char* base_uri = getBaseUri(uri); if (base_uri == NULL) { oidc_errno = OIDC_ENOBASEURI; } else if (state == NULL) { oidc_errno = OIDC_ENOSTATE; } else if (code == NULL) { oidc_errno = OIDC_ENOCODE; } return (struct codeState){.uri = base_uri, .state = state, .code = code}; } void secFreeCodeState(struct codeState cs) { secFree(cs.code); secFree(cs.state); secFree(cs.uri); } char* findCustomSchemeUri(list_t* uris) { list_node_t* node; list_iterator_t* it = list_iterator_new(uris, LIST_HEAD); while ((node = list_iterator_next(it))) { char* uri = node->val; if (strstarts(uri, AGENT_CUSTOM_SCHEME)) { list_iterator_destroy(it); return uri; } } list_iterator_destroy(it); return NULL; } char* extractParameterValueFromUri(const char* uri, const char* parameter) { if (uri == NULL || parameter == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Extracting parameter '%s' from uri '%s'", parameter, uri); char* tmp = oidc_strcopy(uri); char* params = strchr(tmp, '?'); if (params == NULL) { return NULL; } params++; char* param_k = strtok(params, "="); char* param_v = strtok(NULL, "&"); char* value = NULL; while (value == NULL && param_k != NULL && param_v != NULL) { // logger(DEBUG, "URI contains parameter: %s - %s", // param_k, param_v); if (strequal(parameter, param_k)) { value = oidc_strcopy(param_v); break; } param_k = strtok(NULL, "="); param_v = strtok(NULL, "&"); } secFree(tmp); urldecode(value, value); logger(DEBUG, "Extracted value is '%s'", value); return value; } oidc_error_t checkRedirectUrisForErrors(list_t* redirect_uris) { if (redirect_uris == NULL || redirect_uris->len == 0) { return OIDC_EARGNULL; } oidc_error_t err = OIDC_SUCCESS; list_node_t* node; list_iterator_t* it = list_iterator_new(redirect_uris, LIST_HEAD); while ((node = list_iterator_next(it))) { if (strstarts(node->val, AGENT_CUSTOM_SCHEME)) { continue; } unsigned int port = getPortFromUri(node->val); if (port == 0) { printError("%s is not a valid redirect_uri. The redirect uri has to " "be in the following format: http://localhost:[/*] or " "%s\n", (char*)node->val, AGENT_CUSTOM_SCHEME); err = OIDC_EERROR; } else if (port < MIN_PORT || port > MAX_PORT) { printError("The port number has to be between %d and %d\n", MIN_PORT, MAX_PORT); err = OIDC_EERROR; } } list_iterator_destroy(it); return err; } oidc-agent-4.2.6/src/utils/sleeper.c0000644000175000017500000000073114167074355016652 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "sleeper.h" #include #include "utils/logger.h" int msleep(const long t) { const unsigned long nano = t * 1000 * 1000; const unsigned long seconds = nano / 1000 / 1000 / 1000; struct timespec tv = {.tv_sec = seconds, .tv_nsec = nano - seconds * 1000 * 1000 * 1000}; logger(DEBUG, "sleep for %ld ms", t); int ret = nanosleep(&tv, NULL); logger(DEBUG, "Woke up"); return ret; } oidc-agent-4.2.6/src/utils/printer.h0000644000175000017500000000044314120404223016657 0ustar marcusmarcus#ifndef PRINTER_H #define PRINTER_H #include int fprintNormal(FILE* out, char* fmt, ...); int printNormal(char* fmt, ...); int printStdout(char* fmt, ...); int printError(char* fmt, ...); int printPrompt(char* fmt, ...); int printImportant(char* fmt, ...); #endif // PRINTER_H oidc-agent-4.2.6/src/utils/guiChecker.h0000644000175000017500000000017114167074355017267 0ustar marcusmarcus#ifndef OIDC_AGENT_GUICHECKER_H #define OIDC_AGENT_GUICHECKER_H int GUIAvailable(); #endif // OIDC_AGENT_GUICHECKER_H oidc-agent-4.2.6/src/utils/uriUtils.h0000644000175000017500000000102614120404223017012 0ustar marcusmarcus#ifndef OIDC_URIUTILS_H #define OIDC_URIUTILS_H #include "utils/oidc_error.h" #include "wrapper/list.h" struct codeState { char* code; char* state; char* uri; }; struct codeState codeStateFromURI(const char* uri); void secFreeCodeState(struct codeState cs); char* findCustomSchemeUri(list_t* uris); char* extractParameterValueFromUri(const char* uri, const char* parameter); char* getBaseUri(const char* uri); oidc_error_t checkRedirectUrisForErrors(list_t* redirect_uris); #endif // OIDC_URIUTILS_H oidc-agent-4.2.6/src/utils/resultWithEncryptionPassword.h0000644000175000017500000000050014120404223023136 0ustar marcusmarcus#ifndef RESULT_WITH_ENCRYPTION_PASSWORD_H #define RESULT_WITH_ENCRYPTION_PASSWORD_H struct resultWithEncryptionPassword { void* result; char* password; }; #define RESULT_WITH_PASSWORD_NULL \ (struct resultWithEncryptionPassword) { .result = NULL, .password = NULL } #endif // RESULT_WITH_ENCRYPTION_PASSWORD_H oidc-agent-4.2.6/src/utils/errorUtils.c0000644000175000017500000000113714167074355017366 0ustar marcusmarcus#include "errorUtils.h" #include "utils/string/stringUtils.h" /** * combines an error and an error description string into one * @param error the error string * @param error_description the error description * @return a pointer to the combined string; has to be freed after usage. * @c NULL when both not valid */ char* combineError(const char* error, const char* error_description) { if (!strValid(error) && !strValid(error_description)) { return NULL; } if (!strValid(error_description)) { return oidc_strcopy(error); } return oidc_sprintf("%s: %s", error, error_description); } oidc-agent-4.2.6/src/utils/pubClientInfos.h0000644000175000017500000000057114120404223020122 0ustar marcusmarcus#ifndef PUBCLIENT_INFOS_H #define PUBCLIENT_INFOS_H #include "wrapper/list.h" struct pubClientInfos { char* client_id; char* client_secret; char* scope; }; void secFreePubClientInfos(struct pubClientInfos* p); struct pubClientInfos* getPubClientInfos(const char* issuer); list_t* defaultRedirectURIs(); #endif /* PUBCLIENT_INFOS_H */ oidc-agent-4.2.6/src/utils/agentLogger.h0000644000175000017500000000043414120404223017432 0ustar marcusmarcus#ifndef OIDC_AGENT_LOGGER_H #define OIDC_AGENT_LOGGER_H #include "utils/logger.h" void setLogWithTerminal(); void setLogWithoutTerminal(); extern void (*agent_log)(int log_level, const char* msg, ...); void agent_openlog(const char* logger_name); #endif /* OIDC_AGENT_LOGGER_H */ oidc-agent-4.2.6/src/utils/disableTracing.h0000644000175000017500000000020314120404223020101 0ustar marcusmarcus#ifndef OIDC_DISABLE_TRACING_H #define OIDC_DISABLE_TRACING_H void platform_disable_tracing(); #endif // OIDC_DISABLE_TRACING_H oidc-agent-4.2.6/src/utils/versionUtils.h0000644000175000017500000000046614120404223017707 0ustar marcusmarcus#ifndef OIDC_VERSION_UTILS_H #define OIDC_VERSION_UTILS_H #define MIN_BASE64_VERSION "2.1.0" int versionAtLeast(const char* version, const char* minVersion); char* versionLineToSimpleVersion(const char* version_line); char* simpleVersionToVersionLine(const char* version); #endif // OIDC_VERSION_UTILS_H oidc-agent-4.2.6/src/utils/oidc_error.c0000644000175000017500000001523214167074355017344 0ustar marcusmarcus#include "oidc_error.h" #include #include #include "utils/memory.h" #include "utils/memzero.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" int oidc_errno; char oidc_error[1024]; void oidc_seterror(const char* error) { moresecure_memzero(oidc_error, sizeof(oidc_error)); strncpy(oidc_error, error, sizeof(oidc_error) - 1); oidc_error[sizeof(oidc_error) - 1] = '\0'; } void oidc_setInternalError(const char* error) { char error_msg[1024]; strcpy(error_msg, "Internal error: "); strncpy(error_msg + strlen("Internal error: "), error, sizeof(error_msg) - strlen("Internal error: ") - 1); oidc_seterror(error_msg); oidc_errno = OIDC_EINTERNAL; } void oidc_setErrnoError() { oidc_errno = OIDC_EERROR; return oidc_seterror(strerror(errno)); } void oidc_setArgNullFuncError(const char* fncname) { char* err = oidc_sprintf("Argument is NULL in function %s", fncname); oidc_seterror(err); secFree(err); oidc_errno = OIDC_EARGNULLFUNC; } char* oidc_serrorFor(oidc_error_t err) { switch (err) { case OIDC_SUCCESS: return "success"; case OIDC_EERROR: return oidc_error; case OIDC_EALLOC: return "memory alloc failed"; case OIDC_EMEM: return "system out of memory"; case OIDC_EEOF: return "empty file"; case OIDC_EFOPEN: return "could not open file"; case OIDC_EFREAD: return "could not read file"; case OIDC_EWRITE: return "could not write"; case OIDC_EFNEX: return "could not open file - file does not exist"; case OIDC_EPASS: return "wrong password"; case OIDC_ECRYPPUB: return "received suspicious public key"; case OIDC_ECRYPM: return "encryption malformed"; case OIDC_ECRYPMIPC: return "internal error: ipc encrypted message malformed"; case OIDC_EENCRYPT: return "encryption failed"; case OIDC_EDECRYPT: return "decryption failed"; case OIDC_ECRYPHASH: return "could not hash string"; case OIDC_EURL: return "could not connect to url"; case OIDC_ESSL: return "error with ssl cert"; case OIDC_ECURLI: return "could not init curl"; case OIDC_EARGNULL: return "argument is NULL"; case OIDC_EARGNULLFUNC: return oidc_error; case OIDC_EJSONPARS: return "could not parse json"; case OIDC_EJSONOBJ: return "is not a json object"; case OIDC_EJSONARR: return "is not a json array"; case OIDC_EJSONNOFOUND: return "could not find json key"; case OIDC_EJSONADD: return "The json string does not end with '}'"; case OIDC_EJSONMERGE: return "Cannot merge json objects"; case OIDC_EJSONTYPE: return "Unknown cJSON Type"; case OIDC_ETCS: return "error tcsetattr"; case OIDC_EIN: return "error getline"; case OIDC_EBADCONFIG: return "bad configuration"; case OIDC_EOIDC: return oidc_error; case OIDC_ECRED: return "Bad credentials"; case OIDC_ENOREFRSH: return "No or malformed refresh token"; case OIDC_ENODEVICE: return "Device Flow not Supported by OpenID Provider"; case OIDC_EFMT: return "Format Validation Error"; case OIDC_EUNSCOPE: return "Could not register the necessary scopes dynamically"; case OIDC_EPORTRANGE: return "Port not in valid range"; case OIDC_EMKTMP: return "Could not make temp socket directory"; case OIDC_EENVVAR: return oidc_error; case OIDC_EBIND: return "Could not bind ipc-socket"; case OIDC_ECONSOCK: return "Could not connect to oidc-agent"; case OIDC_ECRSOCK: return "Could not create ipc-socket"; case OIDC_EMSGSIZE: return "Message size exceeds maximum package size"; case OIDC_ESOCKINV: return "Invalid socket"; case OIDC_EIOCTL: return "error ioctl"; case OIDC_EIPCDIS: return "the other party disconnected"; case OIDC_ETIMEOUT: return "reached timeout"; case OIDC_EGROUPNF: return "Group does not exist"; case OIDC_ESELECT: return "error select"; case OIDC_EMAXTRIES: return "reached maximum number of tries"; case OIDC_ENOACCOUNT: return "No account configured with that short name"; case OIDC_EHTTPD: return "Could not start http server"; case OIDC_EHTTPPORTS: return "Could not start the http server on any of the registered " "redirect uris."; case OIDC_ENOREURI: return "No redirect_uri specified"; case OIDC_EHTTP0: return "Internal error: Http sent 0"; case OIDC_ENOSTATE: return "redirected uri did not contain state parameter"; case OIDC_ENOCODE: return "redirected uri did not contain code parameter"; case OIDC_ENOBASEURI: return "could not get base uri from redirected uri"; case OIDC_EWRONGSTATE: return "wrong state"; case OIDC_EWRONGDEVICECODE: return "wrong device code"; case OIDC_ENOPRIVCONF: return "Privilege configuration file not found"; case OIDC_ENOSUPREG: return "Dynamic registration is not supported by this issuer. Please " "register a client manually and then run oidc-gen with the -m " "flag."; case OIDC_ENOSUPREV: return "Token revocation is not supported by this issuer."; case OIDC_ENOPUBCLIENT: return "No public client found for this issuer"; case OIDC_ELOCKED: return "Agent locked"; case OIDC_ENOTLOCKED: return "Agent not locked"; case OIDC_EINTERNAL: return oidc_error; case OIDC_EPWNOTFOUND: return "Password not found"; case OIDC_EGERROR: return oidc_error; case OIDC_EUSRPWCNCL: return "user cancelled password prompt"; case OIDC_EFORBIDDEN: return "operation forbidden"; case OIDC_NOTIMPL: return "Not yet implemented"; case OIDC_ENOPE: return "Computer says NO!"; default: return "Computer says NO!"; } } char* oidc_serror() { if (oidc_errno >= 200 && oidc_errno < 600) { char* error = oidc_sprintf("Received Http Status Code %d", oidc_errno); oidc_seterror(error); secFree(error); return oidc_error; } return oidc_serrorFor(oidc_errno); } int errorMessageIsForError(const char* error_msg, oidc_error_t err) { if (error_msg == NULL) { return 0; } return strequal(error_msg, oidc_serrorFor(err)); } void oidc_perror() { printError("Error: %s\n", oidc_serror()); } struct oidc_error_state* saveErrorState() { struct oidc_error_state* state = secAlloc(sizeof(struct oidc_error_state)); state->oidc_errno = oidc_errno; state->oidc_error = oidc_strcopy(oidc_error); return state; } void restoreErrorState(struct oidc_error_state* state) { if (state == NULL) { return; } oidc_errno = state->oidc_errno; oidc_seterror(state->oidc_error); } void restoreAndFreeErrorState(struct oidc_error_state* state) { restoreErrorState(state); secFreeErrorState(state); } void secFreeErrorState(struct oidc_error_state* state) { if (state == NULL) { return; } secFree(state->oidc_error); secFree(state); } oidc-agent-4.2.6/src/utils/oidc_error.h0000644000175000017500000000513314167074355017350 0ustar marcusmarcus#ifndef OIDC_ERROR_H #define OIDC_ERROR_H enum _oidc_error { OIDC_SUCCESS = 0, OIDC_EERROR = -1, OIDC_EALLOC = -2, OIDC_EMEM = -3, OIDC_EEOF = -5, OIDC_EFOPEN = -6, OIDC_EFREAD = -7, OIDC_EWRITE = -8, OIDC_EFNEX = -9, OIDC_EURL = -10, OIDC_ESSL = -11, OIDC_ECURLI = -12, OIDC_ECRYPPUB = -14, OIDC_EDECRYPT = -15, OIDC_EENCRYPT = -16, OIDC_ECRYPHASH = -17, OIDC_EPASS = -18, OIDC_ECRYPM = -19, OIDC_ECRYPMIPC = -190, OIDC_EARGNULL = -20, OIDC_EARGNULLFUNC = -21, OIDC_EJSONPARS = -30, OIDC_EJSONOBJ = -31, OIDC_EJSONARR = -32, OIDC_EJSONNOFOUND = -33, OIDC_EJSONADD = -34, OIDC_EJSONMERGE = -35, OIDC_EJSONTYPE = -36, OIDC_ETCS = -40, OIDC_EIN = -41, OIDC_EBADCONFIG = -50, OIDC_EOIDC = -51, OIDC_ECRED = -52, OIDC_ENOREFRSH = -53, OIDC_ENODEVICE = -54, OIDC_EFMT = -55, OIDC_EUNSCOPE = -56, OIDC_EPORTRANGE = -57, OIDC_EMKTMP = -60, OIDC_EENVVAR = -61, OIDC_EBIND = -62, OIDC_ECONSOCK = -63, OIDC_ECRSOCK = -64, OIDC_ESOCKINV = -65, OIDC_EIPCDIS = -66, OIDC_EMSGSIZE = -67, OIDC_ESELECT = -68, OIDC_EIOCTL = -69, OIDC_ETIMEOUT = -600, OIDC_EGROUPNF = -601, OIDC_EMAXTRIES = -70, OIDC_ENOACCOUNT = -71, OIDC_EHTTPD = -81, OIDC_EHTTPPORTS = -80, OIDC_ENOREURI = -82, OIDC_EHTTP0 = -83, OIDC_ENOSTATE = -85, OIDC_ENOCODE = -86, OIDC_ENOBASEURI = -87, OIDC_EWRONGSTATE = -88, OIDC_EWRONGDEVICECODE = -89, OIDC_ENOPRIVCONF = -90, OIDC_ENOSUPREG = -100, OIDC_ENOSUPREV = -101, OIDC_ENOPUBCLIENT = -106, OIDC_EPWNOTFOUND = -110, OIDC_EGERROR = -111, OIDC_EUSRPWCNCL = -112, OIDC_EFORBIDDEN = -113, OIDC_ELOCKED = -120, OIDC_ENOTLOCKED = -121, OIDC_EINTERNAL = -4242, OIDC_NOTIMPL = -1000, OIDC_ENOPE = -1337, }; typedef enum _oidc_error oidc_error_t; extern int oidc_errno; extern char oidc_error[1024]; struct oidc_error_state { int oidc_errno; char* oidc_error; }; void oidc_seterror(const char* error); void oidc_setInternalError(const char* error); void oidc_setErrnoError(); void oidc_setArgNullFuncError(const char* fncname); char* oidc_serrorFor(oidc_error_t err); char* oidc_serror(); int errorMessageIsForError(const char* error_msg, oidc_error_t err); void oidc_perror(); struct oidc_error_state* saveErrorState(); void restoreErrorState(struct oidc_error_state* state); void restoreAndFreeErrorState(struct oidc_error_state* state); void secFreeErrorState(struct oidc_error_state* state); #endif // OIDC_ERROR_H oidc-agent-4.2.6/src/utils/portUtils.c0000644000175000017500000000402614167074355017221 0ustar marcusmarcus#define _XOPEN_SOURCE 500 #include "portUtils.h" #include #include #include "account/account.h" #include "oidc_error.h" #include "utils/logger.h" #include "utils/string/stringUtils.h" #include "wrapper/list.h" long random_at_most(long max) { // max <= RAND_MAX < ULONG_MAX, so this is okay. unsigned long num_bins = (unsigned long)max + 1, num_rand = (unsigned long)RAND_MAX + 1, bin_size = num_rand / num_bins, defect = num_rand % num_bins; long x; do { x = random(); } while (num_rand - defect <= (unsigned long)x); return x / bin_size; } unsigned short getRandomPort() { return random_at_most(MAX_PORT - MIN_PORT) + MIN_PORT; } char* portToUri(unsigned short port) { if (portIsInRange(port) != OIDC_SUCCESS) { return NULL; } return oidc_sprintf("http://localhost:%hu", port); } /** * don't free the returned value */ char* findRedirectUriByPort(const struct oidc_account* a, unsigned short port) { if (a == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_t* l = account_getRedirectUris(a); if (l == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_iterator_t* it = list_iterator_new(l, LIST_HEAD); list_node_t* node; while ((node = list_iterator_next(it))) { if (getPortFromUri(node->val) == port) { list_iterator_destroy(it); return node->val; } } list_iterator_destroy(it); return NULL; } unsigned int getPortFromUri(const char* uri) { if (uri == NULL) { oidc_setArgNullFuncError(__func__); return 0; } unsigned int port; if (sscanf(uri, "http://localhost:%du", &port) != 1) { if (sscanf(uri, "http://localhost:%du/", &port) != 1) { oidc_errno = OIDC_EFMT; return 0; } } return port; } oidc_error_t portIsInRange(unsigned short port) { if (port >= MIN_PORT && port <= MAX_PORT) { return OIDC_SUCCESS; } oidc_errno = OIDC_EPORTRANGE; logger(ERROR, "Port %hu is not between %d and %d", port, MIN_PORT, MAX_PORT); return oidc_errno; } oidc-agent-4.2.6/src/utils/ipUtils.c0000644000175000017500000000307414167074355016647 0ustar marcusmarcus#define _POSIX_C_SOURCE 200112L #include "ipUtils.h" #include #include #include #include #include "utils/memory.h" #include "utils/string/stringUtils.h" int isValidIP(const char* ipAddress) { if (ipAddress == NULL) { return 0; } struct sockaddr_in sa; return inet_pton(AF_INET, ipAddress, &(sa.sin_addr)) != 0; } char* hostnameToIP(const char* hostname) { if (hostname == NULL) { return NULL; } struct addrinfo* servinfo; struct addrinfo hints = {.ai_family = AF_INET, .ai_socktype = SOCK_STREAM}; if (getaddrinfo(hostname, NULL, &hints, &servinfo) != 0) { return NULL; } char* ip = NULL; // loop through all the results and connect to the first we can for (struct addrinfo* p = servinfo; p != NULL; p = p->ai_next) { struct sockaddr_in* h = (struct sockaddr_in*)p->ai_addr; ip = inet_ntoa(h->sin_addr); } freeaddrinfo(servinfo); // all done with this structure return ip; } // Checks if the given string is a valid ip or a hsotname that resolves to an ip int isValidIPOrHostname(const char* iph) { if (iph == NULL) { return 0; } if (isValidIP(iph)) { return 1; } return hostnameToIP(iph) != NULL; } // Checks if the given string is a valid ip or a hsotname that resolves to an ip // The given string might have an port, e.g. // or : int isValidIPOrHostnameOptionalPort(const char* iph) { char* tmp = oidc_strcopy(iph); char* ip = strtok(tmp, ":"); int ret = isValidIPOrHostname(ip); secFree(tmp); return ret; } oidc-agent-4.2.6/src/utils/pubClientInfos.c0000644000175000017500000000434614167074355020145 0ustar marcusmarcus#include "pubClientInfos.h" #include #include "account/issuer_helper.h" #include "defines/settings.h" #include "utils/file_io/file_io.h" #include "utils/file_io/oidc_file_io.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" void secFreePubClientInfos(struct pubClientInfos* p) { if (p == NULL) { return; } secFree(p->client_id); secFree(p->client_secret); secFree(p->scope); secFree(p); } struct pubClientInfos* _getPubClientInfosFromList(list_t* lines, const char* issuer) { if (lines == NULL) { return NULL; } list_node_t* node; list_iterator_t* it = list_iterator_new(lines, LIST_HEAD); while ((node = list_iterator_next(it))) { char* client = strtok(node->val, "@"); char* iss = strtok(NULL, "@"); char* scope = strtok(NULL, "@"); // logger(DEBUG, "Found public client for '%s'", iss); if (compIssuerUrls(issuer, iss)) { char* client_id = strtok(client, ":"); char* client_secret = strtok(NULL, ":"); struct pubClientInfos* infos = secAlloc(sizeof(struct pubClientInfos)); infos->client_id = oidc_strcopy(client_id); infos->client_secret = oidc_strcopy(client_secret); infos->scope = oidc_strcopy(scope); list_iterator_destroy(it); return infos; } } list_iterator_destroy(it); return NULL; } struct pubClientInfos* getPubClientInfos(const char* issuer) { list_t* pubClientLines = getLinesFromFileWithoutComments(ETC_PUBCLIENTS_CONFIG_FILE); struct pubClientInfos* infos = _getPubClientInfosFromList(pubClientLines, issuer); secFreeList(pubClientLines); if (infos != NULL) { return infos; } pubClientLines = getLinesFromOidcFileWithoutComments(PUBCLIENTS_FILENAME); infos = _getPubClientInfosFromList(pubClientLines, issuer); secFreeList(pubClientLines); return infos; } list_t* defaultRedirectURIs() { list_t* redirect_uris = createList(0, "http://localhost:8080", "http://localhost:4242", "http://localhost:43985", NULL); redirect_uris->match = (matchFunction)strequal; return redirect_uris; } oidc-agent-4.2.6/src/utils/json.c0000644000175000017500000005300414167074355016165 0ustar marcusmarcus#include "json.h" #include #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/oidc_error.h" #include "utils/pass.h" #include "utils/string/stringUtils.h" static cJSON_Hooks hooks; static int jsonInitDone = 0; /** * @brief initializes the cJSON memory allocator and deallocator if not done yet * @internal */ void initCJSON() { if (!jsonInitDone) { hooks.malloc_fn = secAlloc; hooks.free_fn = _secFree; cJSON_InitHooks(&hooks); jsonInitDone = 1; } } /** * @brief converts a cJSON object into a string * @param cjson the cJSON object to be converted * @return a pointer to a string representation of @p cjson. Has to be freed * after usage. */ char* jsonToString(cJSON* cjson) { if (cjson == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); return cJSON_Print(cjson); } char* jsonToStringUnformatted(cJSON* cjson) { char* json = jsonToString(cjson); cJSON_Minify(json); return json; } /** * @brief parses a string into an cJSON object * @param json the json string * @param logError if @c 0 errors are not logged, otherwise error are logged * @return a pointer to a cJSON object. Has to be freed after usage. * @internal */ cJSON* _stringToJson(const char* json, int logError) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); char* minJson = oidc_strcopy(json); cJSON_Minify(minJson); logger(DEBUG, "Parsing json '%s'", minJson); cJSON* cj = cJSON_Parse(minJson); if (cj == NULL) { oidc_errno = OIDC_EJSONPARS; if (logError) { logger(ERROR, "Parsing failed somewhere around %s", cJSON_GetErrorPtr()); } } secFree(minJson); return cj; } /** * @brief parses a string into an cJSON object * @param json the json string * @return a pointer to a cJSON object. Has to be freed after usage. * @note this function logs parsing error */ cJSON* stringToJson(const char* json) { return _stringToJson(json, 1); } /** * @brief parses a string into an cJSON object * @param json the json string * @return a pointer to a cJSON object. Has to be freed after usage. * @note this function does not log parsing error and is mainly used for * checking if a string would parse correctly into a cJSON object */ cJSON* stringToJsonDontLogError(const char* json) { return _stringToJson(json, 0); } /** * @brief checks if a string represents a json object * @param json the (possibly) json string * @return @c 1 if @p json holds a json object, @c 0 if not */ int isJSONObject(const char* json) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return 0; } initCJSON(); cJSON* cj = stringToJsonDontLogError(json); if (cj == NULL) { return 0; } int res = cJSON_IsObject(cj); cJSON_Delete(cj); return res; } /** * @brief safly calls cJSON_Delete freeing the cJSON Object * @param cjson the cJSON Object to be freed */ void _secFreeJson(cJSON* cjson) { if (cjson == NULL) { return; } initCJSON(); cJSON_Delete(cjson); } /** * @brief checks if a json string contains a specific key * @param json a string representing a json object * @param key the string that might be contained as a key * @return @c 1 if @p json contains @p key; @c 0 otherwise * @note if you want to check multiple keys or you might do some other json * operation, you probably should first parse @p json into an cJSON object and * then use @c jsonHasKey */ int jsonStringHasKey(const char* json, const char* key) { if (NULL == json || NULL == key) { oidc_setArgNullFuncError(__func__); return 0; } initCJSON(); cJSON* cj = stringToJson(json); if (cj == NULL) { return 0; } int res = jsonHasKey(cj, key); secFreeJson(cj); return res; } /** * @brief checks if a cSJON object contains a specific key * @param cjson the cJSON object * @param key the string that might be contained as a key * @return @c 1 if @p cjson contains @p key; @c 0 otherwise */ int jsonHasKey(const cJSON* cjson, const char* key) { if (NULL == cjson || NULL == key) { oidc_setArgNullFuncError(__func__); return 0; } initCJSON(); char* value = getJSONValue(cjson, key); if (strValid(value)) { secFree(value); return 1; } else { secFree(value); return 0; } } /** * @brief gets the value field of a cJSON object (represented as string) * @param valueItem the cJSON object * @return a pointer to a string holding the value. Has to be freed after usage. */ char* getJSONItemValue(cJSON* valueItem) { if (NULL == valueItem) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); if (cJSON_IsString(valueItem)) { char* value = cJSON_GetStringValue(valueItem); return strValid(value) ? oidc_strcopy(value) : NULL; } return cJSON_Print(valueItem); } /** * @brief gets the value for a given key from a cJSON object * @param cjson the cJSON object * @param key the key * @return a pointer to a string holding the value for the \p key. Has to be * freed after usage. */ char* getJSONValue(const cJSON* cjson, const char* key) { if (NULL == cjson || NULL == key) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); // logger(DEBUG, "Getting value for key '%s'", key); if (!cJSON_IsObject(cjson)) { oidc_errno = OIDC_EJSONOBJ; return NULL; } if (!cJSON_HasObjectItem(cjson, key)) { oidc_errno = OIDC_EJSONNOFOUND; return NULL; } cJSON* valueItem = cJSON_GetObjectItemCaseSensitive(cjson, key); char* value = getJSONItemValue(valueItem); // logger(DEBUG, "value for key '%s' is '%s'", key, value); return value; } oidc_error_t setJSONValue(cJSON* cjson, const char* key, const char* value) { if (NULL == cjson || NULL == key || value == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } initCJSON(); if (jsonHasKey(cjson, key)) { cJSON_DeleteItemFromObjectCaseSensitive(cjson, key); } cJSON_AddStringToObject(cjson, key, value); return OIDC_SUCCESS; } /** * @brief gets the value for a given key from a json string * @param json a pointer to the json string * @param key the key * @return a pointer to a string holding the value for the \p key. Has to be * freed after usage. */ char* getJSONValueFromString(const char* json, const char* key) { if (NULL == json || NULL == key) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* cj = stringToJson(json); if (cj == NULL) { return NULL; } char* value = getJSONValue(cj, key); secFreeJson(cj); return value; } /** * @brief gets multiple values from a cJSON object * @param cjson the cJSON object * @param pairs an array of key_value pairs. The keys are used as keys. A * pointer to the result is stored in the value field. The previous pointer is * not freed, thus it should be NULL. * @param size the number of key value pairs * @return the number of set values or an error code on failure */ oidc_error_t getJSONValues(const cJSON* cjson, struct key_value* pairs, size_t size) { if (NULL == cjson || NULL == pairs || size == 0) { oidc_setArgNullFuncError(__func__); return oidc_errno; } initCJSON(); if (!cJSON_IsObject(cjson)) { oidc_errno = OIDC_EJSONOBJ; return oidc_errno; } unsigned int i; for (i = 0; i < size; i++) { pairs[i].value = getJSONValue(cjson, pairs[i].key); } return i; } /** * @brief gets multiple values from a json string * @param json the json string to be parsed * @param pairs an array of key_value pairs. The keys are used as keys. A * pointer to the result is stored in the value field. The previous pointer is * not freed, thus it should be NULL. * @param size the number of key value pairs * @return the number of set values or an error code on failure */ oidc_error_t getJSONValuesFromString(const char* json, struct key_value* pairs, size_t size) { if (NULL == json || NULL == pairs || size == 0) { oidc_setArgNullFuncError(__func__); return oidc_errno; } initCJSON(); cJSON* cj = stringToJson(json); if (cj == NULL) { return oidc_errno; } oidc_error_t e = getJSONValues(cj, pairs, size); secFreeJson(cj); return e; } /** * @brief converts a cJSON JSONArray into a list * @param cjson the cJSON JSONArray * @return a pointer to a list. The list has to be freed after usage using * @c secFreeList. */ list_t* JSONArrayToList(const cJSON* cjson) { if (NULL == cjson) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); if (!cJSON_IsArray(cjson)) { oidc_errno = OIDC_EJSONARR; return NULL; } int j; list_t* l = list_new(); l->free = _secFree; l->match = (matchFunction)strequal; for (j = 0; j < cJSON_GetArraySize(cjson); j++) { list_rpush(l, list_node_new(getJSONItemValue(cJSON_GetArrayItem(cjson, j)))); } return l; } /** * @brief converts a JSONArray string into a list * @param json a pointer to a string holding a JSONArray * @return a pointer to a list. The list has to be freed after usage using * @c secFreeList. */ list_t* JSONArrayStringToList(const char* json) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* cj = stringToJson(json); if (cj == NULL) { return NULL; } list_t* l = JSONArrayToList(cj); secFreeJson(cj); return l; } /** * @brief converts a cJSON JSONArray into a string with specified delimiter * @param cjson the cJSON JSONArray * @param delim the delimiting character to be used * @return a pointer to a string holding the delimited list items. Has to be * freed after usage. */ char* JSONArrayToDelimitedString(const cJSON* cjson, char* delim) { if (NULL == cjson) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); list_t* list = JSONArrayToList(cjson); char* str = listToDelimitedString(list, delim); secFreeList(list); return str; } /** * @brief converts a JSONArray string into a string with specified delimiter * @param json a pointer to a string holding a JSONArray * @param delim the delimiting character to be used * @return a pointer to a string holding the delimited list items. Has to be * freed after usage. */ char* JSONArrayStringToDelimitedString(const char* json, char* delim) { if (NULL == json) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* cj = stringToJson(json); if (cj == NULL) { return NULL; } char* str = JSONArrayToDelimitedString(cj, delim); secFreeJson(cj); return str; } cJSON* jsonAddStringValue(cJSON* cjson, const char* key, const char* value) { if (NULL == cjson || NULL == key) { oidc_setArgNullFuncError(__func__); return NULL; } if (value == NULL) { return cjson; } initCJSON(); logger(DEBUG, "Adding value '%s' for key '%s' to a json object", value, key); cJSON_AddStringToObject(cjson, key, value); return cjson; } cJSON* jsonAddNumberValue(cJSON* cjson, const char* key, const double value) { if (NULL == cjson || NULL == key) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON_AddNumberToObject(cjson, key, value); return cjson; } cJSON* jsonAddArrayValue(cJSON* cjson, const char* key, const char* json_array) { if (NULL == cjson || NULL == key || NULL == json_array) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* array = stringToJson(json_array); cJSON_AddItemToObject(cjson, key, array); return cjson; } cJSON* jsonAddObjectValue(cJSON* cjson, const char* key, const char* json_object) { if (NULL == cjson || NULL == key || NULL == json_object) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* object = stringToJson(json_object); cJSON_AddItemToObject(cjson, key, object); return cjson; } cJSON* jsonAddJSON(cJSON* cjson, const char* key, cJSON* item) { if (NULL == cjson || NULL == key || NULL == item) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON_AddItemToObject(cjson, key, item); return cjson; } cJSON* jsonArrayAddStringValue(cJSON* cjson, const char* value) { if (value == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (cjson == NULL) { cjson = cJSON_CreateArray(); } cJSON_AddItemToArray(cjson, cJSON_CreateString(value)); return cjson; } /** * @brief converts a list into a cJSON JSONArray * @param list a pointer to the list to be converted * @return a pointer to a cJSON JSONArray. Has to be freed after usage using * @c secFreeJson */ cJSON* listToJSONArray(list_t* list) { if (list == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* json = cJSON_CreateArray(); if (json == NULL) { oidc_seterror("Coud not create json array"); return NULL; } list_node_t* node; list_iterator_t* it = list_iterator_new(list, LIST_HEAD); while ((node = list_iterator_next(it))) { cJSON_AddItemToArray(json, cJSON_CreateString(node->val)); } list_iterator_destroy(it); return json; } /** * @brief Generates a cJSON JSONObject from key, type, value tuples * @param k1 the key for the first element * @param type1 the type for the first element. Has to be a cJSON Type, e.g. * @c cJSON_String * @param v1 the value for the first element. This cannot be a number. * @param params additional parameters can be specified, but only in tuples of * three: key, type, value. Value has to be either a @c char* or a number (no * floating point numbers supported yet) * @note the last argument MUST be @c NULL, otherwise behavior is unspecified * @return a pointer to a cJSON JSONObject. Has to be freed after usage using * @c secFsecFreeJson */ cJSON* generateJSONObject(const char* k1, int type1, const char* v1, ...) { initCJSON(); logger(DEBUG, "Generating JSONObject"); // logger(DEBUG, "First key:value is %s:%s", k1, v1); cJSON* json = cJSON_CreateObject(); if (json == NULL) { oidc_seterror("Coud not create json object"); return NULL; } va_list args; va_start(args, v1); const char* key = k1; const char* value = v1; int type = type1; long numbervalue = 0; do { // logger(DEBUG, "key:value is %s:%s", key, value); cJSON* (*addFunc)(cJSON*, const char*, const char*); int useNumberAdd = 0; switch (type) { case cJSON_String: addFunc = jsonAddStringValue; break; case cJSON_Object: addFunc = jsonAddObjectValue; break; case cJSON_Array: addFunc = jsonAddArrayValue; break; case cJSON_Number: useNumberAdd = 1; break; default: logger(ERROR, "unknown type %d", type); oidc_errno = OIDC_EJSONTYPE; va_end(args); return NULL; } if (useNumberAdd == 0) { json = addFunc(json, key, value); } else { json = jsonAddNumberValue(json, key, numbervalue); } if (json == NULL) { va_end(args); return NULL; } key = va_arg(args, char*); if (key != NULL) { type = va_arg(args, int); if (type == cJSON_Number) { numbervalue = va_arg(args, long); value = NULL; } else { value = va_arg(args, char*); numbervalue = 0; } } } while (key != NULL); va_end(args); return json; } /** * @brief Generates a cJSON JSONArray from multiple strings * @param v1 the value for the first element. * @param params additional values to be added to the JSONArray * @note the last argument MUST be @c NULL, otherwise behavior is unspecified * @return a pointer to a cJSON JSONArray. Has to be freed after usage using * @c secFsecFreeJson */ cJSON* generateJSONArray(char* v1, ...) { initCJSON(); logger(DEBUG, "Generating JSONArray"); cJSON* json = cJSON_CreateArray(); if (json == NULL) { oidc_seterror("Coud not create json array"); return NULL; } va_list args; va_start(args, v1); char* v = v1; while (v != NULL) { // logger(DEBUG, "value is %s", v); cJSON_AddItemToArray(json, cJSON_CreateString(v)); v = va_arg(args, char*); } va_end(args); return json; } /** * @brief merges two JSONObjects represented as string into one * @param j1 a pointer to the first json string * @param j2 a pointer to the second json string * @note for details see @c mergeJSONObjects * @return a pointer to a string representing the merged JSONObject. Has to be * freed after usage. */ char* mergeJSONObjectStrings(const char* j1, const char* j2) { if (j1 == NULL || j2 == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Merging two json objects:"); logger(DEBUG, "j1 '%s'", j1); logger(DEBUG, "j2 '%s'", j2); initCJSON(); cJSON* cj1 = stringToJson(j1); cJSON* cj2 = stringToJson(j2); if (cj1 == NULL || cj2 == NULL) { secFreeJson(cj1); secFreeJson(cj2); return NULL; } cJSON* j = mergeJSONObjects(cj1, cj2); secFreeJson(cj1); secFreeJson(cj2); if (j == NULL) { return NULL; } char* s = jsonToString(j); secFreeJson(j); logger(DEBUG, "Merge result '%s'", s); return s; } /** * @brief merges two CJSON JSONObjects into one. * The first object is used as a baseline and then elements from the second * object are added. This function cannot merge the objects if there are * conflicts. The following are no conflicts: key not existing in one object; * key existing in both object, but empty value for (at least) one (also for * empty arrays). If there are different values for the two objects for the same * key this is considered a merge conflict. Exception: For the key 'scope' the * value from @p j1 is prioritized over @p j2 without raising an error. * @param j1 a pointer to the first cJSON JSONObject * @param j2 a pointer to the second cJSON JSONObject * @return a pointer to a cJSON JSONObject. Has to be freed after usage using * @c secFreeJson */ cJSON* mergeJSONObjects(const cJSON* j1, const cJSON* j2) { if (j1 == NULL || j2 == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } initCJSON(); cJSON* json = cJSON_Duplicate(j1, cJSON_True); cJSON* el = NULL; cJSON_ArrayForEach(el, j2) { char* key = el->string; if (jsonHasKey(j1, key)) { cJSON* el1 = cJSON_GetObjectItemCaseSensitive(json, key); if (!cJSON_Compare(el1, el, cJSON_True)) { if ((el->type == cJSON_String && !strValid(el->valuestring)) || ((el->type == cJSON_Array || el->type == cJSON_Object) && cJSON_GetArraySize(el) == 0)) { pass; } else if ((el1->type == cJSON_String && !strValid(el1->valuestring)) || ((el1->type == cJSON_Array || el1->type == cJSON_Object) && cJSON_GetArraySize(el1) == 0)) { cJSON* cpy = cJSON_Duplicate(el, cJSON_True); cJSON_ReplaceItemViaPointer(json, el1, cpy); } else if (el1->type == cJSON_String && el->type == cJSON_String && strequal(el1->valuestring, el->valuestring)) { pass; } else if (strequal("scope", key)) { // for scope the the value from j1 is used // despite the value of j2. // The acquired scopes might be different from the requested // scopes, but that's fine. Also the ordering might change pass; } else if (strequal("daeSetByUser", key) && el->type == cJSON_Number && el1->type == cJSON_Number) { // if one has daeSetByUser set this is dominant if (el1->valuedouble == 0) { // if daeSetByUser that is already in // the merged object is 0, overwrite it // cJSON* cpy = cJSON_Duplicate(el, cJSON_True); // cJSON_ReplaceItemViaPointer(json, el1, cpy); el1->valuedouble = el->valuedouble; } } else { oidc_errno = OIDC_EJSONMERGE; char* val1 = jsonToString(el1); char* val2 = jsonToString(el); logger(ERROR, "Cannot merge json objects: Conflict for key '%s' between " "value '%s' and '%s'", key, val1, val2); cJSON_free(val1); cJSON_free(val2); cJSON_Delete(json); return NULL; } } } else { cJSON* (*addFunc)(cJSON*, const char*, const void*) = NULL; void* value = NULL; double numbervalue = 0.; switch (el->type) { case cJSON_String: addFunc = (cJSON * (*)(cJSON*, const char*, const void*)) jsonAddStringValue; value = el->valuestring; break; case cJSON_Object: case cJSON_Array: addFunc = (cJSON * (*)(cJSON*, const char*, const void*)) jsonAddJSON; value = cJSON_Duplicate(el, cJSON_True); break; case cJSON_Number: numbervalue = el->valuedouble; break; default: logger(ERROR, "unknown type %d", el->type); oidc_errno = OIDC_EJSONTYPE; cJSON_Delete(json); return NULL; } if (addFunc && value) { json = addFunc(json, key, value); } else if (numbervalue) { json = jsonAddNumberValue(json, key, numbervalue); } } } return json; } /** * checks if an json array is empty * @param json the cJSON JSONArray * @return @c 1 if @p json is empty, @c 0 if not. If @p json is @c NULL or not * an array an negative error code is returned */ int jsonArrayIsEmpty(cJSON* json) { if (json == NULL) { return OIDC_EARGNULL; } if (json->type != cJSON_Array) { return OIDC_EJSONARR; } return !cJSON_GetArraySize(json); } oidc-agent-4.2.6/src/utils/versionUtils.c0000644000175000017500000000234614167074355017725 0ustar marcusmarcus#include "versionUtils.h" #include #include #include "utils/logger.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" int versionAtLeast(const char* version, const char* minVersion) { if (version == NULL || strlen(version) < 5) { return 0; } logger(DEBUG, "checking if version %s is at least %s", version, minVersion); unsigned short v_maj, v_min, v_pat = 0; unsigned short m_maj, m_min, m_pat = 0; sscanf(version, "%hu.%hu.%hu", &v_maj, &v_min, &v_pat); sscanf(minVersion, "%hu.%hu.%hu", &m_maj, &m_min, &m_pat); if (v_maj > m_maj) { return 1; } if (v_maj < m_maj) { return 0; } if (v_min > m_min) { return 1; } if (v_min < m_min) { return 0; } if (v_pat >= m_pat) { return 1; } return 0; } #define VERSION_LINE_FMT "Generated using version: " char* versionLineToSimpleVersion(const char* version_line) { if (version_line == NULL) { return NULL; } char* tmp = oidc_strcopy(version_line); char* location = strtok(tmp, VERSION_LINE_FMT); char* version = oidc_strcopy(location); secFree(tmp); return version; } char* simpleVersionToVersionLine(const char* version) { return oidc_sprintf("%s%s", VERSION_LINE_FMT, version); } oidc-agent-4.2.6/src/utils/db/0000755000175000017500000000000014167074355015433 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/db/file_db.h0000644000175000017500000000072414120404223017147 0ustar marcusmarcus#ifndef OIDC_DB_FILES_H #define OIDC_DB_FILES_H #include "db.h" void fileDB_new(); #define fileDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_FILES, (match)) void fileDB_removeIfFound(const char* key); void fileDB_addValue(const char* key, const char* data); char* fileDB_findValue(const char* key); #define fileDB_getSize() db_getSize(OIDC_DB_FILES) #define fileDB_reset() \ do { db_reset(OIDC_DB_FILES); } while (0) #endif // OIDC_DB_FILES_H oidc-agent-4.2.6/src/utils/db/db.h0000644000175000017500000000205514167074355016173 0ustar marcusmarcus#ifndef OIDC_DB_H #define OIDC_DB_H #include #include "utils/listUtils.h" typedef unsigned short db_name; #define OIDC_DB_CONNECTIONS 1 #define OIDC_DB_ACCOUNTS 2 #define OIDC_DB_PASSWORDS 3 #define OIDC_DB_CODEVERIFIERS 4 #define OIDC_DB_FILES 5 #define OIDC_DB_DEVICECODES 6 void db_newDB(const db_name db); list_t* db_getDB(const db_name db); matchFunction db_setMatchFunction(const db_name db, matchFunction); freeFunction db_setFreeFunction(const db_name db, freeFunction); void db_removeIfFound(const db_name db, void* value); void db_addValue(const db_name db, void* value); size_t db_getSize(const db_name db); void* db_findValue(const db_name db, void* key); list_t* db_findAllValues(const db_name db, void* key); void* db_findValueWithFunction(const db_name db, void* key, matchFunction); void db_reset(const db_name db); time_t db_getMinDeath(const db_name db, time_t (*deathGetter)(void*)); void* db_getDeathEntry(const db_name db, time_t (*deathGetter)(void*)); #endif // OIDC_DB_H oidc-agent-4.2.6/src/utils/db/db.c0000644000175000017500000000617314167074355016173 0ustar marcusmarcus#include "db.h" #include "utils/deathUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "wrapper/list.h" static list_t* dbs = NULL; struct oidc_db { db_name db; list_t* list; }; int matchDBs(const struct oidc_db* a, const struct oidc_db* b) { return a->db == b->db; } void db_init() { if (dbs != NULL) { return; } dbs = list_new(); dbs->match = (matchFunction)matchDBs; } list_node_t* _getDBNode(const db_name db) { if (dbs == NULL) { return NULL; } struct oidc_db key = {.db = db}; return findInList(dbs, &key); } list_t* db_getDB(const db_name db) { list_node_t* found = _getDBNode(db); if (found == NULL) { return NULL; } return ((struct oidc_db*)found->val)->list; } void db_newDB(const db_name db) { db_init(); if (db_getDB(db) != NULL) { return; } struct oidc_db* db_e = secAlloc(sizeof(struct oidc_db)); db_e->db = db; db_e->list = list_new(); list_rpush(dbs, list_node_new(db_e)); } matchFunction db_setMatchFunction(const db_name db, matchFunction match) { db_init(); list_t* db_list = db_getDB(db); if (db_list == NULL) { db_newDB(db); return db_setMatchFunction(db, match); } matchFunction oldMatch = db_list->match; db_list->match = match; return oldMatch; } freeFunction db_setFreeFunction(const db_name db, void (*free_fn)(void*)) { db_init(); list_t* db_list = db_getDB(db); if (db_list == NULL) { db_newDB(db); return db_setFreeFunction(db, free_fn); } freeFunction oldFree = db_list->free; db_list->free = free_fn; return oldFree; } void db_removeIfFound(const db_name db, void* value) { list_removeIfFound(db_getDB(db), value); } void db_addValue(const db_name db, void* value) { list_rpush(db_getDB(db), list_node_new(value)); logger(DEBUG, "Added value to db %hhu. Now there are %lu entries.", db, db_getSize(db)); } size_t db_getSize(const db_name db) { list_t* dbl = db_getDB(db); return dbl ? dbl->len : 0; } void* db_findValue(const db_name db, void* key) { list_node_t* node = findInList(db_getDB(db), key); return node ? node->val : NULL; } list_t* db_findAllValues(const db_name db, void* key) { return findAllInList(db_getDB(db), key); } void* db_findValueWithFunction(const db_name db, void* key, matchFunction match) { matchFunction oldMatch = db_setMatchFunction(db, match); void* ret = db_findValue(db, key); db_setMatchFunction(db, oldMatch); return ret; } void db_reset(const db_name db) { list_node_t* node = _getDBNode(db); if (node == NULL) { return; } struct oidc_db* db_s = node->val; list_t* list = db_s->list; matchFunction match = list->match; void (*free_fn)(void*) = list->free; secFreeList(list); db_s->list = list_new(); db_s->list->match = match; db_s->list->free = free_fn; } time_t db_getMinDeath(const db_name db, time_t (*deathGetter)(void*)) { return getMinDeathFrom(db_getDB(db), deathGetter); } void* db_getDeathEntry(const db_name db, time_t (*deathGetter)(void*)) { return getDeathElementFrom(db_getDB(db), deathGetter); } oidc-agent-4.2.6/src/utils/db/file_db.c0000644000175000017500000000266114167074355017170 0ustar marcusmarcus#include "file_db.h" #include "utils/crypt/memoryCrypt.h" #include "utils/matcher.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" struct file_dummy { char* filename; char* data; }; void secFreeFileDummy(struct file_dummy* fd) { if (fd == NULL) { return; } secFree(fd->filename); secFree(fd->data); secFree(fd); } int _fd_match(const struct file_dummy* fd1, const struct file_dummy* fd2) { return matchStrings(fd1 ? fd1->filename : NULL, fd2 ? fd2->filename : NULL); } void fileDB_new() { db_newDB(OIDC_DB_FILES); db_setFreeFunction(OIDC_DB_FILES, (freeFunction)secFreeFileDummy); db_setMatchFunction(OIDC_DB_FILES, (matchFunction)_fd_match); } void fileDB_addValue(const char* key, const char* data) { struct file_dummy* value = secAlloc(sizeof(struct file_dummy)); value->filename = oidc_strcopy(key); value->data = memoryEncrypt(data); db_addValue(OIDC_DB_FILES, value); } struct file_dummy* _findValue(const char* filename) { char* tmp = oidc_strcopy(filename); struct file_dummy key = {.filename = tmp}; struct file_dummy* fd = db_findValue(OIDC_DB_FILES, (&key)); secFree(tmp); return fd; } char* fileDB_findValue(const char* filename) { struct file_dummy* fd = _findValue(filename); return fd ? memoryDecrypt(fd->data) : NULL; } void fileDB_removeIfFound(const char* filename) { db_removeIfFound(OIDC_DB_FILES, _findValue(filename)); } oidc-agent-4.2.6/src/utils/db/account_db.h0000644000175000017500000000225614120404223017666 0ustar marcusmarcus#ifndef OIDC_DB_ACCOUNTS_H #define OIDC_DB_ACCOUNTS_H #include "db.h" #define accountDB_new() \ do { db_newDB(OIDC_DB_ACCOUNTS); } while (0) #define accountDB_getList() db_getDB(OIDC_DB_ACCOUNTS) #define accountDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_ACCOUNTS, (match)) #define accountDB_setFreeFunction(free) \ db_setFreeFunction(OIDC_DB_ACCOUNTS, (free)) #define accountDB_removeIfFound(value) \ do { db_removeIfFound(OIDC_DB_ACCOUNTS, (value)); } while (0) #define accountDB_addValue(value) \ do { db_addValue(OIDC_DB_ACCOUNTS, (value)); } while (0) #define accountDB_findValue(key) db_findValue(OIDC_DB_ACCOUNTS, (key)) #define accountDB_findAllValues(key) db_findAllValues(OIDC_DB_ACCOUNTS, (key)) #define accountDB_findValueWithFunction(key, function) \ db_findValueWithFunction(OIDC_DB_ACCOUNTS, (key), (function)) #define accountDB_getMinDeath(getter) db_getMinDeath(OIDC_DB_ACCOUNTS, (getter)) #define accountDB_getDeathEntry(getter) \ db_getDeathEntry(OIDC_DB_ACCOUNTS, (getter)) #define accountDB_getSize() db_getSize(OIDC_DB_ACCOUNTS) #define accountDB_reset() \ do { db_reset(OIDC_DB_ACCOUNTS); } while (0) #endif // OIDC_DB_ACCOUNTS_H oidc-agent-4.2.6/src/utils/db/password_db.h0000644000175000017500000000203114120404223020063 0ustar marcusmarcus#ifndef OIDC_DB_PASSWORDS_H #define OIDC_DB_PASSWORDS_H #include "db.h" #define passwordDB_new() \ do { db_newDB(OIDC_DB_PASSWORDS); } while (0) #define passwordDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_PASSWORDS, (match)) #define passwordDB_setFreeFunction(free) \ db_setFreeFunction(OIDC_DB_PASSWORDS, (free)) #define passwordDB_removeIfFound(value) \ do { db_removeIfFound(OIDC_DB_PASSWORDS, (value)); } while (0) #define passwordDB_addValue(value) \ do { db_addValue(OIDC_DB_PASSWORDS, (value)); } while (0) #define passwordDB_findValue(key) db_findValue(OIDC_DB_PASSWORDS, (key)) #define passwordDB_findAllValues(key) db_findAllValues(OIDC_DB_PASSWORDS, (key)) #define passwordDB_getMinDeath(getter) \ db_getMinDeath(OIDC_DB_PASSWORDS, (getter)) #define passwordDB_getDeathEntry(getter) \ db_getDeathEntry(OIDC_DB_PASSWORDS, (getter)) #define passwordDB_getSize() db_getSize(OIDC_DB_PASSWORDS) #define passwordDB_reset() \ do { db_reset(OIDC_DB_PASSWORDS); } while (0) #endif // OIDC_DB_PASSWORDS_H oidc-agent-4.2.6/src/utils/db/codeVerifier_db.h0000644000175000017500000000250714120404223020637 0ustar marcusmarcus#ifndef OIDC_DB_CODEVERIFIERS_H #define OIDC_DB_CODEVERIFIERS_H #include "db.h" #define codeVerifierDB_new() \ do { db_newDB(OIDC_DB_CODEVERIFIERS); } while (0) #define codeVerifierDB_getList() db_getDB(OIDC_DB_CODEVERIFIERS) #define codeVerifierDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_CODEVERIFIERS, (match)) #define codeVerifierDB_setFreeFunction(free) \ db_setFreeFunction(OIDC_DB_CODEVERIFIERS, (free)) #define codeVerifierDB_removeIfFound(value) \ do { db_removeIfFound(OIDC_DB_CODEVERIFIERS, (value)); } while (0) #define codeVerifierDB_addValue(value) \ do { db_addValue(OIDC_DB_CODEVERIFIERS, (value)); } while (0) #define codeVerifierDB_findValue(key) db_findValue(OIDC_DB_CODEVERIFIERS, (key)) #define codeVerifierDB_findAllValues(key) \ db_findAllValues(OIDC_DB_CODEVERIFIERS, (key)) #define codeVerifierDB_findValueWithFunction(key, function) \ db_findValueWithFunction(OIDC_DB_CODEVERIFIERS, (key), (function)) #define codeVerifierDB_getMinDeath(getter) \ db_getMinDeath(OIDC_DB_CODEVERIFIERS, (getter)) #define codeVerifierDB_getDeathEntry(getter) \ db_getDeathEntry(OIDC_DB_CODEVERIFIERS, (getter)) #define codeVerifierDB_getSize() db_getSize(OIDC_DB_CODEVERIFIERS) #define codeVerifierDB_reset() \ do { db_reset(OIDC_DB_CODEVERIFIERS); } while (0) #endif // OIDC_DB_CODEVERIFIERS_H oidc-agent-4.2.6/src/utils/db/deviceCode_db.h0000644000175000017500000000241514167074355020305 0ustar marcusmarcus#ifndef OIDC_DB_DEVICECODES_H #define OIDC_DB_DEVICECODES_H #include "db.h" #define deviceCodeDB_new() \ do { db_newDB(OIDC_DB_DEVICECODES); } while (0) #define deviceCodeDB_getList() db_getDB(OIDC_DB_DEVICECODES) #define deviceCodeDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_DEVICECODES, (match)) #define deviceCodeDB_setFreeFunction(free) \ db_setFreeFunction(OIDC_DB_DEVICECODES, (free)) #define deviceCodeDB_removeIfFound(value) \ do { db_removeIfFound(OIDC_DB_DEVICECODES, (value)); } while (0) #define deviceCodeDB_addValue(value) \ do { db_addValue(OIDC_DB_DEVICECODES, (value)); } while (0) #define deviceCodeDB_findValue(key) db_findValue(OIDC_DB_DEVICECODES, (key)) #define deviceCodeDB_findAllValues(key) \ db_findAllValues(OIDC_DB_DEVICECODES, (key)) #define deviceCodeDB_findValueWithFunction(key, function) \ db_findValueWithFunction(OIDC_DB_DEVICECODES, (key), (function)) #define deviceCodeDB_getMinDeath(getter) \ db_getMinDeath(OIDC_DB_DEVICECODES, (getter)) #define deviceCodeDB_getDeathEntry(getter) \ db_getDeathEntry(OIDC_DB_DEVICECODES, (getter)) #define deviceCodeDB_getSize() db_getSize(OIDC_DB_DEVICECODES) #define deviceCodeDB_reset() \ do { db_reset(OIDC_DB_DEVICECODES); } while (0) #endif // OIDC_DB_DEVICECODES_H oidc-agent-4.2.6/src/utils/db/connection_db.h0000644000175000017500000000221514120404223020364 0ustar marcusmarcus#ifndef OIDC_DB_CONNECTIONS_H #define OIDC_DB_CONNECTIONS_H #include "db.h" #define connectionDB_new() \ do { db_newDB(OIDC_DB_CONNECTIONS); } while (0) #define connectionDB_getList() db_getDB(OIDC_DB_CONNECTIONS) #define connectionDB_setMatchFunction(match) \ db_setMatchFunction(OIDC_DB_CONNECTIONS, (match)) #define connectionDB_setFreeFunction(free) \ db_setFreeFunction(OIDC_DB_CONNECTIONS, (free)) #define connectionDB_removeIfFound(value) \ do { db_removeIfFound(OIDC_DB_CONNECTIONS, (value)); } while (0) #define connectionDB_addValue(value) \ do { db_addValue(OIDC_DB_CONNECTIONS, (value)); } while (0) #define connectionDB_findValue(key) db_findValue(OIDC_DB_CONNECTIONS, (key)) #define connectionDB_findAllValues(key) \ db_findAllValues(OIDC_DB_CONNECTIONS, (key)) #define connectionDB_getMinDeath(getter) \ db_getMinDeath(OIDC_DB_CONNECTIONS, (getter)) #define connectionDB_getDeathEntry(getter) \ db_getDeathEntry(OIDC_DB_CONNECTIONS, (getter)) #define connectionDB_getSize() db_getSize(OIDC_DB_CONNECTIONS) #define connectionDB_reset() \ do { db_reset(OIDC_DB_CONNECTIONS); } while (0) #endif // OIDC_DB_CONNECTIONS_H oidc-agent-4.2.6/src/utils/ipUtils.h0000644000175000017500000000040514120404223016623 0ustar marcusmarcus#ifndef OIDC_IP_UTILS_H #define OIDC_IP_UTILS_H int isValidIP(const char* ipAddress); char* hostnameToIP(const char* hostname); int isValidIPOrHostname(const char* iph); int isValidIPOrHostnameOptionalPort(const char* iph); #endif /* OIDC_IP_UTILS_H */ oidc-agent-4.2.6/src/utils/accountUtils.c0000644000175000017500000001336114167074355017673 0ustar marcusmarcus#include "accountUtils.h" #include #include "account/account.h" #include "utils/crypt/cryptUtils.h" #include "utils/crypt/gpg/gpg.h" #include "utils/db/account_db.h" #include "utils/file_io/cryptFileUtils.h" #include "utils/file_io/file_io.h" #include "utils/file_io/promptCryptFileUtils.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/promptUtils.h" #include "utils/string/stringUtils.h" /** * @brief returns the minimum death time in an account list * @param accounts a list of (loaded) accounts * @return the minimum time of death; might be @c 0 */ time_t getMinAccountDeath() { logger(DEBUG, "Getting min death time for accounts"); return accountDB_getMinDeath((time_t(*)(void*))account_getDeath); } /** * @brief returns an account that death was prior to the current time * @param accounts a list of (loaded) accounts - searchspace * only one death account is returned per call; to find all death accounts in @p * accounts @c getDeathAccount should be called until it returns @c NULL * @return a pointer to a dead account or @c NULL */ struct oidc_account* getDeathAccount() { logger(DEBUG, "Searching for death accounts"); return accountDB_getDeathEntry((time_t(*)(void*))account_getDeath); } struct oidc_account* getAccountFromMaybeEncryptedFile(const char* filepath) { if (filepath == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* config = readFile(filepath); if (NULL == config) { return NULL; } if (!isJSONObject(config)) { secFree(config); char* tmp = getDecryptedTextWithPromptFor(filepath, 0, NULL, NULL, NULL); if (NULL == tmp) { return NULL; } config = tmp; } struct oidc_account* p = getAccountFromJSON(config); secFree(config); return p; } /** * @brief reads the encrypted configuration for a given short name and decrypts * the configuration. * @param accountname the short name of the account that should be decrypted * @param pw_cmd the command used to get the encryption password, can be * @c NULL * @param pw_file a filepath used to get the encryption password, can be * @c NULL * @return a pointer to an oidc_account. Has to be freed after usage. Null on * failure. */ struct resultWithEncryptionPassword getDecryptedAccountAndPasswordFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (accountname == NULL) { oidc_setArgNullFuncError(__func__); return RESULT_WITH_PASSWORD_NULL; } struct resultWithEncryptionPassword result = getDecryptedOidcFileAndPasswordFor(accountname, pw_cmd, pw_file, pw_env); char* config = result.result; if (NULL == config) { return result; } struct oidc_account* p = getAccountFromJSON(config); secFree(config); result.result = p; return result; } struct oidc_account* getDecryptedAccountFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (accountname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* config = getDecryptedOidcFileFor(accountname, pw_cmd, pw_file, pw_env); if (NULL == config) { return NULL; } struct oidc_account* p = getAccountFromJSON(config); secFree(config); return p; } struct resultWithEncryptionPassword getDecryptedAccountAsStringAndPasswordFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (accountname == NULL) { oidc_setArgNullFuncError(__func__); return RESULT_WITH_PASSWORD_NULL; } struct resultWithEncryptionPassword result = getDecryptedAccountAndPasswordFromFilePrompt(accountname, pw_cmd, pw_file, pw_env); if (NULL == result.result) { return result; } char* json = accountToJSONString(result.result); secFreeAccount(result.result); result.result = json; return result; } char* getDecryptedAccountAsStringFromFilePrompt(const char* accountname, const char* pw_cmd, const char* pw_file, const char* pw_env) { if (accountname == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } struct resultWithEncryptionPassword result = getDecryptedAccountAsStringAndPasswordFromFilePrompt(accountname, pw_cmd, pw_file, pw_env); secFree(result.password); return result.result; } struct oidc_account* db_findAccountByShortname(const char* shortname) { if (shortname == NULL) { return NULL; } char* tmp = oidc_strcopy(shortname); struct oidc_account key = {.shortname = tmp}; struct oidc_account* account = accountDB_findValue(&key); secFree(tmp); return account; } list_t* db_findAccountsByIssuerUrl(const char* issuer_url) { if (issuer_url == NULL) { return NULL; } matchFunction oldMatch = accountDB_setMatchFunction((matchFunction)account_matchByIssuerUrl); char* tmp = oidc_strcopy(issuer_url); struct oidc_issuer iss = {.issuer_url = tmp}; struct oidc_account key = {.issuer = &iss}; list_t* accounts = accountDB_findAllValues(&key); secFree(tmp); accountDB_setMatchFunction(oldMatch); return accounts; } oidc-agent-4.2.6/src/utils/agentLogger.c0000644000175000017500000000054014120404223017423 0ustar marcusmarcus#include "agentLogger.h" #include void (*agent_log)(int log_level, const char* msg, ...); void setLogWithTerminal() { agent_log = loggerTerminal; } void setLogWithoutTerminal() { agent_log = logger; }; void agent_openlog(const char* logger_name) { if (agent_log == NULL) { setLogWithoutTerminal(); } logger_open(logger_name); } oidc-agent-4.2.6/src/utils/colors.c0000644000175000017500000000305314167074355016514 0ustar marcusmarcus#include "colors.h" #include #include "utils/memory.h" #include "utils/string/stringUtils.h" /** * @brief prints a message in a specific color * @param out the FD where the message should be printed * @param colorCode the color code * @param fmt the format string of the message * @param args the arguments of the message * @return the return value of the @c vfprintf function */ int _vprintColored(FILE* out, char* colorCode, char* fmt, va_list args) { char* colored = oidc_sprintf("%s%s%s", colorCode, fmt, C_RESET); int ret = vfprintf(out, colored, args); secFree(colored); return ret; } /** * @brief prints an message colored in C_ERROR * @param fmt the format string of the message * @param args the arguments of the message * @return the return value of the @c _vprintColored function */ int printErrorColored(char* fmt, va_list args) { return _vprintColored(stderr, C_ERROR, fmt, args); } /** * @brief prints an message colored in C_PROMPT * @param fmt the format string of the message * @param args the arguments of the message * @return the return value of the @c _vprintColored function */ int printPromptColored(char* fmt, va_list args) { return _vprintColored(stderr, C_PROMPT, fmt, args); } /** * @brief prints an message colored in C_IMPORTANT * @param fmt the format string of the message * @param args the arguments of the message * @return the return value of the @c _vprintColored function */ int printImportantColored(char* fmt, va_list args) { return _vprintColored(stderr, C_IMPORTANT, fmt, args); } oidc-agent-4.2.6/src/utils/memory.h0000644000175000017500000000166314167074355016535 0ustar marcusmarcus#ifndef MEMORY_H #define MEMORY_H #include #include "oidc-token/export_symbols.h" void* secAlloc(size_t size); void* secCalloc(size_t nmemb, size_t size); void* secRealloc(void* p, size_t size); LIB_PUBLIC void _secFree(void* p); void _secFreeN(void* p, size_t len); void _secFreeArray(char** arr, size_t size); void* oidc_memcopy(void* src, size_t size); void oidc_memshiftr(void* src, size_t size); #ifndef secFree #define secFree(ptr) \ do { \ _secFree((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFree #define secFreeN(ptr, len) \ do { \ _secFreeN((ptr), (len)); \ (ptr) = NULL; \ } while (0) #define secFreeArray(ptr, size) \ do { \ _secFreeArray((ptr), (size)); \ (ptr) = NULL; \ } while (0) #endif // MEMORY_H oidc-agent-4.2.6/src/utils/memzero.h0000644000175000017500000000044114120404223016650 0ustar marcusmarcus#ifndef MEMZERO_H #define MEMZERO_H #include #include static void* (*const volatile memset_ptr)(void*, int, size_t) = memset; static inline void moresecure_memzero(void* p, size_t len) { __asm__("" : : "m"(p)); (memset_ptr)(p, 0, len); } #endif // MEMZERO_H oidc-agent-4.2.6/src/utils/system_runner.h0000644000175000017500000000025514167074355020136 0ustar marcusmarcus#ifndef OIDC_SYSTEM_RUNNER_H #define OIDC_SYSTEM_RUNNER_H char* getOutputFromCommand(const char* cmd); void fireCommand(const char* cmd); #endif // OIDC_SYSTEM_RUNNER_H oidc-agent-4.2.6/src/utils/matcher.c0000644000175000017500000000101114167074355016626 0ustar marcusmarcus#include "matcher.h" #include #include "account/issuer_helper.h" #include "utils/string/stringUtils.h" int matchStrings(const char* a, const char* b) { if (a == NULL && b == NULL) { return 1; } if (a == NULL) { return 0; } if (b == NULL) { return 0; } return strequal(a, b); } int matchUrls(const char* a, const char* b) { if (a == NULL && b == NULL) { return 1; } if (a == NULL) { return 0; } if (b == NULL) { return 0; } return compIssuerUrls(a, b); } oidc-agent-4.2.6/src/utils/colors.h0000644000175000017500000000113714120404223016476 0ustar marcusmarcus#ifndef OIDC_COLORS_H #define OIDC_COLORS_H #define C_RED "\x1B[31m" #define C_GRN "\x1B[32m" #define C_YEL "\x1B[33m" #define C_BLU "\x1B[34m" #define C_MAG "\x1B[35m" #define C_CYN "\x1B[36m" #define C_WHT "\x1B[37m" #define C_RESET "\x1B[0m" // Setting colors #ifndef C_ERROR #define C_ERROR C_RED #endif #ifndef C_PROMPT #define C_PROMPT C_CYN #endif #ifndef C_IMPORTANT #define C_IMPORTANT C_YEL #endif #include int printErrorColored(char* fmt, va_list args); int printPromptColored(char* fmt, va_list args); int printImportantColored(char* fmt, va_list args); #endif // OIDC_COLORS_H oidc-agent-4.2.6/src/utils/parseJson.h0000644000175000017500000000016314120404223017137 0ustar marcusmarcus#ifndef OIDC_PARSE_JSON_H #define OIDC_PARSE_JSON_H char* parseForError(char* res); #endif // OIDC_PARSE_JSON_H oidc-agent-4.2.6/src/utils/crypt/0000755000175000017500000000000014170031216016166 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/crypt/cryptUtils.c0000644000175000017500000001077014167074355020542 0ustar marcusmarcus#include "cryptUtils.h" #include #include #include #include "account/account.h" #include "crypt.h" #include "defines/settings.h" #include "defines/version.h" #include "hexCrypt.h" #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" #include "utils/versionUtils.h" /** * @brief decrypts the content of a file with the given password. * the file content must be content generated by @c encryptWithVersionLine * @param filecontent the filecontent to be decrypted * @param password the password used for encryption * @return a pointer to the decrypted filecontent. It has to be freed after * usage. */ char* decryptFileContent(const char* fileContent, const char* password) { list_t* lines = delimitedStringToList(fileContent, '\n'); char* ret = decryptLinesList(lines, password); secFreeList(lines); return ret; } /** * @brief decrypts the content of a hex encoded file with the given password. * the file must have been generated before version 2.1.0 * @param cipher the filecontent to be decrypted * @param password the password used for encryption * @return a pointer to the decrypted filecontent. It has to be freed after * usage. */ char* decryptHexFileContent(const char* cipher, const char* password) { char* fileText = oidc_strcopy(cipher); unsigned long cipher_len = strToInt(strtok(fileText, ":")); char* salt_encoded = strtok(NULL, ":"); char* nonce_encoded = strtok(NULL, ":"); char* cipher_encoded = strtok(NULL, ":"); if (cipher_len == 0 || salt_encoded == NULL || nonce_encoded == NULL || cipher_encoded == NULL) { oidc_errno = OIDC_ECRYPM; secFree(fileText); return NULL; } unsigned char* decrypted = crypt_decrypt_hex( cipher_encoded, cipher_len, password, nonce_encoded, salt_encoded); secFree(fileText); return (char*)decrypted; } /** * @brief decrypts a list of lines with the given password. * The list has to contain specific information in the correct order; the last * line has to be the version line (if there is one, files encrypted before * 2.1.0 will only have one line). * @param lines the list of lines * @param password the password used for encryption * @return a pointer to the decrypted cipher. It has to be freed after * usage. */ char* decryptLinesList(list_t* lines, const char* password) { if (lines == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_node_t* node = list_at(lines, 0); char* cipher = node ? node->val : NULL; node = list_at(lines, -1); char* version_line = lines->len > 1 ? node ? node->val : NULL : NULL; char* version = versionLineToSimpleVersion(version_line); if (versionAtLeast(version, MIN_BASE64_VERSION)) { secFree(version); return crypt_decryptFromList(lines, password); } else { // old config file format; using hex encoding secFree(version); return decryptHexFileContent(cipher, password); } } /** * @brief encrypts a given text with the given password * @return the encrypted text in a formatted string that holds all relevant * encryption information and that can be passed to @c decryptText * @note when saving the encrypted text you also have to save the oidc-agent * version. But there is a specific function for this. See * @c encryptWithVersionLine * @note before version 2.1.0 this function used hex encoding */ char* encryptText(const char* text, const char* password) { return crypt_encrypt(text, password); } /** * @brief encrypts a given text with the given password and adds the current * oidc-agent version * @return the encrypted text in a formatted string that holds all relevant * encryption information as well as the oidc-agent version. Can be passed to * @c decryptFileContent */ char* encryptWithVersionLine(const char* text, const char* password) { char* crypt = encryptText(text, password); char* version_line = simpleVersionToVersionLine(VERSION); char* ret = oidc_sprintf("%s\n%s", crypt, version_line); secFree(crypt); secFree(version_line); return ret; } char* randomString(size_t len) { char* str = secAlloc(len + 1); randomFillBase64UrlSafe(str, len); size_t shifts; for (shifts = 0; shifts < len && isalnum(str[0]) == 0; shifts++) { // assert first char is alphanumeric oidc_memshiftr(str, len); } if (shifts >= len) { secFree(str); return randomString(len); } return str; } oidc-agent-4.2.6/src/utils/crypt/cryptUtils.h0000644000175000017500000000061214167074355020541 0ustar marcusmarcus#ifndef CRYPT_UTILS_H #define CRYPT_UTILS_H #include "wrapper/list.h" char* encryptText(const char* text, const char* password); char* encryptWithVersionLine(const char* text, const char* password); char* decryptFileContent(const char* fileContent, const char* password); char* decryptLinesList(list_t* lines, const char* password); char* randomString(size_t len); #endif // CRYPT_UTILS_H oidc-agent-4.2.6/src/utils/crypt/hexCrypt.h0000644000175000017500000000110314167074355020161 0ustar marcusmarcus#ifndef HEX_CRYPT_H #define HEX_CRYPT_H #include "cryptdef.h" unsigned char* crypt_decrypt_hex(char* ciphertext, unsigned long cipher_len, const char* password, char nonce_hex[], char salt_hex[]); unsigned char* crypt_keyDerivation_hex(const char* password, char salt_hex[], int generateNewSalt, struct cryptParameter params); char* toHex(const unsigned char* bin, size_t bin_len); #endif // HEX_CRYPT_H oidc-agent-4.2.6/src/utils/crypt/dbCryptUtils.h0000644000175000017500000000103214120404223020760 0ustar marcusmarcus#ifndef DB_CRYPT_UTILS_H #define DB_CRYPT_UTILS_H #include "account/account.h" #include "utils/oidc_error.h" oidc_error_t lockEncrypt(const char* password); oidc_error_t lockDecrypt(const char* password); struct oidc_account* _db_decryptFoundAccount(struct oidc_account* account); struct oidc_account* db_getAccountDecrypted(struct oidc_account* key); struct oidc_account* db_getAccountDecryptedByShortname(const char* shortname); void db_addAccountEncrypted(struct oidc_account* account); #endif // DB_CRYPT_UTILS_H oidc-agent-4.2.6/src/utils/crypt/hexCrypt.c0000644000175000017500000002032314167074355020161 0ustar marcusmarcus#include "hexCrypt.h" #include #include #include "utils/logger.h" #include "utils/oidc_error.h" #include "utils/printer.h" // if the distro used libsodium18 before 2.1.0 -> stretch, xenial #define LEG18_NONCE_LEN 24 #define LEG18_SALT_LEN 16 #define LEG18_MAC_LEN 16 #define LEG18_KEY_LEN 32 #define LEG18_PW_HASH_ALG 1 #define LEG18_PW_HASH_OPSLIMIT 4 #define LEG18_PW_HASH_MEMLIMIT 33554432 // if the distro used libsodium23 before 2.1.0 -> bionic, buster #define LEG23_NONCE_LEN 24 #define LEG23_SALT_LEN 16 #define LEG23_MAC_LEN 16 #define LEG23_KEY_LEN 32 #define LEG23_PW_HASH_ALG 2 #define LEG23_PW_HASH_OPSLIMIT 2 #define LEG23_PW_HASH_MEMLIMIT 67108864 /** * Legacy crypt parameters for file encrypted before 2.1.0 on a system that used * libsodium23 (bionic, buster) */ static struct cryptParameter legacy_23_cryptParams = {LEG23_NONCE_LEN, LEG23_SALT_LEN, LEG23_MAC_LEN, LEG23_KEY_LEN, 0, LEG23_PW_HASH_OPSLIMIT, LEG23_PW_HASH_MEMLIMIT, LEG23_PW_HASH_ALG}; /** * Legacy crypt parameters for file encrypted before 2.1.0 on a system that used * libsodium28 (xenial, stretch) */ static struct cryptParameter legacy_18_cryptParams = {LEG18_NONCE_LEN, LEG18_SALT_LEN, LEG18_MAC_LEN, LEG18_KEY_LEN, 0, LEG18_PW_HASH_OPSLIMIT, LEG18_PW_HASH_MEMLIMIT, LEG18_PW_HASH_ALG}; /** * @brief decrypts a given encrypted text with the given password and * cryptParameter. * @param ciphertext_hex the hex encoded cipher * @param cipher_len the lenght of the ciphertext. This is not the length of the * hex encoded ciphertext, but of the original plaintext + mac_len. * @param password the password used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption fails @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted before version * 2.1.0 */ unsigned char* crypt_decrypt_hex_withParams(char* ciphertext_hex, unsigned long cipher_len, const char* password, char nonce_hex[], char salt_hex[], struct cryptParameter params) { if (cipher_len < params.mac_len) { oidc_errno = OIDC_ECRYPM; return NULL; } logger(DEBUG, "Decrypt using hex encoding"); unsigned char* decrypted = secAlloc(sizeof(unsigned char) * (cipher_len - params.mac_len + 1)); unsigned char* key = crypt_keyDerivation_hex(password, salt_hex, 0, params); if (key == NULL) { return NULL; } unsigned char nonce[params.nonce_len]; unsigned char ciphertext[cipher_len]; sodium_hex2bin(nonce, params.nonce_len, nonce_hex, 2 * params.nonce_len, NULL, NULL, NULL); sodium_hex2bin(ciphertext, cipher_len, ciphertext_hex, 2 * cipher_len, NULL, NULL, NULL); if (crypto_secretbox_open_easy(decrypted, ciphertext, cipher_len, nonce, key) != 0) { secFree(key); logger(NOTICE, "Decryption failed."); secFree(decrypted); /* If we get here, the Message was a forgery. This means someone (or the * network) somehow tried to tamper with the message*/ oidc_errno = OIDC_EPASS; return NULL; } secFree(key); return decrypted; } /** * @brief decrypts a given encrypted text with the given password. * @param ciphertext_hex the hex encoded ciphertext to be decrypted * @param cipher_len the lenght of the ciphertext. This is not the length of the * hex encoded ciphertext, but of the original plaintext + mac_len. * @param password the passwod used for encryption * @param nonce_hex the hex encoded nonce used for encryption * @param salt_hex the hex encoded salt used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption failed @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted before version * 2.1.0 - for other ciphers use @c crypt_decrypt_base64 * @deprecated only use this function for backwards compatibility */ unsigned char* crypt_decrypt_hex(char* ciphertext_hex, unsigned long cipher_len, const char* password, char nonce_hex[], char salt_hex[]) { logger(DEBUG, "Trying to decrypt hex encoded cipher using legacy18Params"); unsigned char* res18 = crypt_decrypt_hex_withParams(ciphertext_hex, cipher_len, password, nonce_hex, salt_hex, legacy_18_cryptParams); oidc_error_t error18 = oidc_errno; if (res18 != NULL) { return res18; } logger(DEBUG, "Trying to decrypt hex encoded cipher using legacy23Params"); unsigned char* res23 = crypt_decrypt_hex_withParams(ciphertext_hex, cipher_len, password, nonce_hex, salt_hex, legacy_23_cryptParams); oidc_error_t error23 = oidc_errno; if (res23 != NULL) { return res23; } if (error23 == error18) { oidc_errno = error18; } else { oidc_errno = OIDC_EPASS; // only errors possible are OIDC_ECRPM and // OIDC_EPASS; if 18 and 23 deliver different // errors, EPASS is "more successful" } return NULL; } /** * @brief derivates a key from the given password * @param password the password to be used for key derivation * @param salt_hex a pointer to a 2*params.salt_len+1 big buffer. If @p * generateNewSalt is set, the generated salt will be stored here, otherwise the * stored salt will be used * @param generateNewSalt indicates if a new salt should be generated or if * @p salt_hex should be used. If you use this function for encryption * @p generateNewSalt should be @c 1; for decryption @c 0 * @param params the crypt parameters to be used * @return a pointer to the derivated key. It has to be freed after usage. * @note this function is only used for keyDerivation with hex encoded salt * (before version 2.1.0) - see also @c crypt_keyDerivation_base64 * @deprecated use this function only to derivate a key to compare it to one * derivated before version 2.1.0; it is deprecated and should not be used to * derivate new keys. */ unsigned char* crypt_keyDerivation_hex(const char* password, char salt_hex[], int generateNewSalt, struct cryptParameter params) { logger(DEBUG, "Derivate key using hex encoding"); if (generateNewSalt == 1) { logger(WARNING, "%s is deprecated", __func__); printImportant("%s is deprecated", __func__); } unsigned char* key = secAlloc(sizeof(unsigned char) * (params.key_len + 1)); unsigned char salt[params.salt_len]; if (generateNewSalt) { /* Choose a random salt */ randombytes_buf(salt, params.salt_len); sodium_bin2hex(salt_hex, 2 * params.salt_len + 1, salt, params.salt_len); } else { sodium_hex2bin(salt, params.salt_len, salt_hex, 2 * params.salt_len, NULL, NULL, NULL); } if (crypto_pwhash(key, params.key_len, password, strlen(password), salt, params.hash_ops_limit, params.hash_mem_limit, params.hash_alg) != 0) { secFree(key); logger(ALERT, "Could not derivate key. Probably because system out of memory.\n"); oidc_errno = OIDC_EMEM; return NULL; } return key; } char* toHex(const unsigned char* bin, size_t bin_len) { size_t hex_len = 2 * bin_len + 1; char* hex = secAlloc(hex_len); sodium_bin2hex(hex, hex_len, bin, bin_len); return hex; } oidc-agent-4.2.6/src/utils/crypt/passwordCrypt.c0000644000175000017500000000270314167074355021241 0ustar marcusmarcus#include "passwordCrypt.h" #include #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/numberString.h" #include "utils/string/stringUtils.h" static const uint64_t maxSupportedPass = 692533995824480255; static uint64_t passwordPass; void initPasswordCrypt() { uint64_t limit = maxSupportedPass - 0xffffffff; uint64_t a = randombytes_uniform(limit); uint32_t b = randombytes_random(); uint64_t pass = (a << 32) | b; passwordPass = pass; } char* encryptPassword(const char* password, const char* salt) { if (password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* pass = numberToString(passwordPass); if (pass == NULL) { oidc_setInternalError("Password encryption password not retrievable"); return NULL; } char* salted_pass = oidc_sprintf("%s%s", salt, pass); secFree(pass); char* ret = crypt_encrypt(password, salted_pass); secFree(salted_pass); return ret; } char* decryptPassword(const char* cypher, const char* salt) { if (cypher == NULL) { // Don't set errno return NULL; } char* pass = numberToString(passwordPass); if (pass == NULL) { oidc_setInternalError("Password encryption password not retrievable"); return NULL; } char* salted_pass = oidc_sprintf("%s%s", salt, pass); secFree(pass); char* ret = crypt_decrypt(cypher, salted_pass); secFree(salted_pass); return ret; } oidc-agent-4.2.6/src/utils/crypt/ipcCryptUtils.h0000644000175000017500000000030314120404223021146 0ustar marcusmarcus#ifndef IPC_CRYPT_UTILS_H #define IPC_CRYPT_UTILS_H char* decryptForIpc(const char*, const unsigned char*); char* encryptForIpc(const char*, const unsigned char*); #endif // IPC_CRYPT_UTILS_H oidc-agent-4.2.6/src/utils/crypt/passwordCrypt.h0000644000175000017500000000034714120404223021224 0ustar marcusmarcus#ifndef PASSWORD_CRYPT_H #define PASSWORD_CRYPT_H void initPasswordCrypt(); char* encryptPassword(const char* password, const char* salt); char* decryptPassword(const char* cypher, const char* salt); #endif // PASSWORD_CRYPT_H oidc-agent-4.2.6/src/utils/crypt/memoryCrypt.h0000644000175000017500000000023214120404223020663 0ustar marcusmarcus#ifndef MEMORY_CRYP_H #define MEMORY_CRYP_H char* memoryEncrypt(const char* str); char* memoryDecrypt(const char* str); void initMemoryCrypt(); #endif oidc-agent-4.2.6/src/utils/crypt/dbCryptUtils.c0000644000175000017500000001175014167074355021007 0ustar marcusmarcus#include "dbCryptUtils.h" #include "crypt.h" #include "cryptUtils.h" #include "memoryCrypt.h" #include "utils/accountUtils.h" #include "utils/db/account_db.h" #include "utils/logger.h" #include "utils/string/stringUtils.h" /** * @brief encrypts sensitive information when the agent is locked. * encrypts all loaded access_token, additional encryption (on top of already in * place xor) for refresh_token, client_id, client_secret * @param loaded the list of currently loaded accounts * @param password the lock password that will be used for encryption * @return an oidc_error code */ oidc_error_t lockEncrypt(const char* password) { list_node_t* node; list_iterator_t* it = list_iterator_new(accountDB_getList(), LIST_HEAD); while ((node = list_iterator_next(it))) { struct oidc_account* acc = node->val; char* tmp = encryptText(account_getAccessToken(acc), password); if (tmp == NULL) { return oidc_errno; } account_setAccessToken(acc, tmp); tmp = encryptText(account_getRefreshToken(acc), password); if (tmp == NULL) { return oidc_errno; } account_setRefreshToken(acc, tmp); tmp = encryptText(account_getClientId(acc), password); if (tmp == NULL) { return oidc_errno; } account_setClientId(acc, tmp); if (strValid(account_getClientSecret(acc))) { tmp = encryptText(account_getClientSecret(acc), password); if (tmp == NULL) { return oidc_errno; } account_setClientSecret(acc, tmp); } } list_iterator_destroy(it); return OIDC_SUCCESS; } /** * @brief decrypts sensitive information when the agent is unlocked. * After this call refresh_token, client_id, and client_secret will still be * xor encrypted * @param loaded the list of currently loaded accounts * @param password the lock password that was used for encryption * @return an oidc_error code */ oidc_error_t lockDecrypt(const char* password) { list_node_t* node; list_iterator_t* it = list_iterator_new(accountDB_getList(), LIST_HEAD); while ((node = list_iterator_next(it))) { struct oidc_account* acc = node->val; char* tmp = crypt_decrypt(account_getAccessToken(acc), password); if (tmp == NULL) { return oidc_errno; } account_setAccessToken(acc, tmp); tmp = crypt_decrypt(account_getRefreshToken(acc), password); if (tmp == NULL) { return oidc_errno; } account_setRefreshToken(acc, tmp); tmp = crypt_decrypt(account_getClientId(acc), password); if (tmp == NULL) { return oidc_errno; } account_setClientId(acc, tmp); tmp = crypt_decrypt(account_getClientSecret(acc), password); if (tmp == NULL) { return oidc_errno; } account_setClientSecret(acc, tmp); } list_iterator_destroy(it); return OIDC_SUCCESS; } struct oidc_account* _db_decryptFoundAccount(struct oidc_account* account) { if (account == NULL) { return NULL; } account_setRefreshToken(account, memoryDecrypt(account_getRefreshToken(account))); account_setClientId(account, memoryDecrypt(account_getClientId(account))); account_setClientSecret(account, memoryDecrypt(account_getClientSecret(account))); return account; } /** * @brief finds an account in the list of currently loaded accounts and * decryptes the sensitive information * @param loaded_accounts the list of currently loaded accounts * @param key a key account that should be searched for * @return a pointer to the decrypted account * @note after usage the account has to be encrypted again by using * @c addAccountToList */ struct oidc_account* db_getAccountDecrypted(struct oidc_account* key) { logger(DEBUG, "Getting / Decrypting account from list"); struct oidc_account* account = accountDB_findValue(key); return _db_decryptFoundAccount(account); } struct oidc_account* db_getAccountDecryptedByShortname(const char* shortname) { logger(DEBUG, "Getting / Decrypting account from list"); struct oidc_account* account = db_findAccountByShortname(shortname); return _db_decryptFoundAccount(account); } /** * @brief encrypts the sensitive information of an account and adds it to the * list of currently loaded accounts. * If there is already a similar account loaded it will be overwritten (removed * and the then the new account is added) * @param loaded_accounts the list of currently loaded accounts * @param account the account that should be added */ void db_addAccountEncrypted(struct oidc_account* account) { logger(DEBUG, "Adding / Reencrypting account to list"); account_setRefreshToken(account, memoryEncrypt(account_getRefreshToken(account))); account_setClientId(account, memoryEncrypt(account_getClientId(account))); account_setClientSecret(account, memoryEncrypt(account_getClientSecret(account))); struct oidc_account* found = accountDB_findValue(account); if (found != account) { if (found) { accountDB_removeIfFound(account); } accountDB_addValue(account); } } oidc-agent-4.2.6/src/utils/crypt/crypt.c0000644000175000017500000004566714170031216017515 0ustar marcusmarcus#include "crypt.h" #include #include #include "utils/listUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" // use these for new encryptions #define SODIUM_KEY_LEN crypto_secretbox_KEYBYTES #define SODIUM_SALT_LEN crypto_pwhash_SALTBYTES #define SODIUM_NONCE_LEN crypto_secretbox_NONCEBYTES #define SODIUM_MAC_LEN crypto_secretbox_MACBYTES #define SODIUM_BASE64_VARIANT sodium_base64_VARIANT_ORIGINAL #define SODIUM_PW_HASH_ALG crypto_pwhash_ALG_DEFAULT #define SODIUM_PW_HASH_OPSLIMIT crypto_pwhash_OPSLIMIT_INTERACTIVE #define SODIUM_PW_HASH_MEMLIMIT crypto_pwhash_MEMLIMIT_INTERACTIVE /** * @brief initializes random number generator */ void initCrypt() { randombytes_stir(); } /** * @brief returns current cryptParameters * @return a cryptParameter struct */ struct cryptParameter newCryptParameters() { return (struct cryptParameter){ SODIUM_NONCE_LEN, SODIUM_SALT_LEN, SODIUM_MAC_LEN, SODIUM_KEY_LEN, SODIUM_BASE64_VARIANT, SODIUM_PW_HASH_OPSLIMIT, SODIUM_PW_HASH_MEMLIMIT, SODIUM_PW_HASH_ALG}; } /** * @brief encrypts a given text with the given password. * @param text the nullterminated text * @param password the nullterminated password, used for encryption * @return a pointer to an encryptionInfo struct; Has to be freed after usage. * usage using @c secFreeEncryptionInfo */ struct encryptionInfo* _crypt_encrypt(const unsigned char* text, const char* password) { if (text == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Encrypt using base64 encoding"); char* salt_base64 = secAlloc(sodium_base64_ENCODED_LEN(SODIUM_SALT_LEN, sodium_base64_VARIANT_ORIGINAL) + 1); struct cryptParameter cryptParams = newCryptParameters(); struct key_set keys = crypt_keyDerivation_base64(password, salt_base64, 1, &cryptParams); if (keys.encryption_key == NULL) { secFree(salt_base64); secFree(keys.hash_key); return NULL; } struct encryptionInfo* result = crypt_encryptWithKey(text, (unsigned char*)keys.encryption_key); secFree(keys.encryption_key); char* hash_key_base64 = toBase64(keys.hash_key, SODIUM_KEY_LEN); secFree(keys.hash_key); result->salt_base64 = salt_base64; result->hash_key_base64 = hash_key_base64; result->cryptParameter = cryptParams; if (result->encrypted_base64 == NULL) { secFreeEncryptionInfo(result); return NULL; } return result; } /** * @brief encrypts a given text with the given key. * @param text the nullterminated text * @param key the key to be used for encryption * @return a pointer to an encryptionInfo struct; Has to be freed after * usage using @c secFreeEncryptionInfo */ struct encryptionInfo* crypt_encryptWithKey(const unsigned char* text, const unsigned char* key) { struct cryptParameter cryptParams = newCryptParameters(); char nonce[cryptParams.nonce_len]; randombytes_buf(nonce, cryptParams.nonce_len); unsigned char ciphertext[cryptParams.mac_len + strlen((char*)text)]; if (crypto_secretbox_easy(ciphertext, text, strlen((char*)text), (unsigned char*)nonce, key) != 0) { oidc_errno = OIDC_EENCRYPT; return NULL; } char* ciphertext_base64 = toBase64((char*)ciphertext, cryptParams.mac_len + strlen((char*)text)); char* nonce_base64 = toBase64(nonce, cryptParams.nonce_len); struct encryptionInfo* crypt = secAlloc(sizeof(struct encryptionInfo)); crypt->encrypted_base64 = ciphertext_base64; crypt->nonce_base64 = nonce_base64; crypt->cryptParameter = cryptParams; return crypt; } /** * @brief encrypts a given text with the given password. * This function uses base64 encoding * @param text the nullterminated text * @param password the nullterminated password, used for encryption * @return a string containing all relevant encryption information; this string * can be passed to @c crypt_decrypt for decryption * @note before version 2.1.0 this function used hex encoding */ char* crypt_encrypt(const char* text, const char* password) { struct encryptionInfo* cry = _crypt_encrypt((unsigned char*)text, password); if (cry == NULL || cry->encrypted_base64 == NULL) { return NULL; } // Current config file format: // 1 cipher_len // 2 nonce_base64 // 3 salt_base64 // 4 crypt parameters // 5 cipher_base64 // 6 hash_key_base64 // [7 version] // Not included here const char* const fmt = "%lu\n%s\n%s\n%lu:%lu:%lu:%lu:%d:%d:%d:%d\n%s\n%s"; size_t cipher_len = strlen(text) + cry->cryptParameter.mac_len; char* ret = oidc_sprintf( fmt, cipher_len, cry->nonce_base64, cry->salt_base64, cry->cryptParameter.nonce_len, cry->cryptParameter.salt_len, cry->cryptParameter.mac_len, cry->cryptParameter.key_len, cry->cryptParameter.base64_variant, cry->cryptParameter.hash_ops_limit, cry->cryptParameter.hash_mem_limit, cry->cryptParameter.hash_alg, cry->encrypted_base64, cry->hash_key_base64); secFreeEncryptionInfo(cry); return ret; } /** * @brief decrypts a given encrypted text with the given password. * @param crypt a encryptionInfo struct containing all relevant encryption * information * @param cipher_len the lenght of the ciphertext. This is not the length of the * base64 encoded ciphertext, but of the original plaintext + mac_len. * @param password the passwod used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption fails @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted with version * 2.1.0 or higher - for ciphers encrypted before 2.1.0 use @c crypt_decrypt_hex */ unsigned char* crypt_decrypt_base64(struct encryptionInfo* crypt, unsigned long cipher_len, const char* password) { if (crypt == NULL || crypt->encrypted_base64 == NULL || crypt->hash_key_base64 == NULL || password == NULL || crypt->nonce_base64 == NULL || crypt->salt_base64 == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } if (cipher_len < crypt->cryptParameter.mac_len) { oidc_errno = OIDC_ECRYPM; return NULL; } struct key_set keys = crypt_keyDerivation_base64(password, crypt->salt_base64, 0, &(crypt->cryptParameter)); if (keys.encryption_key == NULL) { secFree(keys.hash_key); return NULL; } char* computed_hash_key_base64 = toBase64(keys.hash_key, crypt->cryptParameter.key_len); secFree(keys.hash_key); if (sodium_memcmp(computed_hash_key_base64, crypt->hash_key_base64, strlen(crypt->hash_key_base64)) != 0) { secFree(keys.encryption_key); secFree(computed_hash_key_base64); oidc_errno = OIDC_EPASS; return NULL; } secFree(computed_hash_key_base64); unsigned char* decrypted = crypt_decryptWithKey( crypt, cipher_len, (unsigned char*)keys.encryption_key); secFree(keys.encryption_key); return decrypted; } /** * @brief decrypts a given encrypted text with the given key. * @param crypt a encryptionInfo struct containing all relevant encryption * information * @param cipher_len the lenght of the ciphertext. This is not the length of the * base64 encoded ciphertext, but of the original plaintext + mac_len. * @param key the key used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption fails @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted with version * 2.1.0 or higher - for ciphers encrypted before 2.1.0 use @c crypt_decrypt_hex */ unsigned char* crypt_decryptWithKey(const struct encryptionInfo* crypt, unsigned long cipher_len, const unsigned char* key) { unsigned char nonce[crypt->cryptParameter.nonce_len]; unsigned char ciphertext[cipher_len]; fromBase64(crypt->nonce_base64, crypt->cryptParameter.nonce_len, nonce); fromBase64(crypt->encrypted_base64, cipher_len, ciphertext); unsigned char* decrypted = secAlloc( sizeof(unsigned char) * (cipher_len - crypt->cryptParameter.mac_len + 1)); if (crypto_secretbox_open_easy(decrypted, ciphertext, cipher_len, nonce, key) != 0) { logger(NOTICE, "Decryption failed."); secFree(decrypted); /* If we get here, the Message was a forgery. This means someone (or the * network) somehow tried to tamper with the message*/ oidc_errno = OIDC_EDECRYPT; return NULL; } return decrypted; } /** * @brief decrypts a given encrypted text with the given password. * @param lines a list of strings containing all relevant encryption * information * @param password the password used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption fails @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted with version * 2.1.0 or higher - for ciphers encrypted before 2.1.0 use @c crypt_decrypt_hex */ char* crypt_decryptFromList(list_t* lines, const char* password) { if (lines == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } logger(DEBUG, "Decrypt using base64 encoding"); struct encryptionInfo* crypt = secAlloc(sizeof(struct encryptionInfo)); size_t cipher_len = 0; sscanf(list_at(lines, 0)->val, "%lu", &cipher_len); crypt->nonce_base64 = oidc_strcopy(list_at(lines, 1)->val); crypt->salt_base64 = oidc_strcopy(list_at(lines, 2)->val); crypt->encrypted_base64 = oidc_strcopy(list_at(lines, 4)->val); crypt->hash_key_base64 = oidc_strcopy(list_at(lines, 5)->val); char* tmp = list_at(lines, 3)->val; const char* const fmt = "%lu:%lu:%lu:%lu:%d:%d:%d:%d"; sscanf(tmp, fmt, &crypt->cryptParameter.nonce_len, &crypt->cryptParameter.salt_len, &crypt->cryptParameter.mac_len, &crypt->cryptParameter.key_len, &crypt->cryptParameter.base64_variant, &crypt->cryptParameter.hash_ops_limit, &crypt->cryptParameter.hash_mem_limit, &crypt->cryptParameter.hash_alg); char* ret = (char*)crypt_decrypt_base64(crypt, cipher_len, password); secFreeEncryptionInfo(crypt); return ret; } /** * @brief decrypts a given encrypted text with the given password. * @param crypt_str a string containing all relevant encryption information; has * to be in the format as returned from @c crypt_encrypt * @param password the password used for encryption * @return a pointer to the decrypted text. It has to be freed after use. If the * decryption fails @c NULL is returned. * @note this function is only used to decrypt ciphers encrypted with version * 2.1.0 or higher - for ciphers encrypted before 2.1.0 use @c crypt_decrypt_hex */ char* crypt_decrypt(const char* crypt_str, const char* password) { if (crypt_str == NULL || password == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } list_t* lines = delimitedStringToList(crypt_str, '\n'); if (lines == NULL) { return NULL; } if (lines->len < 5) { oidc_errno = OIDC_ECRYPM; secFreeList(lines); return NULL; } char* ret = crypt_decryptFromList(lines, password); secFreeList(lines); return ret; } /** * @brief base64 encodes len bytes of bin * @param bin the binary string that should be encoded * @param len the number of bytes that should be encoded * @param variant the base64 variant to be used * @return a pointer to string holding the base64 encoded binary; has to be * freed after usage. */ char* toBase64WithVariant(const char* bin, size_t len, int variant) { if (bin == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } size_t base64len = sodium_base64_ENCODED_LEN(len, variant); char* base64 = secAlloc(base64len + 1); if (base64 == NULL) { oidc_errno = OIDC_EALLOC; return NULL; } sodium_bin2base64(base64, base64len, (const unsigned char*)bin, len, variant); return base64; } /** * @brief base64 encodes len bytes of bin * @param bin the binary string that should be encoded * @param len the number of bytes that should be encoded * @return a pointer to string holding the base64 encoded binary; has to be * freed after usage. * @note base64 encoding is not url-safe */ char* toBase64(const char* bin, size_t len) { if (bin == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } return toBase64WithVariant(bin, len, sodium_base64_VARIANT_ORIGINAL); } /** * @brief base64 encodes len bytes of bin * @param bin the binary string that should be encoded * @param len the number of bytes that should be encoded * @return a pointer to string holding the base64 encoded binary; has to be * freed after usage. * @note base64 encoding is url-safe */ char* toBase64UrlSafe(const char* bin, size_t len) { if (bin == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } return toBase64WithVariant(bin, len, sodium_base64_VARIANT_URLSAFE_NO_PADDING); } /** * @brief decodes a base64 encoded string an places it in bin * @param base64 the nullterminated base64 encoded string * @param bin_len the length of the buffer @p bin * @param bin the buffer where the decoded string should be placed * @return @c 0 on success, @c -1 otherwise */ int fromBase64(const char* base64, size_t bin_len, unsigned char* bin) { if (base64 == NULL || bin == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return sodium_base642bin( bin, bin_len, base64, strlen(base64), NULL, NULL, NULL, sodium_base64_VARIANT_ORIGINAL); } /** * @brief decodes a base64 encoded string an places it in bin * @param base64 the nullterminated base64 encoded string * @param bin_len the length of the buffer @p bin * @param bin the buffer where the decoded string should be placed * @return @c 0 on success, @c -1 otherwise */ int fromBase64UrlSafe(const char* base64, size_t bin_len, unsigned char* bin) { if (base64 == NULL || bin == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } return sodium_base642bin( bin, bin_len, base64, strlen(base64), NULL, NULL, NULL, sodium_base64_VARIANT_URLSAFE_NO_PADDING); } /** * @brief hashes a string using SHA256 * @param str the nullterminated string that should be hashed * @return a pointer to the sha256 hash; not zeroterminated; has to be * freed after usage. */ char* sha256(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* sha = secAlloc(crypto_hash_sha256_BYTES); if (sha == NULL) { oidc_errno = OIDC_EALLOC; return NULL; } crypto_hash_sha256((unsigned char*)sha, (unsigned char*)str, strlen(str)); return sha; } /** * @brief hashes a string using SHA256 and encodes it with base64 * @param str the nullterminated string that should be hashed * @return a pointer to the s256 hash; has to be * freed after usage. */ char* s256(const char* str) { if (str == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* sha = sha256(str); if (sha == NULL) { return NULL; } char* s = toBase64UrlSafe(sha, crypto_hash_sha256_BYTES); secFree(sha); return s; } /** * @brief derivates two keys from the given password * @param password the password to be used for key derivation * @param salt_base64 a pointer to a big enough buffer. If @p * generateNewSalt is set, the generated salt will be stored here, otherwise the * stored salt will be used * @param generateNewSalt indicates if a new salt should be generated or if * @p salt_base64 should be used. If you use this function for encryption * @p generateNewSalt should be @c 1; for decryption @c 0 * @param cryptParameters a pointer to a cryptParameter struct that holds the * parameters to be used * @return a struct holding two pointers to the derivated keys. They have to be * freed after usage. * @note this function is only used to keyDerivation with base64 encoded salt * (since version 2.1.0) - see also @c crypt_keyDerivation_hex */ struct key_set crypt_keyDerivation_base64(const char* password, char salt_base64[], int generateNewSalt, struct cryptParameter* cryptParams) { logger(DEBUG, "Derivate key using base64 encoding"); char* key = secAlloc(sizeof(unsigned char) * (2 * cryptParams->key_len + 1)); unsigned char salt[cryptParams->key_len]; if (generateNewSalt) { /* Choose a random salt */ randombytes_buf(salt, cryptParams->salt_len); sodium_bin2base64( salt_base64, sodium_base64_ENCODED_LEN(cryptParams->salt_len, sodium_base64_VARIANT_ORIGINAL) + 1, salt, cryptParams->salt_len, sodium_base64_VARIANT_ORIGINAL); } else { fromBase64(salt_base64, cryptParams->salt_len, salt); } if (crypto_pwhash((unsigned char*)key, 2 * cryptParams->key_len, password, strlen(password), salt, crypto_pwhash_OPSLIMIT_INTERACTIVE, crypto_pwhash_MEMLIMIT_INTERACTIVE, crypto_pwhash_ALG_DEFAULT) != 0) { secFree(key); logger(ALERT, "Could not derivate key. Probably because system out of memory.\n"); oidc_errno = OIDC_EMEM; return (struct key_set){NULL, NULL}; } char* encryption_key = oidc_memcopy(key, cryptParams->key_len); char* hash_key = oidc_memcopy(key + cryptParams->key_len, cryptParams->key_len); secFree(key); struct key_set keys = {encryption_key, hash_key}; return keys; } /** * @brief fills a buffer with random base64 characters * this is done by filling a buffer with random (binary) bytes and encoding this * buffer with an url safe base64 variant * @param buffer the buffer that should be filled * @param buffer_size the size of @p buffer * @note the filled buffer is url safe */ void randomFillBase64UrlSafe(char buffer[], size_t buffer_size) { unsigned char bin[buffer_size]; randombytes_buf(bin, buffer_size); char base64[sodium_base64_ENCODED_LEN( buffer_size, sodium_base64_VARIANT_URLSAFE_NO_PADDING)]; sodium_bin2base64(base64, sodium_base64_ENCODED_LEN( buffer_size, sodium_base64_VARIANT_URLSAFE_NO_PADDING), bin, buffer_size, sodium_base64_VARIANT_URLSAFE_NO_PADDING); strncpy(buffer, base64, buffer_size); sodium_memzero(base64, sodium_base64_ENCODED_LEN( buffer_size, sodium_base64_VARIANT_URLSAFE_NO_PADDING)); sodium_memzero(bin, buffer_size); } oidc-agent-4.2.6/src/utils/crypt/gpg/0000755000175000017500000000000014167074355016764 5ustar marcusmarcusoidc-agent-4.2.6/src/utils/crypt/gpg/gpg.h0000644000175000017500000000074414167074355017717 0ustar marcusmarcus#ifndef OIDC_AGENT_GPG_H #define OIDC_AGENT_GPG_H char* decryptPGPMessage(const char* gpg); char* decryptPGPFileContent(const char* content); unsigned char isPGPMessage(const char* content); unsigned char isPGPOIDCFile(const char* shortname); char* encryptPGPWithVersionLine(const char* text, const char* gpg_key); char* extractPGPKeyID(const char* text); char* extractPGPKeyIDFromOIDCFile(const char* shortname); #endif // OIDC_AGENT_GPG_H oidc-agent-4.2.6/src/utils/crypt/gpg/gpg.c0000644000175000017500000000676314167074355017721 0ustar marcusmarcus#include "gpg.h" #include #include #include "defines/version.h" #include "utils/crypt/hexCrypt.h" #include "utils/file_io/oidc_file_io.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" #include "utils/versionUtils.h" char* decryptPGPMessage(const char* gpg) { char* cmd = oidc_sprintf("echo '%s' | gpg -d 2>/dev/null", gpg); char* plain = getOutputFromCommand(cmd); secFree(cmd); return plain; } char* decryptPGPFileContent(const char* content) { char* copy = oidc_strcopy(content); char* index = strstr(copy, "END PGP MESSAGE"); index = strchr(index, '\n'); *index = '\0'; // ignore everything after the end of the pgp message char* plain = decryptPGPMessage(copy); secFree(copy); if (plain == NULL) { oidc_errno = OIDC_EDECRYPT; } return plain; } unsigned char isPGPMessage(const char* content) { return strstarts(content, "-----BEGIN PGP MESSAGE-----"); } unsigned char isPGPOIDCFile(const char* shortname) { char* encrypted = readOidcFile(shortname); if (encrypted == NULL) { return 0; } unsigned char ret = isPGPMessage(encrypted); secFree(encrypted); return ret; } /** * @brief encrypts a given text with the given gpg key id and adds the current * oidc-agent version * @return the encrypted text in a formatted string that holds all relevant * encryption information as well as the oidc-agent version. Can be passed to * @c decryptPGPFileContent */ char* encryptPGPWithVersionLine(const char* text, const char* gpg_key) { char* cmd = oidc_sprintf("echo '%s' | gpg -e -r %s --armor", text, gpg_key); char* crypt = getOutputFromCommand(cmd); secFree(cmd); if (crypt == NULL) { return NULL; } char* version_line = simpleVersionToVersionLine(VERSION); char* ret = oidc_sprintf("%s\n%s", crypt, version_line); secFree(crypt); secFree(version_line); return ret; } #define returnIfNULL(X) \ if (X == NULL) { \ return NULL; \ } char* extractPGPKeyIDFromOIDCFile(const char* shortname) { char* encrypted = readOidcFile(shortname); if (encrypted == NULL) { return NULL; } if (!isPGPMessage(encrypted)) { secFree(encrypted); return NULL; } char* key = extractPGPKeyID(encrypted); secFree(encrypted); return key; } char* extractPGPKeyID(const char* text) { char* copy = oidc_strcopy(text); char* index = strstr(copy, "BEGIN PGP MESSAGE"); returnIfNULL(index); index = strstr(index, "\n\n"); returnIfNULL(index); char* begin = index + 2; index = strstr(copy, "END PGP MESSAGE"); returnIfNULL(index); *index = '\0'; index = strrchr(begin, '\n'); returnIfNULL(index); *index = '\0'; unsigned char* bin = secAlloc(2 * strlen(text)); // At this point begin points to string containing everything within the PGP // MESSAGE blog We still have to remove the last line, since this is not in // the same base64 block index = strrchr(begin, '\n'); returnIfNULL(index); *index = '\0'; // strelim(begin, '\n'); if (sodium_base642bin(bin, strlen(text), begin, strlen(begin), " \t\n", NULL, NULL, sodium_base64_VARIANT_ORIGINAL)) { secFree(bin); secFree(copy); return NULL; } char* keyID = toHex( bin + 4, 8); // keyid is 8byte long starting at the 4th byte // https://datatracker.ietf.org/doc/html/rfc4880#section-5.1 secFree(bin); secFree(copy); return keyID; } oidc-agent-4.2.6/src/utils/crypt/ipcCryptUtils.c0000644000175000017500000000275114167074355021176 0ustar marcusmarcus#include "ipcCryptUtils.h" #include #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" char* encryptForIpc(const char* msg, const unsigned char* key) { struct encryptionInfo* cryptResult = crypt_encryptWithKey((unsigned char*)msg, key); if (cryptResult->encrypted_base64 == NULL) { secFreeEncryptionInfo(cryptResult); return NULL; } char* encoded = oidc_sprintf("%lu:%s:%s", strlen(msg), cryptResult->nonce_base64, cryptResult->encrypted_base64); secFreeEncryptionInfo(cryptResult); return encoded; } char* decryptForIpc(const char* msg, const unsigned char* key) { if (msg == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* msg_tmp = oidc_strcopy(msg); char* len_str = strtok(msg_tmp, ":"); char* nonce_base64 = strtok(NULL, ":"); char* encrypted_base64 = strtok(NULL, ":"); size_t msg_len = strToULong(len_str); if (nonce_base64 == NULL || encrypted_base64 == NULL) { secFree(msg_tmp); oidc_errno = OIDC_ECRYPMIPC; return NULL; } struct encryptionInfo crypt = {}; crypt.nonce_base64 = nonce_base64; crypt.encrypted_base64 = encrypted_base64; crypt.cryptParameter = newCryptParameters(); unsigned char* decryptedMsg = crypt_decryptWithKey(&crypt, msg_len + crypt.cryptParameter.mac_len, key); secFree(msg_tmp); return (char*)decryptedMsg; } oidc-agent-4.2.6/src/utils/crypt/memoryCrypt.c0000644000175000017500000000516114167074355020710 0ustar marcusmarcus#include "memoryCrypt.h" #include #include #include "crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" static uint64_t memoryPass; /** * @brief XORs len bytes of string with key * @param string the string to be encrypted * @param key a 64bit number used as encryption key * @param len the nubmer of bytes to be encrypted * @return a pointer to the encrypted string. It has to be freed after usage. */ char* xorCrypt(const char* string, uint64_t key, size_t len) { size_t length = sizeof(key); char* str = secAlloc(len + 1); char* s = str; for (size_t i = 0; i < len; i++) { *str = *string ^ *((unsigned char*)&key + (i % length)); str++; string++; } return s; } /** * @brief decryptes the memory encrypted cipher * @param cipher the cipher to be decrypted; has to be returned by a previous * call to @c memoryEncrypt * @return a pointer to the decrypted string. It has to be freed after usage. */ char* memoryDecrypt(const char* cipher) { if (!strValid(cipher)) { // oidc_setArgNullFuncError(__func__); return NULL; } // logger(DEBUG, "memory decryption '%s'", cipher); char* tmp = oidc_strcopy(cipher); size_t len = strToInt(strtok(tmp, ":")); char* cipher_base64 = strtok(NULL, ":"); if (len == 0 || cipher_base64 == NULL) { secFree(tmp); oidc_errno = OIDC_ECRYPM; return NULL; } unsigned char* cipher_bin = secAlloc(sizeof(char) * (len + 1)); fromBase64(cipher_base64, len, cipher_bin); char* decrypted = xorCrypt((char*)cipher_bin, memoryPass, len); secFree(cipher_bin); secFree(tmp); return decrypted; } /** * @brief encryptes text * @param text the text to be encrypted * @return a pointer to the base64 encoded encrypted string. It has to be freed * after usage. */ char* memoryEncrypt(const char* text) { if (!strValid(text)) { // oidc_setArgNullFuncError(__func__); return NULL; } // logger(DEBUG, "memory encryption '%s'", text); size_t len = strlen(text); char* cipher = xorCrypt(text, memoryPass, len); char* cipher_base64 = toBase64(cipher, len); secFree(cipher); char* fmt = "%lu:%s"; char* ciphered = oidc_sprintf(fmt, len, cipher_base64); secFree(cipher_base64); return ciphered; } /** * @brief initializes memory encryption * generates a random 64bit memory encryption passnumber */ void initMemoryCrypt() { uint64_t a = randombytes_random(); uint32_t b = randombytes_random(); uint64_t pass = (a << 32) | b; memoryPass = pass; } uint64_t _getMemoryPass() { return memoryPass; } oidc-agent-4.2.6/src/utils/crypt/cryptdef.h0000644000175000017500000000170314167074355020201 0ustar marcusmarcus#ifndef OIDC_CRYPT_DEF_H #define OIDC_CRYPT_DEF_H #include #include "utils/memory.h" struct key_set { char* encryption_key; char* hash_key; }; struct cryptParameter { size_t nonce_len; size_t salt_len; size_t mac_len; size_t key_len; int base64_variant; int hash_ops_limit; int hash_mem_limit; int hash_alg; }; struct encryptionInfo { char* encrypted_base64; char* nonce_base64; char* salt_base64; char* hash_key_base64; struct cryptParameter cryptParameter; }; /** * @brief clears and frees an encryptionInfo * @param crypt a pointer to the encryptionInfo struct to be cleared */ static inline void secFreeEncryptionInfo(struct encryptionInfo* crypt) { secFree(crypt->encrypted_base64); secFree(crypt->nonce_base64); secFree(crypt->salt_base64); secFree(crypt->hash_key_base64); secFree(crypt); } #endif // OIDC_CRYPT_DEF_H oidc-agent-4.2.6/src/utils/crypt/crypt.h0000644000175000017500000000261514120404223017501 0ustar marcusmarcus#ifndef CRYPT_H #define CRYPT_H #include "cryptdef.h" #include "wrapper/list.h" void initCrypt(); char* crypt_encrypt(const char* text, const char* password); struct encryptionInfo* crypt_encryptWithKey(const unsigned char* text, const unsigned char* key); char* crypt_decrypt(const char* crypt_str, const char* password); char* crypt_decryptFromList(list_t* lines, const char* password); unsigned char* crypt_decryptWithKey(const struct encryptionInfo* crypt, unsigned long cipher_len, const unsigned char* key); struct key_set crypt_keyDerivation_base64(const char* password, char salt_base64[], int generateNewSalt, struct cryptParameter* cryptParams); char* toBase64(const char* bin, size_t len); char* toBase64UrlSafe(const char* bin, size_t len); int fromBase64(const char* base64, size_t bin_len, unsigned char* bin); int fromBase64UrlSafe(const char* base64, size_t bin_len, unsigned char* bin); void randomFillBase64UrlSafe(char buffer[], size_t buffer_size); char* s256(const char* str); struct cryptParameter newCryptParameters(); #endif // CRYPT_H oidc-agent-4.2.6/src/utils/commonFeatures.c0000644000175000017500000000230514167074355020201 0ustar marcusmarcus#include "commonFeatures.h" #include "defines/ipc_values.h" #include "ipc/cryptCommunicator.h" #include "utils/file_io/fileUtils.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" #include "utils/system_runner.h" void common_handleListConfiguredAccountConfigs() { list_t* list = getAccountConfigFileList(); if (list == NULL) { oidc_perror(); exit(EXIT_FAILURE); } list_mergeSort(list, (int(*)(const void*, const void*))compareFilesByName); char* str = listToDelimitedString(list, "\n"); secFreeList(list); printStdout("The following account configurations are usable: \n%s\n", str); secFree(str); } void common_assertAgent(unsigned char remote) { char* res = ipc_cryptCommunicate(remote, REQUEST_CHECK); if (res == NULL) { oidc_perror(); exit(EXIT_FAILURE); } secFree(res); } void common_assertOidcPrompt() { char* ret = getOutputFromCommand("oidc-prompt --version"); if (!strValid(ret)) { printError("oidc-prompt not installed. To use GUI prompting please install " "the 'oidc-agent-prompt' package.\n"); exit(EXIT_FAILURE); } secFree(ret); } oidc-agent-4.2.6/src/utils/disableTracing.c0000644000175000017500000000330214167074355020123 0ustar marcusmarcus/* * Copyright (c) 2016 Darren Tucker. All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "disableTracing.h" #include "printer.h" #define HAVE_SYS_PTRACE_H #ifdef __linux__ #include /* For prctl() and PR_SET_DUMPABLE */ #elif __APPLE__ #include // types must be included before ptrace #include #endif // #ifdef HAVE_PRIV_H // #include /* For setpflags() and __PROC_PROTECT */ // #endif void platform_disable_tracing() { #ifdef __linux__ /* Disable ptrace on Linux without sgid bit */ if (prctl(PR_SET_DUMPABLE, 0) != 0) { printError("unable to make the process undumpable"); } // #if defined(HAVE_SETPFLAGS) && defined(__PROC_PROTECT) // /* On Solaris, we should make this process untraceable */ // if (setpflags(__PROC_PROTECT, 1) != 0) // printError("unable to make the process untraceable"); // #endif #elif __APPLE__ /* Mac OS X */ if (ptrace(PT_DENY_ATTACH, 0, 0, 0) == -1) printError("unable to set PT_DENY_ATTACH"); #endif } oidc-agent-4.2.6/src/architecture.ascii0000644000175000017500000000513514120404223017362 0ustar marcusmarcus +-------------------------+ +-------------------+ | | | | | oidc-agent | | OpenID Provider | | | | | | +-------------------+ | OIDC | | | | | |communication| | | | oidc-agent-daemon <----------------> | | | | | +-------------------+ | | | | | | | | | +---+-----+-^-------+ | | | | |forward | +----------------+ | +---------v---------+ | +-------------------+ | | | | | | | | | | oidc-add | load config | | oidc-agent-proxy | | access token request| agent-client | | +---------------------> | <------------------------+ e.g. oidc-token | | <---------------------+ | +------------------------> | | | status | | | | | access token | | +------------+---+ | +--------+-^--------+ | +-------------------+ | | | | | | | +-------------------------+ read| read/write | | | | +----------------+ | | | +------v--+ status| |gen config | | | | | +----->encrypted| | | | account | +------v-+------+ | config | read/write| | | <-----------+ oidc-gen | | | | | | | | | | | | | +---------+ | | +---------------+ oidc-agent-4.2.6/src/oidc-agent-service/0000755000175000017500000000000014167074355017356 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-agent-service/oidc-agent-service0000755000175000017500000000535714167074355022766 0ustar marcusmarcus#!/bin/bash OIDC_AGENT=/usr/bin/oidc-agent TMP="${TMPDIR:-/tmp}" AGENTSERVICEDIR=${TMP}/oidc-agent-service/${UID} PID_FILE=$AGENTSERVICEDIR/oidc-agent.pid SOCK=$AGENTSERVICEDIR/oidc-agent.sock JQ=jq CAT=/bin/cat ECHO=/bin/echo LN=/bin/ln RM=/bin/rm MKDIR=/bin/mkdir REALPATH=realpath OIDC_INCLUDE function stop() { if [ -f "$PID_FILE" ]; then OIDCD_PID=$($CAT $PID_FILE) OIDC_SOCK=$($REALPATH $SOCK) $OIDC_AGENT --kill $ECHO "unset OIDCD_PID_FILE;" $RM -rf $AGENTSERVICEDIR fi } function echo_vars() { $ECHO "OIDC_SOCK=$SOCK; export OIDC_SOCK;" $ECHO "OIDCD_PID=$OIDCD_PID; export OIDCD_PID;" $ECHO "OIDCD_PID_FILE=$PID_FILE; export OIDCD_PID_FILE;" $ECHO "$CAT $PID_FILE" } function start() { json=$($OIDC_AGENT $OIDC_AGENT_OPTS --json) OIDCD_PID=$($ECHO "$json" | $JQ -r ".dpid") OIDC_SOCK=$($ECHO "$json" | $JQ -r ".socket") $MKDIR -p $AGENTSERVICEDIR $ECHO $OIDCD_PID > $PID_FILE $LN -sf $OIDC_SOCK $SOCK echo_vars } function use() { if [ -f "${PID_FILE}" ] && kill -0 $($CAT $PID_FILE) 2>/dev/null; then echo_vars else start fi } function help() { ( echo "oidc-agent-service -- Easily restart oidc-agent" echo "Usage: oidc-agent-service use | start | restart | restart-s | stop | kill" echo echo " Commands:" echo " use Starts the agent, if agent is already running, reuses that agent" echo " start Starts the agent, fails if agent is already running" echo " restart Restarts the agent" echo " restart-s Restarts the agent, but does not print any output" echo " stop Stops the agent" echo " kill Stops the agent" )>&2 } for arg in $@; do if [[ "$arg" == "-h" || "$arg" == "--help" ]]; then help exit fi done if [[ "$1" == "kill" || "$1" == "stop" ]]; then stop exit fi if [[ "$1" == "start-from-x" ]]; then if [ "x${START_AGENT_WITH_XSESSION}" == "xTrue" ]; then use fi exit fi if [[ "$1" == "use" ]]; then use exit fi if [[ "$1" == "start" ]]; then if [ -f "${PID_FILE}" ] && kill -0 $($CAT $PID_FILE) 2>/dev/null; then echo "$ECHO 'It seems like oidc-agent-service already started an oidc-agent for you.' >&2; $ECHO 'Run \"oidc-agent-service stop\" to stop that agent or' >&2; $ECHO 'run \"oidc-agent-service restart\" to restart the agent.' >&2" exit 1 fi start exit fi if [[ "$1" == "restart" ]]; then if [ "x${OIDC_AGENT_RESTART_WITH_SAME_OPTS}" == "xTrue" ]; then STATUS=$($OIDC_AGENT --status --json) OIDC_AGENT_OPTS=$($ECHO "$STATUS" | $JQ -r ".command_line_options") fi stop start exit fi if [[ "$1" == "restart-s" ]]; then stop >/dev/null start >/dev/null exit fi echo "$ECHO \"Usage: $0 use | start | restart | restart-s | stop | kill\"" exit 1 oidc-agent-4.2.6/src/oidc-agent-service/options0000644000175000017500000000203014120404223020743 0ustar marcusmarcus# This file is sourced by including it in oidc-agent-service. OIDC_AGENT_OPTS= START_AGENT_WITH_XSESSION="True" OIDC_AGENT_RESTART_WITH_SAME_OPTS="True" OPTIONS_FILENAME="oidc-agent-service.options" GLOBAL_SERVICE_OPTIONS_FILE=/etc/oidc-agent/${OPTIONS_FILENAME} AGENT_DIR= # Find oidc-agent dir if [ ! -z ${OIDC_CONFIG_DIR+x} ]; then if [ -d "${OIDC_CONFIG_DIR}" ]; then AGENT_DIR="${OIDC_CONFIG_DIR}" fi fi if [ -z "${AGENT_DIR}" ]; then if [ -d "${HOME}/.config" ]; then AGENT_DIR="${HOME}/.config/oidc-agent" fi fi if [ -z "${AGENT_DIR}" ]; then AGENT_DIR="${HOME}/.oidc-agent" fi USER_SERVICE_OPTIONS_FILE="${AGENT_DIR}/${OPTIONS_FILENAME}" # If there is a global config file, source it. # It may overwrite START_AGENT_WITH_XSESSION and OIDC_AGENT_OPTS if [ -f "${GLOBAL_SERVICE_OPTIONS_FILE}" ]; then . ${GLOBAL_SERVICE_OPTIONS_FILE} fi # If there is a user config file, source it. # It may overwrite any value from the global file if [ -f "${USER_SERVICE_OPTIONS_FILE}" ]; then . ${USER_SERVICE_OPTIONS_FILE} fi oidc-agent-4.2.6/src/h2m/0000755000175000017500000000000014120404223014350 5ustar marcusmarcusoidc-agent-4.2.6/src/h2m/oidc-add.h2m0000644000175000017500000000200414120404223016420 0ustar marcusmarcus[NAME] oidc-add \- adds account configurations to oidc-agent [FILES] ~/.config/oidc-agent/ or ~/.oidc-agent/ .RS oidc-add reads account and client configurations in this directory. .RE .PP [EXAMPLES] .PP .nf oidc-add example .fi .RS Adds the 'example' account configuration to oidc-agent. .RE .PP .nf oidc-add example -r .fi .RS Removes the 'example' account configuration from oidc-agent. .RE .PP .nf oidc-add example -t 300 .fi .RS Adds the 'example' account configuration to oidc-agent. It will be automatically removed after 5 minutes (300s). .RE .PP .nf oidc-add -R .fi .RS Removes all loaded account configurations from oidc-agent. .RE .PP .RE .PP .nf oidc-add -x .fi .RS Locks the agent. Use \fB-X\fR to unlock. .RE .PP [SEE ALSO] oidc-agent(1), oidc-gen(1), oidc-token(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-add oidc-agent-4.2.6/src/h2m/oidc-token.h2m0000644000175000017500000000242314120404223017015 0ustar marcusmarcus[NAME] oidc-token \- gets OIDC access token from oidc-agent [FILES] oidc-token does not read or write any files. [EXAMPLES] .PP .nf oidc-token example .fi .RS Gets an access token for the 'example' account configuration. .RE .PP .nf oidc-token example -t 60 .fi .RS Gets an access token for the 'example' account configuration which will be valid for at least 60 seconds. .RE .PP .nf oidc-token example -i .fi .RS Gets the issuer url associated to the requested access token. .RE .PP .nf oidc-token example -a .fi .RS Gets an access token, the associated issuer url, and the expiration date of the token. One information per line. .RE .PP .nf eval `oidc-token example -c` .fi .RS Sets environment variables with the access token, the associated issuer url, and the expiration date of the token. .RE .PP .nf oidc-token example --scope=openid --scope=profile .fi .RS Gets an access token for the 'example' account configuration which will be only valid for the 'openid' and 'profile' scope. .RE .PP [SEE ALSO] oidc-agent(1), oidc-add(1), oidc-gen(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-token oidc-agent-4.2.6/src/h2m/oidc-keychain.h2m0000644000175000017500000000201114120404223017461 0ustar marcusmarcus[NAME] oidc-keychain \- re-uses oidc-agent across logins [FILES] ~/.config/oidc-agent/.keychain or ~/.oidc-agent/.keychain .RS oidc-keychain stores the configuration of a running oidc-agent in one of these files. .RE .PP [EXAMPLES] .PP .nf oidc-keychain .fi .RS Starts oidc-agent if not already started and prints the commands needed for setting the required environment variables. .RE .PP .nf eval `oidc-keychain --accounts myaccount` .fi .RS Starts oidc-agent if not already started, loads the 'myaccount' account if not already loaded, and sets the required environment variables (only for this shell). .RE .PP .nf eval `oidc-keychain -k` .fi .RS Kills a running oidc-agent and unsets the environment variables from this shell. .RE .PP [SEE ALSO] oidc-agent(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-agent/user/oidc-keychain oidc-agent-4.2.6/src/h2m/oidc-agent.h2m0000644000175000017500000000162214120404223016773 0ustar marcusmarcus[NAME] oidc-agent \- OIDC token agent [FILES] $TMPDIR/oidc-XXXXXX/oidc-agent. .RS UNIX-domain sockets used to contain the connection to the agent. .RE .PP [EXAMPLES] .PP .nf oidc-agent .fi .RS Starts oidc-agent and prints the commands needed for setting the required environment variables. .RE .PP .nf eval `oidc-agent` .fi .RS Starts oidc-agent and sets the required environment variables (only for this shell). .RE .PP .nf oidc-agent > ~/tmp/oidc-agent.env .fi .RS Starts oidc-agent and exports the needed shell commands to ~/tmp/oidc-agent.env Can be used to persist the agent. .RE .PP [SEE ALSO] oidc-gen(1), oidc-add(1), oidc-token(1), oidc-keychain(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-agent oidc-agent-4.2.6/src/h2m/oidc-prompt.h2m0000644000175000017500000000054414120404223017220 0ustar marcusmarcus[NAME] oidc-prompt \- interface to create user dialogs [SEE ALSO] oidc-agent(1), oidc-gen(1), oidc-add(1), oidc-token(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/ oidc-agent-4.2.6/src/h2m/oidc-gen.h2m0000644000175000017500000000345114120404223016450 0ustar marcusmarcus[NAME] oidc-gen \- generates account configurations for oidc-agent [FILES] ~/.config/oidc-agent or ~/.oidc-agent .RS oidc-gen reads and writes account and client configurations in this directory. .RE .PP /etc/oidc-agent/issuer.config .RS This file is used by oidc-gen to give a list of possible issuer urls. The user should not edit this file. It might be overwritten when updating oidc-agent. To specify additional issuer urls the user can use the issuer.config located in the oidc-directory. .RE .PP .PP ~/.config/oidc-agent/issuer.config or ~/.oidc-agent/issuer.config .RS This file (combined with /etc/oidc-agent/issuer.config) is used by oidc-gen to give a list of possible issuer urls. The user can add additional issuer urls to this list (one url per line). .RE .PP [EXAMPLES] .PP .nf oidc-gen example .fi .RS Generates new account configuration with name 'example' using dynamic client registration. .RE .PP .nf oidc-gen example -m .fi .RS Generates new account configuration with name 'example' NOT using dynamic client registration. .RE .PP .nf oidc-gen example -f ~/.config/oidc-agent/example.com_2018-01-31_f34a.clientconfig .fi .RS Generates new account configuration using the client configuration stored in ~/.config/oidc-agent/example.com_2018-01-31_f34a.clientconfig .RE .PP .nf oidc-gen example --at=token1234 .fi .RS Generates new account configuration with name 'example' using dynamic client registration. The access token 'token1234' is used for authorization at the (protected) registration endpoint. .RE .PP [SEE ALSO] oidc-agent(1), oidc-add(1), oidc-token(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-gen oidc-agent-4.2.6/src/h2m/oidc-agent-service.h2m0000644000175000017500000000254614120404223020437 0ustar marcusmarcus[NAME] oidc-agent-service \- easily restart oidc-agent throughout a session [FILES] /tmp/oidc-agent-service/$UID/oidc-agent.pid .RS oidc-agent-service creates a symlink from this file to tha active agent's socket. The symlink is updated when the agent is restarted. .RE .PP /etc/oidc-agent/oidc-agent-service.options .RS This file contains options to configure the behavior of oidc-agent-service, e.g. it can be used to configure the command line options with which the agent should be started. It is also used to disable Xsession integration. .RE .PP $OIDCAGENTDIR/oidc-agent-service.options .RS User-space version of \fB/etc/oidc-agent/oidc-agent-service.options\fR. Any value specified in this file overwrite the value defined in the global version. .RE .PP [EXAMPLES] .PP .nf eval `oidc-agent-service use` .fi .RS Starts the agent .RE .PP .nf eval `oidc-agent-service stop` .fi .RS Stops the agent .RE .PP .nf eval `oidc-agent-service restart` .fi .RS Restarts the agent .RE .PP .nf oidc-agent-service restart-s .fi .RS Restarts the agent without printing any information (silent mode). .RE .PP [SEE ALSO] oidc-agent(1) .PP Low-traffic mailing list with updates such as critical security incidents and new releases: https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user .PP Full documentation can be found at https://indigo-dc.gitbooks.io/oidc-agent/user/oidc-agent-service oidc-agent-4.2.6/src/privileges/0000755000175000017500000000000014167074355016057 5ustar marcusmarcusoidc-agent-4.2.6/src/privileges/getBadSysCall.sh0000755000175000017500000000026414120404223021055 0ustar marcusmarcusausyscall `sudo cat /var/log/audit/audit.log | grep sig=31 | grep \ comm=\"$1\" | awk '{ for(i=1; i <= NF; i++) {print $i } }' | grep \ syscall | awk -F'=' '{print $2}' | tail -1` oidc-agent-4.2.6/src/privileges/privileges.h0000644000175000017500000000350614120404223020361 0ustar marcusmarcus#ifndef OIDCAGENT_PRIVILEGES_H #define OIDCAGENT_PRIVILEGES_H #include #ifndef ALLOW_SYSCALL #define ALLOW_SYSCALL(ctx, call) \ do { \ int rc = seccomp_rule_add((ctx), SCMP_ACT_ALLOW, \ seccomp_syscall_resolve_name((call)), 0); \ checkRc(rc, "seccomp_rule_add", (call)); \ } while (0) #endif // ALLOW_SYSCALL #ifndef ALLOW_SYSCALL_PARAM #define ALLOW_SYSCALL_PARAM(ctx, call, param) \ do { \ int rc = \ seccomp_rule_add((ctx), SCMP_ACT_ALLOW, SCMP_SYS(call), 1, (param)); \ checkRc(rc, "seccomp_rule_add", (call)); \ } while (0) #endif // ALLOW_SYSCALL_PARAM void checkRc(int rc, const char* str, const char* syscall); void addSocketSysCalls(scmp_filter_ctx ctx); void addLoggingSysCalls(scmp_filter_ctx ctx); void addPromptingSysCalls(scmp_filter_ctx ctx); void addMemorySysCalls(scmp_filter_ctx ctx); void addGeneralSysCalls(scmp_filter_ctx ctx); void addTimeSysCalls(scmp_filter_ctx ctx); void addFileWriteSysCalls(scmp_filter_ctx ctx); void addFileReadSysCalls(scmp_filter_ctx ctx); void addPrintingSysCalls(scmp_filter_ctx ctx); void addCryptSysCalls(scmp_filter_ctx ctx); void addDaemonSysCalls(scmp_filter_ctx ctx); void addAgentIpcSysCalls(scmp_filter_ctx ctx); void addHttpSysCalls(scmp_filter_ctx ctx); void addHttpServerSysCalls(scmp_filter_ctx ctx); void addKillSysCall(scmp_filter_ctx ctx); void addSignalHandlingSysCalls(scmp_filter_ctx ctx); void addSleepSysCalls(scmp_filter_ctx ctx); #endif // OIDCAGENT_PRIVILEGES_H oidc-agent-4.2.6/src/privileges/add_privileges.c0000644000175000017500000000121014167074355021176 0ustar marcusmarcus#include "add_privileges.h" #include #include #include #include "privileges.h" void initOidcAddPrivileges(struct arguments* arguments) { int rc = -1; scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { perror("seccomp_init"); exit(EXIT_FAILURE); } addGeneralSysCalls(ctx); addPromptingSysCalls(ctx); addLoggingSysCalls(ctx); addSocketSysCalls(ctx); if (!(arguments->lock || arguments->unlock)) { addFileReadSysCalls(ctx); } rc = seccomp_load(ctx); seccomp_release(ctx); checkRc(rc, "seccomp_load", ""); // access("STARTOFPROGRAM", F_OK); } oidc-agent-4.2.6/src/privileges/token_privileges.c0000644000175000017500000000107114167074355021573 0ustar marcusmarcus#include "token_privileges.h" #include #include #include #include "privileges.h" void initOidcTokenPrivileges( __attribute__((unused)) struct arguments* arguments) { int rc = -1; scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { perror("seccomp_init"); exit(EXIT_FAILURE); } addGeneralSysCalls(ctx); addLoggingSysCalls(ctx); addSocketSysCalls(ctx); rc = seccomp_load(ctx); seccomp_release(ctx); checkRc(rc, "seccomp_load", ""); // access("STARTOFPROGRAM", F_OK); } oidc-agent-4.2.6/src/privileges/agent_privileges.c0000644000175000017500000000137314167074355021556 0ustar marcusmarcus#include "agent_privileges.h" #include #include #include #include "privileges.h" void initOidcAgentPrivileges(struct arguments* arguments) { int rc = -1; scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { perror("seccomp_init"); exit(EXIT_FAILURE); } addGeneralSysCalls(ctx); addLoggingSysCalls(ctx); // addPrintingSysCalls(ctx); if (arguments->kill_flag) { addKillSysCall(ctx); } addSocketSysCalls(ctx); addAgentIpcSysCalls(ctx); addCryptSysCalls(ctx); addDaemonSysCalls(ctx); addHttpSysCalls(ctx); addHttpServerSysCalls(ctx); rc = seccomp_load(ctx); seccomp_release(ctx); checkRc(rc, "seccomp_load", ""); // access("STARTOFPROGRAM", F_OK); } oidc-agent-4.2.6/src/privileges/add_privileges.h0000644000175000017500000000026114120404223021164 0ustar marcusmarcus#ifndef PRIVILEGES_ADD_H #define PRIVILEGES_ADD_H #include "oidc-add/oidc-add_options.h" void initOidcAddPrivileges(struct arguments* arguments); #endif // PRIVILEGES_ADD_H oidc-agent-4.2.6/src/privileges/agent_privileges.h0000644000175000017500000000027514120404223021537 0ustar marcusmarcus#ifndef AGENT_PRIVILEGES_H #define AGENT_PRIVILEGES_H #include "oidc-agent/oidc-agent_options.h" void initOidcAgentPrivileges(struct arguments* arguments); #endif // AGENT_PRIVILEGES_H oidc-agent-4.2.6/src/privileges/privileges.c0000644000175000017500000001026214167074355020375 0ustar marcusmarcus#include "privileges.h" #include #include #include #include #include "defines/settings.h" #include "utils/file_io/file_io.h" #include "utils/listUtils.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/printer.h" #include "utils/string/stringUtils.h" #include "wrapper/list.h" void checkRc(int rc, const char* str, const char* syscall) { if (rc < 0) { perror(str); printError("On syscall: %s\n", syscall); exit(-rc); } } void addSysCallsFromConfigFile(scmp_filter_ctx ctx, const char* path) { list_t* lines = getLinesFromFile(path); if (lines == NULL) { oidc_errno = OIDC_ENOPRIVCONF; oidc_perror(); exit(EXIT_FAILURE); } list_node_t* node; list_iterator_t* it = list_iterator_new(lines, LIST_HEAD); while ((node = list_iterator_next(it))) { char* syscall = node->val; ALLOW_SYSCALL(ctx, strtok(syscall, " ")); } list_iterator_destroy(it); secFreeList(lines); } void addSocketSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "socket"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addFileReadSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "read"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addCryptSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "crypt"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addAgentIpcSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "agentIpc"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addDaemonSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "daemon"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addKillSysCall(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "kill"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addHttpSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "http"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addHttpServerSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "httpserver"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addFileWriteSysCalls(scmp_filter_ctx ctx) { addFileReadSysCalls(ctx); char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "write"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addTimeSysCalls(scmp_filter_ctx ctx) { // ALLOW_SYSCALL_PARAM(ctx, open, // SCMP_A0(SCMP_CMP_EQ, (scmp_datum_t) // "/etc/localtime")); char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "time"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addLoggingSysCalls(scmp_filter_ctx ctx) { addTimeSysCalls(ctx); char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "logging"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addPromptingSysCalls(scmp_filter_ctx ctx) { addPrintingSysCalls(ctx); char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "prompt"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addPrintingSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "print"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addMemorySysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "memory"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addGeneralSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "general"); addSysCallsFromConfigFile(ctx, path); secFree(path); addMemorySysCalls(ctx); addPrintingSysCalls(ctx); } void addSignalHandlingSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "signal"); addSysCallsFromConfigFile(ctx, path); secFree(path); } void addSleepSysCalls(scmp_filter_ctx ctx) { char* path = oidc_sprintf("%s/%s.priv", PRIVILEGES_PATH, "sleep"); addSysCallsFromConfigFile(ctx, path); secFree(path); } oidc-agent-4.2.6/src/privileges/token_privileges.h0000644000175000017500000000027514120404223021561 0ustar marcusmarcus#ifndef PRIVILEGES_TOKEN_H #define PRIVILEGES_TOKEN_H #include "oidc-token/oidc-token_options.h" void initOidcTokenPrivileges(struct arguments* arguments); #endif // PRIVILEGES_TOKEN_H oidc-agent-4.2.6/src/privileges/gen_privileges.c0000644000175000017500000000141714167074355021230 0ustar marcusmarcus#include "gen_privileges.h" #include #include #include #include "privileges.h" void initOidcGenPrivileges( __attribute__((unused)) struct arguments* arguments) { int rc = -1; scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); if (ctx == NULL) { perror("seccomp_init"); exit(EXIT_FAILURE); } addGeneralSysCalls(ctx); addLoggingSysCalls(ctx); addPromptingSysCalls(ctx); addSocketSysCalls(ctx); addFileWriteSysCalls(ctx); addCryptSysCalls(ctx); addSignalHandlingSysCalls( ctx); // needed if auth code flow is executed -> not needed if flow!=code addSleepSysCalls(ctx); rc = seccomp_load(ctx); seccomp_release(ctx); checkRc(rc, "seccomp_load", ""); // access("STARTOFPROGRAM", F_OK); } oidc-agent-4.2.6/src/privileges/gen_privileges.h0000644000175000017500000000026114120404223021205 0ustar marcusmarcus#ifndef GEN_PRIVILEGES_H #define GEN_PRIVILEGES_H #include "oidc-gen/oidc-gen_options.h" void initOidcGenPrivileges(struct arguments* arguments); #endif // GEN_PRIVILEGES_H oidc-agent-4.2.6/src/defines/0000755000175000017500000000000014167074355015323 5ustar marcusmarcusoidc-agent-4.2.6/src/defines/ipc_values.h0000644000175000017500000003000614167074355017625 0ustar marcusmarcus#ifndef IPC_VALUES_H #define IPC_VALUES_H #include "oidc_values.h" // IPC KEYS #define IPC_KEY_REQUEST "request" #define IPC_KEY_STATUS "status" #define IPC_KEY_CONFIG "config" #define IPC_KEY_CLIENT "client" #define IPC_KEY_INFO "info" #define IPC_KEY_URI "uri" #define IPC_KEY_SHORTNAME "account" #define IPC_KEY_PASSWORD "password" #define IPC_KEY_AUTHORIZATION "authorization" #define IPC_KEY_DEVICE "oidc_device" #define IPC_KEY_LIFETIME "lifetime" #define IPC_KEY_FLOW "flow" #define IPC_KEY_APPLICATIONHINT "application_hint" #define IPC_KEY_MINVALID "min_valid_period" #define IPC_KEY_PASSWORDENTRY "pw_entry" #define IPC_KEY_CONFIRM "confirm" #define IPC_KEY_ALWAYSALLOWID "always_allow_id" #define IPC_KEY_REDIRECTEDURI "redirect_uri" #define IPC_KEY_FROMGEN "from_gen" #define IPC_KEY_USECUSTOMSCHEMEURL "no_webserver" #define IPC_KEY_NOSCHEME "no_scheme" #define IPC_KEY_ISSUERURL "issuer" #define IPC_KEY_MAXSCOPES "max_scopes" #define IPC_KEY_CERTPATH "cert_path" #define IPC_KEY_AUDIENCE "audience" #define IPC_KEY_FILENAME "filename" #define IPC_KEY_DATA "data" #define IPC_KEY_ONLYAT "only_at" // STATUS #define STATUS_SUCCESS "success" #define STATUS_FAILURE "failure" #define STATUS_ACCEPTED "accepted" #define STATUS_NOTFOUND "NotFound" #define STATUS_FOUNDBUTDONE "FoundButReceived" // REQUEST VALUES #define REQUEST_VALUE_ADD "add" #define REQUEST_VALUE_GEN "gen" #define REQUEST_VALUE_REGISTER "register" #define REQUEST_VALUE_REMOVE "remove" #define REQUEST_VALUE_REMOVEALL "remove_all" #define REQUEST_VALUE_DELETE "delete" #define REQUEST_VALUE_CODEEXCHANGE "code_exchange" #define REQUEST_VALUE_STATELOOKUP "state_lookup" #define REQUEST_VALUE_DEVICELOOKUP "device" #define REQUEST_VALUE_ACCESSTOKEN "access_token" #define REQUEST_VALUE_TERMHTTP "term_http_server" #define REQUEST_VALUE_LOCK "lock" #define REQUEST_VALUE_UNLOCK "unlock" #define REQUEST_VALUE_CHECK "check" #define REQUEST_VALUE_STATUS "status" #define REQUEST_VALUE_STATUS_JSON "status_json" #define REQUEST_VALUE_SCOPES "scopes" #define REQUEST_VALUE_LOADEDACCOUNTS "loaded_accounts" #define REQUEST_VALUE_IDTOKEN "id_token" #define REQUEST_VALUE_FILEWRITE "file_write" #define REQUEST_VALUE_FILEREAD "file_read" #define REQUEST_VALUE_FILEREMOVE "file_remove" #define REQUEST_VALUE_DELETECLIENT "delete_client" #define REQUEST_VALUE_REAUTHENTICATE "reauthenticate" // RESPONSE TEMPLATES #define RESPONSE_SUCCESS "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\"}" #define RESPONSE_SUCCESS_CLIENT_MAXSCOPES \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_CLIENT \ "\":%s,\"" IPC_KEY_MAXSCOPES "\":\"%s\"}" #define RESPONSE_SUCCESS_INFO \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_INFO "\":\"%" \ "s\"}" #define RESPONSE_SUCCESS_INFO_OBJECT \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_INFO "\":%s}" #define RESPONSE_ERROR_CLIENT \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" OIDC_KEY_ERROR \ "\":\"%s\",\"" IPC_KEY_CLIENT "\":%s}" #define RESPONSE_ERROR_CLIENT_INFO \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" OIDC_KEY_ERROR \ "\":\"%s\",\"" IPC_KEY_CLIENT "\":%s,\"" IPC_KEY_INFO "\":\"%s\"}" #define RESPONSE_STATUS_SUCCESS \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\"}" #define RESPONSE_STATUS_CONFIG \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" IPC_KEY_CONFIG "\":%s}" #define RESPONSE_STATUS_ACCESS \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" OIDC_KEY_ACCESSTOKEN \ "\":\"%s\",\"" OIDC_KEY_ISSUER "\":\"%s\"," \ "\"" AGENT_KEY_EXPIRESAT "\":%lu}" #define RESPONSE_STATUS_IDTOKEN \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" OIDC_KEY_IDTOKEN \ "\":\"%s\",\"" OIDC_KEY_ISSUER "\":\"%s\"}" #define RESPONSE_STATUS_REGISTER \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"response\":%s}" #define RESPONSE_STATUS_CODEURI \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" IPC_KEY_URI \ "\":\"%s\",\"" OIDC_KEY_STATE "\":\"%s\"}" #define RESPONSE_STATUS_CODEURI_INFO \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" IPC_KEY_URI \ "\":\"%s\",\"" OIDC_KEY_STATE "\":\"%s\",\"" IPC_KEY_INFO "\":\"%" \ "s\"}" #define RESPONSE_SUCCESS_LOADEDACCOUNTS \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_INFO "\":%s}" #define RESPONSE_ERROR \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" OIDC_KEY_ERROR \ "\":\"%s\"}" #define RESPONSE_ERROR_INFO \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" OIDC_KEY_ERROR \ "\":\"%s\",\"" IPC_KEY_INFO "\":\"%" \ "s\"}" #define RESPONSE_BADREQUEST \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" OIDC_KEY_ERROR \ "\":\"Bad Request: %s\"}" #define RESPONSE_STATUS_INFO \ "{\"" IPC_KEY_STATUS "\":\"%s\",\"" IPC_KEY_INFO "\":\"%s\"}" #define RESPONSE_ACCEPTED_DEVICE \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_ACCEPTED "\",\"" IPC_KEY_DEVICE "\":%s}" #define RESPONSE_SUCCESS_FILE \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_DATA "\":\"%" \ "s\"}" // REQUEST TEMPLATES #define REQUEST "{\"" IPC_KEY_REQUEST "\":\"%s\",%s}" #define REQUEST_STATUS "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_STATUS "\"}" #define REQUEST_STATUS_JSON \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_STATUS_JSON "\"}" #define REQUEST_ADD_LIFETIME \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_ADD "\",\"" IPC_KEY_CONFIG \ "\":%s,\"" IPC_KEY_LIFETIME "\":%lu,\"" IPC_KEY_PASSWORDENTRY \ "\":%s,\"" IPC_KEY_CONFIRM "\":%d,\"" IPC_KEY_ALWAYSALLOWID "\":%d}" #define REQUEST_ADD \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_ADD "\",\"" IPC_KEY_CONFIG \ "\":%s,\"" IPC_KEY_PASSWORDENTRY "\":%s,\"" IPC_KEY_CONFIRM \ "\":%d,\"" IPC_KEY_ALWAYSALLOWID "\":%d}" #define REQUEST_REMOVE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_REMOVE "\",\"" IPC_KEY_SHORTNAME \ "\":\"%s\"}" #define REQUEST_REMOVEALL \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_REMOVEALL "\"}" #define REQUEST_DELETE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_DELETE "\",\"" IPC_KEY_CONFIG \ "\":%s}" #define REQUEST_DELETECLIENT \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_DELETECLIENT \ "\",\"" OIDC_KEY_REGISTRATION_CLIENT_URI \ "\":\"%s\",\"" OIDC_KEY_REGISTRATION_ACCESS_TOKEN \ "\":\"%s\",\"" AGENT_KEY_CERTPATH "\":\"%s\"}" #define REQUEST_GEN \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_GEN "\",\"" IPC_KEY_CONFIG \ "\":%s,\"" IPC_KEY_FLOW "\":%s,\"" IPC_KEY_PASSWORDENTRY \ "\":%s,\"" IPC_KEY_USECUSTOMSCHEMEURL "\":%d,\"" IPC_KEY_NOSCHEME \ "\":%d,\"" IPC_KEY_ONLYAT "\":%d}" #define REQUEST_REGISTER \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_REGISTER "\",\"" IPC_KEY_CONFIG \ "\":%s,\"" IPC_KEY_FLOW "\":%s}" #define REQUEST_REGISTER_AUTH \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_REGISTER "\",\"" IPC_KEY_CONFIG \ "\":%s,\"" IPC_KEY_FLOW "\":%s,\"" IPC_KEY_AUTHORIZATION "\":\"%s\"}" #define REQUEST_CODEEXCHANGE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_CODEEXCHANGE \ "\",\"" IPC_KEY_REDIRECTEDURI "\":\"%s\"}" #define REQUEST_CODEEXCHANGEGEN \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_CODEEXCHANGE \ "\",\"" IPC_KEY_REDIRECTEDURI "\":\"%s\",\"" IPC_KEY_FROMGEN "\":1}" #define REQUEST_STATELOOKUP \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_STATELOOKUP \ "\",\"" OIDC_KEY_STATE "\":\"%s\"}" #define REQUEST_DEVICE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_DEVICELOOKUP \ "\",\"" IPC_KEY_DEVICE "\":%s,\"" IPC_KEY_ONLYAT "\":%d}" #define REQUEST_TERMHTTP \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_TERMHTTP "\",\"" OIDC_KEY_STATE \ "\":\"%s\"}" #define REQUEST_LOCK \ "{\"" IPC_KEY_REQUEST "\":\"%s\",\"" IPC_KEY_PASSWORD "\":\"%s\"}" #define REQUEST_CHECK "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_CHECK "\"}" #define REQUEST_LOADEDACCOUNTS \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_LOADEDACCOUNTS "\"}" #define REQUEST_SCOPES \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_SCOPES "\",\"" IPC_KEY_ISSUERURL \ "\":\"%s\",\"" IPC_KEY_CERTPATH "\":\"%s\"}" #define REQUEST_IDTOKEN_ISSUER \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_IDTOKEN \ "\",\"" IPC_KEY_ISSUERURL "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT \ "\":\"%s\"}" #define REQUEST_IDTOKEN_ACCOUNT \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_IDTOKEN \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT \ "\":\"%s\"}" #define REQUEST_FILEWRITE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_FILEWRITE \ "\",\"" IPC_KEY_FILENAME "\":\"%s\",\"" IPC_KEY_DATA "\":\"%s\"}" #define REQUEST_FILEREAD \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_FILEREAD \ "\",\"" IPC_KEY_FILENAME "\":\"%s\"}" #define REQUEST_FILEREMOVE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_FILEREMOVE \ "\",\"" IPC_KEY_FILENAME "\":\"%s\"}" #define REQUEST_REAUTHENTICATE \ "{\"" IPC_KEY_REQUEST "\":\"" REQUEST_VALUE_REAUTHENTICATE \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\"}" #define ACCOUNT_NOT_LOADED "account not loaded" // internal communication (between oidcp and oidcd) #define INT_REQUEST_VALUE_UPD_REFRESH "update_refresh" #define INT_REQUEST_VALUE_AUTOLOAD "autoload" #define INT_REQUEST_VALUE_CONFIRM "confirm" #define INT_REQUEST_VALUE_CONFIRMIDTOKEN "confirm_id" #define INT_REQUEST_VALUE_QUERY_ACCDEFAULT "query_account_default" #define INT_IPC_KEY_OIDCERRNO "oidc_errno" #define INT_REQUEST_UPD_REFRESH \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_UPD_REFRESH \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" OIDC_KEY_REFRESHTOKEN "\":\"%s\"}" #define INT_REQUEST_AUTOLOAD \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_AUTOLOAD \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT \ "\":\"%s\"}" #define INT_REQUEST_AUTOLOAD_WITH_ISSUER \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_AUTOLOAD \ "\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_ISSUERURL \ "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT "\":\"%s\"}" #define INT_REQUEST_CONFIRM \ "{\"" IPC_KEY_REQUEST "\":\"%s\",\"" IPC_KEY_SHORTNAME \ "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT "\":\"%s\"}" #define INT_REQUEST_CONFIRM_WITH_ISSUER \ "{\"" IPC_KEY_REQUEST "\":\"%s\",\"" IPC_KEY_ISSUERURL \ "\":\"%s\",\"" IPC_KEY_SHORTNAME "\":\"%s\",\"" IPC_KEY_APPLICATIONHINT \ "\":\"%s\"}" #define INT_REQUEST_QUERY_ACCDEFAULT_ISSUER \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_QUERY_ACCDEFAULT \ "\",\"" IPC_KEY_ISSUERURL "\":\"%s\"}" #define INT_REQUEST_QUERY_ACCDEFAULT \ "{\"" IPC_KEY_REQUEST "\":\"" INT_REQUEST_VALUE_QUERY_ACCDEFAULT "\"}" #define INT_RESPONSE_ACCDEFAULT \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_SUCCESS "\",\"" IPC_KEY_SHORTNAME \ "\":\"%s\"}" #define INT_RESPONSE_ERROR \ "{\"" IPC_KEY_STATUS "\":\"" STATUS_FAILURE "\",\"" INT_IPC_KEY_OIDCERRNO \ "\":%d}" #endif // IPC_VALUES_H oidc-agent-4.2.6/src/defines/agent_values.h0000644000175000017500000000110614120404223020123 0ustar marcusmarcus#ifndef AGENT_MAGIC_VALUES_H #define AGENT_MAGIC_VALUES_H #define AGENT_SCOPE_ALL "max" #define AGENT_KEY_ISSUERURL "issuer_url" #define AGENT_KEY_DAESETBYUSER "daeSetByUser" #define AGENT_KEY_SHORTNAME "name" #define AGENT_KEY_CERTPATH "cert_path" #define AGENT_KEY_EXPIRESAT "expires_at" // INTERNAL / CLI FLOW VALUES #define FLOW_VALUE_CODE "code" #define FLOW_VALUE_PASSWORD "password" #define FLOW_VALUE_DEVICE "device" #define FLOW_VALUE_REFRESH "refresh" #define AGENT_CUSTOM_SCHEME "edu.kit.data.oidc-agent:/" #define FORCE_NEW_TOKEN -1 #endif // AGENT_MAGIC_VALUES_H oidc-agent-4.2.6/src/defines/version.h0000644000175000017500000000112614120404223017135 0ustar marcusmarcus#ifndef OIDC_VERSION_H #define OIDC_VERSION_H #ifndef VERSION #define VERSION #include "VERSION" #endif // VERSION #define BUG_ADDRESS \ "\nSubscribe to our " \ "mailing list to receive important updates about oidc-agent: " \ "" #define AGENT_VERSION "oidc-agent " VERSION #define GEN_VERSION "oidc-gen " VERSION #define ADD_VERSION "oidc-add " VERSION #define TOKEN_VERSION "oidc-token " VERSION #endif // OIDC_VERSION_H oidc-agent-4.2.6/src/defines/settings.c0000644000175000017500000000045114120404223017303 0ustar marcusmarcus#include "settings.h" char* possibleCertFiles[4] = { "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL "/etc/ssl/ca-bundle.pem", // OpenSUSE "/etc/pki/tls/cacert.pem" // OpenELEC }; oidc-agent-4.2.6/src/defines/oidc_values.h0000644000175000017500000000773314120404223017757 0ustar marcusmarcus#ifndef OIDC_VALUES_H #define OIDC_VALUES_H // PROVIDER METADATA KEYS #define OIDC_KEY_SCOPES_SUPPORTED "scopes_supported" #define OIDC_KEY_GRANT_TYPES_SUPPORTED "grant_types_supported" #define OIDC_KEY_RESPONSE_TYPES_SUPPORTED "response_types_supported" #define OIDC_KEY_CODE_CHALLENGE_METHODS_SUPPORTED \ "code_challenge_methods_supported" // ENDPOINT KEYS #define OIDC_KEY_TOKEN_ENDPOINT "token_endpoint" #define OIDC_KEY_AUTHORIZATION_ENDPOINT "authorization_endpoint" #define OIDC_KEY_REVOCATION_ENDPOINT "revocation_endpoint" #define OIDC_KEY_REGISTRATION_ENDPOINT "registration_endpoint" #define OIDC_KEY_DEVICE_AUTHORIZATION_ENDPOINT "device_authorization_endpoint" #define OIDC_KEY_ISSUER "issuer" // CLIENT KEYS #define OIDC_KEY_REGISTRATION_CLIENT_URI "registration_client_uri" #define OIDC_KEY_REGISTRATION_ACCESS_TOKEN "registration_access_token" // TOKEN RESPONSE KEYS #define OIDC_KEY_EXPIRESIN "expires_in" #define OIDC_KEY_ACCESSTOKEN "access_token" #define OIDC_KEY_REFRESHTOKEN "refresh_token" #define OIDC_KEY_IDTOKEN "id_token" // REQUEST KEYS #define OIDC_KEY_CLIENTID "client_id" #define OIDC_KEY_CLIENTSECRET "client_secret" #define OIDC_KEY_GRANTTYPE "grant_type" #define OIDC_KEY_RESPONSETYPE "response_type" #define OIDC_KEY_SCOPE "scope" #define OIDC_KEY_AUDIENCE "audience" #define GOOGLE_KEY_ACCESSTYPE "access_type" // AUTH CODE FLOW #define OIDC_KEY_REDIRECTURI "redirect_uri" #define OIDC_KEY_CODE "code" #define OIDC_KEY_STATE "state" #define OIDC_KEY_PROMPT "prompt" #define OIDC_KEY_CODEVERIFIER "code_verifier" #define OIDC_KEY_CODECHALLENGE "code_challenge" #define OIDC_KEY_CODECHALLENGE_METHOD "code_challenge_method" // PASSWORD FLOW #define OIDC_KEY_USERNAME "username" #define OIDC_KEY_PASSWORD "password" // REVOCATION #define OIDC_KEY_TOKENTYPE_HINT "token_type_hint" #define OIDC_KEY_TOKEN "token" // CLIENT REGISTRATION #define OIDC_KEY_APPLICATIONTYPE "application_type" #define OIDC_KEY_CLIENTNAME "client_name" #define OIDC_KEY_GRANTTYPES "grant_types" #define OIDC_KEY_RESPONSETYPES "response_types" #define OIDC_KEY_REDIRECTURIS "redirect_uris" // DEVICE FLOW #define OIDC_KEY_DEVICECODE "device_code" #define OIDC_KEY_USERCODE "user_code" #define OIDC_KEY_VERIFICATIONURI "verification_uri" #define GOOGLE_KEY_VERIFICATIONURI "verification_url" #define OIDC_KEY_VERIFICATIONURI_COMPLETE "verification_uri_complete" #define GOOGLE_KEY_VERIFICATIONURI_COMPLETE "verification_url_complete" #define OIDC_KEY_INTERVAL "interval" #define OIDC_SLOW_DOWN "slow_down" #define OIDC_AUTHORIZATION_PENDING "authorization_pending" // OIDC ERROR #define OIDC_KEY_ERROR "error" #define OIDC_KEY_ERROR_DESCRIPTION "error_description" // GRANTTYPES #define OIDC_GRANTTYPE_PASSWORD "password" #define OIDC_GRANTTYPE_REFRESH "refresh_token" #define OIDC_GRANTTYPE_AUTHCODE "authorization_code" #define OIDC_GRANTTYPE_IMPLICIT "implicit" #define OIDC_GRANTTYPE_DEVICE "urn:ietf:params:oauth:grant-type:device_code" #define OIDC_PROVIDER_DEFAULT_GRANTTYPES \ "[\"" OIDC_GRANTTYPE_AUTHCODE "\", \"" OIDC_GRANTTYPE_IMPLICIT "\"]" // RESPONSETYPES #define OIDC_RESPONSETYPE_TOKEN "token" #define OIDC_RESPONSETYPE_CODE "code" // APPLICATIONTYPES #define OIDC_APPLICATIONTYPES_WEB "web" #define OIDC_APPLICATIONTYPES_NATIVE "native" // SCOPES #define OIDC_SCOPE_OPENID "openid" #define OIDC_SCOPE_OFFLINE_ACCESS "offline_access" // TOKENTYPES #define OIDC_TOKENTYPE_REFRESH "refresh_token" // PROVIDER FIXES #define GOOGLE_ISSUER_URL "https://accounts.google.com/" #define GOOGLE_ACCESSTYPE_OFFLINE "offline" #define ELIXIR_ISSUER_URL "https://login.elixir-czech.org/oidc/" #define ELIXIR_SUPPORTED_SCOPES \ "openid offline_access profile email address phone groupNames " \ "forwardedScopedAffiliations bona_fide_status country eduPersonEntitlement" // PROMPT VALUES #define OIDC_PROMPT_CONSENT "consent" // PKCE #define CODE_CHALLENGE_METHOD_PLAIN "plain" #define CODE_CHALLENGE_METHOD_S256 "S256" #define CODE_VERIFIER_LEN 128 // min: 43 max: 128 #endif // OIDC_VALUES_H oidc-agent-4.2.6/src/defines/settings.h0000644000175000017500000000404114167074355017333 0ustar marcusmarcus#ifndef OIDC_SETTINGS_H #define OIDC_SETTINGS_H // env var names /** * the name of the environment variable used to locate the IPC socket */ #define OIDC_SOCK_ENV_NAME "OIDC_SOCK" /** * the name of the environment variable used to locate the remote TCP socket */ #define OIDC_REMOTE_SOCK_ENV_NAME "OIDC_REMOTE_SOCK" /** * the name of the environment variable that holds the agent pid */ #define OIDC_PID_ENV_NAME "OIDCD_PID" /** * the name of the environment variable that might hold the oidcagentdir * location */ #define OIDC_CONFIG_DIR_ENV_NAME "OIDC_CONFIG_DIR" /** * the scope used as default value */ #define DEFAULT_SCOPE "openid profile offline_access" // Default env var names for arguments #define OIDC_REFRESHTOKEN_ENV_NAME "OIDC_REFRESH_TOKEN" #define OIDC_PASSWORD_ENV_NAME "OIDC_ENCRYPTION_PW" // file names /** * the path to the config dir, if not provided via make */ #ifndef CONFIG_PATH #define CONFIG_PATH "/etc" #endif #define ISSUER_CONFIG_FILENAME "issuer.config" #define ETC_ISSUER_CONFIG_FILE CONFIG_PATH "/oidc-agent/" ISSUER_CONFIG_FILENAME #define PRIVILEGES_PATH CONFIG_PATH "/oidc-agent/privileges" #define PUBCLIENTS_FILENAME "pubclients.config" #define ETC_PUBCLIENTS_CONFIG_FILE \ CONFIG_PATH "/oidc-agent/" PUBCLIENTS_FILENAME #define MAX_PASS_TRIES 3 /** * maximum number of polling tries */ #define MAX_POLL 20 /** * the delta between two pollings in seconds */ #define DELTA_POLL 2 // seconds #define HTTP_DEFAULT_PORT 4242 #define HTTP_FALLBACK_PORT 8080 #define CONF_ENDPOINT_SUFFIX ".well-known/openid-configuration" extern char* possibleCertFiles[4]; /** * prefix for tmp-files generated during account generation; * if dynamic client registration is used, the client config is temporarily * saved in a file prefixed with that string */ #define CLIENT_TMP_PREFIX "/tmp/oidc-gen:" #define AGENTDIR_LOCATION_CONFIG "~/.config/oidc-agent/" #define AGENTDIR_LOCATION_DOT "~/.oidc-agent/" #ifdef __linux__ #define URL_OPENER "xdg-open" #elif __APPLE__ #define URL_OPENER "open" #endif #endif // OIDC_SETTINGS_H oidc-agent-4.2.6/src/ipc/0000755000175000017500000000000014167074355014461 5ustar marcusmarcusoidc-agent-4.2.6/src/ipc/pipe.c0000644000175000017500000000445214167074355015567 0ustar marcusmarcus#define _GNU_SOURCE #include "pipe.h" #include #include #include #include "defines/ipc_values.h" #include "ipc/ipc.h" #include "utils/oidc_error.h" void ipc_closePipes(struct ipcPipe p) { close(p.rx); close(p.tx); } struct pipeSet ipc_pipe_init() { int fd1[2]; int fd2[2]; #ifdef __APPLE__ if (pipe(fd1) != 0) { #else if (pipe2(fd1, O_DIRECT) != 0) { #endif oidc_setErrnoError(); return (struct pipeSet){{-1, -1}, {-1, -1}}; } #ifdef __APPLE__ if (pipe(fd2) != 0) { #else if (pipe2(fd2, O_DIRECT) != 0) { #endif oidc_setErrnoError(); return (struct pipeSet){{-1, -1}, {-1, -1}}; } struct ipcPipe pipe1 = {fd1[0], fd1[1]}; struct ipcPipe pipe2 = {fd2[0], fd2[1]}; return (struct pipeSet){pipe1, pipe2}; } struct ipcPipe toServerPipes(struct pipeSet pipes) { struct ipcPipe server; close(pipes.pipe1.tx); server.rx = pipes.pipe1.rx; close(pipes.pipe2.rx); server.tx = pipes.pipe2.tx; return server; } struct ipcPipe toClientPipes(struct pipeSet pipes) { struct ipcPipe client; close(pipes.pipe1.rx); client.tx = pipes.pipe1.tx; close(pipes.pipe2.tx); client.rx = pipes.pipe2.rx; return client; } oidc_error_t ipc_writeToPipe(struct ipcPipe pipes, const char* fmt, ...) { va_list args; va_start(args, fmt); oidc_error_t ret = ipc_vwriteToPipe(pipes, fmt, args); va_end(args); return ret; } oidc_error_t ipc_vwriteToPipe(struct ipcPipe pipes, const char* fmt, va_list args) { return ipc_vwrite(pipes.tx, fmt, args); } oidc_error_t ipc_writeOidcErrnoToPipe(struct ipcPipe pipes) { return ipc_writeToPipe(pipes, RESPONSE_ERROR, oidc_serror()); } char* ipc_readFromPipe(struct ipcPipe pipes) { return ipc_read(pipes.rx); } char* ipc_readFromPipeWithTimeout(struct ipcPipe pipes, time_t timeout) { return ipc_readWithTimeout(pipes.rx, timeout); } char* ipc_vcommunicateThroughPipe(struct ipcPipe pipes, const char* fmt, va_list args) { if (ipc_vwriteToPipe(pipes, fmt, args) != OIDC_SUCCESS) { return NULL; } return ipc_readFromPipe(pipes); } char* ipc_communicateThroughPipe(struct ipcPipe pipes, const char* fmt, ...) { va_list args; va_start(args, fmt); char* ret = ipc_vcommunicateThroughPipe(pipes, fmt, args); va_end(args); return ret; } oidc-agent-4.2.6/src/ipc/connection.c0000644000175000017500000000170714167074355016771 0ustar marcusmarcus#include "connection.h" #include #include "utils/memory.h" /** @fn int connection_comparator(const void* v1, const void* v2) * @brief compares two connections by their msgsock. * @param v1 pointer to the first element * @param v2 pointer to the second element * @return -1 if v1v2; 0 if v1=v2 */ int connection_comparator(const struct connection* c1, const struct connection* c2) { if (c1->msgsock == NULL && c2->msgsock == NULL) { return 1; } if (c1->msgsock == NULL || c2->msgsock == NULL) { return 0; } if (*(c1->msgsock) == *(c2->msgsock)) { return 1; } return 0; } void _secFreeConnection(struct connection* con) { secFree(con->server); con->server = NULL; secFree(con->tcp_server); con->tcp_server = NULL; secFree(con->sock); con->sock = NULL; if (con->msgsock) { close(*(con->msgsock)); } secFree(con->msgsock); con->msgsock = NULL; secFree(con); } oidc-agent-4.2.6/src/ipc/serveripc.h0000644000175000017500000000173714167074355016644 0ustar marcusmarcus#ifndef IPC_SERVER_H #define IPC_SERVER_H #include #include #include "connection.h" #include "utils/oidc_error.h" oidc_error_t initServerConnection(struct connection* con); struct connection* ipc_readAsyncFromMultipleConnectionsWithTimeout( struct connection, time_t); char* ipc_vcryptCommunicateWithServerPath(const char* fmt, va_list args); char* ipc_cryptCommunicateWithServerPath(const char* fmt, ...); char* getServerSocketPath(); oidc_error_t ipc_server_init(struct connection* con, const char* group_name, const char* socket_path); oidc_error_t ipc_initWithPath(struct connection* con); int ipc_bindAndListen(struct connection* con); void server_ipc_freeLastKey(); char* server_ipc_read(const int); oidc_error_t server_ipc_write(const int, const char*, ...); oidc_error_t server_ipc_writeOidcErrno(const int); oidc_error_t server_ipc_writeOidcErrnoPlain(const int sock); #endif // IPC_SERVER_H oidc-agent-4.2.6/src/ipc/ipc.c0000644000175000017500000002257514167074355015413 0ustar marcusmarcus#include "ipc.h" #include #include #include #include #include #include #include #include #include #include "defines/ipc_values.h" #include "defines/settings.h" #include "utils/ipUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" oidc_error_t initConnectionWithoutPath(struct connection* con, int isServer, int tcp) { con->server = secAlloc(sizeof(struct sockaddr_un)); con->tcp_server = secAlloc(sizeof(struct sockaddr_in)); con->sock = secAlloc(sizeof(int)); if (isServer) { // msgsock is not needed for a client; furthermore if // the client calls ipc_close it would close stdin con->msgsock = secAlloc(sizeof(int)); } if (con->server == NULL || con->sock == NULL || (con->msgsock == NULL && isServer)) { logger(ALERT, "alloc failed\n"); return oidc_errno; } *(con->sock) = socket(tcp ? AF_INET : AF_UNIX, SOCK_STREAM, 0); if (*(con->sock) < 0) { logger(ERROR, "opening stream socket: %m"); oidc_errno = OIDC_ECRSOCK; return oidc_errno; } con->server->sun_family = AF_UNIX; con->tcp_server->sin_family = AF_INET; return OIDC_SUCCESS; } oidc_error_t initClientConnection(struct connection* con, int tcp) { return initConnectionWithoutPath(con, 0, tcp); } oidc_error_t initConnectionWithPath(struct connection* con, const char* socket_path) { logger(DEBUG, "initializing ipc with path %s\n", socket_path); if (initConnectionWithoutPath(con, 0, 0) != OIDC_SUCCESS) { return oidc_errno; } strcpy(con->server->sun_path, socket_path); return OIDC_SUCCESS; } /** * @brief initializes a client unix domain or tcp socket * @param con, a pointer to the connection struct. The relevant fields will be * initialized. * @param env_var_name, the socket_path environment variable name */ oidc_error_t ipc_client_init(struct connection* con, unsigned char remote) { logger(DEBUG, "initializing client ipc"); const char* env_var_name = remote ? OIDC_REMOTE_SOCK_ENV_NAME : OIDC_SOCK_ENV_NAME; const char* path = getenv(env_var_name); if (path == NULL) { char* err = oidc_sprintf("Could not get the socket path from env var '%s'. " "Have you set the env var?\n", env_var_name); logger(WARNING, "Could not get the socket path from env var '%s'", env_var_name); oidc_seterror(err); secFree(err); oidc_errno = OIDC_EENVVAR; return oidc_errno; } if (initClientConnection(con, remote) != OIDC_SUCCESS) { return oidc_errno; } if (remote) { logger(DEBUG, "Using TCP socket"); char* tmp_path = oidc_strcopy(path); char* ip = strtok(tmp_path, ":"); char* port_str = strtok(NULL, ":"); unsigned short port = port_str == NULL ? 0 : strToUShort(port_str); con->tcp_server->sin_port = htons(port ?: 42424); con->tcp_server->sin_addr.s_addr = inet_addr(isValidIP(ip) ? ip : hostnameToIP(ip)); secFree(tmp_path); } else { logger(DEBUG, "Using UNIX domain socket"); strcpy(con->server->sun_path, path); } return OIDC_SUCCESS; } /** * @brief connects to a UNIX Domain or TCP socket * @param con, the connection struct * @return the socket or @c OIDC_ECONSOCK on failure */ int ipc_connect(struct connection con) { struct sockaddr* server = (struct sockaddr*)con.server; size_t server_size = sizeof(struct sockaddr_un); if (con.server->sun_path[0] == '\0') { server = (struct sockaddr*)con.tcp_server; server_size = sizeof(struct sockaddr_in); logger(DEBUG, "connecting tcp ipc %lu:%hu\n", con.tcp_server->sin_addr.s_addr, con.tcp_server->sin_port); } else { logger(DEBUG, "connecting ipc '%s'\n", con.server->sun_path); } if (connect(*(con.sock), server, server_size) < 0) { close(*(con.sock)); logger(ERROR, "connecting stream socket: %m"); oidc_errno = OIDC_ECONSOCK; return OIDC_ECONSOCK; } return *(con.sock); } /** * @brief reads from a socket until a timeout is reached * @param _sock the socket to read from * @param timeout the timeout in seconds, if @c 0 no timeout is used. * @return a pointer to the readed content. Has to be freed after usage. If * @c NULL is returned, it's most likely that the other party disconnected. */ char* ipc_read(const int _sock) { return ipc_readWithTimeout(_sock, 0); } struct timeval* initTimeout(time_t death) { if (death == 0) { oidc_errno = OIDC_SUCCESS; return NULL; } time_t now = time(NULL); if (death < now) { logger(NOTICE, "death was before now"); oidc_errno = OIDC_ETIMEOUT; return NULL; } struct timeval* timeout = secAlloc(sizeof(struct timeval)); timeout->tv_sec = death - now; oidc_errno = OIDC_SUCCESS; return timeout; } /** * @brief reads from a socket until a timeout is reached * @param _sock the socket to read from * @param timeout the timeout in seconds, if @c 0 no timeout is used. * @return a pointer to the readed content. Has to be freed after usage. If an * error occurs or the timeout is reached @c NULL is returned and @c oidc_errno * is set. */ char* ipc_readWithTimeout(const int _sock, time_t death) { logger(DEBUG, "ipc reading from socket %d\n", _sock); if (_sock < 0) { logger(ERROR, "invalid socket in ipc_read"); oidc_errno = OIDC_ESOCKINV; return NULL; } int len = 0; int rv; fd_set set; FD_ZERO(&set); FD_SET(_sock, &set); struct timeval* timeout = initTimeout(death); if (oidc_errno != OIDC_SUCCESS) { // death before now return NULL; } rv = select(_sock + 1, &set, NULL, NULL, timeout); secFree(timeout); if (rv == -1) { logger(ALERT, "error select in %s: %m", __func__); oidc_errno = OIDC_ESELECT; return NULL; } if (rv == 0) { oidc_errno = OIDC_ETIMEOUT; return NULL; } if (ioctl(_sock, FIONREAD, &len) != 0) { logger(ERROR, "ioctl: %m"); oidc_errno = OIDC_EIOCTL; return NULL; } if (len <= 0) { logger(DEBUG, "Client disconnected"); oidc_errno = OIDC_EIPCDIS; return NULL; } char* buf = secAlloc(sizeof(char) * (len + 1)); logger(DEBUG, "ipc want to read %d bytes", len); int read_bytes = 0; while (read_bytes < len) { int read_ret = read(_sock, buf + read_bytes, len - read_bytes); if (read_ret < 0) { oidc_setErrnoError(); secFree(buf); return NULL; } read_bytes += read_ret; logger(DEBUG, "ipc did read %d bytes in total", read_bytes); } logger(DEBUG, "ipc read '%s'", buf); return buf; } /** * @brief writes a message to a socket * @param _sock the socket to write to * @param msg the msg to be written * @return @c 0 on success; on failure an error code is returned */ oidc_error_t ipc_write(int _sock, const char* fmt, ...) { va_list args; va_start(args, fmt); oidc_error_t ret = ipc_vwrite(_sock, fmt, args); va_end(args); return ret; } oidc_error_t ipc_vwrite(int _sock, const char* fmt, va_list args) { char* msg = oidc_vsprintf(fmt, args); if (msg == NULL) { return oidc_errno; } size_t msg_len = strlen(msg); if (msg_len == 0) { // Don't send an empty message. This will be read as // client disconnected msg_len = 1; secFree(msg); msg = oidc_strcopy(" "); } logger(DEBUG, "ipc writing %lu bytes to socket %d", msg_len, _sock); logger(DEBUG, "ipc write message '%s'", msg); ssize_t written_bytes = write(_sock, msg, msg_len); secFree(msg); if (written_bytes < 0) { logger(ALERT, "writing on stream socket: %m"); oidc_errno = OIDC_EWRITE; return oidc_errno; } if ((size_t)written_bytes < msg_len) { oidc_errno = OIDC_EMSGSIZE; return oidc_errno; } return OIDC_SUCCESS; } oidc_error_t ipc_writeOidcErrno(int sock) { return ipc_write(sock, RESPONSE_ERROR, oidc_serror()); } /** * @brief closes a FD * @param _sock the FD to be closed */ int ipc_close(int _sock) { return close(_sock); } /** * @brief closes an ipc connection * @param con, a pointer to the connection struct * @return @c OIDC_SUCCESS on success */ oidc_error_t ipc_closeConnection(struct connection* con) { logger(DEBUG, "close ipc\n"); if (con->sock != NULL) { ipc_close(*(con->sock)); } if (con->msgsock != NULL) { ipc_close(*(con->msgsock)); } secFree(con->server); con->server = NULL; secFree(con->tcp_server); con->tcp_server = NULL; secFree(con->sock); con->sock = NULL; secFree(con->msgsock); con->msgsock = NULL; return OIDC_SUCCESS; } /** * @brief closes an ipc connection and removes the socket * @param con, a pointer to the connection struct * @return @c OIDC_SUCCESS on success */ oidc_error_t ipc_closeAndUnlinkConnection(struct connection* con) { if (con->server->sun_path[0] != '\0') { logger(DEBUG, "Unlinking %s", con->server->sun_path); unlink(con->server->sun_path); } ipc_closeConnection(con); return OIDC_SUCCESS; } char* ipc_vcommunicateWithSock(int sock, const char* fmt, va_list args) { ipc_vwrite(sock, fmt, args); return ipc_read(sock); } char* ipc_communicateWithSock(int sock, const char* fmt, ...) { va_list args; va_start(args, fmt); char* ret = ipc_vcommunicateWithSock(sock, fmt, args); va_end(args); return ret; } oidc-agent-4.2.6/src/ipc/cryptIpc.c0000644000175000017500000001026214167074355016423 0ustar marcusmarcus#include "cryptIpc.h" #include #include #include "ipc.h" #include "utils/crypt/crypt.h" #include "utils/crypt/ipcCryptUtils.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" typedef int (*crypto_kx_session_keys)( unsigned char rx[crypto_kx_SESSIONKEYBYTES], unsigned char tx[crypto_kx_SESSIONKEYBYTES], const unsigned char pk[crypto_kx_PUBLICKEYBYTES], const unsigned char sk[crypto_kx_SECRETKEYBYTES], const unsigned char other_pk[crypto_kx_PUBLICKEYBYTES]); oidc_error_t ipc_cryptWrite(const int sock, const unsigned char* key, const char* fmt, ...) { va_list args; va_start(args, fmt); oidc_error_t ret = ipc_vcryptWrite(sock, key, fmt, args); va_end(args); return ret; } oidc_error_t ipc_vcryptWrite(const int sock, const unsigned char* key, const char* fmt, va_list args) { char* msg = oidc_vsprintf(fmt, args); if (msg == NULL) { return oidc_errno; } logger(DEBUG, "Doing encrypted ipc write of %lu bytes: '%s'", strlen(msg), msg); char* encryptedMessage = encryptForIpc(msg, key); secFree(msg); if (encryptedMessage == NULL) { return oidc_errno; } oidc_error_t e = ipc_write(sock, encryptedMessage); secFree(encryptedMessage); return e; } void secFreePubSecKeySet(struct pubsec_keySet* k) { secFree(k); } struct pubsec_keySet* generatePubSecKeys() { struct pubsec_keySet* keys = secAlloc(sizeof(struct pubsec_keySet)); crypto_kx_keypair(keys->pk, keys->sk); logger(DEBUG, "Generated pub/sec keys"); return keys; } char* communicatePublicKey(const int _sock, const char* publicKey) { if (publicKey == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } char* pk_base64 = toBase64(publicKey, crypto_kx_PUBLICKEYBYTES); logger(DEBUG, "Communicating pub key"); char* res = ipc_communicateWithSock(_sock, pk_base64); secFree(pk_base64); return res; } unsigned char* generateIpcKey(const unsigned char* publicKey, const unsigned char* privateKey) { if (publicKey == NULL || privateKey == NULL) { oidc_setArgNullFuncError(__func__); return NULL; } unsigned char* sharedKey = secAlloc(crypto_box_BEFORENMBYTES); if (crypto_box_beforenm(sharedKey, publicKey, privateKey) != 0) { oidc_errno = OIDC_ECRYPPUB; secFree(sharedKey); return NULL; } return sharedKey; } list_t* encryptionKeys = NULL; char* server_ipc_cryptRead(const int sock, const char* client_pk_base64) { logger(DEBUG, "Doing encrypted ipc read"); unsigned char client_pk[crypto_kx_PUBLICKEYBYTES]; fromBase64(client_pk_base64, crypto_kx_PUBLICKEYBYTES, client_pk); struct pubsec_keySet* pubsec_keys = generatePubSecKeys(); unsigned char* ipc_key = generateIpcKey(client_pk, pubsec_keys->sk); if (ipc_key == NULL) { secFree(ipc_key); return NULL; } char* encrypted_request = communicatePublicKey(sock, (char*)pubsec_keys->pk); secFreePubSecKeySet(pubsec_keys); if (encrypted_request == NULL) { secFree(ipc_key); return NULL; } logger(DEBUG, "Received encrypted request"); char* decryptedRequest = decryptForIpc(encrypted_request, ipc_key); secFree(encrypted_request); logger(DEBUG, "Decrypted request is '%s'", decryptedRequest); if (decryptedRequest != NULL) { if (encryptionKeys == NULL) { encryptionKeys = list_new(); } list_rpush(encryptionKeys, list_node_new(ipc_key)); } else { secFree(ipc_key); } return decryptedRequest; } unsigned char* client_keyExchange(const int sock) { struct pubsec_keySet* pubsec_keys = generatePubSecKeys(); char* server_pk_base64 = communicatePublicKey(sock, (char*)pubsec_keys->pk); if (server_pk_base64 == NULL) { secFreePubSecKeySet(pubsec_keys); return NULL; } logger(DEBUG, "Received server public key"); unsigned char server_pk[crypto_kx_PUBLICKEYBYTES]; fromBase64(server_pk_base64, crypto_kx_PUBLICKEYBYTES, server_pk); secFree(server_pk_base64); unsigned char* ipc_key = generateIpcKey(server_pk, pubsec_keys->sk); secFreePubSecKeySet(pubsec_keys); if (ipc_key == NULL) { return NULL; } return ipc_key; } oidc-agent-4.2.6/src/ipc/serveripc.c0000644000175000017500000003066414167074355016640 0ustar marcusmarcus#ifndef __APPLE__ #define _XOPEN_SOURCE 700 #endif #include "serveripc.h" #include #include #include #include #include #include #include #include #include "cryptIpc.h" #include "defines/ipc_values.h" #include "ipc.h" #include "ipc/cryptCommunicator.h" #include "utils/db/connection_db.h" #include "utils/file_io/fileUtils.h" #include "utils/file_io/file_io.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" #include "wrapper/list.h" #define SOCKET_TMP_DIR "/tmp" #define SOCKET_DIR_PATTERN "oidc-XXXXXX" #define TMPDIR_ENVVAR "TMPDIR" static char* oidc_ipc_dir = NULL; static char* server_socket_path = NULL; char* get_socket_dir_pattern() { const char* tmpdir = getenv(TMPDIR_ENVVAR); if (!tmpdir || !tmpdir[0]) { tmpdir = SOCKET_TMP_DIR; } return oidc_pathcat(tmpdir, SOCKET_DIR_PATTERN); } char* concat_default_socket_name_to_socket_path() { if (oidc_ipc_dir == NULL) { return NULL; } pid_t ppid = getppid(); const char* const prefix = "oidc-agent"; return oidc_sprintf("%s%s%s.%d", oidc_ipc_dir, lastChar(oidc_ipc_dir) == '/' ? "" : "/", prefix, ppid); } char* create_new_socket_path() { if (NULL == oidc_ipc_dir) { oidc_ipc_dir = get_socket_dir_pattern(); if (mkdtemp(oidc_ipc_dir) == NULL) { logger(ALERT, "%m"); oidc_errno = OIDC_EMKTMP; secFree(oidc_ipc_dir); return NULL; } } return concat_default_socket_name_to_socket_path(); } #define mkpath_return_on_error(p) \ if (mkpath(p, 0700) != OIDC_SUCCESS) { \ secFree(oidc_ipc_dir); \ secFree(socket_file); \ return NULL; \ } oidc_error_t create_pre_random_part_path(const int rand_index) { *(oidc_ipc_dir + rand_index) = '\0'; char* startRandomPart = strrchr(oidc_ipc_dir, '/'); if (startRandomPart && startRandomPart != oidc_ipc_dir) { *startRandomPart = '\0'; if (mkpath(oidc_ipc_dir, 0700) != OIDC_SUCCESS) { *(oidc_ipc_dir + rand_index) = 'X'; // start of 'XXXXXX/' was an 'X' *startRandomPart = '/'; return oidc_errno; } *startRandomPart = '/'; } *(oidc_ipc_dir + rand_index) = 'X'; // start of 'XXXXXX/' was an 'X' return OIDC_SUCCESS; } oidc_error_t create_random_part_path(const int rand_index) { *(oidc_ipc_dir + rand_index + 6) = '\0'; if (mkdtemp(oidc_ipc_dir) == NULL) { logger(ERROR, "%s", oidc_ipc_dir); logger(ERROR, "mkdtemp: %m"); oidc_errno = OIDC_EMKTMP; *(oidc_ipc_dir + rand_index + 6) = '/'; return oidc_errno; } *(oidc_ipc_dir + rand_index + 6) = '/'; return OIDC_SUCCESS; } oidc_error_t create_post_random_part_path(const int rand_index) { if (strlen(oidc_ipc_dir) - rand_index <= 7) { return OIDC_SUCCESS; } return mkpath(oidc_ipc_dir, 0700); } oidc_error_t create_path_with_random_part(const int rand_index) { oidc_error_t e = create_pre_random_part_path(rand_index); if (e != OIDC_SUCCESS) { return e; } e = create_random_part_path(rand_index); if (e != OIDC_SUCCESS) { return e; } return create_post_random_part_path(rand_index); } char* create_passed_socket_path(const char* requested_path) { char* socket_file = NULL; oidc_ipc_dir = oidc_strcopy(requested_path); if (lastChar(oidc_ipc_dir) == '/') { // only dir specified lastChar(oidc_ipc_dir) = '\0'; } else { // full path including file specified char* lastSlash = strrchr(oidc_ipc_dir, '/'); socket_file = oidc_strcopy(lastSlash + 1); char* tmp = oidc_strncopy(oidc_ipc_dir, lastSlash - oidc_ipc_dir); secFree(oidc_ipc_dir); oidc_ipc_dir = tmp; } char* random_part = strstr(oidc_ipc_dir, "XXXXXX/") ?: strEnds(oidc_ipc_dir, "XXXXXX") ? oidc_ipc_dir + strlen(oidc_ipc_dir) - 6 : NULL; if (random_part == NULL) { mkpath_return_on_error(oidc_ipc_dir); } else { if (create_path_with_random_part(random_part - oidc_ipc_dir) != OIDC_SUCCESS) { secFree(oidc_ipc_dir); secFree(socket_file); return NULL; } } if (socket_file == NULL) { return concat_default_socket_name_to_socket_path(); } char* socket_path = oidc_pathcat(oidc_ipc_dir, socket_file); secFree(socket_file); return socket_path; } oidc_error_t socket_apply_group(const char* group_name) { if (group_name == NULL) { return OIDC_SUCCESS; } return changeGroup(oidc_ipc_dir, group_name); } /** * @brief generates the socket path and prints commands for setting env vars * @param group_name if not @c NULL, the group ownership is adjusted to the * specified group after creation. * @param socket_path if not @c NULL, the socket will be created at this path. * @return a pointer to the socket_path. Has to be freed after usage. */ char* init_socket_path(const char* group_name, const char* socket_path) { char* created_path = socket_path ? create_passed_socket_path(socket_path) : create_new_socket_path(); if (created_path) { if (socket_apply_group(group_name) != OIDC_SUCCESS) { secFree(created_path); // also sets created_path=NULL } } return created_path; } oidc_error_t initServerConnection(struct connection* con) { return initConnectionWithoutPath(con, 1, 0); } /** * @brief initializes a server unix domain socket * @param con, a pointer to the connection struct. The relevant fields will be * initialized. * @param group_name if not @c NULL, the group ownership is adjusted to the * specified group after creation. */ oidc_error_t ipc_server_init(struct connection* con, const char* group_name, const char* socket_path) { logger(DEBUG, "initializing server ipc"); if (initServerConnection(con) != OIDC_SUCCESS) { return oidc_errno; } char* path = init_socket_path(group_name, socket_path); if (path == NULL) { return oidc_errno; } strcpy(con->server->sun_path, path); secFree(path); server_socket_path = con->server->sun_path; return OIDC_SUCCESS; } /** * @brief initializes unix domain socket with the current server_socket_path * @param con, a pointer to the connection struct. The relevant fields will be * initialized. * @return 0 on success, otherwise a negative error code */ oidc_error_t ipc_initWithPath(struct connection* con) { if (server_socket_path == NULL) { oidc_setArgNullFuncError(__func__); return oidc_errno; } logger(DEBUG, "initializing ipc with path %s\n", server_socket_path); if (initConnectionWithoutPath(con, 0, 1) != OIDC_SUCCESS) { return oidc_errno; } strcpy(con->server->sun_path, server_socket_path); return OIDC_SUCCESS; } char* getServerSocketPath() { return server_socket_path; } /** * @brief binds the server socket and starts listening * @param con, a pointer to the connection struct * @return @c 0 on success or an errorcode on failure */ int ipc_bindAndListen(struct connection* con) { logger(DEBUG, "binding ipc\n"); unlink(con->server->sun_path); if (bind(*(con->sock), (struct sockaddr*)con->server, sizeof(struct sockaddr_un))) { logger(ALERT, "binding stream socket: %m"); close(*(con->sock)); oidc_errno = OIDC_EBIND; return OIDC_EBIND; } int flags; if (-1 == (flags = fcntl(*(con->sock), F_GETFL, 0))) flags = 0; fcntl(*(con->sock), F_SETFL, flags | O_NONBLOCK); logger(DEBUG, "listen ipc\n"); return listen(*(con->sock), 5); } int _determineMaxSockAndAddToReadSet(int sock_listencon, fd_set* readSet) { int maxSock = sock_listencon; list_node_t* node; list_iterator_t* it = list_iterator_new(connectionDB_getList(), LIST_HEAD); while ((node = list_iterator_next(it))) { struct connection* con = node->val; FD_SET(*(con->msgsock), readSet); if (*(con->msgsock) > maxSock) { maxSock = *(con->msgsock); } } list_iterator_destroy(it); return maxSock; } struct connection* _checkClientSocksForMsg(fd_set* readSet) { list_node_t* node; list_iterator_t* it = list_iterator_new(connectionDB_getList(), LIST_TAIL); while ((node = list_iterator_next(it))) { struct connection* con = node->val; logger(DEBUG, "Checking client %d", *(con->msgsock)); if (FD_ISSET(*(con->msgsock), readSet)) { logger(DEBUG, "New message for read av"); list_iterator_destroy(it); return con; } } list_iterator_destroy(it); return NULL; } /** * @brief handles asynchronous server read for multiple sockets * * listens for incoming connections on the listencon and for incoming messages * on multiple client sockets. If a new client connects it is added to the list * of current client connections. If on any client socket is a message * available for reading, a pointer to this connection is returned. * @param listencon the connection struct for the socket accepting new client * connections. The list is updated if a new client connects. * @return A pointer to a client connection. On this connection is either a * message avaible for reading or the client disconnected. */ struct connection* ipc_readAsyncFromMultipleConnectionsWithTimeout( struct connection listencon, time_t death) { while (1) { fd_set readSockSet; FD_ZERO(&readSockSet); FD_SET(*(listencon.sock), &readSockSet); int maxSock = _determineMaxSockAndAddToReadSet(*(listencon.sock), &readSockSet); struct timeval* timeout = initTimeout(death); if (oidc_errno != OIDC_SUCCESS) { // death before now return NULL; } logger(DEBUG, "Calling select with maxSock %d and timeout %lu", maxSock, timeout ? timeout->tv_sec : 0); // Waiting for incoming connections and messages int ret = select(maxSock + 1, &readSockSet, NULL, NULL, timeout); secFree(timeout); if (ret > 0) { if (FD_ISSET(*(listencon.sock), &readSockSet)) { // if listensock read something it means a // new client connected logger(DEBUG, "New incoming client"); struct connection* newClient = secAlloc(sizeof(struct connection)); newClient->msgsock = secAlloc(sizeof(int)); *(newClient->msgsock) = accept(*(listencon.sock), 0, 0); if (*(newClient->msgsock) >= 0) { logger(DEBUG, "accepted new client sock: %d", *(newClient->msgsock)); connectionDB_addValue(newClient); logger(DEBUG, "updated client list"); } else { logger(ERROR, "%m"); } } struct connection* con = _checkClientSocksForMsg(&readSockSet); if (con) { return con; } } else if (ret == 0) { logger(DEBUG, "Reached select timeout"); oidc_errno = OIDC_ETIMEOUT; return NULL; } else { logger(ERROR, "%m"); } } return NULL; } char* ipc_cryptCommunicateWithServerPath(const char* fmt, ...) { va_list args; va_start(args, fmt); char* ret = ipc_vcryptCommunicateWithServerPath(fmt, args); va_end(args); return ret; } char* ipc_vcryptCommunicateWithServerPath(const char* fmt, va_list args) { return ipc_vcryptCommunicateWithPath(server_socket_path, fmt, args); } extern list_t* encryptionKeys; oidc_error_t server_ipc_write(const int sock, const char* fmt, ...) { va_list args; va_start(args, fmt); if (encryptionKeys == NULL || encryptionKeys->len <= 0) { oidc_error_t ret = ipc_vwrite(sock, fmt, args); va_end(args); return ret; } list_node_t* node = list_rpop(encryptionKeys); unsigned char* ipc_key = node->val; LIST_FREE(node); oidc_error_t e = ipc_vcryptWrite(sock, ipc_key, fmt, args); va_end(args); secFree(ipc_key); if (e == OIDC_SUCCESS) { return OIDC_SUCCESS; } return ipc_writeOidcErrno(sock); } char* server_ipc_read(const int sock) { char* msg = ipc_read(sock); if (msg == NULL || isJSONObject(msg)) { return msg; } char* res = server_ipc_cryptRead(sock, msg); secFree(msg); return res; } void server_ipc_freeLastKey() { if (encryptionKeys == NULL || encryptionKeys->len <= 0) { return; } list_node_t* node = list_rpop(encryptionKeys); unsigned char* key = node->val; LIST_FREE(node); secFree(key); } oidc_error_t server_ipc_writeOidcErrno(const int sock) { return server_ipc_write(sock, RESPONSE_ERROR, oidc_serror()); } oidc_error_t server_ipc_writeOidcErrnoPlain(const int sock) { return ipc_writeOidcErrno(sock); } oidc-agent-4.2.6/src/ipc/cryptIpc.h0000644000175000017500000000154014167074355016427 0ustar marcusmarcus#ifndef IPC_CRYPT_H #define IPC_CRYPT_H #include #include #include "utils/oidc_error.h" struct pubsec_keySet { unsigned char pk[crypto_kx_PUBLICKEYBYTES]; unsigned char sk[crypto_kx_SECRETKEYBYTES]; }; char* communicatePublicKey(const int _sock, const char* publicKey); unsigned char* generateIpcKey(const unsigned char* publicKey, const unsigned char* privateKey); struct pubsec_keySet* generatePubSecKeys(); oidc_error_t ipc_cryptWrite(const int, const unsigned char*, const char*, ...); oidc_error_t ipc_vcryptWrite(const int, const unsigned char*, const char*, va_list); void secFreePubSecKeySet(struct pubsec_keySet*); char* server_ipc_cryptRead(const int, const char*); unsigned char* client_keyExchange(const int sock); #endif // IPC_CRYPT_H oidc-agent-4.2.6/src/ipc/ipc.h0000644000175000017500000000173714167074355015415 0ustar marcusmarcus#ifndef IPC_H #define IPC_H #include #include #include "connection.h" #include "utils/oidc_error.h" oidc_error_t initConnectionWithoutPath(struct connection*, int, int); oidc_error_t initConnectionWithPath(struct connection*, const char*); oidc_error_t ipc_client_init(struct connection*, unsigned char); int ipc_connect(struct connection con); char* ipc_read(const int _sock); char* ipc_readWithTimeout(const int _sock, time_t timeout); oidc_error_t ipc_write(int _sock, const char* msg, ...); oidc_error_t ipc_vwrite(int _sock, const char* msg, va_list args); oidc_error_t ipc_writeOidcErrno(int sock); int ipc_close(int _sock); oidc_error_t ipc_closeConnection(struct connection* con); oidc_error_t ipc_closeAndUnlinkConnection(struct connection* con); char* ipc_communicateWithSock(int sock, const char* fmt, ...); char* ipc_vcommunicateWithSock(int sock, const char* fmt, va_list args); struct timeval* initTimeout(time_t death); #endif // IPC_H oidc-agent-4.2.6/src/ipc/cryptCommunicator.c0000644000175000017500000000424214167074355020351 0ustar marcusmarcus#include "cryptCommunicator.h" #include "cryptIpc.h" #include "ipc.h" #include "utils/crypt/ipcCryptUtils.h" #include "utils/json.h" #include "utils/logger.h" #include "utils/oidc_error.h" char* _ipc_vcryptCommunicateWithConnection(struct connection con, const char* fmt, va_list args) { logger(DEBUG, "Doing encrypted ipc communication"); if (ipc_connect(con) < 0) { return NULL; } unsigned char* ipc_key = client_keyExchange(*(con.sock)); if (ipc_key == NULL) { ipc_closeConnection(&con); return NULL; } oidc_error_t e = ipc_vcryptWrite(*(con.sock), ipc_key, fmt, args); if (e != OIDC_SUCCESS) { secFree(ipc_key); ipc_closeConnection(&con); return NULL; } char* encryptedResponse = ipc_read(*(con.sock)); ipc_closeConnection(&con); if (encryptedResponse == NULL) { secFree(ipc_key); return NULL; } if (isJSONObject(encryptedResponse)) { // Response not encrypted secFree(ipc_key); return encryptedResponse; } char* decryptedResponse = decryptForIpc(encryptedResponse, ipc_key); secFree(encryptedResponse); secFree(ipc_key); return decryptedResponse; } char* ipc_cryptCommunicate(unsigned char remote, const char* fmt, ...) { va_list args; va_start(args, fmt); char* ret = ipc_vcryptCommunicate(remote, fmt, args); va_end(args); return ret; } char* ipc_vcryptCommunicate(unsigned char remote, const char* fmt, va_list args) { static struct connection con; if (ipc_client_init(&con, remote) != OIDC_SUCCESS) { return NULL; } return _ipc_vcryptCommunicateWithConnection(con, fmt, args); } char* ipc_vcryptCommunicateWithPath(const char* socket_path, const char* fmt, va_list args) { static struct connection con; if (initConnectionWithPath(&con, socket_path) != OIDC_SUCCESS) { return NULL; } return _ipc_vcryptCommunicateWithConnection(con, fmt, args); } char* ipc_cryptCommunicateWithPath(const char* socket_path, const char* fmt, ...) { va_list args; va_start(args, fmt); return ipc_vcryptCommunicateWithPath(socket_path, fmt, args); } oidc-agent-4.2.6/src/ipc/cryptCommunicator.h0000644000175000017500000000057314167074355020361 0ustar marcusmarcus#ifndef CRYPT_COMMUNICATOR_H #define CRYPT_COMMUNICATOR_H #include char* ipc_cryptCommunicate(unsigned char, const char*, ...); char* ipc_vcryptCommunicate(unsigned char, const char*, va_list); char* ipc_vcryptCommunicateWithPath(const char*, const char*, va_list); char* ipc_cryptCommunicateWithPath(const char*, const char*, ...); #endif // CRYPT_COMMUNICATOR_H oidc-agent-4.2.6/src/ipc/pipe.h0000644000175000017500000000156514167074355015576 0ustar marcusmarcus#ifndef OIDC_IPC_PIPE_H #define OIDC_IPC_PIPE_H #include #include #include "utils/oidc_error.h" struct ipcPipe { int rx; int tx; }; struct pipeSet { struct ipcPipe pipe1; struct ipcPipe pipe2; }; void ipc_closePipes(struct ipcPipe); struct pipeSet ipc_pipe_init(); struct ipcPipe toServerPipes(struct pipeSet); struct ipcPipe toClientPipes(struct pipeSet); oidc_error_t ipc_writeToPipe(struct ipcPipe, const char*, ...); oidc_error_t ipc_vwriteToPipe(struct ipcPipe, const char*, va_list); oidc_error_t ipc_writeOidcErrnoToPipe(struct ipcPipe); char* ipc_readFromPipe(struct ipcPipe); char* ipc_readFromPipeWithTimeout(struct ipcPipe, time_t); char* ipc_communicateThroughPipe(struct ipcPipe, const char*, ...); char* ipc_vcommunicateThroughPipe(struct ipcPipe, const char*, va_list); #endif // OIDC_IPC_PIPE_H oidc-agent-4.2.6/src/ipc/tcp_serveripc.h0000644000175000017500000000047114167074355017504 0ustar marcusmarcus#ifndef IPC_TCP_SERVER_H #define IPC_TCP_SERVER_H #include #include #include "connection.h" #include "utils/oidc_error.h" oidc_error_t ipc_tcp_server_init(struct connection* con, unsigned short port); int ipc_tcp_bindAndListen(struct connection* con); #endif // IPC_TCP_SERVER_H oidc-agent-4.2.6/src/ipc/tcp_serveripc.c0000644000175000017500000000255214167074355017501 0ustar marcusmarcus#include "tcp_serveripc.h" #include #include #include #include "ipc/ipc.h" #include "utils/logger.h" /** * @brief initializes a server tcp socket * @param con, a pointer to the connection struct. The relevant fields will be * initialized. */ oidc_error_t ipc_tcp_server_init(struct connection* con, unsigned short port) { logger(DEBUG, "initializing server ipc"); if (initConnectionWithoutPath(con, 1, 1) != OIDC_SUCCESS) { return oidc_errno; } con->tcp_server->sin_port = htons(port); con->tcp_server->sin_addr.s_addr = INADDR_ANY; // con->tcp_server->sin_addr.s_addr = inet_addr("127.0.0.1"); return OIDC_SUCCESS; } /** * @brief binds the server socket and starts listening * @param con, a pointer to the connection struct * @return @c 0 on success or an errorcode on failure */ int ipc_tcp_bindAndListen(struct connection* con) { logger(DEBUG, "binding tcp ipc\n"); if (bind(*(con->sock), (struct sockaddr*)con->tcp_server, sizeof(struct sockaddr_in))) { logger(ALERT, "binding stream socket: %m"); close(*(con->sock)); oidc_errno = OIDC_EBIND; return OIDC_EBIND; } int flags; if (-1 == (flags = fcntl(*(con->sock), F_GETFL, 0))) flags = 0; fcntl(*(con->sock), F_SETFL, flags | O_NONBLOCK); logger(DEBUG, "listen ipc\n"); return listen(*(con->sock), 5); } oidc-agent-4.2.6/src/ipc/connection.h0000644000175000017500000000122014167074355016764 0ustar marcusmarcus#ifndef IPC_CONNECTION_H #define IPC_CONNECTION_H #include #include #include struct connection { int* sock; int* msgsock; struct sockaddr_un* server; struct sockaddr_in* tcp_server; }; int connection_comparator(const struct connection* c1, const struct connection* c2); void _secFreeConnection(struct connection* con); #ifndef secFreeConnection #define secFreeConnection(ptr) \ do { \ _secFreeConnection((ptr)); \ (ptr) = NULL; \ } while (0) #endif // secFreeConnection #endif // IPC_CONNECTION_H oidc-agent-4.2.6/src/oidc-keychain/0000755000175000017500000000000014120404223016371 5ustar marcusmarcusoidc-agent-4.2.6/src/oidc-keychain/oidc-keychain0000755000175000017500000000711014120404223021025 0ustar marcusmarcus#!/bin/bash # oidc-keychain - re-use oidc-agent between logins # # Inspired by https://www.funtoo.org/Keychain for ssh-agent and gpg-agent # ME=oidc-keychain usage() { ( echo "Usage: $ME [-?|--help|--usage|-V|--version] [-k|--kill]" echo " or: $ME [oidc-agent options] [--accounts ACCOUNT ...]" )>&2 } help() { ( echo "$ME -- Re-use oidc-agent between logins" echo echo "oidc-agent options will be passed to oidc-agent when starting it." echo echo " General:" echo " -k, --kill Kill oidc-agent if it is running" echo " --accounts ACCOUNT ... Load the ACCOUNTs if not already loaded" echo echo " Help:" echo " -?, --help Give this help list" echo " --usage Give a short usage message" echo " -V, --version Print program version" )>&2 } KILL=false AGENT_OPTS="" while [ -n "$1" ]; do # Look for own options case "$1" in -k|--kill) KILL=true ;; "-?"|-h|--help) usage help exit ;; --usage) usage exit ;; -V|--version) echo `oidc-agent -V`|sed 's/agent/keychain/' exit ;; --accounts) shift break ;; *) # Look for options to pass on to oidc-agent if [[ $1 = -* ]]; then if [ -z "$2" ] || [[ $2 = -* ]]; then AGENT_OPTS="$AGENT_OPTS $1" else AGENT_OPTS="$AGENT_OPTS $1 $2" shift fi else echo "$ME: internal error: unprocessed option $1" >&2 exit 2 fi ;; esac shift done # Find the location of the initialization script if [ -z "$OIDC_CONFIG_DIR" ]; then if [ -d $HOME/.config ]; then OIDC_CONFIG_DIR=$HOME/.config/oidc-agent else OIDC_CONFIG_DIR=$HOME/.oidc-agent fi fi mkdir -p $OIDC_CONFIG_DIR/.keychain INITSCRIPT="$OIDC_CONFIG_DIR/.keychain/`uname -n`.sh" make_initscript() { ( echo "OIDC_SOCK=$OIDC_SOCK; export OIDC_SOCK;" echo "OIDCD_PID=$OIDCD_PID; export OIDCD_PID;" ) >$INITSCRIPT } # If not already set, read the agent variables from the initialization script if [ -z "$OIDC_SOCK" ] || [ -z "$OIDCD_PID" ]; then if [ -f "$INITSCRIPT" ]; then source $INITSCRIPT fi elif [ ! -f $INITSCRIPT ] && kill -0 "$OIDCD_PID" 2>/dev/null; then # must have previously ran just oidc-agent, so create the init script make_initscript fi if [ -z "$OIDCD_PID" ] || ! kill -0 "$OIDCD_PID" 2>/dev/null; then # Agent not running if $KILL; then echo "$ME: Agent was already not running" >&2 rm -f $INITSCRIPT echo "false;" exit 1 fi CMDS="`oidc-agent $AGENT_OPTS`" if [ "$?" -ne 0 ]; then RET="$?" echo "$ME: could not start oidc-agent" >&2 echo "false;" exit $RET fi CMDS="${CMDS/echo /echo echo $ME: }" eval "$CMDS" echo ";" make_initscript elif $KILL; then # Kill a running agent if oidc-agent -k; then rm -f $INITSCRIPT else RET=$? echo "false;" exit $RET fi exit else # Reuse agent echo echo "$ME: Reusing agent pid $OIDCD_PID;" fi cat $INITSCRIPT # Add given accounts if they're not already loaded. while IFS= read -r line; do LOADED+=("$line") done < <(oidc-add --loaded 2>/dev/null | tail -n +2) for ACCOUNT; do FOUND=false for L in ${LOADED[@]}; do if [[ "$L" = "$ACCOUNT" ]]; then FOUND=true break fi done if [ "$FOUND" = false ]; then # Account not already loaded, so add it. # Send all messages to stderr and also pipe to grep to look for error if oidc-add $ACCOUNT 2>&1|tee /dev/fd/2|grep -q Error:; then echo "false;" exit 1 fi fi done oidc-agent-4.2.6/Jenkinsfile0000644000175000017500000001024414120404223015260 0ustar marcusmarcus#!/usr/bin/groovy @Library(['github.com/indigo-dc/jenkins-pipeline-library@1.3.6']) _ pipeline { agent { label 'go' } stages { stage('Code fetching') { steps { checkout scm } } stage('Metrics gathering') { agent { label 'sloc' } steps { dir("$WORKSPACE/oidc-agent") { SLOCRun() } } post { success { dir("$WORKSPACE/oidc-agent") { SLOCPublish() } } } } stage('Dependency check') { agent { label 'docker-build' } steps { OWASPDependencyCheckRun("$WORKSPACE/oidc-agent", project="oidc-agent") } post { always { OWASPDependencyCheckPublish(report='**/dependency-check-report.xml') HTMLReport( "$WORKSPACE/oidc-agent", 'dependency-check-report.html', 'OWASP Dependency Report') deleteDir() } } } stage('Style Analysis') { steps { sh ''' GOFMT="/usr/local/go/bin/gofmt -s" bad_files=$(find . -name "*.go" | xargs $GOFMT -l) if [[ -n "${bad_files}" ]]; then echo "!!! '$GOFMT' needs to be run on the following files: " echo "${bad_files}" exit 1 fi ''' } } stage('Build RPM/DEB packages') { when { anyOf { buildingTag() } } parallel { stage('Build on Ubuntu16.04') { agent { label 'bubuntu16' } steps { checkout scm sh ''' echo 'Within build on Ubuntu16.04' echo 'Installing build-dependencies' sudo apt-get update && sudo apt-get install -y wget libcurl4-openssl-dev help2man libseccomp-dev libmicrohttpd-dev software-properties-common check sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 8B48AD6246925553 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7638D0442B90D010 sudo add-apt-repository "deb http://ftp.debian.org/debian stretch-backports main" sudo apt update && sudo apt install -t stretch-backports libsodium23 libsodium-dev sudo make deb mkdir -p UBUNTU PARENT="$(dirname "$WORKSPACE")" echo $PARENT find "$PARENT" -type f -name "*oidc*.deb" -exec mv -v -t UBUNTU \'{}\' \';\' ''' } post { success { archiveArtifacts artifacts: 'UBUNTU/*.deb' } } } stage('Build on CentOS7') { agent { label 'bcentos7' } steps { checkout scm sh ''' echo 'Within build on CentOS7' sudo make rpm mkdir -p RPMS find .. -type f -name "oidc-agent*.rpm" -exec cp -v -t RPMS \'{}\' \';\' ''' } post { success { archiveArtifacts artifacts: 'RPMS/*.rpm' } } } } } } } oidc-agent-4.2.6/debian/0000755000175000017500000000000014170032054014321 5ustar marcusmarcusoidc-agent-4.2.6/debian/oidc-agent-cli.install0000644000175000017500000000055214120404223020466 0ustar marcusmarcusetc/oidc-agent usr/bin/oidc-add usr/bin/oidc-agent usr/bin/oidc-agent-service usr/bin/oidc-gen usr/bin/oidc-keychain usr/bin/oidc-token usr/share/bash-completion/ usr/share/man/man1/oidc-add.1 usr/share/man/man1/oidc-agent.1 usr/share/man/man1/oidc-gen.1 usr/share/man/man1/oidc-keychain.1 usr/share/man/man1/oidc-token.1 usr/share/man/man1/oidc-agent-service.1 oidc-agent-4.2.6/debian/changelog0000644000175000017500000000034614170031216016175 0ustar marcusmarcusoidc-agent (4.2.6-1) unstable; urgency=medium * Initial package for Debian. (Closes: #980462) * Upgrade to standards version 4.6.0.1 (no changes needed) -- Marcus Hardt Tue, 28 Dec 2021 12:51:36 +0200 oidc-agent-4.2.6/debian/control0000644000175000017500000000752614170031216015735 0ustar marcusmarcusSource: oidc-agent Section: misc Priority: optional Maintainer: Marcus Hardt Homepage: https://github.com/indigo-dc/oidc-agent/ Standards-Version: 4.6.0.1 Vcs-Git: https://salsa.debian.org/debian/oidc-agent.git Vcs-Browser: https://salsa.debian.org/debian/oidc-agent Rules-Requires-Root: no Build-Depends: debhelper-compat (= 13), libcurl4-openssl-dev (>= 7.35.0), libsodium-dev (>= 1.0.14), help2man (>= 1.46.4), libseccomp-dev (>= 2.1.1), libmicrohttpd-dev (>= 0.9.33), check (>= 0.10.0), pkg-config (>= 0.29), libsecret-1-dev (>= 0.18.4), libcjson-dev (>= 1.7.10-1.1), libqrencode-dev (>= 3), Package: oidc-agent-cli Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends}, liboidc-agent4 (= ${binary:Version}), jq, libqrencode4 | libqrencode3, Suggests: oidc-agent-desktop (= ${source:Version}) Replaces: oidc-agent (<< 4.1.0-1) Description: Commandline tool for obtaining OpenID Connect Access tokens on the commandline oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command line. These tools follow ssh-agent design, so OIDC tokens can be handled in a similar way as ssh keys. The agent stores multiple configurations and their associated refresh tokens securely. This tool consists of five programs: - oidc-agent that handles communication with the OIDC provider - oidc-gen that generates config files - oidc-add that loads (and unloads) configuration into the agent - oidc-token that can be used to get access token on the command line - oidc-keychain that re-uses oidc-agent across logins Package: liboidc-agent4 Architecture: any Section: libs Depends: ${shlibs:Depends}, ${misc:Depends}, Recommends: oidc-agent-cli (= ${binary:Version}) Description: oidc-agent library oidc-agent is a commandline tool for obtaining OpenID Connect Access tokens on the commandline. . This package provides a library for easy communication with oidc-agent. Applications can use this library to request access token from oidc-agent. Package: liboidc-agent-dev Architecture: any Section: libdevel Depends: ${misc:Depends}, liboidc-agent4 (= ${binary:Version}) Description: oidc-agent library development files oidc-agent is a commandline tool for obtaining OpenID Connect Access tokens on the commandline. . This package provides the development files (static library and headers) required for building applications with liboidc-agent, a library for communicating with oidc-agent. Package: oidc-agent-desktop Architecture: all Depends: ${misc:Depends}, oidc-agent-cli (>= ${source:Version}), xterm | x-terminal-emulator, yad Replaces: oidc-agent (<< 4.1.0-1), oidc-agent-prompt (<< 4.1.0-1) Description: oidc-agent desktop integration Desktop integration files for oidc-gen and oidc-agent and for creating the user dialog. . This package adds two ways for supporting the usage of oidc-agent in a graphical environment. The .desktop file to leverage browser integration to support the authorization code flow in oidc-gen. The Xsession file to consistently set the environment variables necessary to for client tools to connect to the oidc-agent daemon. . This package also provides a bash script as an interface to create different dialog windows. It uses yad to create windows. Package: oidc-agent Architecture: any Depends: ${misc:Depends}, oidc-agent-desktop (= ${source:Version}) Description: Commandline tools for obtaining OpenID Connect Access tokens oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command line. . This metapackage bundles the commandline tools and the files for desktop integration oidc-agent-4.2.6/debian/liboidc-agent4.install0000644000175000017500000000006214167074355020514 0ustar marcusmarcususr/lib/${DEB_HOST_MULTIARCH}/liboidc-agent.so.4* oidc-agent-4.2.6/debian/oidc-agent-desktop.install0000644000175000017500000000014114120404223021362 0ustar marcusmarcusetc/X11/Xsession.d/ usr/bin/oidc-prompt usr/share/applications/ usr/share/man/man1/oidc-prompt.1 oidc-agent-4.2.6/debian/liboidc-agent4.symbols0000644000175000017500000000124214167074355020537 0ustar marcusmarcusliboidc-agent.so.4 liboidc-agent4 #MINVER# * Build-Depends-Package: liboidc-agent-dev _secFree@Base 4.0.0 getAccessToken2@Base 4.0.0 getAccessToken3@Base 4.0.0 getAccessToken@Base 4.0.0 getAccessTokenForIssuer3@Base 4.0.0 getAccessTokenForIssuer@Base 4.0.0 getAgentTokenResponse@Base 4.2.0 getAgentTokenResponseForIssuer@Base 4.2.0 getTokenResponse3@Base 4.0.0 getTokenResponse@Base 4.0.0 getTokenResponseForIssuer3@Base 4.0.0 getTokenResponseForIssuer@Base 4.0.0 oidcagent_perror@Base 4.0.0 oidcagent_printErrorResponse@Base 4.2.0 oidcagent_serror@Base 4.0.0 secFreeAgentResponse@Base 4.2.0 secFreeErrorResponse@Base 4.2.0 secFreeTokenResponse@Base 4.0.0 oidc-agent-4.2.6/debian/not-installed0000644000175000017500000000000014120404223017003 0ustar marcusmarcusoidc-agent-4.2.6/debian/watch0000644000175000017500000000022614120404223015346 0ustar marcusmarcusversion=4 opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/oidc-agent-$1\.tar\.gz/ \ https://github.com/indigo-dc/oidc-agent/tags .*/v?(\d\S+)\.tar\.gz oidc-agent-4.2.6/debian/gbp.conf0000644000175000017500000000021014170031216015730 0ustar marcusmarcus[DEFAULT] debian-branch=debian/unstable upstream-branch=master merge-mode=merge upstream-vcs-tag=v%{version}s upstream-tag=v%{version}s oidc-agent-4.2.6/debian/salsa-ci.yml0000644000175000017500000000062014170031216016535 0ustar marcusmarcus--- include: - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/salsa-ci.yml - https://salsa.debian.org/salsa-ci-team/pipeline/raw/master/pipeline-jobs.yml reprotest: allow_failure: true reprotest-fileordering: allow_failure: true variables: SALSA_CI_IGNORED_BRANCHES: 'master|bump-version-4.2.5' SALSA_CI_REPROTEST_ENABLE_DIFFOSCOPE: 1 SALSA_CI_ENABLE_ATOMIC_REPROTEST: 1 oidc-agent-4.2.6/debian/source/0000755000175000017500000000000014167074355015641 5ustar marcusmarcusoidc-agent-4.2.6/debian/source/format0000644000175000017500000000001414120404223017023 0ustar marcusmarcus3.0 (quilt) oidc-agent-4.2.6/debian/source/options0000644000175000017500000000006614167074355017261 0ustar marcusmarcusextend-diff-ignore = "^(docker|rpm|windows|gitbook)/" oidc-agent-4.2.6/debian/liboidc-agent-dev.install0000644000175000017500000000017014167074355021204 0ustar marcusmarcususr/include/oidc-agent usr/lib/${DEB_TARGET_MULTIARCH}/liboidc-agent.so usr/lib/${DEB_TARGET_MULTIARCH}/liboidc-agent.a oidc-agent-4.2.6/debian/patches/0000755000175000017500000000000014170003371015750 5ustar marcusmarcusoidc-agent-4.2.6/debian/patches/series0000644000175000017500000000000014170003371017153 0ustar marcusmarcusoidc-agent-4.2.6/debian/rules0000755000175000017500000000256114167074355015425 0ustar marcusmarcus#!/usr/bin/make -f # vi: ts=8 sw=8 noet export DEB_BUILD_MAINT_OPTIONS = hardening=+all # USE CJSON_SO / USE_LIST_SO: # If set to 1 it will not use the shipped version, but the shared library # installed via the corresponding lib[cjson|list]dev packages export USE_CJSON_SO = 1 export USE_LIST_SO = 0 export BASEDIR = $(shell pwd)/debian/tmp export BIN_AFTER_INST_PATH = /usr export BIN_PATH = $(BASEDIR)/usr export MAN_PATH = $(BASEDIR)/usr/share/man export CONFIG_PATH = $(BASEDIR)/etc export CONFIG_AFTER_INST_PATH = /etc export BASH_COMPLETION_PATH = $(BASEDIR)/usr/share/bash-completion/completions export DESKTOP_APPLICATION_PATH = $(BASEDIR)/usr/share/applications export XSESSION_PATH = $(BASEDIR)/etc/X11 export PROMPT_MAN_PATH = $(BASEDIR)/usr/share/man export PROMPT_BIN_PATH = $(BASEDIR)/usr #export AGENTSERVER_BIN_PATH = $(BASEDIR)/usr #export AGENTSERVER_MAN_PATH = $(BASEDIR)/usr/share/man export LIB_PATH = $(BASEDIR)/usr/lib/$(DEB_TARGET_MULTIARCH) export LIBDEV_PATH = $(BASEDIR)/usr/lib/$(DEB_TARGET_MULTIARCH) export INCLUDE_PATH = $(BASEDIR)/usr/include %: dh $@ --no-parallel # strange: without the override install_lib seems to happen, install_lib-dev not override_dh_auto_install: make install install_lib install_lib-dev oidc-agent-4.2.6/debian/upstream/0000755000175000017500000000000014120404223016155 5ustar marcusmarcusoidc-agent-4.2.6/debian/upstream/metadata0000644000175000017500000000045314120404223017662 0ustar marcusmarcusBug-Database: https://github.com/indigo-dc/oidc-agent/issues Bug-Submit: https://github.com/indigo-dc/oidc-agent/issues/new/choose Documentation: https://indigo-dc.gitbooks.io/oidc-agent/ Repository: https://github.com/indigo-dc/oidc-agent Repository-Browse: https://github.com/indigo-dc/oidc-agent oidc-agent-4.2.6/debian/copyright0000644000175000017500000001120214167074355016270 0ustar marcusmarcusFormat: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: oidc-agent Upstream-Contact: Gabriel Zachmann Source: https://github.com/indigo-dc/oidc-agent Files: * Copyright: 2017 - 2021 Gabriel Zachmann License: MIT-License License: MIT-License Copyright (c) 2017 - 2021 Karlsruhe Institute of Technology - Steinbuch Centre for Computing . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: lib/cJSON/* Copyright: 2009 - 2017 Dave Gamble License: MIT . Copyright (c) 2009-2017 Dave Gamble and cJSON contributors . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: lib/list/* Copyright: TJ Holowaychuk License: MIT . Copyright (c) 2009-2010 TJ Holowaychuk . Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Files: src/utils/disableTracing.* src/utils/printer.h Copyright: 2016, Darren Tucker License: ISC Copyright (c) 2016 Darren Tucker. All rights reserved. . Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. . THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. oidc-agent-4.2.6/lgtm.yml0000644000175000017500000000010214120404223014552 0ustar marcusmarcuspath_classifiers: library: - lib queries: - include: cpp/ oidc-agent-4.2.6/VERSION0000644000175000017500000000000614170031216014142 0ustar marcusmarcus4.2.6 oidc-agent-4.2.6/config/0000755000175000017500000000000014120404223014340 5ustar marcusmarcusoidc-agent-4.2.6/config/oidc-agent-service.options0000644000175000017500000000030014120404223021416 0ustar marcusmarcus# OIDC_AGENT=/usr/bin/oidc-agent # Default /usr/bin/oidc-agent # START_AGENT_WITH_XSESSION="False" # Default True # OIDC_AGENT_RESTART_WITH_SAME_OPTS="False" # Default True # OIDC_AGENT_OPTS= oidc-agent-4.2.6/config/pubclients.config0000644000175000017500000000541714120404223017706 0ustar marcusmarcus# This file contains public clients that are usable by oidc-gen. # You don't have to edit this file. To pass client credentials (and other # information) to oidc-gen you can also use the -f option. # # The file has the following format: Each line contains one entry (i.e. one # public client). oidc-gen expects that there is only one entry per issuer. To # be more precise the first match will be used. Each entry must have the # following format: # [:]@issuer[@] # is a space separated list of scopes 854595242827-tkecrgj4b8bpchermrba6vs5ig7a4o2c.apps.googleusercontent.com:o7nZynvNCrp7u4WbhqopIpd1@https://accounts.google.com/@openid profile 7b3b85df-1965-41b9-b4e2-476f0eb0d5df@https://oidc.scc.kit.edu/auth/realms/kit/ 70806df4-3fad-4c6c-a7e8-49b554c85b6f@https://iam-test.indigo-datacloud.eu/@address phone openid profile offline_access eduperson_scoped_affiliation aarc email eduperson_entitlement f1f5d80d-849e-4324-9676-8b103604281f@https://iam.deep-hybrid-datacloud.eu/@address iam phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement 441d0d59-bfb3-430e-8142-10b73cae3cc4@https://iam.extreme-datacloud.eu/@address phone openid profile offline_access eduperson_scoped_affiliation email eduperson_entitlement wlcg 222df837-7c79-431f-ae80-5a75ec56e324@https://iam-demo.cloud.cnaf.infn.it/@address phone openid email profile offline_access oidc-agent@https://aai-dev.egi.eu/oidc/@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id cert_entitlement orcid ssh_public_key oidc-agent@https://aai-demo.egi.eu/oidc/@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id oidc-agent@https://aai.egi.eu/oidc/@openid profile email offline_access eduperson_entitlement eduperson_scoped_affiliation eduperson_unique_id 7c98b37b-71ab-4b35-aade-25642d4695e6@https://services.humanbrainproject.eu/oidc/@hbp.users hbp.documents hbp.gerrit offline_access hbp.task hbp.provenance openid hbp.collab hbp.collab.self hbp.groups profile ef13f849-b1ce-4c10-b28f-4a5f19b84fe0@https://login.elixir-czech.org/oidc/@country address forwardedScopedAffiliations phone openid offline_access profile bona_fide_status email eduperson_entitlement 7d6ad1c2-6647-4846-b0d5-c9f6ad0987d0@https://wlcg.cloud.cnaf.infn.it/@openid profile email address phone offline_access wlcg wlcg.groups storage.read:/ storage.create:/ storage.modify:/ compute.read compute.modify compute.create compute.cancel eduperson_entitlement eduperson_scoped_affiliation public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://login.helmholtz.de/oauth2 public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://login-dev.helmholtz.de/oauth2 public-oidc-agent:rE9CsA4T4UkgSVccErSD@https://b2access.eudat.eu/oauth2/ oidc-agent-4.2.6/config/issuer.config0000644000175000017500000000276414120404223017052 0ustar marcusmarcushttps://iam-test.indigo-datacloud.eu/ https://iam-test.indigo-datacloud.eu/manage/dev/dynreg https://iam.deep-hybrid-datacloud.eu/ https://iam.deep-hybrid-datacloud.eu/manage/dev/dynreg deep-hdc-iam-support@lists.cnaf.infn.it https://iam.extreme-datacloud.eu/ https://iam.extreme-hybrid-datacloud.eu/manage/dev/dynreg deep-xdc-iam-support@lists.cnaf.infn.it https://iam-demo.cloud.cnaf.infn.it/ https://iam-demo.cloud.cnaf.infn.it/manage/dev/dynreg https://b2access.eudat.eu/oauth2/ https://b2access.eudat.eu/ https://b2access-integration.fz-juelich.de/oauth2 https://b2access-integration.fz-juelich.de/ https://login-dev.helmholtz.de/oauth2/ https://login-dev.helmholtz.de/ https://login.helmholtz.de/oauth2/ https://login.helmholtz.de/ https://services.humanbrainproject.eu/oidc/ https://services.humanbrainproject.eu/oidc/manage/dev/dynreg platform@humanbrainproject.eu https://accounts.google.com/ https://console.developers.google.com/ https://aai.egi.eu/oidc/ egi-aai-checkin@lists.grnet.gr https://aai-demo.egi.eu/oidc/ egi-aai-checkin@lists.grnet.gr https://aai-dev.egi.eu/oidc https://aai-dev.egi.eu/oidc/manage/admin/client/new egi-aai-checkin@lists.grnet.gr https://login.elixir-czech.org/oidc/ https://login.elixir-czech.org/oidc/manage/dev/dynreg/new aai-contact@elixir-europe.org https://oidc.scc.kit.edu/auth/realms/kit/ https://www.scc.kit.edu/dienste/openid-connect.php https://www.scc.kit.edu/personen/matthias.bonn.php https://wlcg.cloud.cnaf.infn.it/ https://wlcg.cloud.cnaf.infn.it/manage/dev/dynreg oidc-agent-4.2.6/config/Xsession/0000755000175000017500000000000014120404223016153 5ustar marcusmarcusoidc-agent-4.2.6/config/Xsession/91oidc-agent0000644000175000017500000000120114120404223020254 0ustar marcusmarcus# This file is sourced by Xsession(5), not executed. # Do not edit this file. To configure Xsession integration create a file called # oidc-agent-service.options in your oidc-agent directory or edit # /etc/oidc-agent/oidc-agent-service.options for setting defaults for all users. # To disable Xsession integration use the START_AGENT_WITH_XSESSION variable. # Use OIDC_AGENT_OPTS to set the arguments that should be used when the agent is # started. OIDC_AGENT_SERVICE=/usr/bin/oidc-agent-service if [ -x "$OIDC_AGENT_SERVICE" ] && [ -z "$OIDC_SOCK" ]; then eval `$OIDC_AGENT_SERVICE start-from-x` fi # vim:set ai et sts=2 sw=2 tw=80: oidc-agent-4.2.6/config/privileges/0000755000175000017500000000000014120404223016511 5ustar marcusmarcusoidc-agent-4.2.6/config/privileges/memory.priv0000644000175000017500000000002514120404223020720 0ustar marcusmarcusmmap munmap mprotect oidc-agent-4.2.6/config/privileges/httpserver.priv0000644000175000017500000000016314120404223021621 0ustar marcusmarcusprctl PR_SET_PDEATHSIG SIGTERM kill rt_sigsuspend dup dup2 accept4 nanosleep shutdown rt_sigprocmask gettid tgkill oidc-agent-4.2.6/config/privileges/daemon.priv0000644000175000017500000000011714120404223020655 0ustar marcusmarcusclone getppid rt_sigaction set_robust_list setsid chdir / umask open /dev/null oidc-agent-4.2.6/config/privileges/socket.priv0000644000175000017500000000011614120404223020701 0ustar marcusmarcussendto socket read ioctl connect close select write fstat lseek access openat oidc-agent-4.2.6/config/privileges/kill.priv0000644000175000017500000000000514120404223020341 0ustar marcusmarcuskill oidc-agent-4.2.6/config/privileges/write.priv0000644000175000017500000000001614120404223020542 0ustar marcusmarcusopen rw write oidc-agent-4.2.6/config/privileges/logging.priv0000644000175000017500000000003514120404223021037 0ustar marcusmarcusgetpid sendto socket connect oidc-agent-4.2.6/config/privileges/http.priv0000644000175000017500000000022114120404223020365 0ustar marcusmarcuspipe2 poll futex access stat sendmmsg recvfrom recvmsg recvmmsg recv setsockopt getsockopt getpeername getsockname sysinfo getuid madvise writev oidc-agent-4.2.6/config/privileges/signal.priv0000644000175000017500000000003414120404223020665 0ustar marcusmarcusrt_sigaction rt_sigprocmask oidc-agent-4.2.6/config/privileges/print.priv0000644000175000017500000000005614120404223020550 0ustar marcusmarcuswrite stdout|stderr fstat stdin|stdout|stderr oidc-agent-4.2.6/config/privileges/prompt.priv0000644000175000017500000000002714120404223020733 0ustar marcusmarcusread stdin ioctl stdin oidc-agent-4.2.6/config/privileges/general.priv0000644000175000017500000000002414120404223021024 0ustar marcusmarcusexit_group exit brk oidc-agent-4.2.6/config/privileges/agentIpc.priv0000644000175000017500000000010514120404223021141 0ustar marcusmarcusmkdir /tmp/ rmdir /tmp/ unlink /tmp/ bind fcntl listen accept openat oidc-agent-4.2.6/config/privileges/read.priv0000644000175000017500000000005014120404223020321 0ustar marcusmarcusaccess open r read fstat getdents lseek oidc-agent-4.2.6/config/privileges/sleep.priv0000644000175000017500000000001214120404223020514 0ustar marcusmarcusnanosleep oidc-agent-4.2.6/config/privileges/time.priv0000644000175000017500000000002414120404223020345 0ustar marcusmarcusopen /etc/localtime oidc-agent-4.2.6/config/privileges/crypt.priv0000644000175000017500000000003314120404223020550 0ustar marcusmarcusgetrandom open /dev/random oidc-agent-4.2.6/config/scheme_handler/0000755000175000017500000000000014120404223017301 5ustar marcusmarcusoidc-agent-4.2.6/config/scheme_handler/oidc-gen.apple0000644000175000017500000000020414120404223022005 0ustar marcusmarcuson open location this_URL tell application "Terminal" to do script "oidc-gen --codeExchange='" & this_URL & "'" end open location oidc-agent-4.2.6/config/scheme_handler/oidc-gen.desktop0000644000175000017500000000040614120404223022361 0ustar marcusmarcus[Desktop Entry] Version=1.1 Name=oidc-gen Comment=oidc-gen auth code redirect Icon=utilities-terminal Terminal=true Type=Application MimeType=x-scheme-handler/edu.kit.data.oidc-agent; NoDisplay=true Keywords=oidc-agent;authorization code;code exchange;redirect; oidc-agent-4.2.6/config/scheme_handler/Info.plist.template0000644000175000017500000000034414120404223023064 0ustar marcusmarcus CFBundleURLTypes CFBundleURLName oidc-gen CFBundleURLSchemes edu.kit.data.oidc-agent oidc-agent-4.2.6/config/bash-completion/0000755000175000017500000000000014167074355017450 5ustar marcusmarcusoidc-agent-4.2.6/config/bash-completion/oidc-agent0000644000175000017500000002106614167074355021412 0ustar marcusmarcus if [[ -z "${OIDC_CONFIG_DIR}" ]]; then if [ -d ~/.config ];then agentdir="$HOME/.config/oidc-agent" else agentdir="$HOME/.oidc-agent" fi else agentdir="$OIDC_CONFIG_DIR" fi _elementIn () { local e match="$1" shift for e; do [[ "$e" == "$match" ]] && return 0; done return 1 } _getOptions() { IFS=$'\r\n' GLOBIGNORE='*' command eval 'optLines=($($1 -h | grep "^[[:space:]]*-"))' usage="$($1 -h | grep Usage:)" usage=${usage#*\[OPTION...\]} #might add something for ACCOUNT_SHORTNAME here usage=$(echo $usage | sed -r -e 's/^.*\[?ACCOUNT_SHORTNAME\]?[[:space:]]*//') IFS=$'\r\n' GLOBIGNORE='*' command eval 'singleOpts=($(echo "${usage}" | sed "s/|/\n/g"| sed -e "s/^[[:space:]]*//" -e "s/[[:space:]]*$//"))' opts="" singleOptsLong="--help#--usage#--version" for var in "${optLines[@]}" do IFS=$'\r\n' GLOBIGNORE='*' command eval 'elements=($(echo "${var}" | sed -e "s/^[[:space:]]*//" -e "s/,//g" -e "s/[[:space:]]*$//"| sed "s/[[:space:]]/\n/g"))' shortname="" longnames=() for el in "${elements[@]}" do if [[ $el == --* ]] ; then longname=$el longnames+=($longname) if [[ $longname == *=* ]]; then if [[ $longname == *\[=*\] ]]; then longparam="${longname%\[=*}=" opts+="${longname%\[=*} #" argument=${longname#*\[=} argument=${argument:0:-1} else longparam="${longname%=*}=" argument=${longname#*=} fi argument=$(echo $argument | sed 's/|/ &/g') suboptions[$longparam]="$argument " else longparam="$longname " fi opts+="$longparam#" fi if [[ $el == -[a-zA-Z], ]]; then shortname="${el:0:-1}" fi done if _elementIn $shortname "${singleOpts[@]}"; then for longname in "${longnames[@]}"; do singleOptsLong+="#${longname}" done fi done } _matchFiles() { local IFS=$'\n' local LASTCHAR=' ' COMPREPLY=($(compgen -o plusdirs -f -- "$1")) if [ ${#COMPREPLY[@]} = 1 ]; then [ -d "$COMPREPLY" ] && LASTCHAR=/ COMPREPLY=$(printf %q%s "$COMPREPLY" "$LASTCHAR") else for ((i=0; i < ${#COMPREPLY[@]}; i++)); do [ -d "${COMPREPLY[$i]}" ] && COMPREPLY[$i]=${COMPREPLY[$i]}/ done fi return 0 } _suboption() { ret=0 for key in "${!suboptions[@]}"; do if [[ "$key" == "$1" ]]; then option="${suboptions[$key]}" case $option in TIME[[:space:]] ) local value=999999999999 COMPREPLY=( $( compgen -W "{${value:1:((${#value}-2))}}" \ -- "$cur" ) ) ;; FILE[[:space:]] ) _matchFiles ${cur} ;; ISSUER[[:space:]] | ISSUER_URL[[:space:]] ) if [ -f "$agentdir/issuer.config" ]; then issuers=`cat $agentdir/issuer.config | awk '{print $1}' | sed -e 's/$/& /g'` else issuers="" fi compreply=( $(compgen -W "${issuers[*]}" -- "$cur") ) COMPREPLY=( "${compreply[@]}" ) __ltrim_colon_completions "$cur" if [[ ${#COMPREPLY[@]} -gt 1 ]]; then local common_prefix _common_prefix common_prefix "${COMPREPLY[@]}" if [[ $common_prefix == "$CUR" ]]; then COMPREPLY=( "${compreply[@]}" " " ) fi fi ;; [[:upper:]_]* ) ;; [[:lower:]]* ) local IFS=$'|\n' COMPREPLY=( $(compgen -W "${option}" -- ${cur}) ) ;; esac ret=1 break fi done } _isSingleOpt() { for word in $singleOptsLong; do if [[ "$word" = "$1" ]]; then return 0 fi done return 1 } _needArgument() { local IFS=$'#' for ((i=1; i<$COMP_CWORD; i++)); do local word="${COMP_WORDS[i]}" local pre="${COMP_WORDS[i-1]}" if [[ $word != -* ]] && [[ "$word" != "=" ]] && [[ "$pre" != "=" ]]; then return 1 fi if _isSingleOpt $word ; then return 1 fi done return 0 } _oidc-gen() { local cur prev prevprev word cword opts local CUR=$2 COMPREPLY=() _get_comp_words_by_ref -n : cur prev words cword prevprev="${words[cword-2]}" local IFS=$'\t\n' shortnames=`ls $agentdir 2>/dev/null | grep -v "config" | sed -e 's/$/& /g'` declare -A suboptions _getOptions "oidc-gen" local IFS=$'#\n' if [[ "$cur" == "=" ]]; then prev+="=" cur="" fi if [[ "$prev" == "=" ]]; then prev="$prevprev$prev" fi _suboption $prev if [[ "$prev" == "--print=" || "$prev" == "-p" ]]; then _matchFiles ${cur} local oidcDirFiles=`ls $agentdir 2>/dev/null | sed -e 's/$/& /g'` COMPREPLY+=( $(compgen -W "${oidcDirFiles}" -- ${cur}) ) fi if [[ "$prev" == "--update=" || "$prev" == "-u" ]]; then _matchFiles ${cur} local oidcDirFiles=`ls $agentdir 2>/dev/null | sed -e 's/$/& /g'` COMPREPLY+=( $(compgen -W "${oidcDirFiles}" -- ${cur}) ) fi if [[ "$prev" == "--manual" || "$prev" == "-m" ]]; then COMPREPLY=( $(compgen -W "${shortnames}" -- ${cur}) ) fi if [[ "x$ret" == "x1" ]]; then return 0 fi if [[ ${cur} == --* ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi return 0 } _common_prefix() { local vname=$1 local first prefix v shift if [[ $# -eq 0 ]]; then local "$vname" && _upvars -v "$vname" "" return 0 fi first=$1 shift for ((i = 0; i < ${#first}; ++i)); do prefix=${first:0:i+1} for v; do if [[ ${v:0:i+1} != "$prefix" ]]; then local "$vname" && _upvars -v "$vname" "${first:0:i}" return 0 fi done done local "$vname" && _upvars -v "$vname" "$first" return 0 } _oidc-token() { local cur prev prevprev word cword opts local CUR=$2 COMPREPLY=() _get_comp_words_by_ref -n : cur prev words cword prevprev="${words[cword-2]}" local IFS=$'\t\n' declare -A suboptions _getOptions "oidc-token" # shortnames=`ls $agentdir 2>/dev/null | grep -v "config" | sed -e 's/$/& /g'` # issuers=`cat $agentdir/issuer.config | awk '{print $1}' | sed -e 's/$/& /g'` if [ -f "$agentdir/issuer.config" ]; then shortnamesAndIssuers=`{ ls $agentdir 2>/dev/null | grep -v "config" & cat $agentdir/issuer.config | awk '{print $1}'; } | sed -e 's/$/& /g'` else shortnamesAndIssuers="" fi if [[ ${cur} == -* ]] ; then local IFS=$'#\n' COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi if [[ "$cur" == "=" ]]; then prev+="=" cur="" fi if [[ "$prev" == "=" ]]; then prev="$prevprev$prev" fi # _suboption $prev # if [[ "x$ret" == "x1" ]]; then # return 0 # fi if [[ ${cur} == * ]] ; then compreply=( $(compgen -W "${shortnamesAndIssuers[*]}" -- "$cur") ) COMPREPLY=( "${compreply[@]}" ) __ltrim_colon_completions "$cur" if [[ ${#COMPREPLY[@]} -gt 1 ]]; then local common_prefix _common_prefix common_prefix "${COMPREPLY[@]}" if [[ $common_prefix == "$CUR" ]]; then COMPREPLY=( "${compreply[@]}" " " ) fi fi return 0 fi return 0 } _oidc-add() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" prevprev="${COMP_WORDS[COMP_CWORD-2]}" local IFS=$'\t\n' declare -A suboptions _getOptions "oidc-add" shortnames=`ls $agentdir 2>/dev/null | grep -v "config" | sed -e 's/$/& /g'` if [[ "$cur" == "=" ]]; then prev+="=" cur="" fi if [[ "$prev" == "=" ]]; then prev="$prevprev$prev" fi _suboption $prev if [[ "x$ret" == "x1" ]]; then return 0 fi if [[ ${cur} == -* ]] ; then local IFS=$'#\n' COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi if [[ ${cur} == * ]] && _needArgument ; then COMPREPLY=( $(compgen -W "${shortnames}" -- ${cur}) ) return 0 fi return 0 } _oidc-agent() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" local IFS=$'\t\n' declare -A suboptions _getOptions "oidc-agent" if [[ ${cur} == * ]] ; then local IFS=$'#\n' COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi return 0 } _oidc-keychain() { local cur prev opts COMPREPLY=() cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" local IFS=$'\t\n' declare -A suboptions _getOptions "oidc-agent" opts+="--accounts #" if _elementIn "--accounts" "${COMP_WORDS[@]}"; then shortnames=`ls $agentdir 2>/dev/null | grep -v "config" | sed -e 's/$/& /g'` COMPREPLY=( $(compgen -W "${shortnames}" -- ${cur}) ) return 0 fi if [[ ${cur} == * ]] ; then local IFS=$'#\n' COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) return 0 fi return 0 } complete -o nospace -F _oidc-gen oidc-gen complete -o nospace -F _oidc-token oidc-token complete -o nospace -F _oidc-add oidc-add complete -o nospace -F _oidc-agent oidc-agent complete -o nospace -F _oidc-keychain oidc-keychain oidc-agent-4.2.6/config/bash-completion/oidc-agent-service0000644000175000017500000000010314120404223023011 0ustar marcusmarcuscomplete -W "start stop kill restart restart-s" oidc-agent-service oidc-agent-4.2.6/.clang-format0000644000175000017500000000633314167074355015477 0ustar marcusmarcus--- Language: Cpp # BasedOnStyle: Google AccessModifierOffset: -1 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: true AlignConsecutiveDeclarations: true AlignEscapedNewlines: Left AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: true AllowShortCaseLabelsOnASingleLine: true AllowShortFunctionsOnASingleLine: All AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: true BinPackArguments: true BinPackParameters: true BraceWrapping: AfterClass: false AfterControlStatement: false AfterEnum: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false AfterUnion: false AfterExternBlock: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeColon BreakAfterJavaFieldAnnotations: true BreakStringLiterals: true ColumnLimit: 80 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - foreach - Q_FOREACH - BOOST_FOREACH IncludeBlocks: Regroup IncludeCategories: - Regex: '^' Priority: 2 - Regex: '^<.*\.h>' Priority: 1 - Regex: '^<.*' Priority: 2 - Regex: '.*' Priority: 3 IncludeIsMainRegex: '([-_](test|unittest))?$' IndentCaseLabels: true IndentPPDirectives: None IndentWidth: 2 IndentWrappedFunctionNames: false JavaScriptQuotes: Leave JavaScriptWrapImports: true KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '' MacroBlockEnd: '' MaxEmptyLinesToKeep: 1 NamespaceIndentation: None ObjCBlockIndentWidth: 2 ObjCSpaceAfterProperty: false ObjCSpaceBeforeProtocolList: false PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 1 PenaltyBreakComment: 300 PenaltyBreakFirstLessLess: 120 PenaltyBreakString: 1000 PenaltyExcessCharacter: 1000000 PenaltyReturnTypeOnItsOwnLine: 200 PointerAlignment: Left RawStringFormats: - Delimiters: - pb Language: TextProto BasedOnStyle: google ReflowComments: true SortIncludes: true SortUsingDeclarations: true SpaceAfterCStyleCast: false SpaceAfterTemplateKeyword: true SpaceBeforeAssignmentOperators: true SpaceBeforeParens: ControlStatements SpaceInEmptyParentheses: false SpacesBeforeTrailingComments: 2 SpacesInAngles: false SpacesInContainerLiterals: true SpacesInCStyleCastParentheses: false SpacesInParentheses: false SpacesInSquareBrackets: false Standard: Auto TabWidth: 4 UseTab: Never ... oidc-agent-4.2.6/docker/0000755000175000017500000000000014167074355014366 5ustar marcusmarcusoidc-agent-4.2.6/docker/docker-build.sh0000755000175000017500000001134214167074355017272 0ustar marcusmarcus#!/bin/bash ### Build using: # DIST=ubuntu_bionic # IMAGE=build-oidc-agent-ubuntu:bionic # DIST=fedora_34 # IMAGE=build-oidc-agent-fedora:34 # docker run -it --rm -v `dirname $PWD`:/home/build $IMAGE /home/build/`basename $PWD`/docker/docker-build.sh `basename $PWD` $DIST ## ASSUMPTION: /home/build/$PACKAGE_DIR holds the sources for the package to be built ## ASSUMPTION: /home/build is on the host system. ## ASSUMPTION: /home/build/results is on the host system. BASE="/home/build" PACKAGE_DIR=$1 DIST=$2 ACTION=$3 OUTPUT="$BASE/results" echo "====================================================================" echo "=========docker-build.sh============================================" echo "export BASE=$BASE" echo "export PACKAGE_DIR=$PACKAGE_DIR" echo "export DIST=$DIST" echo "export ACTION=$ACTION" echo "export OUTPUT=$OUTPUT" test -z $DIST && { echo "Must specify DIST as 2nd parameter" exit } common_prepare_dirs() { mkdir -p /tmp/build mkdir -p $OUTPUT/$DIST cp -af $BASE/$PACKAGE_DIR /tmp/build cd /tmp/build/$PACKAGE_DIR } common_fix_output_permissions() { UP_UID=`stat -c '%u' $BASE` UP_GID=`stat -c '%g' $BASE` chown $UP_UID:$UP_GID $OUTPUT chown -R $UP_UID:$UP_GID $OUTPUT/$DIST } bionic_build_package() { make bionic-debsource && \ dpkg-buildpackage -uc -us } buster_build_package() { make buster-debsource && \ dpkg-buildpackage -uc -us } focal_build_package() { make focal-debsource && \ dpkg-buildpackage -uc -us } debian_build_package() { make debsource && \ dpkg-buildpackage -uc -us } debian_copy_output() { echo "Moving output:" ls -l .. mv ../${PACKAGE_DIR}[_-]* $OUTPUT/$DIST mv ../lib* $OUTPUT/$DIST } rpm_centos7_patch() { make centos7_patch } rpm_build_package() { cd /tmp/build/$PACKAGE_DIR make distclean make rpmsource make rpms } rpm_copy_output() { ls -l rpm/rpmbuild/RPMS/*/* ls -l rpm/rpmbuild/SRPMS/ echo "-----" mv rpm/rpmbuild/RPMS/*/*rpm $OUTPUT/$DIST mv rpm/rpmbuild/SRPMS/*rpm $OUTPUT/$DIST } ########################################################################### common_prepare_dirs [ "x${ACTION}" == "xtest" ] && { case "$DIST" in debian_*|ubuntu_*) echo -e "\n\ninstalling oidc-agent-cli and liboidc-agent4" apt install -y \ $OUTPUT/$DIST/oidc-agent-cli_*deb \ $OUTPUT/$DIST/liboidc-agent4_*deb || exit 1 echo -e "\n\ninstalling oidc-agent-desktop" apt install -y \ $OUTPUT/$DIST/oidc-agent-desktop_4*deb || exit 2 echo -e "\n\ninstalling liboidc-agent-dev" apt install -y \ $OUTPUT/$DIST/liboidc-agent-dev_4*deb || exit 3 ;; fedora_*|centos_*) echo -e "\n\ninstalling oidc-agent-cli and liboidc-agent4" yum install -y \ $OUTPUT/$DIST/oidc-agent-cli-4*rpm \ $OUTPUT/$DIST/liboidc-agent4-4*rpm || exit 1 echo -e "\n\ninstalling oidc-agent-desktop" yum install -y \ $OUTPUT/$DIST/oidc-agent-desktop-4*rpm || exit 2 echo -e "\n\ninstalling liboidc-agent-dev" yum install -y \ $OUTPUT/$DIST/liboidc-agent-devel-4*rpm || exit 3 ;; opensuse_*) echo -e "\n\ninstalling oidc-agent-cli and liboidc-agent4" zypper -n --no-gpg-checks install \ $OUTPUT/$DIST/oidc-agent-cli-4*rpm \ $OUTPUT/$DIST/liboidc-agent4-4*rpm || exit 1 #echo -e "\n\ninstalling oidc-agent-desktop" #zypper -n --no-gpg-checks install \ # $OUTPUT/$DIST/oidc-agent-desktop-4*rpm || exit 2 echo -e "\n\ninstalling liboidc-agent-dev" zypper -n --no-gpg-checks install \ $OUTPUT/$DIST/liboidc-agent-devel-4*rpm || exit 3 ;; esac } [ "x${ACTION}" == "xtest" ] || { case "$DIST" in debian_bullseye|debian_bookworm) debian_build_package debian_copy_output ;; debian_buster) buster_build_package debian_copy_output ;; ubuntu_bionic) bionic_build_package debian_copy_output ;; ubuntu_focal|ubuntu_hirsute|ubuntu_impish) focal_build_package debian_copy_output ;; centos_7) rpm_centos7_patch rpm_build_package rpm_copy_output ;; centos_8|centos_7|fedora*) rpm_build_package rpm_copy_output ;; opensuse_15*|opensuse_tumbleweed|sle*) rpm_build_package rpm_copy_output ;; esac common_fix_output_permissions } oidc-agent-4.2.6/docker/fedora:34.dependencies0000644000175000017500000000021714167074355020437 0ustar marcusmarcusdesktop-file-utils help2man libcurl-devel libmicrohttpd-devel libseccomp-devel libsecret-devel libsodium-devel libsodium-static qrencode-devel oidc-agent-4.2.6/docker/opensuse_tumbleweed:latest.dependencies0000777000175000017500000000000014167074355032132 2opensuse_leap:15.2.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/ubuntu:impish.dependencies0000777000175000017500000000000014167074355026552 2ubuntu:focal.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/ubuntu:focal.dependencies0000644000175000017500000000017614167074355021363 0ustar marcusmarcuscheck help2man libcurl4-openssl-dev libmicrohttpd-dev libqrencode-dev libseccomp-dev libsecret-1-dev libsodium-dev pkg-config oidc-agent-4.2.6/docker/opensuse_leap:15.3.dependencies0000777000175000017500000000000014167074355030010 2opensuse_leap:15.2.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/ubuntu:hirsute.dependencies0000777000175000017500000000000014167074355026744 2ubuntu:focal.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/centos:8.dependencies0000777000175000017500000000000014167074355024434 2centos:7.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/debian:bookworm.dependencies0000777000175000017500000000000014167074355027460 2debian:bullseye.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/centos:7.dependencies0000644000175000017500000000021714167074355020412 0ustar marcusmarcusdesktop-file-utils help2man libcurl-devel libmicrohttpd-devel libseccomp-devel libsecret-devel libsodium-devel libsodium-static qrencode-devel oidc-agent-4.2.6/docker/docker.mk0000644000175000017500000005230114167074355016167 0ustar marcusmarcus# vim: ft=make # Parallel builds: MAKEFLAGS += -j9 DOCKER_BASE=`dirname ${PWD}` PACKAGE_DIR=`basename ${PWD}` DOCKER_RUN_PARAMS=--tty -it --rm SHELL=bash DOCKER_GEN_FROM_IMAGE = "FROM `echo $@ | sed s/docker_//| cut -d: -f 1`:`echo $@ | sed s/docker_//| cut -d: -f 2`" DOCKER_APT_INIT = "ENV DEBIAN_FRONTEND noninteractive\nRUN apt update && apt -y upgrade" DOCKER_APT_BUILD_ESSENTIALS = "RUN apt install -y build-essential dh-make quilt devscripts" DOCKER_YUM_DISABLE_FASTESTMIRROR= "RUN sed s/enabled=1/enabled=0/ -i /etc/yum/pluginconf.d/fastestmirror.conf" DOCKER_YUM_BUILD_ESSENTIALS = "RUN yum -y install make rpm-build" DOCKER_YUM_GROUPS_BASE = "RUN yum -y groups mark convert" DOCKER_YUM_GROUPS_DEVELTOOLS = "RUN yum -y groupinstall \"Development tools\"" DOCKER_YUM_EPEL_RELEASE = "RUN yum -y install epel-release" DOCKER_YUM_REMI_RELEASE = "RUN dnf -y install https://rpms.remirepo.net/enterprise/remi-release-8.rpm" DOCKER_YUM_ENABLE_POWERTOOLS = "RUN dnf config-manager --set-enabled powertools" DOCKER_ZYP_BUILD_ESSENTIALS = "RUN zypper -n install make rpm-build" DOCKER_ZYP_GROUP_DEVEL = "RUN zypper -n install -t pattern devel_C_C++" DOCKER_COPY_DEPENDENCIES = "COPY docker/`echo $@ | sed s/docker_// \ | sed s%registry.opensuse.org/opensuse%opensuse%g \ | sed s%/%_%g`.dependencies /tmp/dependencies.cache" DOCKER_APT_INST_DEPENDENCIES = "RUN xargs apt -y install < /tmp/dependencies.cache" DOCKER_YUM_INST_DEPENDENCIES = "RUN xargs yum -y install < /tmp/dependencies.cache" DOCKER_ZYP_INST_DEPENDENCIES = "RUN xargs zypper -n install < /tmp/dependencies.cache" DOCKER_TAG = "build-$(PACKAGE_DIR)-`echo $@ | sed s/docker_//\ | sed s%registry.opensuse.org/opensuse%opensuse%g \ | sed s%/%_%g`" DOCKER_DIST = "`echo $@ | sed s/dockerised_rpm_//\ | sed s/dockerised_deb_//\ | sed s/dockerised_test_//`" DOCKER_LOG = "docker/log/`echo $@ | sed s/docker_//\ | sed s%registry.opensuse.org/opensuse/leap%opensuse%g \ | sed s%registry.opensuse.org/opensuse/tumbleweed:latest%opensuse_tumbleweed%g \ | sed s%/%_%g\ | sed s/:/_/`.log" DOCKER_BUILD_LOG = "docker/log/`echo $@ | sed s/dockerised_rpm_//\ | sed s/dockerised_deb_//\ | sed s/dockerised_test_//`.log" DOCKER_CONTAINER = "`echo $@ | sed s/dockerised_test_//\ | sed s/dockerised_deb_//\ | sed s/dockerised_rpm_//\ | sed s/_/:/\ | sed s/opensuse:1/opensuse_leap:1/\ | sed s/opensuse:tumbleweed/opensuse_tumbleweed:latest/`" ########################################## DOCKERS ########################################## .PHONY: dockerised_latest_packages dockerised_latest_packages: dockerised_deb_debian_bullseye\ dockerised_deb_debian_bookworm\ dockerised_deb_ubuntu_focal\ dockerised_deb_ubuntu_hirsute\ dockerised_rpm_centos_8\ dockerised_rpm_opensuse_tumbleweed\ dockerised_rpm_fedora35 .PHONY: dockerised_all_deb_packages dockerised_all_deb_packages: dockerised_deb_debian_buster\ dockerised_deb_debian_bullseye\ dockerised_deb_debian_bookworm\ dockerised_deb_ubuntu_bionic\ dockerised_deb_ubuntu_focal\ dockerised_deb_ubuntu_hirsute .PHONY: dockerised_all_rpm_packages dockerised_all_rpm_packages: dockerised_rpm_centos_7\ dockerised_rpm_centos_8\ dockerised_rpm_opensuse_15.2\ dockerised_rpm_opensuse_15.3\ dockerised_rpm_opensuse_tumbleweed\ dockerised_rpm_fedora_34 .PHONY: dockerised_all_packages dockerised_all_packages: dockerised_deb_debian_buster\ dockerised_deb_debian_bullseye\ dockerised_deb_debian_bookworm\ dockerised_deb_ubuntu_bionic\ dockerised_deb_ubuntu_focal\ dockerised_deb_ubuntu_hirsute\ dockerised_rpm_centos_7\ dockerised_rpm_centos_8\ dockerised_rpm_opensuse_15.2\ dockerised_rpm_opensuse_15.3\ dockerised_rpm_opensuse_tumbleweed\ dockerised_rpm_fedora_34 .PHONY: dockerised_test_all dockerised_test_all: dockerised_test_debian_buster\ dockerised_test_debian_bullseye\ dockerised_test_debian_bookworm\ dockerised_test_ubuntu_bionic\ dockerised_test_ubuntu_focal\ dockerised_test_ubuntu_hirsute\ dockerised_test_centos_7\ dockerised_test_centos_8\ dockerised_test_fedora_34\ dockerised_test_opensuse_15.2\ dockerised_test_opensuse_15.3\ dockerised_test_opensuse_tumbleweed .PHONY: dockerised_test_debs dockerised_test_debs: dockerised_test_debian_buster\ dockerised_test_debian_bullseye\ dockerised_test_debian_bookworm\ dockerised_test_ubuntu_bionic\ dockerised_test_ubuntu_focal\ dockerised_test_ubuntu_hirsute .PHONY: dockerised_test_rpms dockerised_test_rpms: dockerised_test_centos_7\ dockerised_test_centos_8\ dockerised_test_fedora_34\ dockerised_test_opensuse_15.2\ dockerised_test_opensuse_15.3\ dockerised_test_opensuse_tumbleweed .PHONY: docker_images docker_images: docker_debian\:buster\ docker_debian\:bullseye\ docker_debian\:bookworm\ docker_ubuntu\:bionic\ docker_ubuntu\:focal\ docker_ubuntu\:hirsute\ docker_centos\:7\ docker_centos\:8\ docker_registry.opensuse.org/opensuse/leap\:15.2\ docker_registry.opensuse.org/opensuse/leap\:15.3\ docker_registry.opensuse.org/opensuse/tumbleweed\:latest\ docker_fedora\:34 .PHONY: docker_clean docker_clean: echo docker image rm build-$(PACKAGE_DIR)-debian:buster || true @docker image rm build-$(PACKAGE_DIR)-debian:bullseye || true @docker image rm build-$(PACKAGE_DIR)-debian:bookworm || true @docker image rm build-$(PACKAGE_DIR)-ubuntu:bionic || true @docker image rm build-$(PACKAGE_DIR)-ubuntu:focal || true @docker image rm build-$(PACKAGE_DIR)-ubuntu:hirsute || true @docker image rm build-$(PACKAGE_DIR)-ubuntu:impish || true @docker image rm build-$(PACKAGE_DIR)-centos:7 || true @docker image rm build-$(PACKAGE_DIR)-centos:8 || true @docker image rm build-$(PACKAGE_DIR)-opensuse_leap:15.2 || true @docker image rm build-$(PACKAGE_DIR)-opensuse_leap:15.3 || true @docker image rm build-$(PACKAGE_DIR)-opensuse_tumbleweed:latest || true @docker image rm build-$(PACKAGE_DIR)-fedora:34 || true @docker image rm build-$(PACKAGE_DIR)-fedora:35 || true ########################################## DEBIAN ########################################## .PHONY: dockerised_deb_buster dockerised_deb_buster: dockerised_deb_debian_buster .PHONY: dockerised_deb_debian_buster dockerised_deb_debian_buster: docker_debian\:buster @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_debian\:buster docker_debian\:buster: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_debian_buster: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_deb_debian_bullseye dockerised_deb_debian_bullseye: docker_debian\:bullseye @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_debian\:bullseye docker_debian\:bullseye: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_debian_bullseye: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_deb_debian_bookworm dockerised_deb_debian_bookworm: docker_debian\:bookworm @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_debian\:bookworm docker_debian\:bookworm: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_debian_bookworm: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} ########################################## UBUNTU ########################################## .PHONY: dockerised_deb_ubuntu_bionic dockerised_deb_ubuntu_bionic: docker_ubuntu\:bionic @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-ubuntu:bionic \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_ubuntu\:bionic docker_ubuntu\:bionic: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_ubuntu_bionic: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_deb_ubuntu_focal dockerised_deb_ubuntu_focal: docker_ubuntu\:focal @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-ubuntu:focal \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_ubuntu\:focal docker_ubuntu\:focal: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_ubuntu_focal: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-debian:buster \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_deb_ubuntu_hirsute dockerised_deb_ubuntu_hirsute: docker_ubuntu\:hirsute @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-ubuntu:hirsute \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_ubuntu\:hirsute docker_ubuntu\:hirsute: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_ubuntu_hirsute: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_deb_ubuntu_impish dockerised_deb_ubuntu_impish: docker_ubuntu\:impish @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-ubuntu:impish \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_ubuntu\:impish docker_ubuntu\:impish: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_APT_INIT)"\n" \ $(DOCKER_APT_BUILD_ESSENTIALS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_APT_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_ubuntu_impish: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} ########################################## CENTOS ########################################## .PHONY: dockerised_rpm_centos_7 dockerised_rpm_centos_7: docker_centos\:7 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-centos:7 \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_centos\:7 docker_centos\:7: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_YUM_DISABLE_FASTESTMIRROR)"\n" \ $(DOCKER_YUM_BUILD_ESSENTIALS)"\n" \ $(DOCKER_YUM_GROUPS_BASE)"\n" \ $(DOCKER_YUM_GROUPS_DEVELTOOLS)"\n" \ $(DOCKER_YUM_EPEL_RELEASE)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_YUM_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_centos_7: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_rpm_centos_8 dockerised_rpm_centos_8: docker_centos\:8 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-centos:8 \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_centos\:8 docker_centos\:8: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_YUM_BUILD_ESSENTIALS)"\n" \ $(DOCKER_YUM_GROUPS_DEVELTOOLS)"\n" \ $(DOCKER_YUM_REMI_RELEASE)"\n" \ $(DOCKER_YUM_ENABLE_POWERTOOLS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_YUM_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_centos_8: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} ########################################## SUSE ########################################## .PHONY: dockerised_rpm_opensuse_15.2 dockerised_rpm_opensuse_15.2: docker_registry.opensuse.org/opensuse/leap\:15.2 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_registry.opensuse.org/opensuse/leap\:15.2 docker_registry.opensuse.org/opensuse/leap\:15.2: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_ZYP_BUILD_ESSENTIALS)"\n" \ $(DOCKER_ZYP_GROUP_DEVEL)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_ZYP_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_opensuse_15.2: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_rpm_opensuse_15.3 dockerised_rpm_opensuse_15.3: docker_registry.opensuse.org/opensuse/leap\:15.3 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_registry.opensuse.org/opensuse/leap\:15.3 docker_registry.opensuse.org/opensuse/leap\:15.3: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_ZYP_BUILD_ESSENTIALS)"\n" \ $(DOCKER_ZYP_GROUP_DEVEL)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_ZYP_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_opensuse_15.3: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_rpm_opensuse_tumbleweed dockerised_rpm_opensuse_tumbleweed: docker_registry.opensuse.org/opensuse/tumbleweed\:latest @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_registry.opensuse.org/opensuse/tumbleweed\:latest docker_registry.opensuse.org/opensuse/tumbleweed\:latest: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_ZYP_BUILD_ESSENTIALS)"\n" \ $(DOCKER_ZYP_GROUP_DEVEL)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_ZYP_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_opensuse_tumbleweed: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} ########################################## FEDORA ########################################## .PHONY: dockerised_rpm_fedora_34 dockerised_rpm_fedora_34: docker_fedora\:34 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-fedora:34\ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_fedora\:34 docker_fedora\:34: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log @echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_YUM_BUILD_ESSENTIALS)"\n" \ $(DOCKER_YUM_GROUPS_DEVELTOOLS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_YUM_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_fedora_34: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} ########################################## NON FUNCTIONAL TARGETS HERE ########################################## .PHONY: dockerised_rpm_fedora_35 dockerised_rpm_fedora_35: docker_fedora\:35 @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-fedora:35\ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_fedora\:35 docker_fedora\:35: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_YUM_BUILD_ESSENTIALS)"\n" \ "RUN echo \"nameserver 9.9.9.9\" > /etc/resolv.conf \n" \ $(DOCKER_YUM_GROUPS_DEVELTOOLS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_YUM_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} dockerised_test_fedora_35: @echo "Logging $@ to ${DOCKER_BUILD_LOG}" @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-${DOCKER_CONTAINER} \ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} test >> ${DOCKER_BUILD_LOG} .PHONY: dockerised_rpm_fedora_rawhide dockerised_rpm_fedora_rawhide: docker_fedora\:rawhide @docker run ${DOCKER_RUN_PARAMS} -v ${DOCKER_BASE}:/home/build \ build-$(PACKAGE_DIR)-fedora:rawhide\ /home/build/${PACKAGE_DIR}/docker/docker-build.sh ${PACKAGE_DIR} ${DOCKER_DIST} >> ${DOCKER_BUILD_LOG} .PHONY: docker_fedora\:rawhide docker_fedora\:rawhide: @echo Logging to ${DOCKER_LOG} @test -d docker/log || mkdir -p docker/log echo -e \ $(DOCKER_GEN_FROM_IMAGE)"\n" \ $(DOCKER_YUM_BUILD_ESSENTIALS)"\n" \ $(DOCKER_YUM_GROUPS_DEVELTOOLS)"\n" \ $(DOCKER_COPY_DEPENDENCIES)"\n" \ $(DOCKER_YUM_INST_DEPENDENCIES) \ | docker build --tag $(DOCKER_TAG) -f - . >> ${DOCKER_LOG} delme: "RUN sed s_https://_http://_g -i /etc/yum.repos.d/*repo \n" \ "RUN echo \"nameserver 9.9.9.9\" > /etc/resolv.conf \n" \ oidc-agent-4.2.6/docker/opensuse_leap:15.2.dependencies0000644000175000017500000000022014167074355022172 0ustar marcusmarcusdesktop-file-utils help2man libcurl-devel libmicrohttpd-devel libseccomp-devel libsecret-devel libsodium-devel libsodium23 qrencode-devel unzip oidc-agent-4.2.6/docker/fedora:35.dependencies0000777000175000017500000000000014167074355024506 2fedora:34.dependenciesustar marcusmarcusoidc-agent-4.2.6/docker/ubuntu:bionic.dependencies0000644000175000017500000000021714167074355021536 0ustar marcusmarcuscheck help2man libcurl4-openssl-dev libmicrohttpd-dev libqrencode-dev libseccomp-dev libsecret-1-dev libsodium-dev pkg-config debhelper/bionic oidc-agent-4.2.6/docker/log/0000755000175000017500000000000014162555070015140 5ustar marcusmarcusoidc-agent-4.2.6/docker/debian:bullseye.dependencies0000644000175000017500000000021314167074355022013 0ustar marcusmarcuscheck help2man libcjson-dev libcurl4-openssl-dev libmicrohttpd-dev libqrencode-dev libseccomp-dev libsecret-1-dev libsodium-dev pkg-config oidc-agent-4.2.6/docker/debian:buster.dependencies0000777000175000017500000000000014167074355027125 2debian:bullseye.dependenciesustar marcusmarcusoidc-agent-4.2.6/LICENSE0000644000175000017500000000215214120404223014100 0ustar marcusmarcusMIT License Copyright (c) 2017 - 2021 Karlsruhe Institute of Technology - Steinbuch Centre for Computing Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. oidc-agent-4.2.6/lib/0000755000175000017500000000000014167775443013673 5ustar marcusmarcusoidc-agent-4.2.6/lib/cJSON/0000755000175000017500000000000014120404223014555 5ustar marcusmarcusoidc-agent-4.2.6/lib/cJSON/cJSON.h0000644000175000017500000003672414120404223015656 0ustar marcusmarcus/* Copyright (c) 2009-2017 Dave Gamble and cJSON contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef cJSON__h #define cJSON__h #ifdef __cplusplus extern "C" { #endif #if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) #define __WINDOWS__ #endif #ifdef __WINDOWS__ /* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol For *nix builds that support visibility attribute, you can define similar behavior by setting default visibility to hidden by adding -fvisibility=hidden (for gcc) or -xldscope=hidden (for sun cc) to CFLAGS then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does */ #define CJSON_CDECL __cdecl #define CJSON_STDCALL __stdcall /* export symbols by default, this is necessary for copy pasting the C and header file */ #if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) #define CJSON_EXPORT_SYMBOLS #endif #if defined(CJSON_HIDE_SYMBOLS) #define CJSON_PUBLIC(type) type CJSON_STDCALL #elif defined(CJSON_EXPORT_SYMBOLS) #define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL #elif defined(CJSON_IMPORT_SYMBOLS) #define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL #endif #else /* !__WINDOWS__ */ #define CJSON_CDECL #define CJSON_STDCALL #if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) #define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type #else #define CJSON_PUBLIC(type) type #endif #endif /* project version */ #define CJSON_VERSION_MAJOR 1 #define CJSON_VERSION_MINOR 7 #define CJSON_VERSION_PATCH 14 #include /* cJSON Types: */ #define cJSON_Invalid (0) #define cJSON_False (1 << 0) #define cJSON_True (1 << 1) #define cJSON_NULL (1 << 2) #define cJSON_Number (1 << 3) #define cJSON_String (1 << 4) #define cJSON_Array (1 << 5) #define cJSON_Object (1 << 6) #define cJSON_Raw (1 << 7) /* raw json */ #define cJSON_IsReference 256 #define cJSON_StringIsConst 512 /* The cJSON structure: */ typedef struct cJSON { /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ struct cJSON *next; struct cJSON *prev; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ struct cJSON *child; /* The type of the item, as above. */ int type; /* The item's string, if type==cJSON_String and type == cJSON_Raw */ char *valuestring; /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ int valueint; /* The item's number, if type==cJSON_Number */ double valuedouble; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ char *string; } cJSON; typedef struct cJSON_Hooks { /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ void *(CJSON_CDECL *malloc_fn)(size_t sz); void (CJSON_CDECL *free_fn)(void *ptr); } cJSON_Hooks; typedef int cJSON_bool; /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. * This is to prevent stack overflows. */ #ifndef CJSON_NESTING_LIMIT #define CJSON_NESTING_LIMIT 1000 #endif /* returns the version of cJSON as a string */ CJSON_PUBLIC(const char*) cJSON_Version(void); /* Supply malloc, realloc and free functions to cJSON */ CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); /* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ /* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ /* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); /* Render a cJSON entity to text for transfer/storage. */ CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); /* Render a cJSON entity to text for transfer/storage without any formatting. */ CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); /* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); /* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ /* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); /* Delete a cJSON entity and all subentities. */ CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); /* Returns the number of items in an array (or object). */ CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); /* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); /* Get item "string" from object. Case insensitive. */ CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); /* Check item type and return its value */ CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); /* These functions check the type of an item */ CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); /* These calls create a cJSON item of the appropriate type. */ CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); /* raw json */ CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); /* Create a string where valuestring references a string so * it will not be freed by cJSON_Delete */ CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); /* Create an object/array that only references it's elements so * they will not be freed by cJSON_Delete */ CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); /* These utilities create an Array of count items. * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); /* Append item to the specified array/object. */ CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before * writing to `item->string` */ CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); /* Remove/Detach items from Arrays/Objects. */ CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); /* Update array items. */ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); /* Duplicate a cJSON item */ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will * need to be released. With recurse!=0, it will duplicate any children connected to the item. * The item->next and ->prev pointers are always zero on return from Duplicate. */ /* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); /* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. * The input pointer json cannot point to a read-only address area, such as a string constant, * but should point to a readable and writable adress area. */ CJSON_PUBLIC(void) cJSON_Minify(char *json); /* Helper functions for creating and adding items to an object at the same time. * They return the added item or NULL on failure. */ CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); /* When assigning an integer value, it needs to be propagated to valuedouble too. */ #define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) /* helper for the cJSON_SetNumberValue macro */ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); #define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); /* Macro for iterating over an array or object */ #define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) /* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ CJSON_PUBLIC(void *) cJSON_malloc(size_t size); CJSON_PUBLIC(void) cJSON_free(void *object); #ifdef __cplusplus } #endif #endif oidc-agent-4.2.6/lib/cJSON/cJSON.c0000644000175000017500000022773714120404223015657 0ustar marcusmarcus/* Copyright (c) 2009-2017 Dave Gamble and cJSON contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* cJSON */ /* JSON parser in C. */ /* disable warnings about old C89 functions in MSVC */ #if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) #define _CRT_SECURE_NO_DEPRECATE #endif #ifdef __GNUC__ #pragma GCC visibility push(default) #endif #if defined(_MSC_VER) #pragma warning (push) /* disable warning about single line comments in system headers */ #pragma warning (disable : 4001) #endif #include #include #include #include #include #include #include #ifdef ENABLE_LOCALES #include #endif #if defined(_MSC_VER) #pragma warning (pop) #endif #ifdef __GNUC__ #pragma GCC visibility pop #endif #include "cJSON.h" /* define our own boolean type */ #ifdef true #undef true #endif #define true ((cJSON_bool)1) #ifdef false #undef false #endif #define false ((cJSON_bool)0) /* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ #ifndef isinf #define isinf(d) (isnan((d - d)) && !isnan(d)) #endif #ifndef isnan #define isnan(d) (d != d) #endif #ifndef NAN #ifdef _WIN32 #define NAN sqrt(-1.0) #else #define NAN 0.0/0.0 #endif #endif typedef struct { const unsigned char *json; size_t position; } error; static error global_error = { NULL, 0 }; CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) { return (const char*) (global_error.json + global_error.position); } CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) { if (!cJSON_IsString(item)) { return NULL; } return item->valuestring; } CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) { if (!cJSON_IsNumber(item)) { return (double) NAN; } return item->valuedouble; } /* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ #if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 14) #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. #endif CJSON_PUBLIC(const char*) cJSON_Version(void) { static char version[15]; sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); return version; } /* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) { if ((string1 == NULL) || (string2 == NULL)) { return 1; } if (string1 == string2) { return 0; } for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) { if (*string1 == '\0') { return 0; } } return tolower(*string1) - tolower(*string2); } typedef struct internal_hooks { void *(CJSON_CDECL *allocate)(size_t size); void (CJSON_CDECL *deallocate)(void *pointer); void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); } internal_hooks; #if defined(_MSC_VER) /* work around MSVC error C2322: '...' address of dllimport '...' is not static */ static void * CJSON_CDECL internal_malloc(size_t size) { return malloc(size); } static void CJSON_CDECL internal_free(void *pointer) { free(pointer); } static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) { return realloc(pointer, size); } #else #define internal_malloc malloc #define internal_free free #define internal_realloc realloc #endif /* strlen of character literals resolved at compile time */ #define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) { size_t length = 0; unsigned char *copy = NULL; if (string == NULL) { return NULL; } length = strlen((const char*)string) + sizeof(""); copy = (unsigned char*)hooks->allocate(length); if (copy == NULL) { return NULL; } memcpy(copy, string, length); return copy; } CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) { if (hooks == NULL) { /* Reset hooks */ global_hooks.allocate = malloc; global_hooks.deallocate = free; global_hooks.reallocate = realloc; return; } global_hooks.allocate = malloc; if (hooks->malloc_fn != NULL) { global_hooks.allocate = hooks->malloc_fn; } global_hooks.deallocate = free; if (hooks->free_fn != NULL) { global_hooks.deallocate = hooks->free_fn; } /* use realloc only if both free and malloc are used */ global_hooks.reallocate = NULL; if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) { global_hooks.reallocate = realloc; } } /* Internal constructor. */ static cJSON *cJSON_New_Item(const internal_hooks * const hooks) { cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); if (node) { memset(node, '\0', sizeof(cJSON)); } return node; } /* Delete a cJSON structure. */ CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) { cJSON *next = NULL; while (item != NULL) { next = item->next; if (!(item->type & cJSON_IsReference) && (item->child != NULL)) { cJSON_Delete(item->child); } if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) { global_hooks.deallocate(item->valuestring); } if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { global_hooks.deallocate(item->string); } global_hooks.deallocate(item); item = next; } } /* get the decimal point character of the current locale */ static unsigned char get_decimal_point(void) { #ifdef ENABLE_LOCALES struct lconv *lconv = localeconv(); return (unsigned char) lconv->decimal_point[0]; #else return '.'; #endif } typedef struct { const unsigned char *content; size_t length; size_t offset; size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ internal_hooks hooks; } parse_buffer; /* check if the given size is left to read in a given parse buffer (starting with 1) */ #define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) /* check if the buffer can be accessed at the given index (starting with 0) */ #define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) #define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) /* get a pointer to the buffer at the position */ #define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) /* Parse the input text to generate a number, and populate the result into item. */ static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) { double number = 0; unsigned char *after_end = NULL; unsigned char number_c_string[64]; unsigned char decimal_point = get_decimal_point(); size_t i = 0; if ((input_buffer == NULL) || (input_buffer->content == NULL)) { return false; } /* copy the number into a temporary buffer and replace '.' with the decimal point * of the current locale (for strtod) * This also takes care of '\0' not necessarily being available for marking the end of the input */ for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) { switch (buffer_at_offset(input_buffer)[i]) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '+': case '-': case 'e': case 'E': number_c_string[i] = buffer_at_offset(input_buffer)[i]; break; case '.': number_c_string[i] = decimal_point; break; default: goto loop_end; } } loop_end: number_c_string[i] = '\0'; number = strtod((const char*)number_c_string, (char**)&after_end); if (number_c_string == after_end) { return false; /* parse_error */ } item->valuedouble = number; /* use saturation in case of overflow */ if (number >= INT_MAX) { item->valueint = INT_MAX; } else if (number <= (double)INT_MIN) { item->valueint = INT_MIN; } else { item->valueint = (int)number; } item->type = cJSON_Number; input_buffer->offset += (size_t)(after_end - number_c_string); return true; } /* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) { if (number >= INT_MAX) { object->valueint = INT_MAX; } else if (number <= (double)INT_MIN) { object->valueint = INT_MIN; } else { object->valueint = (int)number; } return object->valuedouble = number; } CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) { char *copy = NULL; /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) { return NULL; } if (strlen(valuestring) <= strlen(object->valuestring)) { strcpy(object->valuestring, valuestring); return object->valuestring; } copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); if (copy == NULL) { return NULL; } if (object->valuestring != NULL) { cJSON_free(object->valuestring); } object->valuestring = copy; return copy; } typedef struct { unsigned char *buffer; size_t length; size_t offset; size_t depth; /* current nesting depth (for formatted printing) */ cJSON_bool noalloc; cJSON_bool format; /* is this print a formatted print */ internal_hooks hooks; } printbuffer; /* realloc printbuffer if necessary to have at least "needed" bytes more */ static unsigned char* ensure(printbuffer * const p, size_t needed) { unsigned char *newbuffer = NULL; size_t newsize = 0; if ((p == NULL) || (p->buffer == NULL)) { return NULL; } if ((p->length > 0) && (p->offset >= p->length)) { /* make sure that offset is valid */ return NULL; } if (needed > INT_MAX) { /* sizes bigger than INT_MAX are currently not supported */ return NULL; } needed += p->offset + 1; if (needed <= p->length) { return p->buffer + p->offset; } if (p->noalloc) { return NULL; } /* calculate new buffer size */ if (needed > (INT_MAX / 2)) { /* overflow of int, use INT_MAX if possible */ if (needed <= INT_MAX) { newsize = INT_MAX; } else { return NULL; } } else { newsize = needed * 2; } if (p->hooks.reallocate != NULL) { /* reallocate with realloc if available */ newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); if (newbuffer == NULL) { p->hooks.deallocate(p->buffer); p->length = 0; p->buffer = NULL; return NULL; } } else { /* otherwise reallocate manually */ newbuffer = (unsigned char*)p->hooks.allocate(newsize); if (!newbuffer) { p->hooks.deallocate(p->buffer); p->length = 0; p->buffer = NULL; return NULL; } memcpy(newbuffer, p->buffer, p->offset + 1); p->hooks.deallocate(p->buffer); } p->length = newsize; p->buffer = newbuffer; return newbuffer + p->offset; } /* calculate the new length of the string in a printbuffer and update the offset */ static void update_offset(printbuffer * const buffer) { const unsigned char *buffer_pointer = NULL; if ((buffer == NULL) || (buffer->buffer == NULL)) { return; } buffer_pointer = buffer->buffer + buffer->offset; buffer->offset += strlen((const char*)buffer_pointer); } /* securely comparison of floating-point variables */ static cJSON_bool compare_double(double a, double b) { double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); return (fabs(a - b) <= maxVal * DBL_EPSILON); } /* Render the number nicely from the given item into a string. */ static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output_pointer = NULL; double d = item->valuedouble; int length = 0; size_t i = 0; unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ unsigned char decimal_point = get_decimal_point(); double test = 0.0; if (output_buffer == NULL) { return false; } /* This checks for NaN and Infinity */ if (isnan(d) || isinf(d)) { length = sprintf((char*)number_buffer, "null"); } else { /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ length = sprintf((char*)number_buffer, "%1.15g", d); /* Check whether the original double can be recovered */ if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) { /* If not, print with 17 decimal places of precision */ length = sprintf((char*)number_buffer, "%1.17g", d); } } /* sprintf failed or buffer overrun occurred */ if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) { return false; } /* reserve appropriate space in the output */ output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); if (output_pointer == NULL) { return false; } /* copy the printed number to the output and replace locale * dependent decimal point with '.' */ for (i = 0; i < ((size_t)length); i++) { if (number_buffer[i] == decimal_point) { output_pointer[i] = '.'; continue; } output_pointer[i] = number_buffer[i]; } output_pointer[i] = '\0'; output_buffer->offset += (size_t)length; return true; } /* parse 4 digit hexadecimal number */ static unsigned parse_hex4(const unsigned char * const input) { unsigned int h = 0; size_t i = 0; for (i = 0; i < 4; i++) { /* parse digit */ if ((input[i] >= '0') && (input[i] <= '9')) { h += (unsigned int) input[i] - '0'; } else if ((input[i] >= 'A') && (input[i] <= 'F')) { h += (unsigned int) 10 + input[i] - 'A'; } else if ((input[i] >= 'a') && (input[i] <= 'f')) { h += (unsigned int) 10 + input[i] - 'a'; } else /* invalid */ { return 0; } if (i < 3) { /* shift left to make place for the next nibble */ h = h << 4; } } return h; } /* converts a UTF-16 literal to UTF-8 * A literal can be one or two sequences of the form \uXXXX */ static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) { long unsigned int codepoint = 0; unsigned int first_code = 0; const unsigned char *first_sequence = input_pointer; unsigned char utf8_length = 0; unsigned char utf8_position = 0; unsigned char sequence_length = 0; unsigned char first_byte_mark = 0; if ((input_end - first_sequence) < 6) { /* input ends unexpectedly */ goto fail; } /* get the first utf16 sequence */ first_code = parse_hex4(first_sequence + 2); /* check that the code is valid */ if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) { goto fail; } /* UTF16 surrogate pair */ if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) { const unsigned char *second_sequence = first_sequence + 6; unsigned int second_code = 0; sequence_length = 12; /* \uXXXX\uXXXX */ if ((input_end - second_sequence) < 6) { /* input ends unexpectedly */ goto fail; } if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) { /* missing second half of the surrogate pair */ goto fail; } /* get the second utf16 sequence */ second_code = parse_hex4(second_sequence + 2); /* check that the code is valid */ if ((second_code < 0xDC00) || (second_code > 0xDFFF)) { /* invalid second half of the surrogate pair */ goto fail; } /* calculate the unicode codepoint from the surrogate pair */ codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); } else { sequence_length = 6; /* \uXXXX */ codepoint = first_code; } /* encode as UTF-8 * takes at maximum 4 bytes to encode: * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ if (codepoint < 0x80) { /* normal ascii, encoding 0xxxxxxx */ utf8_length = 1; } else if (codepoint < 0x800) { /* two bytes, encoding 110xxxxx 10xxxxxx */ utf8_length = 2; first_byte_mark = 0xC0; /* 11000000 */ } else if (codepoint < 0x10000) { /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ utf8_length = 3; first_byte_mark = 0xE0; /* 11100000 */ } else if (codepoint <= 0x10FFFF) { /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ utf8_length = 4; first_byte_mark = 0xF0; /* 11110000 */ } else { /* invalid unicode codepoint */ goto fail; } /* encode as utf8 */ for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) { /* 10xxxxxx */ (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); codepoint >>= 6; } /* encode first byte */ if (utf8_length > 1) { (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); } else { (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); } *output_pointer += utf8_length; return sequence_length; fail: return 0; } /* Parse the input text into an unescaped cinput, and populate item. */ static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) { const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; unsigned char *output_pointer = NULL; unsigned char *output = NULL; /* not a string */ if (buffer_at_offset(input_buffer)[0] != '\"') { goto fail; } { /* calculate approximate size of the output (overestimate) */ size_t allocation_length = 0; size_t skipped_bytes = 0; while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) { /* is escape sequence */ if (input_end[0] == '\\') { if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) { /* prevent buffer overflow when last input character is a backslash */ goto fail; } skipped_bytes++; input_end++; } input_end++; } if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) { goto fail; /* string ended unexpectedly */ } /* This is at most how much we need for the output */ allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); if (output == NULL) { goto fail; /* allocation failure */ } } output_pointer = output; /* loop through the string literal */ while (input_pointer < input_end) { if (*input_pointer != '\\') { *output_pointer++ = *input_pointer++; } /* escape sequence */ else { unsigned char sequence_length = 2; if ((input_end - input_pointer) < 1) { goto fail; } switch (input_pointer[1]) { case 'b': *output_pointer++ = '\b'; break; case 'f': *output_pointer++ = '\f'; break; case 'n': *output_pointer++ = '\n'; break; case 'r': *output_pointer++ = '\r'; break; case 't': *output_pointer++ = '\t'; break; case '\"': case '\\': case '/': *output_pointer++ = input_pointer[1]; break; /* UTF-16 literal */ case 'u': sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); if (sequence_length == 0) { /* failed to convert UTF16-literal to UTF-8 */ goto fail; } break; default: goto fail; } input_pointer += sequence_length; } } /* zero terminate the output */ *output_pointer = '\0'; item->type = cJSON_String; item->valuestring = (char*)output; input_buffer->offset = (size_t) (input_end - input_buffer->content); input_buffer->offset++; return true; fail: if (output != NULL) { input_buffer->hooks.deallocate(output); } if (input_pointer != NULL) { input_buffer->offset = (size_t)(input_pointer - input_buffer->content); } return false; } /* Render the cstring provided to an escaped version that can be printed. */ static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) { const unsigned char *input_pointer = NULL; unsigned char *output = NULL; unsigned char *output_pointer = NULL; size_t output_length = 0; /* numbers of additional characters needed for escaping */ size_t escape_characters = 0; if (output_buffer == NULL) { return false; } /* empty string */ if (input == NULL) { output = ensure(output_buffer, sizeof("\"\"")); if (output == NULL) { return false; } strcpy((char*)output, "\"\""); return true; } /* set "flag" to 1 if something needs to be escaped */ for (input_pointer = input; *input_pointer; input_pointer++) { switch (*input_pointer) { case '\"': case '\\': case '\b': case '\f': case '\n': case '\r': case '\t': /* one character escape sequence */ escape_characters++; break; default: if (*input_pointer < 32) { /* UTF-16 escape sequence uXXXX */ escape_characters += 5; } break; } } output_length = (size_t)(input_pointer - input) + escape_characters; output = ensure(output_buffer, output_length + sizeof("\"\"")); if (output == NULL) { return false; } /* no characters have to be escaped */ if (escape_characters == 0) { output[0] = '\"'; memcpy(output + 1, input, output_length); output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; return true; } output[0] = '\"'; output_pointer = output + 1; /* copy the string */ for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) { if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) { /* normal character, copy */ *output_pointer = *input_pointer; } else { /* character needs to be escaped */ *output_pointer++ = '\\'; switch (*input_pointer) { case '\\': *output_pointer = '\\'; break; case '\"': *output_pointer = '\"'; break; case '\b': *output_pointer = 'b'; break; case '\f': *output_pointer = 'f'; break; case '\n': *output_pointer = 'n'; break; case '\r': *output_pointer = 'r'; break; case '\t': *output_pointer = 't'; break; default: /* escape and print as unicode codepoint */ sprintf((char*)output_pointer, "u%04x", *input_pointer); output_pointer += 4; break; } } } output[output_length + 1] = '\"'; output[output_length + 2] = '\0'; return true; } /* Invoke print_string_ptr (which is useful) on an item. */ static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) { return print_string_ptr((unsigned char*)item->valuestring, p); } /* Predeclare these prototypes. */ static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); /* Utility to jump whitespace and cr/lf */ static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) { if ((buffer == NULL) || (buffer->content == NULL)) { return NULL; } if (cannot_access_at_index(buffer, 0)) { return buffer; } while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) { buffer->offset++; } if (buffer->offset == buffer->length) { buffer->offset--; } return buffer; } /* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) { if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) { return NULL; } if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) { buffer->offset += 3; } return buffer; } CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) { size_t buffer_length; if (NULL == value) { return NULL; } /* Adding null character size due to require_null_terminated. */ buffer_length = strlen(value) + sizeof(""); return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); } /* Parse an object - create a new root, and populate. */ CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) { parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; cJSON *item = NULL; /* reset error position */ global_error.json = NULL; global_error.position = 0; if (value == NULL || 0 == buffer_length) { goto fail; } buffer.content = (const unsigned char*)value; buffer.length = buffer_length; buffer.offset = 0; buffer.hooks = global_hooks; item = cJSON_New_Item(&global_hooks); if (item == NULL) /* memory fail */ { goto fail; } if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) { /* parse failure. ep is set. */ goto fail; } /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ if (require_null_terminated) { buffer_skip_whitespace(&buffer); if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') { goto fail; } } if (return_parse_end) { *return_parse_end = (const char*)buffer_at_offset(&buffer); } return item; fail: if (item != NULL) { cJSON_Delete(item); } if (value != NULL) { error local_error; local_error.json = (const unsigned char*)value; local_error.position = 0; if (buffer.offset < buffer.length) { local_error.position = buffer.offset; } else if (buffer.length > 0) { local_error.position = buffer.length - 1; } if (return_parse_end != NULL) { *return_parse_end = (const char*)local_error.json + local_error.position; } global_error = local_error; } return NULL; } /* Default options for cJSON_Parse */ CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) { return cJSON_ParseWithOpts(value, 0, 0); } CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) { return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); } #define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) { static const size_t default_buffer_size = 256; printbuffer buffer[1]; unsigned char *printed = NULL; memset(buffer, 0, sizeof(buffer)); /* create buffer */ buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); buffer->length = default_buffer_size; buffer->format = format; buffer->hooks = *hooks; if (buffer->buffer == NULL) { goto fail; } /* print the value */ if (!print_value(item, buffer)) { goto fail; } update_offset(buffer); /* check if reallocate is available */ if (hooks->reallocate != NULL) { printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); if (printed == NULL) { goto fail; } buffer->buffer = NULL; } else /* otherwise copy the JSON over to a new buffer */ { printed = (unsigned char*) hooks->allocate(buffer->offset + 1); if (printed == NULL) { goto fail; } memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); printed[buffer->offset] = '\0'; /* just to be sure */ /* free the buffer */ hooks->deallocate(buffer->buffer); } return printed; fail: if (buffer->buffer != NULL) { hooks->deallocate(buffer->buffer); } if (printed != NULL) { hooks->deallocate(printed); } return NULL; } /* Render a cJSON item/entity/structure to text. */ CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) { return (char*)print(item, true, &global_hooks); } CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) { return (char*)print(item, false, &global_hooks); } CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; if (prebuffer < 0) { return NULL; } p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); if (!p.buffer) { return NULL; } p.length = (size_t)prebuffer; p.offset = 0; p.noalloc = false; p.format = fmt; p.hooks = global_hooks; if (!print_value(item, &p)) { global_hooks.deallocate(p.buffer); return NULL; } return (char*)p.buffer; } CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) { printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; if ((length < 0) || (buffer == NULL)) { return false; } p.buffer = (unsigned char*)buffer; p.length = (size_t)length; p.offset = 0; p.noalloc = true; p.format = format; p.hooks = global_hooks; return print_value(item, &p); } /* Parser core - when encountering text, process appropriately. */ static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) { if ((input_buffer == NULL) || (input_buffer->content == NULL)) { return false; /* no input */ } /* parse the different types of values */ /* null */ if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) { item->type = cJSON_NULL; input_buffer->offset += 4; return true; } /* false */ if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) { item->type = cJSON_False; input_buffer->offset += 5; return true; } /* true */ if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) { item->type = cJSON_True; item->valueint = 1; input_buffer->offset += 4; return true; } /* string */ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) { return parse_string(item, input_buffer); } /* number */ if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) { return parse_number(item, input_buffer); } /* array */ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) { return parse_array(item, input_buffer); } /* object */ if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) { return parse_object(item, input_buffer); } return false; } /* Render a value to text. */ static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output = NULL; if ((item == NULL) || (output_buffer == NULL)) { return false; } switch ((item->type) & 0xFF) { case cJSON_NULL: output = ensure(output_buffer, 5); if (output == NULL) { return false; } strcpy((char*)output, "null"); return true; case cJSON_False: output = ensure(output_buffer, 6); if (output == NULL) { return false; } strcpy((char*)output, "false"); return true; case cJSON_True: output = ensure(output_buffer, 5); if (output == NULL) { return false; } strcpy((char*)output, "true"); return true; case cJSON_Number: return print_number(item, output_buffer); case cJSON_Raw: { size_t raw_length = 0; if (item->valuestring == NULL) { return false; } raw_length = strlen(item->valuestring) + sizeof(""); output = ensure(output_buffer, raw_length); if (output == NULL) { return false; } memcpy(output, item->valuestring, raw_length); return true; } case cJSON_String: return print_string(item, output_buffer); case cJSON_Array: return print_array(item, output_buffer); case cJSON_Object: return print_object(item, output_buffer); default: return false; } } /* Build an array from input text. */ static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) { cJSON *head = NULL; /* head of the linked list */ cJSON *current_item = NULL; if (input_buffer->depth >= CJSON_NESTING_LIMIT) { return false; /* to deeply nested */ } input_buffer->depth++; if (buffer_at_offset(input_buffer)[0] != '[') { /* not an array */ goto fail; } input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) { /* empty array */ goto success; } /* check if we skipped to the end of the buffer */ if (cannot_access_at_index(input_buffer, 0)) { input_buffer->offset--; goto fail; } /* step back to character in front of the first element */ input_buffer->offset--; /* loop through the comma separated array elements */ do { /* allocate next item */ cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); if (new_item == NULL) { goto fail; /* allocation failure */ } /* attach next item to list */ if (head == NULL) { /* start the linked list */ current_item = head = new_item; } else { /* add to the end and advance */ current_item->next = new_item; new_item->prev = current_item; current_item = new_item; } /* parse next value */ input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (!parse_value(current_item, input_buffer)) { goto fail; /* failed to parse value */ } buffer_skip_whitespace(input_buffer); } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') { goto fail; /* expected end of array */ } success: input_buffer->depth--; if (head != NULL) { head->prev = current_item; } item->type = cJSON_Array; item->child = head; input_buffer->offset++; return true; fail: if (head != NULL) { cJSON_Delete(head); } return false; } /* Render an array to text */ static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output_pointer = NULL; size_t length = 0; cJSON *current_element = item->child; if (output_buffer == NULL) { return false; } /* Compose the output array. */ /* opening square bracket */ output_pointer = ensure(output_buffer, 1); if (output_pointer == NULL) { return false; } *output_pointer = '['; output_buffer->offset++; output_buffer->depth++; while (current_element != NULL) { if (!print_value(current_element, output_buffer)) { return false; } update_offset(output_buffer); if (current_element->next) { length = (size_t) (output_buffer->format ? 2 : 1); output_pointer = ensure(output_buffer, length + 1); if (output_pointer == NULL) { return false; } *output_pointer++ = ','; if(output_buffer->format) { *output_pointer++ = ' '; } *output_pointer = '\0'; output_buffer->offset += length; } current_element = current_element->next; } output_pointer = ensure(output_buffer, 2); if (output_pointer == NULL) { return false; } *output_pointer++ = ']'; *output_pointer = '\0'; output_buffer->depth--; return true; } /* Build an object from the text. */ static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) { cJSON *head = NULL; /* linked list head */ cJSON *current_item = NULL; if (input_buffer->depth >= CJSON_NESTING_LIMIT) { return false; /* to deeply nested */ } input_buffer->depth++; if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) { goto fail; /* not an object */ } input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) { goto success; /* empty object */ } /* check if we skipped to the end of the buffer */ if (cannot_access_at_index(input_buffer, 0)) { input_buffer->offset--; goto fail; } /* step back to character in front of the first element */ input_buffer->offset--; /* loop through the comma separated array elements */ do { /* allocate next item */ cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); if (new_item == NULL) { goto fail; /* allocation failure */ } /* attach next item to list */ if (head == NULL) { /* start the linked list */ current_item = head = new_item; } else { /* add to the end and advance */ current_item->next = new_item; new_item->prev = current_item; current_item = new_item; } /* parse the name of the child */ input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (!parse_string(current_item, input_buffer)) { goto fail; /* failed to parse name */ } buffer_skip_whitespace(input_buffer); /* swap valuestring and string, because we parsed the name */ current_item->string = current_item->valuestring; current_item->valuestring = NULL; if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) { goto fail; /* invalid object */ } /* parse the value */ input_buffer->offset++; buffer_skip_whitespace(input_buffer); if (!parse_value(current_item, input_buffer)) { goto fail; /* failed to parse value */ } buffer_skip_whitespace(input_buffer); } while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) { goto fail; /* expected end of object */ } success: input_buffer->depth--; if (head != NULL) { head->prev = current_item; } item->type = cJSON_Object; item->child = head; input_buffer->offset++; return true; fail: if (head != NULL) { cJSON_Delete(head); } return false; } /* Render an object to text. */ static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output_pointer = NULL; size_t length = 0; cJSON *current_item = item->child; if (output_buffer == NULL) { return false; } /* Compose the output: */ length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ output_pointer = ensure(output_buffer, length + 1); if (output_pointer == NULL) { return false; } *output_pointer++ = '{'; output_buffer->depth++; if (output_buffer->format) { *output_pointer++ = '\n'; } output_buffer->offset += length; while (current_item) { if (output_buffer->format) { size_t i; output_pointer = ensure(output_buffer, output_buffer->depth); if (output_pointer == NULL) { return false; } for (i = 0; i < output_buffer->depth; i++) { *output_pointer++ = '\t'; } output_buffer->offset += output_buffer->depth; } /* print key */ if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) { return false; } update_offset(output_buffer); length = (size_t) (output_buffer->format ? 2 : 1); output_pointer = ensure(output_buffer, length); if (output_pointer == NULL) { return false; } *output_pointer++ = ':'; if (output_buffer->format) { *output_pointer++ = '\t'; } output_buffer->offset += length; /* print value */ if (!print_value(current_item, output_buffer)) { return false; } update_offset(output_buffer); /* print comma if not last */ length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); output_pointer = ensure(output_buffer, length + 1); if (output_pointer == NULL) { return false; } if (current_item->next) { *output_pointer++ = ','; } if (output_buffer->format) { *output_pointer++ = '\n'; } *output_pointer = '\0'; output_buffer->offset += length; current_item = current_item->next; } output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); if (output_pointer == NULL) { return false; } if (output_buffer->format) { size_t i; for (i = 0; i < (output_buffer->depth - 1); i++) { *output_pointer++ = '\t'; } } *output_pointer++ = '}'; *output_pointer = '\0'; output_buffer->depth--; return true; } /* Get Array size/item / object item. */ CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) { cJSON *child = NULL; size_t size = 0; if (array == NULL) { return 0; } child = array->child; while(child != NULL) { size++; child = child->next; } /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ return (int)size; } static cJSON* get_array_item(const cJSON *array, size_t index) { cJSON *current_child = NULL; if (array == NULL) { return NULL; } current_child = array->child; while ((current_child != NULL) && (index > 0)) { index--; current_child = current_child->next; } return current_child; } CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) { if (index < 0) { return NULL; } return get_array_item(array, (size_t)index); } static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) { cJSON *current_element = NULL; if ((object == NULL) || (name == NULL)) { return NULL; } current_element = object->child; if (case_sensitive) { while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) { current_element = current_element->next; } } else { while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) { current_element = current_element->next; } } if ((current_element == NULL) || (current_element->string == NULL)) { return NULL; } return current_element; } CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) { return get_object_item(object, string, false); } CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) { return get_object_item(object, string, true); } CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) { return cJSON_GetObjectItem(object, string) ? 1 : 0; } /* Utility for array list handling. */ static void suffix_object(cJSON *prev, cJSON *item) { prev->next = item; item->prev = prev; } /* Utility for handling references. */ static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) { cJSON *reference = NULL; if (item == NULL) { return NULL; } reference = cJSON_New_Item(hooks); if (reference == NULL) { return NULL; } memcpy(reference, item, sizeof(cJSON)); reference->string = NULL; reference->type |= cJSON_IsReference; reference->next = reference->prev = NULL; return reference; } static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) { cJSON *child = NULL; if ((item == NULL) || (array == NULL) || (array == item)) { return false; } child = array->child; /* * To find the last item in array quickly, we use prev in array */ if (child == NULL) { /* list is empty, start new one */ array->child = item; item->prev = item; item->next = NULL; } else { /* append to the end */ if (child->prev) { suffix_object(child->prev, item); array->child->prev = item; } } return true; } /* Add item to array/object. */ CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) { return add_item_to_array(array, item); } #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) #pragma GCC diagnostic push #endif #ifdef __GNUC__ #pragma GCC diagnostic ignored "-Wcast-qual" #endif /* helper function to cast away const */ static void* cast_away_const(const void* string) { return (void*)string; } #if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) #pragma GCC diagnostic pop #endif static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) { char *new_key = NULL; int new_type = cJSON_Invalid; if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) { return false; } if (constant_key) { new_key = (char*)cast_away_const(string); new_type = item->type | cJSON_StringIsConst; } else { new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); if (new_key == NULL) { return false; } new_type = item->type & ~cJSON_StringIsConst; } if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) { hooks->deallocate(item->string); } item->string = new_key; item->type = new_type; return add_item_to_array(object, item); } CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) { return add_item_to_object(object, string, item, &global_hooks, false); } /* Add an item to an object with constant string as key */ CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) { return add_item_to_object(object, string, item, &global_hooks, true); } CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) { if (array == NULL) { return false; } return add_item_to_array(array, create_reference(item, &global_hooks)); } CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) { if ((object == NULL) || (string == NULL)) { return false; } return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); } CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) { cJSON *null = cJSON_CreateNull(); if (add_item_to_object(object, name, null, &global_hooks, false)) { return null; } cJSON_Delete(null); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) { cJSON *true_item = cJSON_CreateTrue(); if (add_item_to_object(object, name, true_item, &global_hooks, false)) { return true_item; } cJSON_Delete(true_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) { cJSON *false_item = cJSON_CreateFalse(); if (add_item_to_object(object, name, false_item, &global_hooks, false)) { return false_item; } cJSON_Delete(false_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) { cJSON *bool_item = cJSON_CreateBool(boolean); if (add_item_to_object(object, name, bool_item, &global_hooks, false)) { return bool_item; } cJSON_Delete(bool_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) { cJSON *number_item = cJSON_CreateNumber(number); if (add_item_to_object(object, name, number_item, &global_hooks, false)) { return number_item; } cJSON_Delete(number_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) { cJSON *string_item = cJSON_CreateString(string); if (add_item_to_object(object, name, string_item, &global_hooks, false)) { return string_item; } cJSON_Delete(string_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) { cJSON *raw_item = cJSON_CreateRaw(raw); if (add_item_to_object(object, name, raw_item, &global_hooks, false)) { return raw_item; } cJSON_Delete(raw_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) { cJSON *object_item = cJSON_CreateObject(); if (add_item_to_object(object, name, object_item, &global_hooks, false)) { return object_item; } cJSON_Delete(object_item); return NULL; } CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) { cJSON *array = cJSON_CreateArray(); if (add_item_to_object(object, name, array, &global_hooks, false)) { return array; } cJSON_Delete(array); return NULL; } CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) { if ((parent == NULL) || (item == NULL)) { return NULL; } if (item != parent->child) { /* not the first element */ item->prev->next = item->next; } if (item->next != NULL) { /* not the last element */ item->next->prev = item->prev; } if (item == parent->child) { /* first element */ parent->child = item->next; } else if (item->next == NULL) { /* last element */ parent->child->prev = item->prev; } /* make sure the detached item doesn't point anywhere anymore */ item->prev = NULL; item->next = NULL; return item; } CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) { if (which < 0) { return NULL; } return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); } CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) { cJSON_Delete(cJSON_DetachItemFromArray(array, which)); } CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) { cJSON *to_detach = cJSON_GetObjectItem(object, string); return cJSON_DetachItemViaPointer(object, to_detach); } CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) { cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); return cJSON_DetachItemViaPointer(object, to_detach); } CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) { cJSON_Delete(cJSON_DetachItemFromObject(object, string)); } CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) { cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); } /* Replace array/object items with new ones. */ CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) { cJSON *after_inserted = NULL; if (which < 0) { return false; } after_inserted = get_array_item(array, (size_t)which); if (after_inserted == NULL) { return add_item_to_array(array, newitem); } newitem->next = after_inserted; newitem->prev = after_inserted->prev; after_inserted->prev = newitem; if (after_inserted == array->child) { array->child = newitem; } else { newitem->prev->next = newitem; } return true; } CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) { if ((parent == NULL) || (replacement == NULL) || (item == NULL)) { return false; } if (replacement == item) { return true; } replacement->next = item->next; replacement->prev = item->prev; if (replacement->next != NULL) { replacement->next->prev = replacement; } if (parent->child == item) { if (parent->child->prev == parent->child) { replacement->prev = replacement; } parent->child = replacement; } else { /* * To find the last item in array quickly, we use prev in array. * We can't modify the last item's next pointer where this item was the parent's child */ if (replacement->prev != NULL) { replacement->prev->next = replacement; } if (replacement->next == NULL) { parent->child->prev = replacement; } } item->next = NULL; item->prev = NULL; cJSON_Delete(item); return true; } CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) { if (which < 0) { return false; } return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); } static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) { if ((replacement == NULL) || (string == NULL)) { return false; } /* replace the name in the replacement */ if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) { cJSON_free(replacement->string); } replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); replacement->type &= ~cJSON_StringIsConst; return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); } CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) { return replace_item_in_object(object, string, newitem, false); } CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) { return replace_item_in_object(object, string, newitem, true); } /* Create basic types: */ CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_NULL; } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_True; } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_False; } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = boolean ? cJSON_True : cJSON_False; } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_Number; item->valuedouble = num; /* use saturation in case of overflow */ if (num >= INT_MAX) { item->valueint = INT_MAX; } else if (num <= (double)INT_MIN) { item->valueint = INT_MIN; } else { item->valueint = (int)num; } } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_String; item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); if(!item->valuestring) { cJSON_Delete(item); return NULL; } } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) { cJSON *item = cJSON_New_Item(&global_hooks); if (item != NULL) { item->type = cJSON_String | cJSON_IsReference; item->valuestring = (char*)cast_away_const(string); } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) { cJSON *item = cJSON_New_Item(&global_hooks); if (item != NULL) { item->type = cJSON_Object | cJSON_IsReference; item->child = (cJSON*)cast_away_const(child); } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { cJSON *item = cJSON_New_Item(&global_hooks); if (item != NULL) { item->type = cJSON_Array | cJSON_IsReference; item->child = (cJSON*)cast_away_const(child); } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type = cJSON_Raw; item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); if(!item->valuestring) { cJSON_Delete(item); return NULL; } } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) { cJSON *item = cJSON_New_Item(&global_hooks); if(item) { item->type=cJSON_Array; } return item; } CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) { cJSON *item = cJSON_New_Item(&global_hooks); if (item) { item->type = cJSON_Object; } return item; } /* Create Arrays: */ CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) { size_t i = 0; cJSON *n = NULL; cJSON *p = NULL; cJSON *a = NULL; if ((count < 0) || (numbers == NULL)) { return NULL; } a = cJSON_CreateArray(); for(i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateNumber(numbers[i]); if (!n) { cJSON_Delete(a); return NULL; } if(!i) { a->child = n; } else { suffix_object(p, n); } p = n; } if (a && a->child) { a->child->prev = n; } return a; } CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) { size_t i = 0; cJSON *n = NULL; cJSON *p = NULL; cJSON *a = NULL; if ((count < 0) || (numbers == NULL)) { return NULL; } a = cJSON_CreateArray(); for(i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateNumber((double)numbers[i]); if(!n) { cJSON_Delete(a); return NULL; } if(!i) { a->child = n; } else { suffix_object(p, n); } p = n; } if (a && a->child) { a->child->prev = n; } return a; } CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) { size_t i = 0; cJSON *n = NULL; cJSON *p = NULL; cJSON *a = NULL; if ((count < 0) || (numbers == NULL)) { return NULL; } a = cJSON_CreateArray(); for(i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateNumber(numbers[i]); if(!n) { cJSON_Delete(a); return NULL; } if(!i) { a->child = n; } else { suffix_object(p, n); } p = n; } if (a && a->child) { a->child->prev = n; } return a; } CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) { size_t i = 0; cJSON *n = NULL; cJSON *p = NULL; cJSON *a = NULL; if ((count < 0) || (strings == NULL)) { return NULL; } a = cJSON_CreateArray(); for (i = 0; a && (i < (size_t)count); i++) { n = cJSON_CreateString(strings[i]); if(!n) { cJSON_Delete(a); return NULL; } if(!i) { a->child = n; } else { suffix_object(p,n); } p = n; } if (a && a->child) { a->child->prev = n; } return a; } /* Duplication */ CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) { cJSON *newitem = NULL; cJSON *child = NULL; cJSON *next = NULL; cJSON *newchild = NULL; /* Bail on bad ptr */ if (!item) { goto fail; } /* Create new item */ newitem = cJSON_New_Item(&global_hooks); if (!newitem) { goto fail; } /* Copy over all vars */ newitem->type = item->type & (~cJSON_IsReference); newitem->valueint = item->valueint; newitem->valuedouble = item->valuedouble; if (item->valuestring) { newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); if (!newitem->valuestring) { goto fail; } } if (item->string) { newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); if (!newitem->string) { goto fail; } } /* If non-recursive, then we're done! */ if (!recurse) { return newitem; } /* Walk the ->next chain for the child. */ child = item->child; while (child != NULL) { newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ if (!newchild) { goto fail; } if (next != NULL) { /* If newitem->child already set, then crosswire ->prev and ->next and move on */ next->next = newchild; newchild->prev = next; next = newchild; } else { /* Set newitem->child and move to it */ newitem->child = newchild; next = newchild; } child = child->next; } if (newitem && newitem->child) { newitem->child->prev = newchild; } return newitem; fail: if (newitem != NULL) { cJSON_Delete(newitem); } return NULL; } static void skip_oneline_comment(char **input) { *input += static_strlen("//"); for (; (*input)[0] != '\0'; ++(*input)) { if ((*input)[0] == '\n') { *input += static_strlen("\n"); return; } } } static void skip_multiline_comment(char **input) { *input += static_strlen("/*"); for (; (*input)[0] != '\0'; ++(*input)) { if (((*input)[0] == '*') && ((*input)[1] == '/')) { *input += static_strlen("*/"); return; } } } static void minify_string(char **input, char **output) { (*output)[0] = (*input)[0]; *input += static_strlen("\""); *output += static_strlen("\""); for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { (*output)[0] = (*input)[0]; if ((*input)[0] == '\"') { (*output)[0] = '\"'; *input += static_strlen("\""); *output += static_strlen("\""); return; } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { (*output)[1] = (*input)[1]; *input += static_strlen("\""); *output += static_strlen("\""); } } } CJSON_PUBLIC(void) cJSON_Minify(char *json) { char *into = json; if (json == NULL) { return; } while (json[0] != '\0') { switch (json[0]) { case ' ': case '\t': case '\r': case '\n': json++; break; case '/': if (json[1] == '/') { skip_oneline_comment(&json); } else if (json[1] == '*') { skip_multiline_comment(&json); } else { json++; } break; case '\"': minify_string(&json, (char**)&into); break; default: into[0] = json[0]; json++; into++; } } /* and null-terminate. */ *into = '\0'; } CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_Invalid; } CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_False; } CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xff) == cJSON_True; } CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & (cJSON_True | cJSON_False)) != 0; } CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_NULL; } CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_Number; } CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_String; } CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_Array; } CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_Object; } CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) { if (item == NULL) { return false; } return (item->type & 0xFF) == cJSON_Raw; } CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) { if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) { return false; } /* check if type is valid */ switch (a->type & 0xFF) { case cJSON_False: case cJSON_True: case cJSON_NULL: case cJSON_Number: case cJSON_String: case cJSON_Raw: case cJSON_Array: case cJSON_Object: break; default: return false; } /* identical objects are equal */ if (a == b) { return true; } switch (a->type & 0xFF) { /* in these cases and equal type is enough */ case cJSON_False: case cJSON_True: case cJSON_NULL: return true; case cJSON_Number: if (compare_double(a->valuedouble, b->valuedouble)) { return true; } return false; case cJSON_String: case cJSON_Raw: if ((a->valuestring == NULL) || (b->valuestring == NULL)) { return false; } if (strcmp(a->valuestring, b->valuestring) == 0) { return true; } return false; case cJSON_Array: { cJSON *a_element = a->child; cJSON *b_element = b->child; for (; (a_element != NULL) && (b_element != NULL);) { if (!cJSON_Compare(a_element, b_element, case_sensitive)) { return false; } a_element = a_element->next; b_element = b_element->next; } /* one of the arrays is longer than the other */ if (a_element != b_element) { return false; } return true; } case cJSON_Object: { cJSON *a_element = NULL; cJSON *b_element = NULL; cJSON_ArrayForEach(a_element, a) { /* TODO This has O(n^2) runtime, which is horrible! */ b_element = get_object_item(b, a_element->string, case_sensitive); if (b_element == NULL) { return false; } if (!cJSON_Compare(a_element, b_element, case_sensitive)) { return false; } } /* doing this twice, once on a and b to prevent true comparison if a subset of b * TODO: Do this the proper way, this is just a fix for now */ cJSON_ArrayForEach(b_element, b) { a_element = get_object_item(a, b_element->string, case_sensitive); if (a_element == NULL) { return false; } if (!cJSON_Compare(b_element, a_element, case_sensitive)) { return false; } } return true; } default: return false; } } CJSON_PUBLIC(void *) cJSON_malloc(size_t size) { return global_hooks.allocate(size); } CJSON_PUBLIC(void) cJSON_free(void *object) { global_hooks.deallocate(object); } oidc-agent-4.2.6/lib/cJSON/README.md0000644000175000017500000000044514120404223016037 0ustar marcusmarcusoidc-agent uses the MIT-licensed library [cJSON](https://github.com/DaveGamble/cJSON). One can check for updates by using the following commands: ``` $ git clone https://github.com/DaveGamble/cJSON.git /tmp/cJSON $ diff /tmp/cJSON/cJSON.c lib/cJSON/ $ diff /tmp/cJSON/cJSON.h lib/cJSON/ ``` oidc-agent-4.2.6/lib/wrapper/0000755000175000017500000000000014167074355015345 5ustar marcusmarcusoidc-agent-4.2.6/lib/wrapper/cjson.h0000644000175000017500000000025514167074355016634 0ustar marcusmarcus#ifndef OIDC_CJSON_H #define OIDC_CJSON_H #ifndef USE_CJSON_SO #include "cJSON/cJSON.h" #else #include #endif /* USE_CJSON_SO */ #endif /* OIDC_CJSON_H */ oidc-agent-4.2.6/lib/wrapper/list.h0000644000175000017500000000024414167074355016471 0ustar marcusmarcus#ifndef OIDC_LIST_H #define OIDC_LIST_H #ifndef USE_LIST_SO #include "list/list.h" #else #include #endif /* USE_LIST_SO */ #endif /* OIDC_LIST_H */ oidc-agent-4.2.6/lib/wrapper/README.md0000644000175000017500000000042014120404223016574 0ustar marcusmarcusThe Header files in this directory are wrappers that include the correct header files for the library. On compile time one can choose to use the system's shared library or the provided source code. oidc-agent source code must include these header files instead of other. oidc-agent-4.2.6/lib/list/0000755000175000017500000000000014120404223014614 5ustar marcusmarcusoidc-agent-4.2.6/lib/list/list_node.c0000644000175000017500000000055114120404223016741 0ustar marcusmarcus // // node.c // // Copyright (c) 2010 TJ Holowaychuk // #include "list.h" /* * Allocates a new list_node_t. NULL on failure. */ list_node_t * list_node_new(void *val) { list_node_t *self; if (!(self = LIST_MALLOC(sizeof(list_node_t)))) return NULL; self->prev = NULL; self->next = NULL; self->val = val; return self; }oidc-agent-4.2.6/lib/list/README.MD0000644000175000017500000000036414120404223015776 0ustar marcusmarcus oidc-agent uses the MIT-licensed library [clibs/list](https://github.com/clibs/list). One can check for updates by using the following commands: ``` $ git clone https://github.com/clibs/list.git /tmp/list $ diff /tmp/list/src/ lib/list/ ``` oidc-agent-4.2.6/lib/list/list_iterator.c0000644000175000017500000000230014120404223017637 0ustar marcusmarcus // // iterator.c // // Copyright (c) 2010 TJ Holowaychuk // #include "list.h" /* * Allocate a new list_iterator_t. NULL on failure. * Accepts a direction, which may be LIST_HEAD or LIST_TAIL. */ list_iterator_t * list_iterator_new(list_t *list, list_direction_t direction) { list_node_t *node = direction == LIST_HEAD ? list->head : list->tail; return list_iterator_new_from_node(node, direction); } /* * Allocate a new list_iterator_t with the given start * node. NULL on failure. */ list_iterator_t * list_iterator_new_from_node(list_node_t *node, list_direction_t direction) { list_iterator_t *self; if (!(self = LIST_MALLOC(sizeof(list_iterator_t)))) return NULL; self->next = node; self->direction = direction; return self; } /* * Return the next list_node_t or NULL when no more * nodes remain in the list. */ list_node_t * list_iterator_next(list_iterator_t *self) { list_node_t *curr = self->next; if (curr) { self->next = self->direction == LIST_HEAD ? curr->next : curr->prev; } return curr; } /* * Free the list iterator. */ void list_iterator_destroy(list_iterator_t *self) { LIST_FREE(self); self = NULL; } oidc-agent-4.2.6/lib/list/list.h0000644000175000017500000000343314120404223015743 0ustar marcusmarcus // // list.h // // Copyright (c) 2010 TJ Holowaychuk // #ifndef LIST_H #define LIST_H #ifdef __cplusplus extern "C" { #endif #include // Library version #define LIST_VERSION "0.0.6" // Memory management macros #ifndef LIST_MALLOC #define LIST_MALLOC malloc #endif #ifndef LIST_FREE #define LIST_FREE free #endif /* * list_t iterator direction. */ typedef enum { LIST_HEAD, LIST_TAIL } list_direction_t; /* * list_t node struct. */ typedef struct list_node { struct list_node* prev; struct list_node* next; void* val; } list_node_t; /* * list_t struct. */ typedef struct { list_node_t* head; list_node_t* tail; unsigned int len; void (*free)(void* val); int (*match)(const void* a, const void* b); } list_t; /* * list_t iterator struct. */ typedef struct { list_node_t* next; list_direction_t direction; } list_iterator_t; // Node prototypes. list_node_t* list_node_new(void* val); // list_t prototypes. list_t* list_new(); list_node_t* list_rpush(list_t* self, list_node_t* node); list_node_t* list_lpush(list_t* self, list_node_t* node); list_node_t* list_find(list_t* self, const void* val); list_node_t* list_at(list_t* self, int index); list_node_t* list_rpop(list_t* self); list_node_t* list_lpop(list_t* self); void list_remove(list_t* self, list_node_t* node); void list_destroy(list_t* self); // list_t iterator prototypes. list_iterator_t* list_iterator_new(list_t* list, list_direction_t direction); list_iterator_t* list_iterator_new_from_node(list_node_t* node, list_direction_t direction); list_node_t* list_iterator_next(list_iterator_t* self); void list_iterator_destroy(list_iterator_t* self); #ifdef __cplusplus } #endif #endif /* LIST_H */ oidc-agent-4.2.6/lib/list/list.c0000644000175000017500000000705614120404223015743 0ustar marcusmarcus // // list.c // // Copyright (c) 2010 TJ Holowaychuk // #include "list.h" /* * Allocate a new list_t. NULL on failure. */ list_t* list_new() { list_t* self; if (!(self = LIST_MALLOC(sizeof(list_t)))) return NULL; self->head = NULL; self->tail = NULL; self->free = NULL; self->match = NULL; self->len = 0; return self; } /* * Free the list. */ void list_destroy(list_t* self) { unsigned int len = self->len; list_node_t* next; list_node_t* curr = self->head; while (len--) { next = curr->next; if (self->free) self->free(curr->val); LIST_FREE(curr); curr = next; } LIST_FREE(self); } /* * Append the given node to the list * and return the node, NULL on failure. */ list_node_t* list_rpush(list_t* self, list_node_t* node) { if (!node) return NULL; if (self->len) { node->prev = self->tail; node->next = NULL; self->tail->next = node; self->tail = node; } else { self->head = self->tail = node; node->prev = node->next = NULL; } ++self->len; return node; } /* * Return / detach the last node in the list, or NULL. */ list_node_t* list_rpop(list_t* self) { if (!self->len) return NULL; list_node_t* node = self->tail; if (--self->len) { (self->tail = node->prev)->next = NULL; } else { self->tail = self->head = NULL; } node->next = node->prev = NULL; return node; } /* * Return / detach the first node in the list, or NULL. */ list_node_t* list_lpop(list_t* self) { if (!self->len) return NULL; list_node_t* node = self->head; if (--self->len) { (self->head = node->next)->prev = NULL; } else { self->head = self->tail = NULL; } node->next = node->prev = NULL; return node; } /* * Prepend the given node to the list * and return the node, NULL on failure. */ list_node_t* list_lpush(list_t* self, list_node_t* node) { if (!node) return NULL; if (self->len) { node->next = self->head; node->prev = NULL; self->head->prev = node; self->head = node; } else { self->head = self->tail = node; node->prev = node->next = NULL; } ++self->len; return node; } /* * Return the node associated to val or NULL. */ list_node_t* list_find(list_t* self, const void* val) { list_iterator_t* it = list_iterator_new(self, LIST_HEAD); list_node_t* node; while ((node = list_iterator_next(it))) { if (self->match) { if (self->match(val, node->val)) { list_iterator_destroy(it); return node; } } else { if (val == node->val) { list_iterator_destroy(it); return node; } } } list_iterator_destroy(it); return NULL; } /* * Return the node at the given index or NULL. */ list_node_t* list_at(list_t* self, int index) { list_direction_t direction = LIST_HEAD; if (index < 0) { direction = LIST_TAIL; index = ~index; } if ((unsigned)index < self->len) { list_iterator_t* it = list_iterator_new(self, direction); list_node_t* node = list_iterator_next(it); while (index--) node = list_iterator_next(it); list_iterator_destroy(it); return node; } return NULL; } /* * Remove the given node from the list, freeing it and it's value. */ void list_remove(list_t* self, list_node_t* node) { node->prev ? (node->prev->next = node->next) : (self->head = node->next); node->next ? (node->next->prev = node->prev) : (self->tail = node->prev); if (self->free) self->free(node->val); LIST_FREE(node); --self->len; } oidc-agent-4.2.6/rpm/0000755000175000017500000000000014170031216013674 5ustar marcusmarcusoidc-agent-4.2.6/rpm/oidc-agent.spec0000644000175000017500000001617614170031216016575 0ustar marcusmarcusName: oidc-agent Version: 4.2.6 Release: 1%{?dist} Summary: Command-line tool for obtaining OpenID Connect access tokens %if 0%{?suse_version} > 0 Group: Misc %endif License: MIT URL: https://github.com/indigo-dc/oidc-agent # use `make rpmsource` to generate the required tarball #Source0: https://github.com/indigo-dc/oidc-agent/archive/refs/heads/master.zip #Source0: https://github.com/indigo-dc/oidc-agent/archive/refs/heads/docker-builds.zip Source0: https://github.com/indigo-dc/oidc-agent/archive/refs/tags/v%{version}.tar.gz #DO_NOT_REPLACE_THIS_LINE BuildRequires: gcc >= 4.8 BuildRequires: libcurl-devel >= 7.29 BuildRequires: libsodium-devel >= 1.0.14 %if 0%{?suse_version} > 0 BuildRequires: unzip >= 6 %endif %if 0%{?suse_version} > 0 BuildRequires: libsodium23 >= 1.0.14 %else BuildRequires: libsodium-static >= 1.0.16 %endif BuildRequires: libmicrohttpd-devel >= 0.9.33 BuildRequires: libseccomp-devel >= 2.3 BuildRequires: help2man >= 1.41 BuildRequires: libsecret-devel >= 0.18.4 BuildRequires: desktop-file-utils BuildRequires: qrencode-devel >= 3 Requires: oidc-agent-desktop == %{version}-%{release} BuildRoot: %{_tmppath}/%{name} #cp /home/build/oidc-agent/rpm/oidc-agent.spec rpm && rpmbuild --define "_topdir /tmp/build/oidc-agent/rpm/rpmbuild" -bb rpm/oidc-agent.spec %files %defattr(-,root,root,-) %doc %{_defaultdocdir}/%{name}-%{version} %doc %{_defaultdocdir}/%{name}-%{version}/README.md %license LICENSE %package -n oidc-agent-cli Summary: Command-line tool for obtaining OpenID Connect Access tokens Requires: liboidc-agent4 == %{version}-%{release} Requires: libsecret >= 0.18.6 Requires: glib2 >= 2.56.1 Requires: jq %if 0%{?suse_version} > 0 Requires: libqrencode4 >= 4 Requires: libsodium23 >= 1.0.16 Requires: libcurl4 >= 7.29 Requires: libmicrohttpd12 >= 0.9 Requires: libseccomp2 >= 2.3.1 %else Requires: qrencode-libs >= 3 Requires: libsodium >= 1.0.18 Requires: libcurl >= 7.29 Requires: libmicrohttpd >= 0.9 Requires: libseccomp >= 2.3.1 %endif %package -n liboidc-agent4 Summary: oidc-agent library %if 0%{?suse_version} > 0 Requires: libsodium23 >= 1.0.16 %else Requires: libsodium >= 1.0.18 %endif %package -n liboidc-agent-devel Summary: oidc-agent library development files Requires: liboidc-agent4 == %{version}-%{release} %package -n oidc-agent-desktop Summary: GUI integration for obtaining OpenID Connect Access tokens on the command-line Requires: oidc-agent-cli == %{version}-%{release} Requires: yad Requires: xterm %description oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command-line. This meta-package bundles the command-line tools and the files for desktop integration %description -n oidc-agent-cli oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command-line. These tools follow ssh-agent design, so OIDC tokens can be handled in a similar way as ssh keys. The agent stores multiple configurations and their associated refresh tokens securely. This tool consists of five programs: - oidc-agent that handles communication with the OIDC provider - oidc-gen that generates config files - oidc-add that loads (and unloads) configuration into the agent - oidc-token that can be used to get access token on the command-line - oidc-key-chain that re-uses oidc-agent across logins %description -n liboidc-agent4 oidc-agent is a command-line tool for obtaining OpenID Connect Access tokens on the command-line. This package provides a library for easy communication with oidc-agent. Applications can use this library to request access token from oidc-agent. %description -n liboidc-agent-devel oidc-agent is a command-line tool for obtaining OpenID Connect Access tokens on the command-line. This package provides the development files (static library and headers) required for building applications with liboidc-agent, a library for communicating with oidc-agent. %description -n oidc-agent-desktop Desktop integration files for oidc-gen and oidc-agent and for creating the user dialog. This package adds two ways for supporting the usage of oidc-agent in a graphical environment. The .desktop file to leverage browser integration to support the authorization code flow in oidc-gen. The Xsession file to consistently set the environment variables necessary to for client tools to connect to the oidc-agent daemon. This package also provides a bash script as an interface to create different dialog windows. It uses yad to create windows. %prep %setup -q %build export USE_CJSON_SO=0 export USE_LIST_SO=0 make %install echo "Buildroot: %{buildroot}" make install install_lib install_lib-dev \ BIN_AFTER_INST_PATH=%{buildroot}%{_prefix}\ BIN_PATH=%{buildroot}%{_prefix}\ MAN_PATH=%{buildroot}%{_mandir}\ CONFIG_PATH=%{buildroot}%{_sysconfdir}\ CONFIG_AFTER_INST_PATH=${_sysconfdir}\ BASH_COMPLETION_PATH=%{buildroot}%{_datarootdir}/bash-completion/completions\ DESKTOP_APPLICATION_PATH=%{buildroot}%{_datarootdir}/applications\ XSESSION_PATH=%{buildroot}%{_sysconfdir}/X11\ PROMPT_MAN_PATH=%{buildroot}%{_mandir}\ PROMPT_BIN_PATH=%{buildroot}%{_prefix}\ LIB_PATH=%{buildroot}%{_libdir}\ LIBDEV_PATH=%{buildroot}%{_libdir}\ INCLUDE_PATH=%{buildroot}%{_includedir} # FIXME: This ought to be fixed elsewhere! # fix paths in installed files sed -i -e "s!%{buildroot}!!g" %{buildroot}%{_sysconfdir}/X11/Xsession.d/91oidc-agent sed -i -e "s!%{buildroot}!!g" %{buildroot}%{_datarootdir}/applications/oidc-gen.desktop mkdir -p %{buildroot}/%{_defaultdocdir}/%{name}-%{version} cp README.md %{buildroot}/%{_defaultdocdir}/%{name}-%{version}/README.md %check desktop-file-validate %{buildroot}/%{_datadir}/applications/oidc-gen.desktop %files -n oidc-agent-cli %defattr(-,root,root,-) %license LICENSE %config(noreplace) /etc/oidc-agent/privileges/ %config(noreplace) /etc/oidc-agent/issuer.config %config(noreplace) /etc/oidc-agent/oidc-agent-service.options %config(noreplace) /etc/oidc-agent/pubclients.config /usr/share/bash-completion/completions/ %attr(0644, root, root) %doc /usr/share/man/man1/oidc-agent.1.gz %attr(0644, root, root) %doc /usr/share/man/man1/oidc-gen.1.gz %attr(0644, root, root) %doc /usr/share/man/man1/oidc-add.1.gz %attr(0644, root, root) %doc /usr/share/man/man1/oidc-keychain.1.gz %attr(0644, root, root) %doc /usr/share/man/man1/oidc-token.1.gz %attr(0644, root, root) %doc /usr/share/man/man1/oidc-agent-service.1.gz %{_bindir}/oidc-add %{_bindir}/oidc-agent %{_bindir}/oidc-agent-service %{_bindir}/oidc-gen %{_bindir}/oidc-keychain %{_bindir}/oidc-token %files -n liboidc-agent4 %defattr(-,root,root,-) %license LICENSE %{_libdir}/liboidc-agent.so.4 %{_libdir}/liboidc-agent.so.%{version} %files -n liboidc-agent-devel %defattr(-,root,root,-) %license LICENSE %{_includedir}/oidc-agent %{_libdir}/liboidc-agent.so %attr(0644, root, root) %{_libdir}/liboidc-agent.a %files -n oidc-agent-desktop %defattr(-,root,root,-) %license LICENSE %{_bindir}/oidc-prompt %doc /usr/share/man/man1/oidc-prompt.1.gz %config(noreplace) /etc/X11/Xsession.d/91oidc-agent /usr/share/applications/oidc-gen.desktop %changelog * Wed Aug 25 2021 Marcus Hardt - 4.1.1-3 - Restructured rpm packages to reflect debian structure oidc-agent-4.2.6/logo.png0000644000175000017500000003167714120404223014557 0ustar marcusmarcusPNG  IHDRC zTXtRaw profile type exifxڝiv:s$@r9 7y=',K$Bp!$ͺY(tvOo#?G8-/?_Og*uipk25e.Vc>}mdUĜKo;Syd?w9W\Cz /saH~~r'z~MBؖrH@ФmˣVIsdZҕ7N J*9w2o˖wWG|yȸvXC<"3y ct6 C6@L[J֍J.x H&%_@[{[S{OVIgr:zdn{%3V6~m.0zA ɍmHW >K`S]I8XֱK˲ a=c֧e&OGR $B鋾dldAym׼Ǧ C"{(|X3ʘ^{2+Nixr@98B 0UnH 7; {7ᓾu Q== b`\Rz"[Du(\e" ijl&@W(.յ[lNFLQ2a4tx@?z V #ܰQL8 dALt-3u#gvD).gŋ!gʫ}NePw"Bx}/$R,UH@Vܝ*Z"I!)k+"ف|8]3p!Tٶ4aSTf4$!{@Q;tyqPߤ`|8u#L2Fy +J)VJ$Vն:r<ڔ"h\E9~쳗M# /T R1p !3GBt7~m>:Pb3>NMruT†2A4GB2f:fh)1h^2VPbL.ԣR"kp媑yCINWuC%Ԃg-;T h+*]9~4#}=tucN3@D4B:iFfG$; =CMd5=QY&9bp}*١-Zh 4DD ~|sBHOXQ#" '05j (5N:Ed Ԉ! NڹkvHn(Y5h FwtӋ "|T mU:R#0gJʃJt" |ΦKWH ur8$M߂`,$+9FPѴ^AwĄ_zϝh}b{* !%j<>xF#c 39HIбNJEWK)Xw#zy22<Gf0DToGB(f 0zJU3؁Gs>r2ҾoLOH+W?zxh>|LƆ ȋ ͵{}?dz{7K]sBITO IDATxj9B)$Lۦ 1bzL7 fRy@Zx\^-nko 6墍$t 3s=Y'+"3PUJ EF7#~qV{݄;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;AO xyCw  pw  pw  pw  Ҟ>{O2;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@;@@~<{ ̸@;@@;@@}6(܀뷢(on^q 67~nHg_~S;0ѯlo=wqyu|fǿ= { po/EQ˾d0vhѫGpU :9~(E6BӘknhwNhoWCgv{#%܁4w,VN޴4w.v$ZXރpϝ8;KT;ufZhphjAf wv'h4ij=mi<f\j`f\_bٙqhٹjUfeqXj`qwv$pj{`>^w9v:z_rrzg{n;..߾T; TnoϻBF,0gU;]\^T;mvV~zvރ,( wņt?|8;>@a @ VV{z0Jɪ]f- @ժ]߿YLkNݼk3C_~{} Ɏf=#ɣjcfl_]6;Lg|`r;]εv4>QΩu{Er.͍w'oVgu7q]u{YgX; 0;̸AΌ;;̸Akrk3Dp9H`s?yj/o`so'`N=C;[N<}Q$3$T&}wƝz~>ָ;=+}.}u*8~mŧCʃg /_g+?Ha"D?=vvʇjɤ]}|y[ +x1N~6c9kӳ(|V)={O=l5r|mҋʟ3^lq5'vb>mR:9~vkWȭG7jj1y?/Jtj~^Vw0>KuNM= s&=M:KF0Az֟#^Iy_A챥w3&qTK֍=n.8fXFpwØ%+5|}_\g'\z9 .oW\{q˼SQmlJWoڝnv0pu%˴lv-7/˼Nzۡ,a}V6Ȫ-o̫rxY6fWf-M+|ݹ7l1"0Ѯו?9=;_w[n@F^ѫNz`樸|Eq}s{ZlK: Dho>5$\?"T 켃vܼx*u%TgꇅU]e 4˝dNsj*g9բ"wZT?6bIU6ܨ2)32׸J4[eRBmo޾dxu6> Pͥӳ%ʗwsa.3$>m}zϤ{erWQiVCKU\˟Xv3e{V5^opIf܉ iKezޭ{L) _A{oU9kYєW_V`:o#,Oӊ6^VO3N}}Cqs4LL)/_߫jo܈IqT"q].Y̽H~ҍ{v\²+K;-3`s#ԽGfIdƝ_>ۛF-j&_v޿qQ3 +gu~G8eW2'n7Wm͌;;@c|6zt.6~fוrg{kow UDL$fI$ܹoV<5Z"ti$EZG$Ls6~m슥fo{wϘq'p羹7k2c`b>| K|q_ܾ8|9Mv{~x{35z_xUՋ×֖q VFWB\FJZj/ ]'kU Z6Qv˽󧏍?6 ;-O7dRo j& lʹY 2.7U0d7d75Q:9~=zd5Ό<, 67|Jvկ }̫`s~Ыar\n77%w^2E#.(ε78EVTˏ|-j2o]o6Ns GXɽeLK~`wgKͤjhO --8*zwu99OǩŠt黓7 ONEԘ~X8Y9xCVF&-N{O={VC\.b?H#{ӳYuo/_\ge3J=y2?^Lgxc*ˠg|VNzJomw3a=/k^:qʃ8ӿ1ۼXƏgƍVЌYXp#S^]?.SKR{>}XGr^dG# IDATOUf[#4_g)͵ 7oe9^kz 7}[(MՋ!M)e~S-^fnͮipSS^q9F4rE7匏H5~uw;5xޞdbov#RKFƃz9Je2!w1e$&5o͹}Ҧre{-t{˼ 4iΥqޕgÏK'u,#s0S*vS~s;a N&+tekG:86btkׯ>zRoxTLx>ξyGO_w%Y~36+\3`<6z¼x=bEwRU}z,:\Tw{Z~:~%)H{U]®on+FZ{6w"Nsf w"?;7Vp ^scoV ;@ʺʲ%Uu<";IM667*mXH|)NތI=D| `ow 67v+nX)[c\?wFh?\H{V\\^IW)/)+O@#;&..߾BXAl͍_Y2IENDB`oidc-agent-4.2.6/.gitignore0000644000175000017500000000065314167074355015113 0ustar marcusmarcusbin/ obj/ pic-obj/ lib/api/ man/ *.swp doc/ debian/.debhelper/* debian/debhelper-build-stamp debian/oidc-agent/ debian/oidc-agent-server/ debian/oidc-agent-prompt/ debian/liboidc-agent?/ debian/liboidc-agent-dev/ debian/oidc-agent-prompt/ debian/oidc-agent-cli/ debian/oidc-agent-desktop/ debian/tmp debian/files debian/*.substvars debian/*.debhelper* rpm/rpmbuild/ *Session.vim tags oidc-gen.app/ .pc/ .idea/ *.log docker/log oidc-agent-4.2.6/test/0000755000175000017500000000000014167775443014104 5ustar marcusmarcusoidc-agent-4.2.6/test/src/0000755000175000017500000000000014167074355014665 5ustar marcusmarcusoidc-agent-4.2.6/test/src/account/0000755000175000017500000000000014120404223016275 5ustar marcusmarcusoidc-agent-4.2.6/test/src/account/account/0000755000175000017500000000000014167074355017755 5ustar marcusmarcusoidc-agent-4.2.6/test/src/account/account/tc_defineUsableScopes.h0000644000175000017500000000032614120404223024334 0ustar marcusmarcus#ifndef TEST_ACCOUNT_ACCOUNT_DEFINEUSABLESCOPES_H #define TEST_ACCOUNT_ACCOUNT_DEFINEUSABLESCOPES_H #include TCase* test_case_defineUsableScopes(); #endif // TEST_ACCOUNT_ACCOUNT_DEFINEUSABLESCOPES_H oidc-agent-4.2.6/test/src/account/account/tc_defineUsableScopes.c0000644000175000017500000000341214167074355024352 0ustar marcusmarcus#include "tc_defineUsableScopes.h" #include "account/account.h" #include "defines/oidc_values.h" #include "list/list.h" #include "utils/string/stringUtils.h" extern list_t* defineUsableScopeList(const struct oidc_account* account); extern void _printList(list_t* l); START_TEST(test_null) { struct oidc_account account = {}; account_setScope(&account, NULL); list_t* list = defineUsableScopeList(&account); // _printList(list); ck_assert_ptr_ne(list, NULL); ck_assert_ptr_ne(list_find(list, OIDC_SCOPE_OPENID), NULL); ck_assert_ptr_ne(list_find(list, OIDC_SCOPE_OFFLINE_ACCESS), NULL); } END_TEST START_TEST(test_nullGoogle) { struct oidc_account account = {}; account_setScope(&account, NULL); account_setIssuerUrl(&account, GOOGLE_ISSUER_URL); list_t* list = defineUsableScopeList(&account); // _printList(list); ck_assert_ptr_ne(list, NULL); ck_assert_ptr_ne(list_find(list, OIDC_SCOPE_OPENID), NULL); ck_assert_ptr_eq(list_find(list, OIDC_SCOPE_OFFLINE_ACCESS), NULL); } END_TEST START_TEST(test_valid) { struct oidc_account account = {}; account_setScope(&account, oidc_strcopy("profile email offline_access handy")); list_t* list = defineUsableScopeList(&account); // _printList(list); ck_assert_ptr_ne(list, NULL); ck_assert_ptr_ne(list_find(list, OIDC_SCOPE_OPENID), NULL); ck_assert_ptr_ne(list_find(list, OIDC_SCOPE_OFFLINE_ACCESS), NULL); ck_assert_ptr_ne(list_find(list, "profile"), NULL); ck_assert_ptr_ne(list_find(list, "email"), NULL); ck_assert_ptr_ne(list_find(list, "handy"), NULL); } END_TEST TCase* test_case_defineUsableScopes() { TCase* tc = tcase_create("defineUsableScopes"); tcase_add_test(tc, test_nullGoogle); tcase_add_test(tc, test_null); tcase_add_test(tc, test_valid); return tc; } oidc-agent-4.2.6/test/src/account/account/suite.c0000644000175000017500000000033314167074355021251 0ustar marcusmarcus#include "suite.h" #include "tc_defineUsableScopes.h" Suite* test_suite_account() { Suite* ts_account = suite_create("account"); suite_add_tcase(ts_account, test_case_defineUsableScopes()); return ts_account; } oidc-agent-4.2.6/test/src/account/account/suite.h0000644000175000017500000000024514120404223021234 0ustar marcusmarcus#ifndef TEST_ACCOUNT_ACCOUNT_SUITE_H #define TEST_ACCOUNT_ACCOUNT_SUITE_H #include Suite* test_suite_account(); #endif // TEST_ACCOUNT_ACCOUNT_SUITE_H oidc-agent-4.2.6/test/src/utils/0000755000175000017500000000000014120404223016001 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/json/0000755000175000017500000000000014167074355016776 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/json/suite.c0000644000175000017500000000042314167074355020272 0ustar marcusmarcus#include "suite.h" #include "tc_isJSONObject.h" #include "tc_setJSONValue.h" Suite* test_suite_json() { Suite* ts_json = suite_create("json"); suite_add_tcase(ts_json, test_case_isJSONObject()); suite_add_tcase(ts_json, test_case_setJSONValue()); return ts_json; } oidc-agent-4.2.6/test/src/utils/json/tc_isJSONObject.c0000644000175000017500000000307714167074355022073 0ustar marcusmarcus#include "tc_isJSONObject.h" #include #include "utils/json.h" START_TEST(test_emptyObject) { const char* json = "{}"; ck_assert(isJSONObject(json)); } END_TEST START_TEST(test_noObject) { const char* json = "[]"; ck_assert(!isJSONObject(json)); json = "just a string"; ck_assert(!isJSONObject(json)); } END_TEST START_TEST(test_objectWithStringElement) { const char* json = "{\"key\":\"value\"}"; ck_assert(isJSONObject(json)); } END_TEST START_TEST(test_objectWithNumberElement) { const char* json = "{\"key\":42}"; ck_assert(isJSONObject(json)); } END_TEST START_TEST(test_objectWithArrayElement) { const char* json = "{\"key\":[10,20]}"; ck_assert(isJSONObject(json)); } END_TEST START_TEST(test_objectWithObjectElement) { const char* json = "{\"key\":{\"innerkey\":42}}"; ck_assert(isJSONObject(json)); } END_TEST START_TEST(test_objectWithMultipleElement) { const char* json = "{\"key\":\"value\",\"key2\":\"value2\", \"key3\" : \"value3\"\n}"; ck_assert(isJSONObject(json)); } END_TEST TCase* test_case_isJSONObject() { TCase* tc_isJSONObject = tcase_create("isJSONObject"); tcase_add_test(tc_isJSONObject, test_emptyObject); tcase_add_test(tc_isJSONObject, test_noObject); tcase_add_test(tc_isJSONObject, test_objectWithStringElement); tcase_add_test(tc_isJSONObject, test_objectWithNumberElement); tcase_add_test(tc_isJSONObject, test_objectWithArrayElement); tcase_add_test(tc_isJSONObject, test_objectWithObjectElement); tcase_add_test(tc_isJSONObject, test_objectWithMultipleElement); return tc_isJSONObject; } oidc-agent-4.2.6/test/src/utils/json/tc_setJSONValue.h0000644000175000017500000000025714120404223022077 0ustar marcusmarcus#ifndef TEST_UTILS_JSON_SETJSONVALUE_H #define TEST_UTILS_JSON_SETJSONVALUE_H #include TCase* test_case_setJSONValue(); #endif // TEST_UTILS_JSON_SETJSONVALUE_H oidc-agent-4.2.6/test/src/utils/json/tc_setJSONValue.c0000644000175000017500000000362114167074355022114 0ustar marcusmarcus#include "tc_setJSONValue.h" #include "utils/json.h" #include "utils/memory.h" #include "utils/string/stringUtils.h" START_TEST(test_update) { cJSON* cjson1 = generateJSONObject("key", cJSON_String, "value1", "otherKey", cJSON_String, "otherValue", NULL); cJSON* cjson2 = generateJSONObject("key", cJSON_String, "value2", "otherKey", cJSON_String, "otherValue", NULL); ck_assert_int_eq(setJSONValue(cjson1, "key", "value2"), OIDC_SUCCESS); ck_assert_msg(cJSON_Compare(cjson1, cjson2, 1), "cjson1 and cjson2 not equal: cjson1 = '%s', cjson2 = '%s'", cJSON_Print(cjson1), cJSON_Print(cjson2)); } END_TEST START_TEST(test_insert) { cJSON* cjson1 = generateJSONObject("otherKey", cJSON_String, "otherValue", NULL); cJSON* cjson2 = generateJSONObject("key", cJSON_String, "value2", "otherKey", cJSON_String, "otherValue", NULL); ck_assert_int_eq(setJSONValue(cjson1, "key", "value2"), OIDC_SUCCESS); ck_assert_msg(cJSON_Compare(cjson1, cjson2, 1), "cjson1 and cjson2 not equal: cjson1 = '%s' cjson2 = '%s'", cJSON_Print(cjson1), cJSON_Print(cjson2)); } END_TEST START_TEST(test_cjsonNULL) { ck_assert_int_eq(setJSONValue(NULL, "key", "value2"), OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_keyNULL) { ck_assert_int_eq(setJSONValue((cJSON*)"dummy", NULL, "value2"), OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_valueNULL) { ck_assert_int_eq(setJSONValue((cJSON*)"dummy", "key", NULL), OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_setJSONValue() { TCase* tc = tcase_create("setJSONValue"); tcase_add_test(tc, test_update); tcase_add_test(tc, test_insert); tcase_add_test(tc, test_cjsonNULL); tcase_add_test(tc, test_keyNULL); tcase_add_test(tc, test_valueNULL); return tc; } oidc-agent-4.2.6/test/src/utils/json/tc_isJSONObject.h0000644000175000017500000000025714120404223022051 0ustar marcusmarcus#ifndef TEST_UTILS_JSON_ISJSONOBJECT_H #define TEST_UTILS_JSON_ISJSONOBJECT_H #include TCase* test_case_isJSONObject(); #endif // TEST_UTILS_JSON_ISJSONOBJECT_H oidc-agent-4.2.6/test/src/utils/json/suite.h0000644000175000017500000000022314120404223020251 0ustar marcusmarcus#ifndef TEST_UTILS_JSON_SUITE_H #define TEST_UTILS_JSON_SUITE_H #include Suite* test_suite_json(); #endif // TEST_UTILS_JSON_SUITE_H oidc-agent-4.2.6/test/src/utils/stringUtils/0000755000175000017500000000000014167074355020354 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/stringUtils/tc_strToInt.c0000644000175000017500000000124414167074355022775 0ustar marcusmarcus#include "tc_strToInt.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_positive) { ck_assert_int_eq(strToInt("42"), 42); } END_TEST START_TEST(test_zero) { ck_assert_int_eq(strToInt("0"), 0); } END_TEST START_TEST(test_negative) { ck_assert_int_eq(strToInt("-42"), -42); } END_TEST START_TEST(test_NULL) { ck_assert_int_eq(strToInt(NULL), 0); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strToInt() { TCase* tc = tcase_create("strToInt"); tcase_add_test(tc, test_zero); tcase_add_test(tc, test_positive); tcase_add_test(tc, test_negative); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strEnds.h0000644000175000017500000000026014120404223022607 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRENDS_H #define TEST_UTILS_STRINGUTILS_STRENDS_H #include TCase* test_case_strEnds(); #endif // TEST_UTILS_STRINGUTILS_STRENDS_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelim.c0000644000175000017500000000146714167074355022675 0ustar marcusmarcus#include "tc_strelim.h" #include #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_noFound) { const char* const str = "abcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelim(s, 'x'), str); } END_TEST START_TEST(test_elim) { const char* const str = "abcdeffedcbaabcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelim(s, 'b'), "acdeffedcaacdeffedca"); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(strelim(NULL, 'b'), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strelim() { TCase* tc = tcase_create("strelim"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_elim); tcase_add_test(tc, test_noFound); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_escapeCharInStr.h0000644000175000017500000000032014120404223024200 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_ESCAPECHARINSTR_H #define TEST_UTILS_STRINGUTILS_ESCAPECHARINSTR_H #include TCase* test_case_escapeCharInStr(); #endif // TEST_UTILS_STRINGUTILS_ESCAPECHARINSTR_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strValid.h0000644000175000017500000000026414120404223022761 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRVALID_H #define TEST_UTILS_STRINGUTILS_STRVALID_H #include TCase* test_case_strValid(); #endif // TEST_UTILS_STRINGUTILS_STRVALID_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strcopy.h0000644000175000017500000000030414120404223023665 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_OIDC_STRCOPY_H #define TEST_UTILS_STRINGUTILS_OIDC_STRCOPY_H #include TCase* test_case_oidc_strcopy(); #endif // TEST_UTILS_STRINGUTILS_OIDC_STRCOPY_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strcat.c0000644000175000017500000000142214167074355023503 0ustar marcusmarcus#include "tc_oidc_strcat.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_concat) { char* s = oidc_strcat("asdf", "1234"); ck_assert_str_eq(s, "asdf1234"); secFree(s); } END_TEST START_TEST(test_NULL1) { char* s = oidc_strcat(NULL, "1234"); ck_assert_ptr_eq(s, NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); secFree(s); } END_TEST START_TEST(test_NULL2) { char* s = oidc_strcat("asdf", NULL); ck_assert_ptr_eq(s, NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); secFree(s); } END_TEST TCase* test_case_oidc_strcat() { TCase* tc = tcase_create("oidc_strcat"); tcase_add_test(tc, test_concat); tcase_add_test(tc, test_NULL1); tcase_add_test(tc, test_NULL2); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strEnds.c0000644000175000017500000000211314167074355022625 0ustar marcusmarcus#include "tc_strEnds.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_ends) { ck_assert(strEnds("testSuffix", "Suffix")); } END_TEST START_TEST(test_notend) { ck_assert(!strEnds("testSufix", "Suffix")); } END_TEST START_TEST(test_emptySuffix) { ck_assert(strEnds("testString", "")); } END_TEST START_TEST(test_emptyString) { ck_assert(!strEnds("", "suffix")); } END_TEST START_TEST(test_bothEmpty) { ck_assert(strEnds("", "")); } END_TEST START_TEST(test_suffixNULL) { ck_assert(!strEnds("anything", NULL)); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_stringNULL) { ck_assert(!strEnds(NULL, "anything")); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strEnds() { TCase* tc = tcase_create("strEnds"); tcase_add_test(tc, test_ends); tcase_add_test(tc, test_notend); tcase_add_test(tc, test_emptySuffix); tcase_add_test(tc, test_emptyString); tcase_add_test(tc, test_bothEmpty); tcase_add_test(tc, test_suffixNULL); tcase_add_test(tc, test_stringNULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strCountChar.c0000644000175000017500000000113414167074355023624 0ustar marcusmarcus#include "tc_strCountChar.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_NULL) { ck_assert_int_eq(strCountChar(NULL, 'b'), 0); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_notFound) { ck_assert_int_eq(strCountChar("test", 'b'), 0); } END_TEST START_TEST(test_found) { ck_assert_int_eq(strCountChar("test", 't'), 2); } END_TEST TCase* test_case_strCountChar() { TCase* tc = tcase_create("strCountChar"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_found); tcase_add_test(tc, test_notFound); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strncopy.c0000644000175000017500000000225114167074355024065 0ustar marcusmarcus#include "tc_oidc_strncopy.h" #include #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_copy) { const char* const str = "someTestString"; char* s = oidc_strncopy(str, strlen(str)); ck_assert_str_eq(s, str); secFree(s); } END_TEST START_TEST(test_copyn) { char* s = oidc_strncopy("someTestString", 5); ck_assert_str_eq("someT", s); secFree(s); } END_TEST START_TEST(test_copyBigLen) { const char* const str = "someTestString"; char* s = oidc_strncopy(str, 9000); ck_assert_str_eq(s, str); secFree(s); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(oidc_strncopy(NULL, 10), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_zeroLen) { ck_assert_ptr_eq(oidc_strncopy("anything", 0), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_oidc_strncopy() { TCase* tc = tcase_create("oidc_strncopy"); tcase_add_test(tc, test_copy); tcase_add_test(tc, test_copyn); tcase_add_test(tc, test_copyBigLen); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_zeroLen); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelimIfAfter.h0000644000175000017500000000031414120404223024105 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRELIMIFAFTER_H #define TEST_UTILS_STRINGUTILS_STRELIMIFAFTER_H #include TCase* test_case_strelimIfAfter(); #endif // TEST_UTILS_STRINGUTILS_STRELIMIFAFTER_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelim.h0000644000175000017500000000026014120404223022644 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRELIM_H #define TEST_UTILS_STRINGUTILS_STRELIM_H #include TCase* test_case_strelim(); #endif // TEST_UTILS_STRINGUTILS_STRELIM_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strcat.h0000644000175000017500000000030014120404223023456 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_OIDC_STRCAT_H #define TEST_UTILS_STRINGUTILS_OIDC_STRCAT_H #include TCase* test_case_oidc_strcat(); #endif // TEST_UTILS_STRINGUTILS_OIDC_STRCAT_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strToULong.c0000644000175000017500000000131014167074355023261 0ustar marcusmarcus#include "tc_strToULong.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_positive) { ck_assert_uint_eq(strToULong("42"), 42); } END_TEST START_TEST(test_zero) { ck_assert_uint_eq(strToULong("0"), 0); } END_TEST START_TEST(test_negative) { ck_assert_uint_eq(strToULong("-42"), (unsigned long)-42); } END_TEST START_TEST(test_NULL) { ck_assert_uint_eq(strToULong(NULL), 0); ck_assert_uint_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strToULong() { TCase* tc = tcase_create("strToULong"); tcase_add_test(tc, test_zero); tcase_add_test(tc, test_positive); tcase_add_test(tc, test_negative); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelimIfFollowed.c0000644000175000017500000000215214167074355024640 0ustar marcusmarcus#include "tc_strelimIfFollowed.h" #include #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_noElim) { const char* const str = "abcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfFollowed(s, 'b', 'x'), str); } END_TEST START_TEST(test_noFound) { const char* const str = "abcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfFollowed(s, 'x', 'a'), str); } END_TEST START_TEST(test_elim) { const char* const str = "abcdeffedcbaabcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfFollowed(s, 'b', 'c'), "acdeffedcbaacdeffedcba"); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(strelimIfFollowed(NULL, 'b', 'c'), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strelimIfFollowed() { TCase* tc = tcase_create("strelimIfFollowed"); tcase_add_test(tc, test_noElim); tcase_add_test(tc, test_noFound); tcase_add_test(tc, test_elim); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strValid.c0000644000175000017500000000121514167074355022775 0ustar marcusmarcus#include "tc_strValid.h" #include "utils/string/stringUtils.h" START_TEST(test_valid) { ck_assert(strValid("validString")); } END_TEST START_TEST(test_empty) { ck_assert(!strValid("")); } END_TEST START_TEST(test_NULL) { ck_assert(!strValid(NULL)); } END_TEST START_TEST(test_null) { ck_assert(!strValid("null")); } END_TEST START_TEST(test_null2) { ck_assert(!strValid("(null)")); } END_TEST TCase* test_case_strValid() { TCase* tc = tcase_create("strValid"); tcase_add_test(tc, test_valid); tcase_add_test(tc, test_empty); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_null); tcase_add_test(tc, test_null2); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strncopy.h0000644000175000017500000000031014120404223024040 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_OIDC_STRNCOPY_H #define TEST_UTILS_STRINGUTILS_OIDC_STRNCOPY_H #include TCase* test_case_oidc_strncopy(); #endif // TEST_UTILS_STRINGUTILS_OIDC_STRNCOPY_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelimIfFollowed.h0000644000175000017500000000033014120404223024615 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRELIMIFFOLLOWED_H #define TEST_UTILS_STRINGUTILS_STRELIMIFFOLLOWED_H #include TCase* test_case_strelimIfFollowed(); #endif // TEST_UTILS_STRINGUTILS_STRELIMIFFOLLOWED_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strstarts.c0000644000175000017500000000215314167074355023260 0ustar marcusmarcus#include "tc_strstarts.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_starts) { ck_assert(strstarts("prefixxxTest", "prefix")); } END_TEST START_TEST(test_notstart) { ck_assert(!strstarts("pefixxxTest", "prefix")); } END_TEST START_TEST(test_emptyPrefix) { ck_assert(strstarts("testString", "")); } END_TEST START_TEST(test_emptyString) { ck_assert(!strstarts("", "prefix")); } END_TEST START_TEST(test_bothEmpty) { ck_assert(strstarts("", "")); } END_TEST START_TEST(test_prefixNULL) { ck_assert(!strstarts("anything", NULL)); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_stringNULL) { ck_assert(!strstarts(NULL, "anything")); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strstarts() { TCase* tc = tcase_create("strstarts"); tcase_add_test(tc, test_starts); tcase_add_test(tc, test_notstart); tcase_add_test(tc, test_emptyPrefix); tcase_add_test(tc, test_emptyString); tcase_add_test(tc, test_bothEmpty); tcase_add_test(tc, test_prefixNULL); tcase_add_test(tc, test_stringNULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_strcopy.c0000644000175000017500000000111014167074355023700 0ustar marcusmarcus#include "tc_oidc_strcopy.h" #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_copy) { const char* const str = "someTestString"; char* s = oidc_strcopy(str); ck_assert_str_eq(s, str); secFree(s); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(oidc_strcopy(NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_oidc_strcopy() { TCase* tc = tcase_create("oidc_strcopy"); tcase_add_test(tc, test_copy); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strstarts.h0000644000175000017500000000027014120404223023237 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRSTARTS_H #define TEST_UTILS_STRINGUTILS_STRSTARTS_H #include TCase* test_case_strstarts(); #endif // TEST_UTILS_STRINGUTILS_STRSTARTS_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_escapeCharInStr.c0000644000175000017500000000147014167074355024226 0ustar marcusmarcus#include "tc_escapeCharInStr.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_NULL) { ck_assert_ptr_eq(escapeCharInStr(NULL, 'x'), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_notFound) { const char* const str = "abcdef"; char* s = escapeCharInStr(str, 'x'); ck_assert_ptr_ne(s, NULL); ck_assert_str_eq(s, str); } END_TEST START_TEST(test_escape) { const char* const str = "abcdef"; char* s = escapeCharInStr(str, 'c'); ck_assert_ptr_ne(s, NULL); ck_assert_str_eq(s, "ab\\cdef"); } END_TEST TCase* test_case_escapeCharInStr() { TCase* tc = tcase_create("escapeCharInStr"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_notFound); tcase_add_test(tc, test_escape); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_sprintf.c0000644000175000017500000000156714167074355023702 0ustar marcusmarcus#include "tc_oidc_sprintf.h" #include #include #include "utils/memory.h" #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_sprintf) { const char* const fmt = "%s%d%lusomething%s"; const char* const s1 = "string"; const char* const s2 = "something"; int i = 85192394; unsigned long l = -1; char* os = oidc_sprintf(fmt, s1, i, l, s2); char* s = calloc(sizeof(char), 1024); sprintf(s, fmt, s1, i, l, s2); ck_assert_str_eq(os, s); free(s); secFree(os); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(oidc_sprintf(NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_oidc_sprintf() { TCase* tc = tcase_create("oidc_sprintf"); tcase_add_test(tc, test_sprintf); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/stringUtils/suite.c0000644000175000017500000000253414167074355021655 0ustar marcusmarcus#include "suite.h" #include "tc_escapeCharInStr.h" #include "tc_oidc_sprintf.h" #include "tc_oidc_strcat.h" #include "tc_oidc_strcopy.h" #include "tc_oidc_strncopy.h" #include "tc_strCountChar.h" #include "tc_strEnds.h" #include "tc_strToInt.h" #include "tc_strToULong.h" #include "tc_strValid.h" #include "tc_strelim.h" #include "tc_strelimIfAfter.h" #include "tc_strelimIfFollowed.h" #include "tc_strstarts.h" Suite* test_suite_stringUtils() { Suite* ts_stringUtils = suite_create("stringUtils"); suite_add_tcase(ts_stringUtils, test_case_escapeCharInStr()); suite_add_tcase(ts_stringUtils, test_case_oidc_sprintf()); suite_add_tcase(ts_stringUtils, test_case_oidc_strcat()); suite_add_tcase(ts_stringUtils, test_case_oidc_strcopy()); suite_add_tcase(ts_stringUtils, test_case_oidc_strncopy()); suite_add_tcase(ts_stringUtils, test_case_strCountChar()); suite_add_tcase(ts_stringUtils, test_case_strEnds()); suite_add_tcase(ts_stringUtils, test_case_strToInt()); suite_add_tcase(ts_stringUtils, test_case_strToULong()); suite_add_tcase(ts_stringUtils, test_case_strValid()); suite_add_tcase(ts_stringUtils, test_case_strelim()); suite_add_tcase(ts_stringUtils, test_case_strelimIfAfter()); suite_add_tcase(ts_stringUtils, test_case_strelimIfFollowed()); suite_add_tcase(ts_stringUtils, test_case_strstarts()); return ts_stringUtils; } oidc-agent-4.2.6/test/src/utils/stringUtils/tc_oidc_sprintf.h0000644000175000017500000000030414120404223023647 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_OIDC_SPRINTF_H #define TEST_UTILS_STRINGUTILS_OIDC_SPRINTF_H #include TCase* test_case_oidc_sprintf(); #endif // TEST_UTILS_STRINGUTILS_OIDC_SPRINTF_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strCountChar.h0000644000175000017500000000030414120404223023603 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRCOUNTCHAR_H #define TEST_UTILS_STRINGUTILS_STRCOUNTCHAR_H #include TCase* test_case_strCountChar(); #endif // TEST_UTILS_STRINGUTILS_STRCOUNTCHAR_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strToInt.h0000644000175000017500000000026414120404223022757 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRTOINT_H #define TEST_UTILS_STRINGUTILS_STRTOINT_H #include TCase* test_case_strToInt(); #endif // TEST_UTILS_STRINGUTILS_STRTOINT_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strToULong.h0000644000175000017500000000027414120404223023252 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_STRTOULONG_H #define TEST_UTILS_STRINGUTILS_STRTOULONG_H #include TCase* test_case_strToULong(); #endif // TEST_UTILS_STRINGUTILS_STRTOULONG_H oidc-agent-4.2.6/test/src/utils/stringUtils/suite.h0000644000175000017500000000025714120404223021636 0ustar marcusmarcus#ifndef TEST_UTILS_STRINGUTILS_SUITE_H #define TEST_UTILS_STRINGUTILS_SUITE_H #include Suite* test_suite_stringUtils(); #endif // TEST_UTILS_STRINGUTILS_SUITE_H oidc-agent-4.2.6/test/src/utils/stringUtils/tc_strelimIfAfter.c0000644000175000017500000000212514167074355024126 0ustar marcusmarcus#include "tc_strelimIfAfter.h" #include #include "utils/oidc_error.h" #include "utils/string/stringUtils.h" START_TEST(test_noElim) { const char* const str = "abcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfAfter(s, 'b', 'x'), str); } END_TEST START_TEST(test_noFound) { const char* const str = "abcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfAfter(s, 'x', 'a'), str); } END_TEST START_TEST(test_elim) { const char* const str = "abcdeffedcbaabcdeffedcba"; char s[strlen(str) + 1]; strcpy(s, str); ck_assert_str_eq(strelimIfAfter(s, 'b', 'c'), "abcdeffedcaabcdeffedca"); } END_TEST START_TEST(test_NULL) { ck_assert_ptr_eq(strelimIfAfter(NULL, 'b', 'c'), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST TCase* test_case_strelimIfAfter() { TCase* tc = tcase_create("strelimIfAfter"); tcase_add_test(tc, test_noElim); tcase_add_test(tc, test_noFound); tcase_add_test(tc, test_elim); tcase_add_test(tc, test_NULL); return tc; } oidc-agent-4.2.6/test/src/utils/uriUtils/0000755000175000017500000000000014167074355017645 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/uriUtils/tc_codeStateFromURI.c0000644000175000017500000000472714120404223023604 0ustar marcusmarcus#include "tc_codeStateFromURI.h" #include "utils/oidc_error.h" #include "utils/uriUtils.h" START_TEST(test_NULL) { struct codeState codeState = codeStateFromURI(NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); ck_assert_ptr_eq(codeState.code, NULL); ck_assert_ptr_eq(codeState.state, NULL); ck_assert_ptr_eq(codeState.uri, NULL); } END_TEST START_TEST(test_Empty) { struct codeState codeState = codeStateFromURI(""); ck_assert_ptr_eq(codeState.code, NULL); ck_assert_ptr_eq(codeState.state, NULL); ck_assert_ptr_eq(codeState.uri, NULL); ck_assert_int_eq(oidc_errno, OIDC_ENOBASEURI); } END_TEST START_TEST(test_noParam) { struct codeState codeState = codeStateFromURI("http://localhost:4242/"); ck_assert_ptr_eq(codeState.code, NULL); ck_assert_ptr_eq(codeState.state, NULL); ck_assert_str_eq(codeState.uri, "http://localhost:4242/"); ck_assert_int_eq(oidc_errno, OIDC_ENOSTATE); } END_TEST START_TEST(test_noState) { struct codeState codeState = codeStateFromURI("http://localhost:4242/?code=1234"); ck_assert_ptr_eq(codeState.state, NULL); ck_assert_str_eq(codeState.code, "1234"); ck_assert_str_eq(codeState.uri, "http://localhost:4242/"); ck_assert_int_eq(oidc_errno, OIDC_ENOSTATE); } END_TEST START_TEST(test_noCode) { struct codeState codeState = codeStateFromURI("http://localhost:4242/?state=1234"); ck_assert_ptr_eq(codeState.code, NULL); ck_assert_str_eq(codeState.state, "1234"); ck_assert_str_eq(codeState.uri, "http://localhost:4242/"); ck_assert_int_eq(oidc_errno, OIDC_ENOCODE); } END_TEST START_TEST(test_statecode) { struct codeState codeState = codeStateFromURI("http://localhost:4242/?state=1234&code=asdf"); ck_assert_str_eq(codeState.code, "asdf"); ck_assert_str_eq(codeState.state, "1234"); ck_assert_str_eq(codeState.uri, "http://localhost:4242/"); } END_TEST START_TEST(test_codestate) { struct codeState codeState = codeStateFromURI("http://localhost:4242/?code=asdf&state=1234"); ck_assert_str_eq(codeState.code, "asdf"); ck_assert_str_eq(codeState.state, "1234"); ck_assert_str_eq(codeState.uri, "http://localhost:4242/"); } END_TEST TCase* test_case_codeStateFromURI() { TCase* tc = tcase_create("codeStateFromURI"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_Empty); tcase_add_test(tc, test_noParam); tcase_add_test(tc, test_noCode); tcase_add_test(tc, test_noState); tcase_add_test(tc, test_statecode); tcase_add_test(tc, test_codestate); return tc; } oidc-agent-4.2.6/test/src/utils/uriUtils/tc_codeStateFromURI.h0000644000175000017500000000031314120404223023574 0ustar marcusmarcus#ifndef TEST_UTILS_URIUTILS_CODESTATEFROMURI_H #define TEST_UTILS_URIUTILS_CODESTATEFROMURI_H #include TCase* test_case_codeStateFromURI(); #endif // TEST_UTILS_URIUTILS_CODESTATEFROMURI_H oidc-agent-4.2.6/test/src/utils/uriUtils/tc_extractParameterValueFromUri.c0000644000175000017500000000404014120404223026265 0ustar marcusmarcus#include "tc_extractParameterValueFromUri.h" #include "utils/oidc_error.h" #include "utils/uriUtils.h" START_TEST(test_uriNULL) { char* param = extractParameterValueFromUri(NULL, "code"); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); ck_assert_ptr_eq(param, NULL); } END_TEST START_TEST(test_paramNULL) { char* param = extractParameterValueFromUri("http://localhost:4242/?code=1234", NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); ck_assert_ptr_eq(param, NULL); } END_TEST START_TEST(test_emptyUri) { char* param = extractParameterValueFromUri("", "code"); ck_assert_ptr_eq(param, NULL); } END_TEST START_TEST(test_firstParam) { char* param = extractParameterValueFromUri( "http://localhost:4242/?code=1234&other=5", "code"); ck_assert_ptr_ne(param, NULL); ck_assert_str_eq(param, "1234"); } END_TEST START_TEST(test_lastParam) { char* param = extractParameterValueFromUri( "http://localhost:4242/?code=1234&other=5", "other"); ck_assert_ptr_ne(param, NULL); ck_assert_str_eq(param, "5"); } END_TEST START_TEST(test_middleParam) { char* param = extractParameterValueFromUri( "http://localhost:4242/?code=1234&other=5&last=last", "other"); ck_assert_ptr_ne(param, NULL); ck_assert_str_eq(param, "5"); } END_TEST START_TEST(test_onlyParam) { char* param = extractParameterValueFromUri("http://localhost:4242/?code=1234", "code"); ck_assert_ptr_ne(param, NULL); ck_assert_str_eq(param, "1234"); } END_TEST START_TEST(test_notFound) { char* param = extractParameterValueFromUri("http://localhost:4242/?code=1234", "other"); ck_assert_ptr_eq(param, NULL); } END_TEST TCase* test_case_extractParameterValueFromUri() { TCase* tc = tcase_create("codeStateFromURI"); tcase_add_test(tc, test_uriNULL); tcase_add_test(tc, test_paramNULL); tcase_add_test(tc, test_emptyUri); tcase_add_test(tc, test_firstParam); tcase_add_test(tc, test_lastParam); tcase_add_test(tc, test_middleParam); tcase_add_test(tc, test_onlyParam); tcase_add_test(tc, test_notFound); return tc; } oidc-agent-4.2.6/test/src/utils/uriUtils/tc_extractParameterValueFromUri.h0000644000175000017500000000037314120404223026277 0ustar marcusmarcus#ifndef TEST_UTILS_URIUTILS_EXTRACTPARAMETERVALUEFROMURI_H #define TEST_UTILS_URIUTILS_EXTRACTPARAMETERVALUEFROMURI_H #include TCase* test_case_extractParameterValueFromUri(); #endif // TEST_UTILS_URIUTILS_EXTRACTPARAMETERVALUEFROMURI_H oidc-agent-4.2.6/test/src/utils/uriUtils/suite.c0000644000175000017500000000052314167074355021142 0ustar marcusmarcus#include "suite.h" #include "tc_codeStateFromURI.h" #include "tc_extractParameterValueFromUri.h" Suite* test_suite_uriUtils() { Suite* ts_uriUtils = suite_create("uriUtils"); suite_add_tcase(ts_uriUtils, test_case_codeStateFromURI()); suite_add_tcase(ts_uriUtils, test_case_extractParameterValueFromUri()); return ts_uriUtils; } oidc-agent-4.2.6/test/src/utils/uriUtils/suite.h0000644000175000017500000000024314120404223021122 0ustar marcusmarcus#ifndef TEST_UTILS_URIUTILS_SUITE_H #define TEST_UTILS_URIUTILS_SUITE_H #include Suite* test_suite_uriUtils(); #endif // TEST_UTILS_URIUTILS_SUITE_H oidc-agent-4.2.6/test/src/utils/portUtils/0000755000175000017500000000000014167074355020032 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/portUtils/tc_findRedirectUriByPort.h0000644000175000017500000000034214120404223025066 0ustar marcusmarcus#ifndef TEST_UTILS_PORTUTILS_FINDREDIRECTURIBYPORT_H #define TEST_UTILS_PORTUTILS_FINDREDIRECTURIBYPORT_H #include TCase* test_case_findRedirectUriByPort(); #endif // TEST_UTILS_PORTUTILS_FINDREDIRECTURIBYPORT_H oidc-agent-4.2.6/test/src/utils/portUtils/tc_getPortFromUri.c0000644000175000017500000000242414120404223023572 0ustar marcusmarcus#include "tc_getPortFromUri.h" #include "utils/oidc_error.h" #include "utils/portUtils.h" START_TEST(test_4242s) { ck_assert_int_eq(getPortFromUri("http://localhost:4242/"), 4242); } END_TEST START_TEST(test_4242) { ck_assert_int_eq(getPortFromUri("http://localhost:4242"), 4242); } END_TEST START_TEST(test_4242path) { ck_assert_int_eq(getPortFromUri("http://localhost:4242/redirect"), 4242); } END_TEST START_TEST(test_empty) { ck_assert_int_eq(getPortFromUri(""), 0); ck_assert_int_eq(oidc_errno, OIDC_EFMT); } END_TEST START_TEST(test_NULL) { ck_assert_int_eq(getPortFromUri(NULL), 0); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_noPort) { ck_assert_int_eq(getPortFromUri("http://localhost/"), 0); ck_assert_int_eq(oidc_errno, OIDC_EFMT); } END_TEST START_TEST(test_noPort2) { ck_assert_int_eq(getPortFromUri("http://localhost:noPort/"), 0); ck_assert_int_eq(oidc_errno, OIDC_EFMT); } END_TEST TCase* test_case_getPortFromUri() { TCase* tc = tcase_create("getPortFromUri"); tcase_add_test(tc, test_4242); tcase_add_test(tc, test_4242s); tcase_add_test(tc, test_4242path); tcase_add_test(tc, test_empty); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_noPort); tcase_add_test(tc, test_noPort2); return tc; } oidc-agent-4.2.6/test/src/utils/portUtils/tc_getPortFromUri.h0000644000175000017500000000030614120404223023574 0ustar marcusmarcus#ifndef TEST_UTILS_PORTUTILS_GETPORTFROMURI_H #define TEST_UTILS_PORTUTILS_GETPORTFROMURI_H #include TCase* test_case_getPortFromUri(); #endif // TEST_UTILS_PORTUTILS_GETPORTFROMURI_H oidc-agent-4.2.6/test/src/utils/portUtils/tc_portToUri.c0000644000175000017500000000116014120404223022605 0ustar marcusmarcus#include "tc_portToUri.h" #include "utils/portUtils.h" START_TEST(test_4242) { ck_assert_str_eq(portToUri(4242), "http://localhost:4242"); } END_TEST START_TEST(test_0) { ck_assert_ptr_eq(portToUri(0), NULL); ck_assert_int_eq(oidc_errno, OIDC_EPORTRANGE); } END_TEST START_TEST(test_overflow) { unsigned short port = -1; ck_assert_ptr_eq(portToUri(port), NULL); ck_assert_int_eq(oidc_errno, OIDC_EPORTRANGE); } END_TEST TCase* test_case_portToUri() { TCase* tc = tcase_create("portToUri"); tcase_add_test(tc, test_0); tcase_add_test(tc, test_4242); tcase_add_test(tc, test_overflow); return tc; } oidc-agent-4.2.6/test/src/utils/portUtils/tc_findRedirectUriByPort.c0000644000175000017500000000307014120404223025062 0ustar marcusmarcus#include "tc_findRedirectUriByPort.h" #include "utils/oidc_error.h" #include "utils/portUtils.h" START_TEST(test_NULL) { ck_assert_ptr_eq(findRedirectUriByPort(NULL, 0), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_nested_NULL) { struct oidc_account a = {}; ck_assert_ptr_eq(findRedirectUriByPort(&a, 0), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_oneUri) { struct oidc_account a = {}; list_t* list = list_new(); list_rpush(list, list_node_new("http://localhost:4242")); account_setRedirectUris(&a, list); ck_assert_str_eq(findRedirectUriByPort(&a, 4242), "http://localhost:4242"); ck_assert_ptr_eq(findRedirectUriByPort(&a, 8080), NULL); account_setRedirectUris(&a, NULL); } END_TEST START_TEST(test_multipleUri) { struct oidc_account a = {}; list_t* list = list_new(); list_rpush(list, list_node_new("http://localhost:4242")); list_rpush(list, list_node_new("http://localhost:8080")); account_setRedirectUris(&a, list); ck_assert_str_eq(findRedirectUriByPort(&a, 4242), "http://localhost:4242"); ck_assert_str_eq(findRedirectUriByPort(&a, 8080), "http://localhost:8080"); ck_assert_ptr_eq(findRedirectUriByPort(&a, 8090), NULL); account_setRedirectUris(&a, NULL); } END_TEST TCase* test_case_findRedirectUriByPort() { TCase* tc = tcase_create("findRedirectUriByPort"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_nested_NULL); tcase_add_test(tc, test_oneUri); tcase_add_test(tc, test_multipleUri); return tc; } oidc-agent-4.2.6/test/src/utils/portUtils/suite.c0000644000175000017500000000063114167074355021327 0ustar marcusmarcus#include "suite.h" #include "tc_findRedirectUriByPort.h" #include "tc_getPortFromUri.h" #include "tc_portToUri.h" Suite* test_suite_portUtils() { Suite* ts_portUtils = suite_create("portUtils"); suite_add_tcase(ts_portUtils, test_case_portToUri()); suite_add_tcase(ts_portUtils, test_case_getPortFromUri()); suite_add_tcase(ts_portUtils, test_case_findRedirectUriByPort()); return ts_portUtils; } oidc-agent-4.2.6/test/src/utils/portUtils/tc_portToUri.h0000644000175000017500000000026214120404223022614 0ustar marcusmarcus#ifndef TEST_UTILS_PORTUTILS_PORTTOURI_H #define TEST_UTILS_PORTUTILS_PORTTOURI_H #include TCase* test_case_portToUri(); #endif // TEST_UTILS_PORTUTILS_PORTTOURI_H oidc-agent-4.2.6/test/src/utils/portUtils/suite.h0000644000175000017500000000024714120404223021313 0ustar marcusmarcus#ifndef TEST_UTILS_PORTUTILS_SUITE_H #define TEST_UTILS_PORTUTILS_SUITE_H #include Suite* test_suite_portUtils(); #endif // TEST_UTILS_PORTUTILS_SUITE_H oidc-agent-4.2.6/test/src/utils/crypt/0000755000175000017500000000000014120404223017142 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/0000755000175000017500000000000014167074355021520 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_initMemoryCrypt.c0000644000175000017500000000072214120404223025505 0ustar marcusmarcus#include "tc_initMemoryCrypt.h" #include "utils/crypt/memoryCrypt.h" #include "utils/memory.h" START_TEST(test_differ) { extern uint64_t _getMemoryPass(); initMemoryCrypt(); unsigned long pass1 = _getMemoryPass(); initMemoryCrypt(); unsigned long pass2 = _getMemoryPass(); ck_assert_uint_ne(pass1, pass2); } END_TEST TCase* test_case_initMemoryCrypt() { TCase* tc = tcase_create("initMemoryCrypt"); tcase_add_test(tc, test_differ); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_memoryEncrypt.c0000644000175000017500000000111214120404223025176 0ustar marcusmarcus#include "tc_memoryEncrypt.h" #include "utils/crypt/memoryCrypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { ck_assert_ptr_eq(memoryEncrypt(NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_SUCCESS); } END_TEST START_TEST(test_encrypt) { char* cipher = memoryEncrypt("test"); ck_assert_ptr_ne(cipher, NULL); ck_assert_str_ne(cipher, "test"); secFree(cipher); } END_TEST TCase* test_case_memoryEncrypt() { TCase* tc = tcase_create("memoryEncrypt"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_encrypt); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_initMemoryCrypt.h0000644000175000017500000000034214120404223025510 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_MEMORYCRYPT_INITMEMORYCRYPT_H #define TEST_UTILS_CRYPT_MEMORYCRYPT_INITMEMORYCRYPT_H #include TCase* test_case_initMemoryCrypt(); #endif // TEST_UTILS_CRYPT_MEMORYCRYPT_INITMEMORYCRYPT_H oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_memoryEncrypt.h0000644000175000017500000000033214120404223025206 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYENCRYPT_H #define TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYENCRYPT_H #include TCase* test_case_memoryEncrypt(); #endif // TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYENCRYPT_H oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_memoryDecrypt.c0000644000175000017500000000120214120404223025164 0ustar marcusmarcus#include "tc_memoryDecrypt.h" #include "utils/crypt/memoryCrypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { ck_assert_ptr_eq(memoryDecrypt(NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_SUCCESS); } END_TEST START_TEST(test_decrypt) { char* cipher = memoryEncrypt("test"); char* plain = memoryDecrypt(cipher); secFree(cipher); ck_assert_ptr_ne(plain, NULL); ck_assert_str_eq(plain, "test"); secFree(plain); } END_TEST TCase* test_case_memoryDecrypt() { TCase* tc = tcase_create("memoryDecrypt"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_decrypt); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/suite.c0000644000175000017500000000064214167074355023017 0ustar marcusmarcus#include "suite.h" #include "tc_initMemoryCrypt.h" #include "tc_memoryDecrypt.h" #include "tc_memoryEncrypt.h" Suite* test_suite_memoryCrypt() { Suite* ts_memoryCrypt = suite_create("memoryCrypt"); suite_add_tcase(ts_memoryCrypt, test_case_memoryDecrypt()); suite_add_tcase(ts_memoryCrypt, test_case_memoryEncrypt()); suite_add_tcase(ts_memoryCrypt, test_case_initMemoryCrypt()); return ts_memoryCrypt; } oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/suite.h0000644000175000017500000000030114120404223022770 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_MEMORYCRYPT_SUITE_H #define TEST_UTILS_CRYPT_MEMORYCRYPT_SUITE_H #include Suite* test_suite_memoryCrypt(); #endif // TEST_UTILS_CRYPT_MEMORYCRYPT_SUITE_H oidc-agent-4.2.6/test/src/utils/crypt/memoryCrypt/tc_memoryDecrypt.h0000644000175000017500000000033214120404223025174 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYDECRYPT_H #define TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYDECRYPT_H #include TCase* test_case_memoryDecrypt(); #endif // TEST_UTILS_CRYPT_MEMORYCRYPT_MEMORYDECRYPT_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/0000755000175000017500000000000014170031216020306 5ustar marcusmarcusoidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_fromBase64.c0000644000175000017500000000154114170031216023051 0ustar marcusmarcus#include "tc_fromBase64.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { char* buffer = "buffer"; ck_assert_int_eq(fromBase64(NULL, 5, (unsigned char*)buffer), OIDC_EARGNULLFUNC); ck_assert_int_eq(fromBase64(buffer, 5, NULL), OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_decode) { unsigned char s[5] = {0}; int r = fromBase64("dGVzdA==", 4, s); ck_assert_int_eq(r, 0); ck_assert_str_eq((char*)s, "test"); } END_TEST START_TEST(test_wrong_decode) { unsigned char s[5] = {0}; int r = fromBase64("dGVzdA=", 4, s); ck_assert_int_ne(r, 0); } END_TEST TCase* test_case_fromBase64() { TCase* tc = tcase_create("fromBase64"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_decode); tcase_add_test(tc, test_wrong_decode); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_s256.h0000644000175000017500000000024414120404223021641 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_S256_H #define TEST_UTILS_CRYPT_CRYPT_S256_H #include TCase* test_case_s256(); #endif // TEST_UTILS_CRYPT_CRYPT_S256_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_crypt_decrypt.c0000644000175000017500000000173714120404223024040 0ustar marcusmarcus#include "tc_crypt_decrypt.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { char* buffer = "buffer"; ck_assert_ptr_eq(crypt_decrypt(NULL, buffer), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); ck_assert_ptr_eq(crypt_encrypt(buffer, NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_decrypt) { char* plain = crypt_decrypt("20\nlzCHhZr0UL0or/" "mk1hsoBU5euCQVLv5U\nCpq4aAHr+22bIH535xnKFQ==\n24:16:16:32:" "1:2:67108864:2\n7vG6GHd45pLNqV5vdCGsAwat37o=" "\nD9MfcJjzEnXFD232v1MMbRQjHfkaJkifqz/uPPbLCXs=", "password"); ck_assert_ptr_ne(plain, NULL); ck_assert_str_eq(plain, "test"); secFree(plain); } END_TEST TCase* test_case_crypt_decrypt() { TCase* tc = tcase_create("crypt_decrypt"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_decrypt); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_toBase64.h0000644000175000017500000000026414120404223022533 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_TOBASE64_H #define TEST_UTILS_CRYPT_CRYPT_TOBASE64_H #include TCase* test_case_toBase64(); #endif // TEST_UTILS_CRYPT_CRYPT_TOBASE64_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_toBase64.c0000644000175000017500000000104414120404223022523 0ustar marcusmarcus#include "tc_toBase64.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { ck_assert_ptr_eq(toBase64(NULL, 5), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_encode) { char* s = toBase64("test", 4); ck_assert_ptr_ne(s, NULL); ck_assert_str_eq(s, "dGVzdA=="); secFree(s); } END_TEST TCase* test_case_toBase64() { TCase* tc = tcase_create("toBase64"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_encode); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_fromBase64UrlSafe.h0000644000175000017500000000033014120404223024330 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_FROMBASE64URLSAFE_H #define TEST_UTILS_CRYPT_CRYPT_FROMBASE64URLSAFE_H #include TCase* test_case_fromBase64UrlSafe(); #endif // TEST_UTILS_CRYPT_CRYPT_FROMBASE64URLSAFE_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_s256.c0000644000175000017500000000105114120404223021631 0ustar marcusmarcus#include "tc_s256.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { ck_assert_ptr_eq(s256(NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_s256) { char* s = s256("test"); ck_assert_ptr_ne(s, NULL); ck_assert_str_eq(s, "n4bQgYhMfWWaL-qgxVrQFaO_TxsrC4Is0V1sFbDwCgg"); secFree(s); } END_TEST TCase* test_case_s256() { TCase* tc = tcase_create("s256"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_s256); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/suite.c0000644000175000017500000000126614167074355021631 0ustar marcusmarcus#include "suite.h" #include "tc_crypt_decrypt.h" #include "tc_crypt_encrypt.h" #include "tc_fromBase64.h" #include "tc_fromBase64UrlSafe.h" #include "tc_s256.h" #include "tc_toBase64.h" #include "tc_toBase64UrlSafe.h" Suite* test_suite_crypt() { Suite* ts_crypt = suite_create("crypt"); suite_add_tcase(ts_crypt, test_case_crypt_decrypt()); suite_add_tcase(ts_crypt, test_case_crypt_encrypt()); suite_add_tcase(ts_crypt, test_case_fromBase64()); suite_add_tcase(ts_crypt, test_case_fromBase64UrlSafe()); suite_add_tcase(ts_crypt, test_case_s256()); suite_add_tcase(ts_crypt, test_case_toBase64()); suite_add_tcase(ts_crypt, test_case_toBase64UrlSafe()); return ts_crypt; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_crypt_encrypt.h0000644000175000017500000000031014120404223024041 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_CRYPT_ENCRYPT_H #define TEST_UTILS_CRYPT_CRYPT_CRYPT_ENCRYPT_H #include TCase* test_case_crypt_encrypt(); #endif // TEST_UTILS_CRYPT_CRYPT_CRYPT_ENCRYPT_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_fromBase64UrlSafe.c0000644000175000017500000000161414170031216024334 0ustar marcusmarcus#include "tc_fromBase64UrlSafe.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { char* buffer = NULL; ck_assert_int_eq(fromBase64UrlSafe(NULL, 5, (unsigned char*)buffer), OIDC_EARGNULLFUNC); ck_assert_int_eq(fromBase64UrlSafe(buffer, 5, NULL), OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_decode) { unsigned char s[5] = {0}; int r = fromBase64UrlSafe("dGVzdA", 4, s); ck_assert_int_eq(r, 0); ck_assert_str_eq((char*)s, "test"); } END_TEST START_TEST(test_wrong_decode) { unsigned char s[5] = {0}; int r = fromBase64UrlSafe("dGVzdA=", 4, s); ck_assert_int_ne(r, 0); } END_TEST TCase* test_case_fromBase64UrlSafe() { TCase* tc = tcase_create("fromBase64UrlSafe"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_decode); tcase_add_test(tc, test_wrong_decode); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_fromBase64.h0000644000175000017500000000027414120404223023055 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_FROMBASE64_H #define TEST_UTILS_CRYPT_CRYPT_FROMBASE64_H #include TCase* test_case_fromBase64(); #endif // TEST_UTILS_CRYPT_CRYPT_FROMBASE64_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_toBase64UrlSafe.h0000644000175000017500000000032014120404223024006 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_TOBASE64URLSAFE_H #define TEST_UTILS_CRYPT_CRYPT_TOBASE64URLSAFE_H #include TCase* test_case_toBase64UrlSafe(); #endif // TEST_UTILS_CRYPT_CRYPT_TOBASE64URLSAFE_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_crypt_encrypt.c0000644000175000017500000000134214120404223024042 0ustar marcusmarcus#include "tc_crypt_encrypt.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { char* buffer = "buffer"; ck_assert_ptr_eq(crypt_encrypt(NULL, buffer), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); ck_assert_ptr_eq(crypt_encrypt(buffer, NULL), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_encrypt) { char* cipher = crypt_encrypt("test", "password"); ck_assert_ptr_ne(cipher, NULL); ck_assert_str_ne(cipher, "test"); secFree(cipher); } END_TEST TCase* test_case_crypt_encrypt() { TCase* tc = tcase_create("crypt_encrypt"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_encrypt); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_crypt_decrypt.h0000644000175000017500000000031014120404223024027 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_CRYPT_DECRYPT_H #define TEST_UTILS_CRYPT_CRYPT_CRYPT_DECRYPT_H #include TCase* test_case_crypt_decrypt(); #endif // TEST_UTILS_CRYPT_CRYPT_CRYPT_DECRYPT_H oidc-agent-4.2.6/test/src/utils/crypt/crypt/tc_toBase64UrlSafe.c0000644000175000017500000000110514120404223024003 0ustar marcusmarcus#include "tc_toBase64UrlSafe.h" #include "utils/crypt/crypt.h" #include "utils/memory.h" #include "utils/oidc_error.h" START_TEST(test_NULL) { ck_assert_ptr_eq(toBase64UrlSafe(NULL, 5), NULL); ck_assert_int_eq(oidc_errno, OIDC_EARGNULLFUNC); } END_TEST START_TEST(test_encode) { char* s = toBase64UrlSafe("test", 4); ck_assert_ptr_ne(s, NULL); ck_assert_str_eq(s, "dGVzdA"); secFree(s); } END_TEST TCase* test_case_toBase64UrlSafe() { TCase* tc = tcase_create("toBase64UrlSafe"); tcase_add_test(tc, test_NULL); tcase_add_test(tc, test_encode); return tc; } oidc-agent-4.2.6/test/src/utils/crypt/crypt/suite.h0000644000175000017500000000025114120404223021603 0ustar marcusmarcus#ifndef TEST_UTILS_CRYPT_CRYPT_SUITE_H #define TEST_UTILS_CRYPT_CRYPT_SUITE_H #include Suite* test_suite_crypt(); #endif // TEST_UTILS_CRYPT_CRYPT_SUITE_H oidc-agent-4.2.6/test/src/prepareTestsFor.sh0000755000175000017500000000353514120404223020336 0ustar marcusmarcus#!/bin/bash # e.g. utils/stringUtils NAME=$1 [ -z $1 ] && { echo "The source file must be the only parameter" exit 1 } SRC_FILENAME="${NAME}.h" SRC_FILE="../../src/$SRC_FILENAME" [ ! -f $SRC_FILE ] && { echo "Source file not found." exit 1 } SHORTNAME="${NAME##*/}" BIGNAME=$(echo "${NAME//\//_}" | tr [a-z] [A-Z]) # echo $SRC_FILENAME # echo $SRC_FILE # echo $NAME # echo $SHORTNAME # echo $BIGNAME FUNCTIONNAMES=$(ctags -x --c-kinds=fp $SRC_FILE | awk '{print $1}') touch /tmp/now mkdir -p $NAME cp -n ../template/suite.h ${NAME}/suite.h cp -n ../template/suite.c ${NAME}/suite.c for F in $FUNCTIONNAMES; do cp -n ../template/tc.h ${NAME}/tc_${F}.h cp -n ../template/tc.c ${NAME}/tc_${F}.c done cd $NAME [ ! /tmp/now -nt suite.h ] && sed -i -e 's/\$BIG\$/'"$BIGNAME"'/g' suite.h [ ! /tmp/now -nt suite.h ] && sed -i -e 's/\$NORMAL\$/'"$SHORTNAME"'/g' suite.h for F in $FUNCTIONNAMES; do BIGF=$(echo $F | tr [a-z] [A-Z]) [ ! /tmp/now -nt tc_${F}.h ] && sed -i -e 's/\$BIG\$/'"$BIGNAME"'_'"$BIGF"'/g' tc_${F}.h [ ! /tmp/now -nt tc_${F}.h ] && sed -i -e 's/\$NORMAL\$/'"$F"'/g' tc_${F}.h [ ! /tmp/now -nt tc_${F}.c ] && sed -i -e 's/\$BIG\$/'"$BIGNAME"'_'"$BIGF"'/g' tc_${F}.c [ ! /tmp/now -nt tc_${F}.c ] && sed -i -e 's/\$NORMAL\$/'"$F"'/g' tc_${F}.c [ ! /tmp/now -nt tc_${F}.c ] && sed -i -e 's!\$SRCHEADER\$!'"$SRC_FILENAME"'!g' tc_${F}.c INCLUDES+="#include \"tc_${F}.h\"\n" ADDCASES+="\tsuite_add_tcase(ts_${SHORTNAME}, test_case_${F}());\n" done # echo $INCLUDES # echo $ADDCASES [ ! /tmp/now -nt suite.c ] && sed -i -e 's/\$NORMAL\$/'"$SHORTNAME"'/g' suite.c [ ! /tmp/now -nt suite.c ] && sed -i -e 's/\$INCLUDES\$/'"$INCLUDES"'/g' suite.c [ ! /tmp/now -nt suite.c ] && sed -i -e 's/\$ADDCASES\$/'"$ADDCASES"'/g' suite.c echo "OK. Prepared TestSuite." echo "You still have to add the test suite to test/src/main.c and implement the tests." oidc-agent-4.2.6/test/src/main.c0000755000175000017500000000210414167074355015755 0ustar marcusmarcus#include #include #include #include "test/src/account/account/suite.h" #include "test/src/utils/crypt/crypt/suite.h" #include "test/src/utils/crypt/memoryCrypt/suite.h" #include "test/src/utils/json/suite.h" #include "test/src/utils/portUtils/suite.h" #include "test/src/utils/stringUtils/suite.h" #include "test/src/utils/uriUtils/suite.h" int runSuite(Suite* suite) { int number_failed; SRunner* sr = srunner_create(suite); srunner_run_all(sr, CK_NORMAL); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return number_failed; } int main() { setlogmask(LOG_UPTO(LOG_ERR)); int number_failed = 0; number_failed |= runSuite(test_suite_json()); number_failed |= runSuite(test_suite_portUtils()); number_failed |= runSuite(test_suite_stringUtils()); number_failed |= runSuite(test_suite_memoryCrypt()); number_failed |= runSuite(test_suite_crypt()); number_failed |= runSuite(test_suite_account()); number_failed |= runSuite(test_suite_uriUtils()); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } oidc-agent-4.2.6/test/template/0000755000175000017500000000000014167074355015711 5ustar marcusmarcusoidc-agent-4.2.6/test/template/tc.h0000644000175000017500000000016514120404223016446 0ustar marcusmarcus#ifndef TEST_$BIG$_H #define TEST_$BIG$_H #include TCase* test_case_$NORMAL$(); #endif // TEST_$BIG$_H oidc-agent-4.2.6/test/template/tc.c0000644000175000017500000000035114120404223016436 0ustar marcusmarcus#include "tc_$NORMAL$.h" #include "$SRCHEADER$" // TODO START_TEST(test_) { ck_assert(1 == 1); } END_TEST TCase* test_case_$NORMAL$() { TCase* tc = tcase_create("$NORMAL$"); tcase_add_test(tc, test_); // TODO return tc; } oidc-agent-4.2.6/test/template/suite.c0000644000175000017500000000022514167074355017205 0ustar marcusmarcus#include "suite.h" $INCLUDES$ Suite* test_suite_$NORMAL$() { Suite* ts_$NORMAL$ = suite_create("$NORMAL$"); $ADDCASES$ return ts_$NORMAL$; } oidc-agent-4.2.6/test/template/suite.h0000644000175000017500000000021014120404223017160 0ustar marcusmarcus#ifndef TEST_$BIG$_SUITE_H #define TEST_$BIG$_SUITE_H #include Suite* test_suite_$NORMAL$(); #endif // TEST_$BIG$_SUITE_H oidc-agent-4.2.6/README.md0000644000175000017500000001470714167074355014407 0ustar marcusmarcus![oidc-agent logo](https://raw.githubusercontent.com/indigo-dc/oidc-agent/master/logo_wide.png) [![License](https://img.shields.io/github/license/indigo-dc/oidc-agent.svg)](https://github.com/indigo-dc/oidc-agent/blob/master/LICENSE) [![Total alerts](https://img.shields.io/lgtm/alerts/g/indigo-dc/oidc-agent.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/indigo-dc/oidc-agent/alerts/) [![Language grade](https://img.shields.io/lgtm/grade/cpp/g/indigo-dc/oidc-agent.svg?logo=lgtm&logoWidth=18&label=code%20quality)](https://lgtm.com/projects/g/indigo-dc/oidc-agent/context:cpp) [![Code size](https://img.shields.io/github/languages/code-size/indigo-dc/oidc-agent.svg)](https://github.com/indigo-dc/oidc-agent/tree/master/src) [![Release date](https://img.shields.io/github/release-date/indigo-dc/oidc-agent.svg)](https://github.com/indigo-dc/oidc-agent/releases/latest) [![Release version](https://img.shields.io/github/release/indigo-dc/oidc-agent.svg)](https://github.com/indigo-dc/oidc-agent/releases/latest) # oidc-agent oidc-agent is a set of tools to manage OpenID Connect tokens and make them easily usable from the command line. We followed the [`ssh-agent`](https://www.openssh.com/) design, so users can handle OIDC tokens in a similar way as they do with ssh keys. `oidc-agent` is usually started in the beginning of an X-session or a login session. Through use of environment variables the agent can be located and used to handle OIDC tokens. The agent initially does not have any account configurations loaded. You can load an account configuration by using `oidc-add`. Multiple account configurations may be loaded in `oidc-agent` concurrently. `oidc-add` is also used to remove a loaded configuration from `oidc-agent`. `oidc-gen` is used to initially generate an account configurations file [(Help for different providers)](https://indigo-dc.gitbooks.io/oidc-agent/provider.html). **Full documentation** can be found at https://indigo-dc.gitbooks.io/oidc-agent/. We have a low-traffic **mailing list** with updates such as critical security incidents and new releases: [Subscribe oidc-agent-user](https://www.lists.kit.edu/sympa/subscribe/oidc-agent-user) ## Installation Current releases are available at [GitHub](https://github.com/indigo-dc/oidc-agent/releases) or http://repo.data.kit.edu/ ### Debian Packages - `sudo apt-key adv --keyserver hkp://pgp.surfnet.nl --recv-keys ACDFB08FDC962044D87FF00B512839863D487A87` - Depending on your distribution, choose one of the following lines: ``` sudo add-apt-repository "deb http://repo.data.kit.edu/debian/buster ./" sudo add-apt-repository "deb http://repo.data.kit.edu/debian/bullseye ./" sudo add-apt-repository "deb http://repo.data.kit.edu/ubuntu/bionic ./" ``` - `sudo apt-get update` - `sudo apt-get install oidc-agent` ### MacOS ``` brew tap indigo-dc/oidc-agent brew install oidc-agent brew cask install pashua # optionally, needed for gui prompting ``` ### From Source Refer to the [documentation](https://indigo-dc.gitbook.io/oidc-agent/installation/install#from-source) #### Debian: ``` make deb ``` ### Quickstart After [installation](https://indigo-dc.gitbook.io/oidc-agent/installation/install) the agent has to be started. Usually the agent is started on system startup and is then available on all terminals ( see [integration](https://indigo-dc.gitbook.io/oidc-agent/configuration/integration)). Therefore, after installation the options are to restart your X-Session or to start the agent manually. ``` eval `oidc-agent-service start` ``` This starts the agent and sets the required environment variables. #### Create an agent account configuration with oidc-gen For most OpenID Connect providers an agent account configuration can be created with one of the following calls. Make sure that you can run a web-browser on the same host where you run the `oidc-gen` command. ``` oidc-gen oidc-gen --pub ``` For more information on the different providers refer to [integrate with different providers](https://indigo-dc.gitbook.io/oidc-agent/user/oidc-gen/provider). **`oidc-gen` supports different OIDC flows. To use the device flow instead of the authorization code flow include the `--flow=device` option.** After an account configuration is created it can be used with the shortname to obtain access tokens. One does not need to run `oidc-gen` again unless to update or create a new account configuration. #### Use oidc-add to load an account configuration ``` oidc-add ``` However, usually it is not necessary to load an account configuration with `oidc-add`. One can directly request an access token for a configuration and `oidc-agent` will automatically load it if it is not already loaded. #### Obtaining an access token ``` oidc-token ``` Alternatively, it is also possible to request an access token without specifying the shortname of a configuration but with the issuer url: ``` oidc-token ``` This way is recommended when writing scripts that utilize oidc-agent to obtain access tokens. This allows that the script can be easily used by others without them having to update the shortname. #### List existing configuration ``` oidc-add -l oidc-gen -l ``` These commands both give a list of all existing account configurations. A list of the currently loaded accounts can be retrieved with: ``` oidc-add -a ``` #### Updating an existing account configuration An existing account configuration can be updated with `oidc-gen`: ``` oidc-gen -m ``` #### Reauthenticating If the refresh token stored in the account configuration expired a new one must be created. However, it is not required to create a new account configuration, it is enough to run: ``` oidc-gen --reauthenticate ``` oidc-agent-4.2.6/CHANGELOG.md0000644000175000017500000007425114170031216014720 0ustar marcusmarcus ## oidc-agent 4.2.6 ### Bugfixes - Fixed a bug where in the base64 decoding the wrong number was passed to the library function which on some platforms could lead to errors ## oidc-agent 4.2.5 ### Other - Fix formatting in gitbook ## oidc-agent 4.2.4 ### Bugfixes: - Fixed potential uncontrolled format string ## oidc-agent 4.2.3 ### Bugfixes: - Fixed cleanup of tmp directory for `oidc-agent-service`; in `4.2.2` we deleted too much ## oidc-agent 4.2.2 ### Bugfixes: - Fixed cleanup of tmp directory for `oidc-agent-service` - Fixed typo that could cause a wrongly formatted error message ### Other - Fixed a typo - Fixed cast warning on libmicrohttpd >= 0.9.71 ## oidc-agent 4.2.1 ### Enhancements - Encoding spaces printed authorization url, so it can be easily opened. ### Bugfixes - Fixed problems on MacOS where automatic url opening did not work. ## oidc-agent 4.2.0 ### Features - Add option to encrypt account config file through gpg agent with an existing gpg key instead of using an encryption password - This feature comes very handy for accounts where the refresh tokens changes often (but can be used with any account configuration file) - To use gpg encryption when creating a new account include the `--gpg=` option to your `oidc-gen` call - To update an existing account configuration to use gpg encryption run `oidc-gen -u --gpg=` - Add Auto-re-authentication feature: When `oidc-agent` discovers that a refresh token expired it automatically triggers a re-authentication flow. ### API - IPC-API: - The error response for an Access Token Request now might contain an `info` field. If present this field contains a formatted help message that gives instructions to the user how the problem can most likely be solved. Applications should display this message to the user if it is present. - The `C` library `liboidcagent4` now has functions that return an `agent_response` that on error include the error and the help message. For details see https://indigo-dc.gitbook.io/oidc-agent/api/api-c#error-handling - The `go` and `python` libraries have been adapted to support the help message. For details refer to: - https://indigo-dc.gitbook.io/oidc-agent/api/api-go - https://indigo-dc.gitbook.io/oidc-agent/api/api-py#error-handling ### Enhancements - Now using `libqrencode` to print a QR code when using the device flow; instead of using `qrencode` only if already installed. - Token revocation can now handle cases where there must be provided a `client_id` in the request. ### Bugfixes - Fixed a bug where an error message was printed even tough no error occurred when `oidc-gen` tried to read a tmp file from `oidc-agent` and `oidc-gen` could not connect to agent. - Fixed bug on MacOS where command line flags that are aliases would not accept argument - Excluded `.log` files from account list - Fixed bugs where some `--pw-*` options (mainly `--pw-file` and `--pw-env`) where not used by `oidc-agent` - Fixed memory leaks in `oidc-agent`. - Fixed handling of multiple OIDC flows by `oidc-agent`. - Fixed bash completion on bullseye printing deprecation message - Fixed potential TOCTOU filesystem race condition ### Dependencies - Now (directly) depending on `libqrencode` instead of optionally using `qrencode` binary. ## oidc-agent 4.1.1 ### OpenID Provider - Fixed scopes for EGI public clients - Added compute.* scopes for WLCG public client - Removed https://unity.eudat-aai.fz-juelich.de/oauth2/ - Added public client for B2ACCESS ## oidc-agent 4.1.0 ### oidc-agent-server - Support for `oidc-agent-server` has been dropped. ### Features - Added option to `oidc-gen` to read the refresh token from environment variable. - Added option to `oidc-gen` and `oidc-add` to read the encryption password from environment variable. - Added option to `oidc-agent` to silence pid echo. - Added option to `oidc-agent` to obtain env var values as json. - Added option to `oidc-gen` to allow account generation without saving it. - Added `oidc-agent-service` to easily start, stop, and restart an agent throughout a session. ### Enhancements - Improved Xsession integration by using `oidc-agent-service`. - Improved unexpected error message when account not loaded. - Added success message at the end of `oidc-gen`. - Public clients are now also read from the oidc-agent directory ### Bugfixes - Fixed compilation issues on modern compilers - Fixed `oidc-agent` output on `--status` if `$OIDC_SOCK` not set. ### Dependencies - Update cJSON library. ## oidc-agent 4.0.2 ### Bugfixes - Fixed a json merge conflict when device authorization endpoint was set by user - Fixed a bug where a message was printed to terminal when using the device flow when qrencode was not installed on the user's system ## oidc-agent 4.0.1 ### Bugfixes - Fixed a bug in liboidc-agent where getAccessTokenforIssuer never returned. - Fixed agent forwarding with liboidc-agent. ## oidc-agent 4.0.0 ### Incompatible Changes - IPC encryption changed, therefore agents and clients (oidc-gen, oidc-add, oidc-token, etc.) must have the same major version to be able to communicate. **Agent must be restarted after updating!** - Some options were removed from `oidc-gen`; these options are: - `--output` Splitting client configuration and agent account configuration is no longer supported. - `--qr` If `qrencode` is installed a QR code is automatically printed to the terminal. - `--qrt` If `qrencode` is installed a QR code is automatically printed to the terminal. - `--split-config` Splitting client configuration and agent account configuration is no longer supported. - `--clients` Splitting client configuration and agent account configuration is no longer supported. ### Features - Add option `--only-at` to obtain AT through oidc-gen without creating an account configuration. - Add oidc-agent-server an oidc-agent version that can run as a central server. - `oidc-add` can now load locally existing configurations to a remote `oidc-agent-server`. - `oidc-token` can also be used to obtain tokens from a remote `oidc-agent-server`. - oidc-gen can now be used completely non-interactive - Add `--pw-file` option to read decryption password from file - Allow users to rename accounts. - Add status command to oidc-agent to get information about the currently running agent. - Add possibility to easily force a new AT through oidc-token. ### API - Add encryption to liboidc-agent (now depends on libsodium). - Also add encryption to the go and python library. - The libraries now automatically support obtaining tokens from a remote `oidc-agent-server`. ### Enhancements - User can now choose between cli and gui prompts (or none for `oidc-gen`). - Add several new options for passing information to oidc-gen. - When the 'max' keyword is used for scopes and a public client is used, this now uses the maximum scopes for that public client, not the issuer. - Change how the symmetric key is derived in ipc communication to be able to support ipc encryption with golang lib. - On default cnid (oidc-gen) is set to the hostname; so the hostname is included in the client name. - Improve password prompt on autoload. - Improve bash completion of oidc-gen short options. - Delete oidc client when deleting agent configuration. - Write temporary data to oidc-agent instead of tmp file. ### Bugfixes - Fix a possible conflict between the application type 'web' and custom scheme redirect uris. - Fix bug where oidc-gen would use a public client instead of aborting when generating an account configuration with a shortname that is already loaded. - Fix duplicated output of oidc-agent when redirecting the stdout output. - Fix segmentation fault in oidc-gen issuer selection when selecting 0 - Fix more segmentation faults. - Fix memory leaks. ### OpenID Provider - Add public client for aai-demo.egi.eu - Add aai-demo.egi.eu ### Dependencies - `liboidc-agent4` now depends on `libsodium`. - Update cJSON library. ## oidc-agent 3.3.5 ### OpenID Provider - Add public client for login-dev.helmholtz.de/oauth2/ ## oidc-agent 3.3.4 ### OpenID Provider - Add public client for dev.helmholtz.de/oauth2/ ## oidc-agent 3.3.3 ### Bugfixes - Fix bash completion of shortnames if `$OIDC_CONFIG_DIR` is used. ### OpenID Provider - Updated the issuer urls of HDF. ## oidc-agent 3.3.2 ### Bugfixes - Fix --pw-cmd not correctly working when output does not end with newline character - Fix duplicated output of oidc-agent when redirecting - Fix oidc-agent dies when client disconnects before agent can write back. ## oidc-agent 3.3.1 ## Bugfixes - Add a missing header line in the `oidc-add --loaded` output - Remove dot files from configured account config listing. ## oidc-agent 3.3.0 ### Features - Add option to `oidc-add` to list currently loaded accounts. - Add support to request tokens with specific audience. - Add `--id-token` option to `oidc-token` to request an id-token from the agent. - Add `oidc-keychain` to reuse `oidc-agent` across logins - Add option to `oidc-token` to specify name of calling application. - Add option to `oidc-agent` that allows log message printed to stderr. ### API - Add the option to request access tokens with a specific audience to the `C`- `Go`- and `python`-libraries. ### Enhancements - Add wlcg.cloud.cnaf.infn.it - Add public client for wlcg.cloud.cnaf.infn.it - Exit `oidc-gen` when error during scope lookup. - Update cJSON library. ### Bugfixes - Fix scope lookup not using cert path. - Fix no-scheme option not working if first url is scheme url. - Fix that some information is printed to stderr instead of stdout. - Fix scopes not set when using password flow. - Fix some minor bugs. ## oidc-agent 3.2.7 ### Enhancements - Improve RPM build ## oidc-agent 3.2.6 ### Bugfixes - Now adjusting X11settings only when the configuration file already exists. - Fixed some spelling errors. ### Enhancements - Increased `oidc-gen` polling interval and duration. - `oidc-gen` now displays the scopes supported by the provider. - Scopes provided to `oidc-gen` are no longer silently dropped when they are not advertised by the provider as supported. ## oidc-agent 3.2.5 ### Bugfixes - Fixed bug that might cause problems with providers that do not support PKCE. No longer sending code_verifier on auth code exchange requests. ## oidc-agent 3.2.4 ### Enhancements - Added new provider iam-demo.cloud.cnaf.infn.it/ - Added public client for iam-demo.cloud.cnaf.infn.it ## oidc-agent 3.2.3 ### Enhancements - Added public client for deep datacloud - Added public client for extreme datacloud ## oidc-agent 3.2.2 ### Enhancements - Add possibility to avoid custom uri scheme (useful when running on a remote server) - Now displaying warning message when client registration could not register all requested scopes. ### Bugfixes: - Fixed bug with doubled communication when not all required scopes could be registered ## oidc-agent 3.2.0 ### Features - Added the possibility to allow applications that run under another user to obtain tokens from the agent, when starting the agent with the [`--with-group`](https://indigo-dc.gitbooks.io/oidc-agent/content/oidc-agent.html#-with-group) option ## oidc-agent 3.1.2 ### Bugfixes - Fixed a bug due to which no error message was displayed when trying to load an account configuration and the oidc-agent directory was not accessible for oidc-add. - This bug also caused the agent to crash if oidc-token was used to load this account configuration on the fly and the oidc-agent directory was not accessible for oidc-agent. ## oidc-agent 3.1.1 ### Bugfixes - Fixed a bug that did not save the information from dynamic client registration (did not save merged data). ### Dependencies - Updated the cJSON library ## oidc-agent 3.1.0 - Support on MacOS ## oidc-agent 3.0.2 ### Bugfixes - Fixed behavior of oidc-gen -p when the passed file does not exist. - Fixed segfault if the issuer.config in the oidc-agent directory doesn't exist and an AT is requested by issuer. - Fixed a segfault if the pubclients.conf file does not exist ## oidc-agent 3.0.1 ### Provider - Added the elixir public client to the list of public clients ## oidc-agent 3.0.0 ### Features - Support for [agent forwarding](https://indigo-dc.gitbooks.io/oidc-agent/configure.html#agent-forwarding) - Support for default account configuration for providers: - Defaults can be set in the `issuer.config` file in the oidc-agent directory - Other applications can request access tokens by the issuer (IPC-API, liboidc-agent) - `oidc-token` can be used with issuer url ### API - **Incompatible!** Changed the type of the oidc-agent socket from `SOCK_SEQPACKET` to `SOCK_STREAM` - Added `getAccessToken2` to liboidc-agent; should be used if only an access token is requested - Added `getAccessTokenForIssuer` and `getTokenResponseForIssuer` to liboidc-agent to request access tokens by issuer and not by shortname. ## oidc-agent 2.3.1 ### Bugfixes - Fixed the course of a bug that would not utilize the cached AT when an application requests an AT with an empty scope value. This fix might have also fixed other unknown bugs. - Improved the user prompt message for autoload when the application does not send an application_hint - Fix a bug related to the confirm feature: after a request is declined it was impossible to get an access token for this configuration without reloading the configuration. ### Enhancements - Improved error handling when a wrong refresh token is used ## oidc-agent 2.3.0 ### Features - Autoload: If an application requests an access token for an account configuration that is not yet loaded the user can be prompted to load it and then the application can receive the requested access token. No need to run `oidc-add` preventively. See also the [Tips section in the documentation](https://indigo-dc.gitbooks.io/oidc-agent/tips.html#autoloading-and-autounloading-account-configurations) . - Confirmation: When loading an account configuration with `oidc-add` the new `-c`/`--confirm` option can be used. Similar to `ssh-add` this option requires confirmation by the user whenever the account configuration should be used, i.e. whenever an application requests an access token for that account configuration the user will be prompted if he wants to allow or deny this usage. The option can also be turned on for all configuration loaded into the agent when specifying this option on agent startup. - Changing refresh token: A provider might decide that it issues a new refresh token whenever an access token is issued. In that case `oidc-agent` has to update the account configuration file. To do this the agent requires the encryption password. The agent supports user prompting, keeping it encrypted in memory, reading it from a user provided command, and saving it in the system's keyring. - Custom uri schemes: By using a redirect uri of the form `edu.kit.data.oidc-agent:/` the agent can skip the normally started httpserver and redirect directly to `oidc-gen` to complete the account configuration generation process. - Manual redirect: The auth code flow can now be done completly without the httpserver started by `oidc-agent`. Either through usage of a custom uri scheme redirect url or by manually copying the url the user is redirect to from the browser and passing it to `oidc-gen --codeExchange=''`. - XSession integration: `oidc-agent` is now integrated with Xsession to automatically be available in all terminals throughout an Xsession. ### Changes - Changed the underlying architecture by splitting `oidc-agent` internally into two components - Changed the `oidc-agent` flag for console mode from `-c` to `-d` - Changed the default port for redirect urls registered with dynamically registered clients from `2912` to `4242` ### Enhancements - When the auth code flow fails at the redirect because of problems with the httpserver, the url can be passed manually to `oidc-gen --codeExchange=''` - When a refresh token expired the user has to reauthenticate to obtain a new valid refresh token. Instead of using `oidc-gen -m` to do this the user can also use the new `oidc-gen --reauthenticate` option (the user won't have to confirm that all other data should not be changed). - The `oidc-gen -u` option that updates an encrypted file to the newest encryption and file format version can now also be used with unencrypted files - When using `oidc-gen -d` the account config now does not have to be loaded. The refresh token can also be revoked if not loaded. - Improved the [documentation](https://indigo-dc.gitbooks.io/oidc-agent/) - Communication between the agent and its httpserver is now encrypted - Improved usability of `oidc-gen` with some smaller enhancements at various places - Other smaller enhancements ### OpenID Provider - Added a public client for HBP - Added a public client for Elixir ### Bugfixes - Fixed some memory leaks - Fixed a segmentation fault that would happen when an agent with a public client loaded is locked - Fixed other theoretically possible segmentation faults - Other smaller fixes ## oidc-agent 2.2.6 ### Bugfixes - Removed an unnecessary client_id from post data, that caused problems with iam when using the device flow. ## oidc-agent 2.2.5 ### Bugfixes - Fixed a bug that made it impossible to use the device flow ## oidc-agent 2.2.4 ### Bugfixes - Fixed a possible seg fault - Fixed a bug with file location that use the oidcdir specified in the `OIDC_CONFIG_DIR` env var, if that value does not have a trailing slash ## oidc-agent 2.2.3 ### Bugfixes - Fixed a bug that might have leaked sensitive information to the system log (see #176) - Added the `profile` scope back to default scopes during oidc-gen ### Enhancements - Added an option to manually specify the redirect port used during dynamic client registration (`--port`) - Made the location of the oidcagentdir customizable using the `OIDC_CONFIG_DIR` environment variable ## oidc-agent 2.2.2 ### Provider - Added public client for aai.egi.eu ## oidc-agent 2.2.1 ### Bugfixes - Improved error message when necessary scopes cannot be registered during dynamic client registration - If necessary scopes cannot be registered during dynamic client registration, a public client is tried - Fixed memory leaks - Allow updating of public clients by using the -m and --pub option ## oidc-agent 2.2.0 ### Features - Support for PKCE - Public clients: If dynamic client registration is not supported by a provider, public clients can be used (for some providers) so that a user does not have to register its own client manually. ### Bugfixes - Fixed some code flaws - Fixed seg fault when dynamic client registration failed - Fixed more possible seg faults - Improved error handling when authorization flow not possible - Fixed a bug where it was possible to display issuer urls that only differ in the trayling slash twice when using oidc-gen - Enforce usage of openid and offline_access scope in all cases - Fixed a bug due to which oidc-agent would return a wrong already loaded account config when generating a new account config ### Packages - Support for RPM packages ## oidc-agent 2.1.3 ### Bugfixes - Fixed superfluous error logs when checking if a string is a json object - Fixed strange additional parameters in the auth code exchange request - Fixed a problem with unity OP where access token did not have any scope - Fixed build error if bin dir not existed ### Enhancements - Changed encoding for memory encryption from hex to base64 ## oidc-agent 2.1.2 ### Bugfixes - Fixed a bug due to which errors during token revocation were ignored - Fixed a bug displaying a (wrong) error message when token revocation succeeded and the server answered with an empty response. This bug was introduced with encrypted ipc communication. - Fixed a bug where the browser would not redirect to the werbser when the chosen port was to high -> Now explicitly checking the port range when the user provides the redirect url - Fixed a segmentation fault if the config tmp file did not contain the account shortname - Fixed bash completion that would fail if oidcdir does not exist (yet) ## oidc-agent 2.1.1 ### Bugfixes - Fixed a bug causing problems with the device flow - Fixed memory leaks ## oidc-agent 2.1.0 ### Features - Added possibility to update a configuration file to the newest file format / encryption: `oidc-ggen -u ` - Encrypted IPC: oidc-gen and oidc-add now encrypt all communication with oidc-agent ### Enhancements - Now using base64 encoding instead of hex encoding for all new encryptions - Updated the file format for configuration file. Storing all important encryption parameters and also the version with which it was generated. - When building from source the libcjson package can be used over the local files using `make HAS_CJSON=1` - Using `oidc-gen --dae` now enforces registration of the needed grant type, even if the provider does not advertise it as supported. - Improved the account listing output. ### Library & API - We now also provide a shared library (see also [Packaging](#packaging-dependencies)) ### Bugfixes - Fixed some segmentation faults that were possible - Fixed oidc-agent responding twixe when a check request was sending while being locked - Fixed some memory leaks - Fixed some possibilities for double frees - Fixed missing authorization for device access token requests - Fixed invalid read in stringToJSON when parsing fails - Fixed a wrongly included grant_type parameter in the authorization code url. - Fixed incompatibilities between account configuration files that were generated with oidc-agent using different versions of libsodium. ### Packaging & Dependencies - Removed the user dependency for libsodium. Now linked as a static library - We now provide addition packages: `liboidc-agent2` and `liboidc-agent-dev` for the oidc-agent library ## oidc-agent 2.0.3 ### Behavior Changes - seccomp is now disabled on default. It can be enabled with the `--seccomp` option. The `--no-seccomp` option was removed. ### Bugfixes - Fixed a bug that autoremoved also accounts with infinite lifetime when an account with limited lifetime expired. - Added missing seccomp syscalls - Fixed a bug that broke bash completion - Fixed possible segmentation faults ### Other Changes - increased the maximum length of error message - Disabled Tracing: Cannnot longer attach using ptrace ## oidc-agent 2.0.2 ### Bugfixes - Fixed a bug that disabled seccomp for oidc-add and oidc-token - Fixed a bug where modifying the default scope (dyn client reg) could fail the client registration. ### Enhancements - Internal Improvements to bash-completion ## oidc-agent 2.0.1 ### Bugfixes - Fixed a bug related to merging json objects - Fixed a missing seccomp syscall ### Enhancements - Improved oidc-gen user interface: - oidc-gen now does not prompt for a refresh token on default. Instead the `--rt` option can be used. - oidc-gen now only prompts for credentials if the password flow is used (`--flow=password`) - Improved internal flow handling of dynamic client registration ## oidc-agent 2.0.0 ### Features - **Combined Configuration File:** When using dynamic client registration the default behavior is now to generate only one configuration file containing both client configuration and account configuration. **Under very rare conditions this might break an old configuration file.** If this happens, use `oidc-gen -p ` to display the decrypted content. You can then use this information to generate a new account configuration (using `oidc-gen -m`). - **Account Lifetime:** Added to possibility to set a lifetime for account configurations. After this time the account is automatically removed from the agent. It is possible to set a default lifetime for all account configurations when starting `oidc-agent` using the new `-t` option. It is also possible to specify a lifetime with `-t` when loading a configuration with `oidc-add`. - **Better Support for Turning Colors Off:** It is now possible to turn colors off in different ways: - set the `NO_COLOR` environment variable: Color support is turned off if this variable is presented (regardless of its value). - set `TERM` to `dumb`: color support is turned off if the `TERM` variable is set to `dumb`. - set `OIDC_AGENT_NOCOLOR` to a non zero value. Colors can be turned on for oidc-agent regardless of the above mentioned variables by setting the `OIDC_AGENT_NOCOLOR` environemnt variable to `0`. Furthermore color is turned off if not connected to a tty (e.g. if output redirected to a file). - **Memory Encryption:** Sensitive Information is obfuscated in memory to improve security. - **Agent Lockable:** Added the possibility to lock the agent. When locked the agent refuses any operation until it is unlocked. While being locked additional encryption is applied to the sensitive information kept in memory. - **Seccomp:** Restricted the set of syscalls that can be made by each component. If this feature causes problems on a specific system it can be turned off with the `--no-seccomp` option. - **List Currently Loaded Account Configurations:** This feature was removed. - **Automatically Open Authorization URL:** Added possibility to turn off the automatic opening of the authorization url (authorization code flow) using the `--no-url-call` option. - **Unloading Accounts:** Unloading an account configuration does not require the password anymore. Also added an option to unload all loaded account configuration at once. - **oidc-token:** Added the possibility to not only get an access token with `oidc-token` but also get the associated issuer and the expiration time of this token. To do so the new `-o`, `-i`, `-e`, `-a`, and `-c` options can be used. This also allows calling oidc-token with `eval` to directly set one or multiple environment variables. ### Changes to the CLI - Added support for bash completion - No longer using space delimited lists. To provide multiple values for an option the option can be provided multiple times. ### API #### C-API - Removed `char* getLoadedAccounts()`: It is not possible anymore to get the list of currently loaded configuration from the agent. - A TokenResponse now includes the token, the issuer, and the expiration date. - A TokenRequest should include an application hint. For detailed information refer to the [documentation](https://indigo-dc.gitbooks.io/oidc-agent/api.html) #### IPC-API - Removed the `account_list` request. Applications that use this request to check if an account is loaded before requesting an access token for it, should simply request the access token. If the account is not loaded, an error is returned. - Access token request should now include an `application_hint`. - The Response to a token request now includes the expiration time of the token (as well as the token and the associated issuer url). For detailed information refer to the [documentation](https://indigo-dc.gitbooks.io/oidc-agent/api.html) ### Bugfixes - Fixed a bug where conflicting response types were registered. - Fixed a bug where the automatic account configuration generation failed after dynamic client registration. - Fixed a bug where only the first 4096 bytes of an ipc message were sent. - Fixed a bug related to token revocation. - Fixed a bug with empty IPC messages. - Fixed numerous bugs added during development. - Fixed some smaller bugs. ### Dependencies - The json parser was changed to cJSON - Dependencies are not longer included as static library but included in this repo ## oidc-agent 1.3.0 ### Bugfixes - Fixes static library ### Enhancements - Hides client secret - Validation for redirect url format - Optionally prints the device code url QR-code directly to the terminal - Adds optional client name identifier when using dynamic registration ### API - Backward-compatible API-change: ipc access token requests now also contain the associated issuer; also the C-API includes it ## oidc-agent 1.2.7 ### Bugfixes - fixed segmentation fault for an unchecked file existence ### Provider - Added DEEP - Added HDF ## oidc-agent 1.2.6 ### Library - Now providing C-API as a static library - oidc-token uses that library ### Provider - Added KIT ## oidc-agent 1.2.1 - Support for providing the device authorization endpoint manually ## oidc-agent 1.2.0 ### Features - Support for Authorization Code Flow - Support for Device Flow - Support to choose used flow - Support for user defined scopes - List account configurations - List client configurations - Print decrypted file content - Colored output ### API #### C-API - The function `getAccessToken` has an additional parameter scope. It can be used to pass a space delimited list of scope values. To use the default scope values pass NULL. #### IPC-API - When performing a token request the field min_valid_period is now optional instead of required. The default value is 0. - When performing a token request the new optional field scope can be used to provide a space delimited list of scope values. ### Enhancements - yes ### Bugfixes - yes ## oidc-agent 1.1.0 ### Features - Dynamic registration (`oidc-gen -r`) is now the default option for oidc-gen. If a user does not want to use dynamic client registration `oidc-gen -m` can be used. ### API - Provider configurations are renamed to account configurations. This effects the API in fields like `account_list` ### Bugfixes - fixes agent's response when it could not get a refresh token. It was success; changed now to failure. ## oidc-agent 1.0.5 ### Features - Adds the `-c` flag for oidc-agent. It will skip the daemonizing. ### IPC-API - The provider list is now returned as JSON Array of Strings. - Changed the socket type from SOCK_STREAM to SOCK_SEQPACKET ## oidc-agent 1.0.4 ### Bugfixes - Fixed bug where oidc-agent would crash if it receives non-json message ## oidc-agent 1.0.3 ### Bugfixes - Fixed segfault - Fixed bug where the client config file was not saved - Fixed that the encrypted client config file could not be used by oidc-gen -f ## oidc-agent 1.0.0 First release of oidc-agent, including oidc-gen, oidc-add, oidc-token and a client api.