unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

extjwt.c (31694B)

      1 /*
      2  *   IRC - Internet Relay Chat, src/modules/extjwt.c
      3  *   (C) 2021 The UnrealIRCd Team
      4  *
      5  *   See file AUTHORS in IRC package for additional names of
      6  *   the programmers.
      7  *
      8  *   This program is free software; you can redistribute it and/or modify
      9  *   it under the terms of the GNU General Public License as published by
     10  *   the Free Software Foundation; either version 1, or (at your option)
     11  *   any later version.
     12  *
     13  *   This program is distributed in the hope that it will be useful,
     14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
     15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     16  *   GNU General Public License for more details.
     17  *
     18  *   You should have received a copy of the GNU General Public License
     19  *   along with this program; if not, write to the Free Software
     20  *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     21  */
     22 
     23 #include "unrealircd.h"
     24 
     25 #if defined(__GNUC__)
     26 /* Temporarily ignore these for this entire file. FIXME later when updating the code for OpenSSL 3: */
     27 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
     28 #endif
     29 
     30 /* internal definitions */
     31 
     32 #define MSG_EXTJWT	"EXTJWT"
     33 #define MYCONF "extjwt"
     34 
     35 #undef NEW_ISUPPORT /* enable this for https://github.com/ircv3/ircv3-specifications/pull/341#issuecomment-617038799 */
     36 
     37 #define EXTJWT_METHOD_NOT_SET 0
     38 #define EXTJWT_METHOD_HS256 1
     39 #define EXTJWT_METHOD_HS384 2
     40 #define EXTJWT_METHOD_HS512 3
     41 #define EXTJWT_METHOD_RS256 4
     42 #define EXTJWT_METHOD_RS384 5
     43 #define EXTJWT_METHOD_RS512 6
     44 #define EXTJWT_METHOD_ES256 7
     45 #define EXTJWT_METHOD_ES384 8
     46 #define EXTJWT_METHOD_ES512 9
     47 #define EXTJWT_METHOD_NONE 10
     48 
     49 #define NEEDS_KEY(x) (x>=EXTJWT_METHOD_RS256 && x<=EXTJWT_METHOD_ES512)
     50 
     51 #define URL_LENGTH 4096
     52 #define MODES_SIZE 41 /* about 10 mode chars */
     53 #define TS_LENGTH 19 /* 64-bit integer */
     54 #define MAX_TOKEN_CHUNK (510-sizeof(extjwt_message_pattern)-HOSTLEN-CHANNELLEN)
     55 
     56 /* OpenSSL 1.0.x compatibility */
     57 
     58 #if (OPENSSL_VERSION_NUMBER < 0x10100000L)
     59 void ECDSA_SIG_get0(const ECDSA_SIG *sig, const BIGNUM **pr, const BIGNUM **ps)
     60 {
     61 	if (pr != NULL)
     62 		*pr = sig->r;
     63 	if (ps != NULL)
     64 		*ps = sig->s;
     65 }
     66 #endif
     67 
     68 /* struct definitions */
     69 
     70 struct extjwt_config {
     71 	time_t exp_delay;
     72 	char *secret;
     73 	int method;
     74 	char *vfy;
     75 };
     76 
     77 struct jwt_service {
     78 	char *name;
     79 	struct extjwt_config *cfg;
     80 	struct jwt_service *next;
     81 };
     82 
     83 /* function declarations */
     84 
     85 CMD_FUNC(cmd_extjwt);
     86 char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config);
     87 char *extjwt_generate_token(const char *payload, struct extjwt_config *config);
     88 void b64url(char *b64);
     89 unsigned char *extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
     90 unsigned char *extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
     91 unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen);
     92 char *extjwt_gen_header(int method);
     93 int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs);
     94 int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type);
     95 int extjwt_configposttest(int *errs);
     96 void extjwt_free_services(struct jwt_service **services);
     97 struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name);
     98 int extjwt_valid_integer_string(const char *in, int min, int max);
     99 char *extjwt_test_key(const char *file, int method);
    100 char *extjwt_read_file_contents(const char *file, int absolute, int *size);
    101 int EXTJWT_METHOD_from_string(const char *in);
    102 #ifdef NEW_ISUPPORT
    103 char *extjwt_isupport_param(void);
    104 #endif
    105 
    106 /* string constants */
    107 
    108 const char extjwt_message_pattern[] = ":%s EXTJWT %s %s %s%s";
    109 
    110 /* global structs */
    111 
    112 ModuleHeader MOD_HEADER = {
    113 	"extjwt",
    114 	"6.0",
    115 	"Command /EXTJWT (web service authorization)", 
    116 	"UnrealIRCd Team",
    117 	"unrealircd-6"
    118 };
    119 
    120 struct {
    121 	int have_secret;
    122 	int have_key;
    123 	int have_method;
    124 	int have_expire;
    125 	int have_vfy;
    126 	char *key_filename;
    127 } cfg_state;
    128 
    129 struct extjwt_config cfg;
    130 struct jwt_service *jwt_services;
    131 
    132 MOD_TEST()
    133 {
    134 	memset(&cfg_state, 0, sizeof(cfg_state));
    135 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, extjwt_configtest);
    136 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGPOSTTEST, 0, extjwt_configposttest);
    137 	return MOD_SUCCESS;
    138 }
    139 
    140 MOD_INIT()
    141 {
    142 	CommandAdd(modinfo->handle, MSG_EXTJWT, cmd_extjwt, 2, CMD_USER);
    143 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, extjwt_configrun);
    144 	return MOD_SUCCESS;
    145 }
    146 
    147 MOD_LOAD()
    148 {
    149 	struct jwt_service *service = jwt_services;
    150 #ifdef NEW_ISUPPORT
    151 	ISupportAdd(modinfo->handle, "EXTJWT", extjwt_isupport_param());
    152 #else
    153 	ISupportAdd(modinfo->handle, "EXTJWT", "1");
    154 #endif
    155 	while (service)
    156 	{ /* copy default exp to all services not having one specified */
    157 		if (service->cfg->exp_delay == 0)
    158 			service->cfg->exp_delay = cfg.exp_delay;
    159 		service = service->next;
    160 	}
    161 	return MOD_SUCCESS;
    162 }
    163 
    164 MOD_UNLOAD()
    165 {
    166 	extjwt_free_services(&jwt_services);
    167 	return MOD_SUCCESS;
    168 }
    169 
    170 #ifdef NEW_ISUPPORT
    171 char *extjwt_isupport_param(void)
    172 {
    173 	struct jwt_service *services = jwt_services;
    174 	int count = 0;
    175 	static char buf[500];
    176 	strlcpy(buf, "V:1", sizeof(buf));
    177 	while (services)
    178 	{
    179 		strlcat(buf, count?",":"&S:", sizeof(buf));
    180 		strlcat(buf, services->name, sizeof(buf));
    181 		count++;
    182 		services = services->next;
    183 	}
    184 	return buf;
    185 }
    186 #endif
    187 
    188 void extjwt_free_services(struct jwt_service **services){
    189 	struct jwt_service *ss, *next;
    190 	ss = *services;
    191 	while (ss)
    192 	{
    193 		next = ss->next;
    194 		safe_free(ss->name);
    195 		if (ss->cfg)
    196 			safe_free(ss->cfg->secret);
    197 		safe_free(ss->cfg);
    198 		safe_free(ss);
    199 		ss = next;
    200 	}
    201 	*services = NULL;
    202 }
    203 
    204 struct jwt_service *find_jwt_service(struct jwt_service *services, const char *name)
    205 {
    206 	if (!name)
    207 		return NULL;
    208 	while (services)
    209 	{
    210 		if (services->name && !strcmp(services->name, name))
    211 			return services;
    212 		services = services->next;
    213 	}
    214 	return NULL;
    215 }
    216 
    217 int extjwt_valid_integer_string(const char *in, int min, int max)
    218 {
    219 	int i, val;
    220 	if (BadPtr(in))
    221 		return 0;
    222 	for (i=0; in[i]; i++){
    223 		if (!isdigit(in[i]))
    224 			return 0;
    225 	}
    226 	val = atoi(in);
    227 	if (val < min || val > max)
    228 		return 0;
    229 	return 1;
    230 }
    231 
    232 int vfy_url_is_valid(const char *string)
    233 {
    234 	if (strstr(string, "http://") == string || strstr(string, "https://") == string)
    235 	{
    236 		if (strstr(string, "%s"))
    237 			return 1;
    238 	}
    239 	return 0;
    240 }
    241 
    242 char *extjwt_test_key(const char *file, int method)
    243 { /* returns NULL when valid */
    244 	int fsize;
    245 	char *fcontent = NULL;
    246 	char *retval = NULL;
    247 	BIO *bufkey = NULL;
    248 	EVP_PKEY *pkey = NULL;
    249 	int type, pkey_type;
    250 	do {
    251 		switch (method)
    252 		{
    253 			case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512:
    254 				type = EVP_PKEY_RSA;
    255 				break;
    256 			case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512:
    257 				type = EVP_PKEY_EC;
    258 				break;
    259 			default:
    260 				retval = "Internal error (invalid type)";
    261 				return retval;
    262 		}
    263 		fcontent = extjwt_read_file_contents(file, 0, &fsize);
    264 		if (!fcontent)
    265 		{
    266 			retval = "Cannot open file";
    267 			break;
    268 		}
    269 		if (fsize == 0)
    270 		{
    271 			retval = "File is empty";
    272 			break;
    273 		}
    274 		if (!(bufkey = BIO_new_mem_buf(fcontent, fsize)))
    275 		{
    276 			retval = "Unknown error";
    277 			break;
    278 		}
    279 		if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL)))
    280 		{
    281 			retval = "Key is invalid";
    282 			break;
    283 		}
    284 		pkey_type = EVP_PKEY_id(pkey);
    285 		if (type != pkey_type)
    286 		{
    287 			retval = "Key does not match method";
    288 			break;
    289 		}
    290 	} while (0);
    291 	safe_free(fcontent);
    292 	if (bufkey)
    293 		BIO_free(bufkey);
    294 	if (pkey)
    295 		EVP_PKEY_free(pkey);
    296 	return retval;
    297 }
    298 
    299 int EXTJWT_METHOD_from_string(const char *in)
    300 {
    301 	if (!strcmp(in, "HS256"))
    302 		return EXTJWT_METHOD_HS256;
    303 	if (!strcmp(in, "HS384"))
    304 		return EXTJWT_METHOD_HS384;
    305 	if (!strcmp(in, "HS512"))
    306 		return EXTJWT_METHOD_HS512;
    307 	if (!strcmp(in, "RS256"))
    308 		return EXTJWT_METHOD_RS256;
    309 	if (!strcmp(in, "RS384"))
    310 		return EXTJWT_METHOD_RS384;
    311 	if (!strcmp(in, "RS512"))
    312 		return EXTJWT_METHOD_RS512;
    313 	if (!strcmp(in, "ES256"))
    314 		return EXTJWT_METHOD_ES256;
    315 	if (!strcmp(in, "ES384"))
    316 		return EXTJWT_METHOD_ES384;
    317 	if (!strcmp(in, "ES512"))
    318 		return EXTJWT_METHOD_ES512;
    319 	if (!strcmp(in, "NONE"))
    320 		return EXTJWT_METHOD_NONE;
    321 	return EXTJWT_METHOD_NOT_SET;
    322 }
    323 
    324 /* Configuration is described in conf/modules.optional.conf */
    325 
    326 int extjwt_configtest(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    327 {
    328 	int errors = 0;
    329 	ConfigEntry *cep, *cep2;
    330 	int i;
    331 	struct jwt_service *services = NULL;
    332 	struct jwt_service **ss = &services; /* list for checking whether service names repeat */
    333 	int have_ssecret, have_smethod, have_svfy, have_scert;
    334 	unsigned int sfilename_line_number = 0;
    335 	char *sfilename = NULL;
    336 
    337 	if (type != CONFIG_MAIN)
    338 		return 0;
    339 
    340 	if (!ce || strcmp(ce->name, MYCONF))
    341 		return 0;
    342 
    343 	for (cep = ce->items; cep; cep = cep->next)
    344 	{
    345 		if (!cep->value)
    346 		{
    347 			config_error("%s:%i: blank %s::%s without value", cep->file->filename, cep->line_number, MYCONF, cep->name);
    348 			errors++;
    349 			continue;
    350 		}
    351 		if (!strcmp(cep->name, "method"))
    352 		{
    353 			if (cfg_state.have_method)
    354 			{
    355 				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
    356 				errors++;
    357 				continue;
    358 			}
    359 			cfg_state.have_method = EXTJWT_METHOD_from_string(cep->value);
    360 			if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET)
    361 			{
    362 				config_error("%s:%i: invalid value %s::%s \"%s\" (check docs for allowed options)", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value);
    363 				errors++;
    364 			}
    365 			continue;
    366 		}
    367 		if (!strcmp(cep->name, "expire-after"))
    368 		{
    369 			if (!extjwt_valid_integer_string(cep->value, 1, 9999))
    370 			{
    371 				config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep->file->filename, cep->line_number, MYCONF, cep->name);
    372 				errors++;
    373 			}
    374 			continue;
    375 		}
    376 		if (!strcmp(cep->name, "secret"))
    377 		{
    378 			if (cfg_state.have_secret)
    379 			{
    380 				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
    381 				errors++;
    382 				continue;
    383 			}
    384 			cfg_state.have_secret = 1;
    385 			if (strlen(cep->value) < 4)
    386 			{
    387 				config_error("%s:%i: Secret specified in %s::%s is too short!", cep->file->filename, cep->line_number, MYCONF, cep->name);
    388 				errors++;
    389 			}
    390 			continue;
    391 		}
    392 		if (!strcmp(cep->name, "key"))
    393 		{
    394 			if (cfg_state.have_key)
    395 			{
    396 				config_error("%s:%i: duplicate %s::%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
    397 				errors++;
    398 				continue;
    399 			}
    400 			if (!is_file_readable(cep->value, CONFDIR))
    401 			{
    402 				config_error("%s:%i: Cannot open file \"%s\" specified in %s::%s for reading", cep->file->filename, cep->line_number, cep->value, MYCONF, cep->name);
    403 				errors++;
    404 			}
    405 			safe_strdup(cfg_state.key_filename, cep->value);
    406 			cfg_state.have_key = 1;
    407 			continue;
    408 		}
    409 		if (!strcmp(cep->name, "verify-url"))
    410 		{
    411 			if (cfg_state.have_vfy)
    412 			{
    413 				config_error("%s:%i: duplicate %s:%s item", cep->file->filename, cep->line_number, MYCONF, cep->name);
    414 				errors++;
    415 				continue;
    416 			}
    417 			cfg_state.have_vfy = 1;
    418 			if (!vfy_url_is_valid(cep->value))
    419 			{
    420 				config_error("%s:%i: Optional URL specified in %s::%s is invalid!", cep->file->filename, cep->line_number, MYCONF, cep->name);
    421 				errors++;
    422 				continue;
    423 			}
    424 			if (strlen(cep->value) > URL_LENGTH)
    425 			{
    426 				config_error("%s:%i: Optional URL specified in %s::%s is too long!", cep->file->filename, cep->line_number, MYCONF, cep->name);
    427 				errors++;
    428 			}
    429 			continue;
    430 		}
    431 		if (!strcmp(cep->name, "service"))
    432 		{
    433 			have_ssecret = 0;
    434 			have_smethod = 0;
    435 			have_svfy = 0;
    436 			have_scert = 0;
    437 			if (strchr(cep->value, ' ') || strchr(cep->value, ','))
    438 			{
    439 				config_error("%s:%i: Invalid %s::%s name (contains spaces or commas)", cep->file->filename, cep->line_number, MYCONF, cep->name);
    440 				errors++;
    441 				continue;
    442 			}
    443 			if (find_jwt_service(services, cep->value))
    444 			{
    445 				config_error("%s:%i: Duplicate %s::%s name \"%s\"", cep->file->filename, cep->line_number, MYCONF, cep->name, cep->value);
    446 				errors++;
    447 				continue;
    448 			}
    449 			*ss = safe_alloc(sizeof(struct jwt_service)); /* store the new name for further checking */
    450 			safe_strdup((*ss)->name, cep->value);
    451 			ss = &(*ss)->next;
    452 			for (cep2 = cep->items; cep2; cep2 = cep2->next)
    453 			{
    454 				if (!cep2->name || !cep2->value || !cep2->value[0])
    455 				{
    456 					config_error("%s:%i: blank/incomplete %s::service entry", cep2->file->filename, cep2->line_number, MYCONF);
    457 					errors++;
    458 					continue;
    459 				}
    460 
    461 				if (!strcmp(cep2->name, "method"))
    462 				{
    463 					if (have_smethod)
    464 					{
    465 						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    466 						errors++;
    467 						continue;
    468 					}
    469 					have_smethod = EXTJWT_METHOD_from_string(cep2->value);
    470 					if (have_smethod == EXTJWT_METHOD_NOT_SET || have_smethod == EXTJWT_METHOD_NONE)
    471 					{
    472 						config_error("%s:%i: invalid value of optional %s::service::%s \"%s\" (check docs for allowed options)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name, cep2->value);
    473 						errors++;
    474 					}
    475 					continue;
    476 				}
    477 
    478 				if (!strcmp(cep2->name, "secret"))
    479 				{
    480 					if (have_ssecret)
    481 					{
    482 						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    483 						errors++;
    484 						continue;
    485 					}
    486 					have_ssecret = 1;
    487 					if (strlen(cep2->value) < 4) /* TODO maybe a better check? */
    488 					{
    489 						config_error("%s:%i: Secret specified in %s::service::%s is too short!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    490 						errors++;
    491 					}
    492 					continue;
    493 				}
    494 
    495 				if (!strcmp(cep2->name, "key"))
    496 				{
    497 					if (have_scert)
    498 					{
    499 						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    500 						errors++;
    501 						continue;
    502 					}
    503 					if (!is_file_readable(cep2->value, CONFDIR))
    504 					{
    505 						config_error("%s:%i: Cannot open file \"%s\" specified in %s::service::%s for reading", cep2->file->filename, cep2->line_number, cep2->value, MYCONF, cep2->name);
    506 						errors++;
    507 					}
    508 					have_scert = 1;
    509 					safe_strdup(sfilename, cep2->value);
    510 					sfilename_line_number = cep2->line_number;
    511 					continue;
    512 				}
    513 
    514 				if (!strcmp(cep2->name, "expire-after"))
    515 				{
    516 					if (!extjwt_valid_integer_string(cep2->value, 1, 9999))
    517 					{
    518 						config_error("%s:%i: %s::%s must be an integer between 1 and 9999 (seconds)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    519 						errors++;
    520 					}
    521 					continue;
    522 				}
    523 
    524 				if (!strcmp(cep2->name, "verify-url"))
    525 				{
    526 					if (have_svfy)
    527 					{
    528 						config_error("%s:%i: duplicate %s::service::%s item", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    529 						errors++;
    530 						continue;
    531 					}
    532 					have_svfy = 1;
    533 					if (!vfy_url_is_valid(cep2->value))
    534 					{
    535 						config_error("%s:%i: Optional URL specified in %s::service::%s is invalid!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    536 						errors++;
    537 						continue;
    538 					}
    539 					if (strlen(cep2->value) > URL_LENGTH)
    540 					{
    541 						config_error("%s:%i: Optional URL specified in %s::service::%s is too long!", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    542 						errors++;
    543 					}
    544 					continue;
    545 				}
    546 
    547 				config_error("%s:%i: invalid %s::service attribute %s (must be one of: name, secret, expire-after)", cep2->file->filename, cep2->line_number, MYCONF, cep2->name);
    548 				errors++;
    549 			}
    550 			if (!have_smethod)
    551 			{
    552 				config_error("%s:%i: invalid %s::service entry (no %s::service::method specfied)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
    553 				errors++;
    554 				continue;
    555 			}
    556 			if (have_ssecret && NEEDS_KEY(have_smethod))
    557 			{
    558 				config_error("%s:%i: invalid %s::service entry (this method needs %s::service::key and not %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF);
    559 				errors++;
    560 				continue;
    561 			}
    562 			if (have_scert && !NEEDS_KEY(have_smethod))
    563 			{
    564 				config_error("%s:%i: invalid %s::service entry (this method needs %s::service::secret and not %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF, MYCONF);
    565 				errors++;
    566 				continue;
    567 			}
    568 			if (!have_ssecret && !NEEDS_KEY(have_smethod))
    569 			{
    570 				config_error("%s:%i: invalid %s::service entry (must contain %s::service::secret option)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
    571 				errors++;
    572 				continue;
    573 			}
    574 			if (!have_scert && NEEDS_KEY(have_smethod)) {
    575 				config_error("%s:%i: invalid %s::service entry (must contain %s::service::key option)", cep->file->filename, cep->line_number, MYCONF, MYCONF);
    576 				errors++;
    577 				continue;
    578 			}
    579 			if (NEEDS_KEY(have_smethod) && have_scert)
    580 			{
    581 				char *keyerr;
    582 				keyerr = extjwt_test_key(sfilename, have_smethod);
    583 				if (keyerr)
    584 				{
    585 					config_error("%s:%i: Invalid key file specified for %s::key: %s", cep->file->filename, sfilename_line_number, MYCONF, keyerr);
    586 					errors++;
    587 				}
    588 			}
    589 			continue;
    590 		}
    591 		config_error("%s:%i: unknown directive %s::%s", cep->file->filename, cep->line_number, MYCONF, cep->name);
    592 		errors++;
    593 	}
    594 	*errs = errors;
    595 	extjwt_free_services(&services);
    596 	if (errors)
    597 		safe_free(cfg_state.key_filename);
    598 	safe_free(sfilename);
    599 	return errors ? -1 : 1;
    600 }
    601 
    602 int extjwt_configposttest(int *errs)
    603 {
    604 	int errors = 0;
    605 	if (cfg_state.have_method == EXTJWT_METHOD_NOT_SET)
    606 	{
    607 		config_error("No %s::method specfied!", MYCONF);
    608 		errors++;
    609 	} else
    610 	{
    611 		if (cfg_state.have_method != EXTJWT_METHOD_NONE && !NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_secret)
    612 		{
    613 			config_error("No %s::secret specfied as required by requested method!", MYCONF);
    614 			errors++;
    615 		}
    616 		if ((cfg_state.have_method == EXTJWT_METHOD_NONE || NEEDS_KEY(cfg_state.have_method)) && cfg_state.have_secret)
    617 		{
    618 			config_error("A %s::secret specfied but it should not be when using requested method!", MYCONF);
    619 			errors++;
    620 		}
    621 		if (NEEDS_KEY(cfg_state.have_method) && !cfg_state.have_key)
    622 		{
    623 			config_error("No %s::key specfied as required by requested method!", MYCONF);
    624 			errors++;
    625 		}
    626 		if (!NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key)
    627 		{
    628 			config_error("A %s::key specfied but it should not be when using requested method!", MYCONF);
    629 			errors++;
    630 		}
    631 		if (NEEDS_KEY(cfg_state.have_method) && cfg_state.have_key && cfg_state.key_filename)
    632 		{
    633 			char *keyerr;
    634 			
    635 			keyerr = extjwt_test_key(cfg_state.key_filename, cfg_state.have_method);
    636 			if (keyerr)
    637 			{
    638 				config_error("Invalid key file specified for %s::key: %s", MYCONF, keyerr);
    639 				errors++;
    640 			}
    641 		}
    642 	}
    643 	safe_free(cfg_state.key_filename);
    644 	if (errors)
    645 	{
    646 		*errs = errors;
    647 		return -1;
    648 	}
    649 	/* setting defaults, FIXME this may behave incorrectly if there's another module failing POSTTEST */
    650 	if (!cfg_state.have_expire)
    651 		cfg.exp_delay = 30;
    652 	/* prepare service list to load new data */
    653 	extjwt_free_services(&jwt_services);
    654 	return 1;
    655 }
    656 
    657 int extjwt_configrun(ConfigFile *cf, ConfigEntry *ce, int type)
    658 { /* actually use the new configuration data */
    659 	ConfigEntry *cep, *cep2;
    660 	struct jwt_service **ss = &jwt_services;
    661 	if (*ss)
    662 		ss = &((*ss)->next);
    663 
    664 	if (type != CONFIG_MAIN)
    665 		return 0;
    666 
    667 	if (!ce || strcmp(ce->name, MYCONF))
    668 		return 0;
    669 
    670 	for (cep = ce->items; cep; cep = cep->next)
    671 	{
    672 		if (!strcmp(cep->name, "method"))
    673 		{
    674 			cfg.method = EXTJWT_METHOD_from_string(cep->value);
    675 			continue;
    676 		}
    677 		if (!strcmp(cep->name, "expire-after"))
    678 		{
    679 			cfg.exp_delay = atoi(cep->value);
    680 			continue;
    681 		}
    682 		if (!strcmp(cep->name, "secret"))
    683 		{
    684 			cfg.secret = strdup(cep->value);
    685 			continue;
    686 		}
    687 		if (!strcmp(cep->name, "key"))
    688 		{
    689 			cfg.secret = extjwt_read_file_contents(cep->value, 0, NULL);
    690 			continue;
    691 		}
    692 		if (!strcmp(cep->name, "verify-url"))
    693 		{
    694 			cfg.vfy = strdup(cep->value);
    695 			continue;
    696 		}
    697 		if (!strcmp(cep->name, "service"))
    698 		{ /* nested block */
    699 			*ss = safe_alloc(sizeof(struct jwt_service));
    700 			(*ss)->cfg = safe_alloc(sizeof(struct extjwt_config));
    701 			safe_strdup((*ss)->name, cep->value); /* copy the service name */
    702 			for (cep2 = cep->items; cep2; cep2 = cep2->next)
    703 			{
    704 				if (!strcmp(cep2->name, "method"))
    705 				{
    706 					(*ss)->cfg->method = EXTJWT_METHOD_from_string(cep2->value);
    707 					continue;
    708 				}
    709 				if (!strcmp(cep2->name, "expire-after"))
    710 				{
    711 					(*ss)->cfg->exp_delay = atoi(cep2->value);
    712 					continue;
    713 				}
    714 				if (!strcmp(cep2->name, "secret"))
    715 				{
    716 					(*ss)->cfg->secret = strdup(cep2->value);
    717 					continue;
    718 				}
    719 				if (!strcmp(cep2->name, "key"))
    720 				{
    721 					(*ss)->cfg->secret = extjwt_read_file_contents(cep2->value, 0, NULL);
    722 					continue;
    723 				}
    724 				if (!strcmp(cep2->name, "verify-url"))
    725 				{
    726 					(*ss)->cfg->vfy = strdup(cep2->value);
    727 					continue;
    728 				}
    729 			}
    730 			ss = &((*ss)->next);
    731 		}
    732 	}
    733 	return 1;
    734 }
    735 
    736 char *extjwt_read_file_contents(const char *file, int absolute, int *size)
    737 {
    738 	FILE *f = NULL;
    739 	int fsize;
    740 	char *filename = NULL;
    741 	char *buf = NULL;
    742 	do
    743 	{
    744 		safe_strdup(filename, file);
    745 		if (!absolute)
    746 			convert_to_absolute_path(&filename, CONFDIR);
    747 		f = fopen(filename, "rb");
    748 		if (!f)
    749 			break;
    750 		fseek(f, 0, SEEK_END);
    751 		fsize = ftell(f);
    752 		fseek(f, 0, SEEK_SET);
    753 		buf = safe_alloc(fsize + 1);
    754 		fsize = fread(buf, 1, fsize, f);
    755 		buf[fsize] = '\0';
    756 		if (size)
    757 			*size = fsize;
    758 		fclose(f);
    759 	} while (0);
    760 	safe_free(filename);
    761 	if (!buf && size)
    762 		*size = 0;
    763 	return buf;
    764 }
    765 
    766 CMD_FUNC(cmd_extjwt)
    767 {
    768 	Channel *channel;
    769 	char *payload;
    770 	char *token, *full_token;
    771 	struct jwt_service *service = NULL;
    772 	struct extjwt_config *config;
    773 	int last = 0;
    774 	char message[MAX_TOKEN_CHUNK+1];
    775 	if (parc < 2 || BadPtr(parv[1]))
    776 	{
    777 		sendnumeric(client, ERR_NEEDMOREPARAMS, MSG_EXTJWT);
    778 		return;
    779 	}
    780 	if (parv[1][0] == '*' && parv[1][1] == '\0')
    781 	{
    782 		channel = NULL; /* not linked to a channel */
    783 	} else
    784 	{
    785 		channel = find_channel(parv[1]);
    786 		if (!channel)
    787 		{
    788 			sendnumeric(client, ERR_NOSUCHNICK, parv[1]);
    789 			return;
    790 		}
    791 	}
    792 	if (parc > 2 && !BadPtr(parv[2]))
    793 	{
    794 		service = find_jwt_service(jwt_services, parv[2]);
    795 		if (!service)
    796 		{
    797 			sendto_one(client, NULL, ":%s FAIL %s NO_SUCH_SERVICE :No such service", me.name, MSG_EXTJWT);
    798 			return;
    799 		}
    800 	}
    801 	if (service){
    802 		config = service->cfg; /* service config */
    803 	} else {
    804 		config = &cfg; /* default config */
    805 	}
    806 	if (!(payload = extjwt_make_payload(client, channel, config)) || !(full_token = extjwt_generate_token(payload, config)))
    807 	{
    808 		sendto_one(client, NULL, ":%s FAIL %s UNKNOWN_ERROR :Failed to generate token", me.name, MSG_EXTJWT);
    809 		return;
    810 	}
    811 	safe_free(payload);
    812 	token = full_token;
    813 	do
    814 	{
    815 		if (strlen(token) <= MAX_TOKEN_CHUNK)
    816 		{ /* the remaining data (or whole token) will fit a single irc message */
    817 			last = 1;
    818 			strcpy(message, token);
    819 		} else
    820 		{ /* send a chunk and shift buffer */
    821 			strlcpy(message, token, MAX_TOKEN_CHUNK+1);
    822 			token += MAX_TOKEN_CHUNK;
    823 		}
    824 		sendto_one(client, NULL, extjwt_message_pattern, me.name, parv[1], "*", last?"":"* ", message);
    825 	} while (!last);
    826 	safe_free(full_token);
    827 }
    828 
    829 char *extjwt_make_payload(Client *client, Channel *channel, struct extjwt_config *config)
    830 {
    831 	Membership *lp;
    832 	json_t *payload = NULL;
    833 	json_t *modes = NULL;
    834 	json_t *umodes = NULL;
    835 	char *modestring;
    836 	char singlemode[2] = { '\0' };
    837 	char *result;
    838 
    839 	if (!IsUser(client))
    840 		return NULL;
    841 
    842 	payload = json_object();
    843 	modes = json_array();
    844 	umodes = json_array();
    845 	
    846 	json_object_set_new(payload, "exp", json_integer(TStime()+config->exp_delay));
    847 	json_object_set_new(payload, "iss", json_string_unreal(me.name));
    848 	json_object_set_new(payload, "sub", json_string_unreal(client->name));
    849 	json_object_set_new(payload, "account", json_string_unreal(IsLoggedIn(client)?client->user->account:""));
    850 	
    851 	if (config->vfy) /* also add the URL */
    852 		json_object_set_new(payload, "vfy", json_string_unreal(config->vfy));
    853 
    854 	if (IsOper(client)) /* add "o" ircop flag */
    855 		json_array_append_new(umodes, json_string("o"));
    856 	json_object_set_new(payload, "umodes", umodes);
    857 
    858 	if (channel)
    859 	{ /* fill in channel information and user flags */
    860 		lp = find_membership_link(client->user->channel, channel);
    861 		if (lp)
    862 		{
    863 			modestring = lp->member_modes;
    864 			while (*modestring)
    865 			{
    866 				singlemode[0] = *modestring;
    867 				json_array_append_new(modes, json_string(singlemode));
    868 				modestring++;
    869 			}
    870 		}
    871 		json_object_set_new(payload, "channel", json_string_unreal(channel->name));
    872 		json_object_set_new(payload, "joined", json_integer(lp?1:0));
    873 		json_object_set_new(payload, "cmodes", modes);
    874 	}
    875 	result = json_dumps(payload, JSON_COMPACT);
    876 	json_decref(modes);
    877 	json_decref(umodes);
    878 	json_decref(payload);
    879 	return result;
    880 }
    881 
    882 void b64url(char *b64)
    883 { /* convert base64 to base64-url */
    884 	while (*b64)
    885 	{
    886 		if (*b64 == '+')
    887 			*b64 = '-';
    888 		if (*b64 == '/')
    889 			*b64 = '_';
    890 		if (*b64 == '=')
    891 		{
    892 			*b64 = '\0';
    893 			return;
    894 		}
    895 		b64++;
    896 	}
    897 }
    898 
    899 unsigned char *extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
    900 {
    901 	switch(method)
    902 	{
    903 		case EXTJWT_METHOD_HS256: case EXTJWT_METHOD_HS384: case EXTJWT_METHOD_HS512:
    904 			return extjwt_hmac_extjwt_hash(method, key, keylen, data, datalen, resultlen);
    905 		case EXTJWT_METHOD_RS256: case EXTJWT_METHOD_RS384: case EXTJWT_METHOD_RS512: case EXTJWT_METHOD_ES256: case EXTJWT_METHOD_ES384: case EXTJWT_METHOD_ES512:
    906 			return extjwt_sha_pem_extjwt_hash(method, key, keylen, data, datalen, resultlen);
    907 	}
    908 	return NULL;
    909 }
    910 
    911 unsigned char* extjwt_sha_pem_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
    912 {
    913 	EVP_MD_CTX *mdctx = NULL;
    914 	ECDSA_SIG *ec_sig = NULL;
    915 	const BIGNUM *ec_sig_r = NULL;
    916 	const BIGNUM *ec_sig_s = NULL;
    917 	BIO *bufkey = NULL;
    918 	const EVP_MD *alg;
    919 	int type;
    920 	EVP_PKEY *pkey = NULL;
    921 	int pkey_type;
    922 	unsigned char *sig = NULL;
    923 	int ret = 0;
    924 	size_t slen;
    925 	char *retval = NULL;
    926 	char *output = NULL;
    927 	char *sig_ptr;
    928 
    929 	do
    930 	{
    931 		switch (method)
    932 		{
    933 			case EXTJWT_METHOD_RS256:
    934 				alg = EVP_sha256();
    935 				type = EVP_PKEY_RSA;
    936 				break;
    937 			case EXTJWT_METHOD_RS384:
    938 				alg = EVP_sha384();
    939 				type = EVP_PKEY_RSA;
    940 				break;
    941 			case EXTJWT_METHOD_RS512:
    942 				alg = EVP_sha512();
    943 				type = EVP_PKEY_RSA;
    944 				break;
    945 			case EXTJWT_METHOD_ES256:
    946 				alg = EVP_sha256();
    947 				type = EVP_PKEY_EC;
    948 				break;
    949 			case EXTJWT_METHOD_ES384:
    950 				alg = EVP_sha384();
    951 				type = EVP_PKEY_EC;
    952 				break;
    953 			case EXTJWT_METHOD_ES512:
    954 				alg = EVP_sha512();
    955 				type = EVP_PKEY_EC;
    956 				break;
    957 			default:
    958 				return NULL;
    959 		}
    960 
    961 #if (OPENSSL_VERSION_NUMBER < 0x10100003L) /* https://github.com/openssl/openssl/commit/8ab31975bacb9c907261088937d3aa4102e3af84 */
    962 		if (!(bufkey = BIO_new_mem_buf((void *)key, keylen)))
    963 			break; /* out of memory */
    964 #else
    965 		if (!(bufkey = BIO_new_mem_buf(key, keylen)))
    966 			break; /* out of memory */
    967 #endif
    968 		if (!(pkey = PEM_read_bio_PrivateKey(bufkey, NULL, NULL, NULL)))
    969 			break; /* invalid key? */
    970 		pkey_type = EVP_PKEY_id(pkey);
    971 		if (type != pkey_type)
    972 			break; /* invalid key type */
    973 		if (!(mdctx = EVP_MD_CTX_create()))
    974 			break; /* out of memory */
    975 		if (EVP_DigestSignInit(mdctx, NULL, alg, NULL, pkey) != 1)
    976 			break; /* initialize error */
    977 		if (EVP_DigestSignUpdate(mdctx, data, datalen) != 1)
    978 			break; /* signing error */
    979 		if (EVP_DigestSignFinal(mdctx, NULL, &slen) != 1) /* get required buffer length */
    980 			break;
    981 		sig = safe_alloc(slen);
    982 		if (EVP_DigestSignFinal(mdctx, sig, &slen) != 1)
    983 			break;
    984 		if (pkey_type != EVP_PKEY_EC)
    985 		{
    986 			*resultlen = slen;
    987 			output = safe_alloc(slen);
    988 			memcpy(output, sig, slen);
    989 			retval = output;
    990 		} else
    991 		{
    992 			unsigned int degree, bn_len, r_len, s_len, buf_len;
    993 			unsigned char *raw_buf = NULL;
    994 			EC_KEY *ec_key;
    995 			if (!(ec_key = EVP_PKEY_get1_EC_KEY(pkey)))
    996 				break; /* out of memory */
    997 			degree = EC_GROUP_get_degree(EC_KEY_get0_group(ec_key));
    998 			EC_KEY_free(ec_key);
    999 			sig_ptr = sig;
   1000 			if (!(ec_sig = d2i_ECDSA_SIG(NULL, (const unsigned char **)&sig_ptr, slen)))
   1001 				break; /* out of memory */
   1002 			ECDSA_SIG_get0(ec_sig, &ec_sig_r, &ec_sig_s);
   1003 			r_len = BN_num_bytes(ec_sig_r);
   1004 			s_len = BN_num_bytes(ec_sig_s);
   1005 			bn_len = (degree+7)/8;
   1006 			if (r_len>bn_len || s_len > bn_len)
   1007 				break;
   1008 			buf_len = bn_len*2;
   1009 			raw_buf = safe_alloc(buf_len);
   1010 			BN_bn2bin(ec_sig_r, raw_buf+bn_len-r_len);
   1011 			BN_bn2bin(ec_sig_s, raw_buf+buf_len-s_len);
   1012 			output = safe_alloc(buf_len);
   1013 			*resultlen = buf_len;
   1014 			memcpy(output, raw_buf, buf_len);
   1015 			retval = output;
   1016 			safe_free(raw_buf);
   1017 		}
   1018 	} while (0);
   1019 
   1020 	if (bufkey)
   1021 		BIO_free(bufkey);
   1022 	if (pkey)
   1023 		EVP_PKEY_free(pkey);
   1024 	if (mdctx)
   1025 		EVP_MD_CTX_destroy(mdctx);
   1026 	if (ec_sig)
   1027 		ECDSA_SIG_free(ec_sig);
   1028 	safe_free(sig);
   1029 	return retval;
   1030 }
   1031 
   1032 unsigned char* extjwt_hmac_extjwt_hash(int method, const void *key, int keylen, const unsigned char *data, int datalen, unsigned int* resultlen)
   1033 {
   1034 	const EVP_MD* typ;
   1035 	char *hmac = safe_alloc(EVP_MAX_MD_SIZE);
   1036 	switch (method)
   1037 	{
   1038 		default:
   1039 		case EXTJWT_METHOD_HS256:
   1040 			typ = EVP_sha256();
   1041 			break;
   1042 		case EXTJWT_METHOD_HS384:
   1043 			typ = EVP_sha384();
   1044 			break;
   1045 		case EXTJWT_METHOD_HS512:
   1046 			typ = EVP_sha512();
   1047 			break;
   1048 	}
   1049 	if (HMAC(typ, key, keylen, data, datalen, hmac, resultlen))
   1050 	{ /* openssl call */
   1051 		return hmac;
   1052 	} else {
   1053 		safe_free(hmac);
   1054 		return NULL;
   1055 	}
   1056 }
   1057 
   1058 char *extjwt_gen_header(int method)
   1059 { /* returns header json */
   1060 	json_t *header = NULL;
   1061 	json_t *alg;
   1062 	char *result;
   1063 
   1064 	header = json_object();
   1065 	json_object_set_new(header, "typ", json_string("JWT"));
   1066 
   1067 	switch (method)
   1068 	{
   1069 		default:
   1070 		case EXTJWT_METHOD_HS256:
   1071 			alg = json_string("HS256");
   1072 			break;
   1073 		case EXTJWT_METHOD_HS384:
   1074 			alg = json_string("HS384");
   1075 			break;
   1076 		case EXTJWT_METHOD_HS512:
   1077 			alg = json_string("HS512");
   1078 			break;
   1079 		case EXTJWT_METHOD_RS256:
   1080 			alg = json_string("RS256");
   1081 			break;
   1082 		case EXTJWT_METHOD_RS384:
   1083 			alg = json_string("RS384");
   1084 			break;
   1085 		case EXTJWT_METHOD_RS512:
   1086 			alg = json_string("RS512");
   1087 			break;
   1088 		case EXTJWT_METHOD_ES256:
   1089 			alg = json_string("ES256");
   1090 			break;
   1091 		case EXTJWT_METHOD_ES384:
   1092 			alg = json_string("ES384");
   1093 			break;
   1094 		case EXTJWT_METHOD_ES512:
   1095 			alg = json_string("ES512");
   1096 			break;
   1097 		case EXTJWT_METHOD_NONE:
   1098 			alg = json_string("none");
   1099 			break;
   1100 	}
   1101 	json_object_set_new(header, "alg", alg);
   1102 	result = json_dumps(header, JSON_COMPACT);
   1103 	json_decref(header);
   1104 	return result;
   1105 }
   1106 
   1107 char *extjwt_generate_token(const char *payload, struct extjwt_config *config)
   1108 {
   1109 	char *header = extjwt_gen_header(config->method);
   1110 	size_t b64header_size = strlen(header)*4/3 + 8; // base64 has 4/3 overhead
   1111 	size_t b64payload_size = strlen(payload)*4/3 + 8;
   1112 	size_t b64sig_size = 4096*4/3 + 8;
   1113 	size_t b64data_size = b64header_size + b64payload_size + b64sig_size + 4;
   1114 	char *b64header = safe_alloc(b64header_size);
   1115 	char *b64payload = safe_alloc(b64payload_size);
   1116 	char *b64sig = safe_alloc(b64sig_size);
   1117 	char *b64data = safe_alloc(b64data_size);
   1118 	unsigned int extjwt_hashsize;
   1119 	char *extjwt_hash_val = NULL;
   1120 	char *retval = NULL;
   1121 	b64_encode(header, strlen(header), b64header, b64header_size);
   1122 	b64_encode(payload, strlen(payload), b64payload, b64payload_size);
   1123 	b64url(b64header);
   1124 	b64url(b64payload);
   1125 	snprintf(b64data, b64data_size, "%s.%s", b64header, b64payload); // generate first part of the token
   1126 	if (config->method != EXTJWT_METHOD_NONE)
   1127 	{
   1128 		extjwt_hash_val = extjwt_hash(config->method, config->secret, strlen(config->secret), b64data, strlen(b64data), &extjwt_hashsize); // calculate the signature extjwt_hash
   1129 		if (extjwt_hash_val)
   1130 		{
   1131 			b64_encode(extjwt_hash_val, extjwt_hashsize, b64sig, b64sig_size);
   1132 			b64url(b64sig);
   1133 			strlcat(b64data, ".", b64data_size); // append signature extjwt_hash to token
   1134 			strlcat(b64data, b64sig, b64data_size);
   1135 			retval = b64data;
   1136 		}
   1137 	} else
   1138 	{
   1139 		retval = b64data;
   1140 	}
   1141 	safe_free(header);
   1142 	safe_free(b64header);
   1143 	safe_free(b64payload);
   1144 	safe_free(b64sig);
   1145 	safe_free(extjwt_hash_val);
   1146 
   1147 	if (retval != b64data)
   1148 		safe_free(b64data);
   1149 
   1150 	return retval;
   1151 }