diff options
Diffstat (limited to 'src/backend/libpq/hba.c')
-rw-r--r-- | src/backend/libpq/hba.c | 306 |
1 files changed, 209 insertions, 97 deletions
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 77fe11f05a7..43b5e04b46f 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.190 2009/09/01 02:54:51 alvherre Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.191 2009/10/01 01:58:57 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -42,6 +42,14 @@ #define MAX_TOKEN 256 +/* callback data for check_network_callback */ +typedef struct check_network_data +{ + IPCompareMethod method; /* test method */ + SockAddr *raddr; /* client's actual address */ + bool result; /* set to true if match */ +} check_network_data; + /* pre-parsed content of HBA config file: list of HbaLine structs */ static List *parsed_hba_lines = NIL; @@ -512,6 +520,99 @@ check_db(const char *dbname, const char *role, Oid roleid, char *param_str) return false; } +/* + * Check to see if a connecting IP matches the given address and netmask. + */ +static bool +check_ip(SockAddr *raddr, struct sockaddr *addr, struct sockaddr *mask) +{ + if (raddr->addr.ss_family == addr->sa_family) + { + /* Same address family */ + if (!pg_range_sockaddr(&raddr->addr, + (struct sockaddr_storage*)addr, + (struct sockaddr_storage*)mask)) + return false; + } +#ifdef HAVE_IPV6 + else if (addr->sa_family == AF_INET && + raddr->addr.ss_family == AF_INET6) + { + /* + * If we're connected on IPv6 but the file specifies an IPv4 address + * to match against, promote the latter to an IPv6 address + * before trying to match the client's address. + */ + struct sockaddr_storage addrcopy, + maskcopy; + + memcpy(&addrcopy, &addr, sizeof(addrcopy)); + memcpy(&maskcopy, &mask, sizeof(maskcopy)); + pg_promote_v4_to_v6_addr(&addrcopy); + pg_promote_v4_to_v6_mask(&maskcopy); + + if (!pg_range_sockaddr(&raddr->addr, &addrcopy, &maskcopy)) + return false; + } +#endif /* HAVE_IPV6 */ + else + { + /* Wrong address family, no IPV6 */ + return false; + } + + return true; +} + +/* + * pg_foreach_ifaddr callback: does client addr match this machine interface? + */ +static void +check_network_callback(struct sockaddr *addr, struct sockaddr *netmask, + void *cb_data) +{ + check_network_data *cn = (check_network_data *) cb_data; + struct sockaddr_storage mask; + + /* Already found a match? */ + if (cn->result) + return; + + if (cn->method == ipCmpSameHost) + { + /* Make an all-ones netmask of appropriate length for family */ + pg_sockaddr_cidr_mask(&mask, NULL, addr->sa_family); + cn->result = check_ip(cn->raddr, addr, (struct sockaddr*) &mask); + } + else + { + /* Use the netmask of the interface itself */ + cn->result = check_ip(cn->raddr, addr, netmask); + } +} + +/* + * Use pg_foreach_ifaddr to check a samehost or samenet match + */ +static bool +check_same_host_or_net(SockAddr *raddr, IPCompareMethod method) +{ + check_network_data cn; + + cn.method = method; + cn.raddr = raddr; + cn.result = false; + + errno = 0; + if (pg_foreach_ifaddr(check_network_callback, &cn) < 0) + { + elog(LOG, "error enumerating network interfaces: %m"); + return false; + } + + return cn.result; +} + /* * Macros used to check and report on invalid configuration options. @@ -658,99 +759,121 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) line_num, HbaFileName))); return false; } - token = pstrdup(lfirst(line_item)); - - /* Check if it has a CIDR suffix and if so isolate it */ - cidr_slash = strchr(token, '/'); - if (cidr_slash) - *cidr_slash = '\0'; - - /* Get the IP address either way */ - hints.ai_flags = AI_NUMERICHOST; - hints.ai_family = PF_UNSPEC; - hints.ai_socktype = 0; - hints.ai_protocol = 0; - hints.ai_addrlen = 0; - hints.ai_canonname = NULL; - hints.ai_addr = NULL; - hints.ai_next = NULL; - - ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result); - if (ret || !gai_result) + token = lfirst(line_item); + + /* Is it equal to 'samehost' or 'samenet'? */ + if (strcmp(token, "samehost") == 0) { - ereport(LOG, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid IP address \"%s\": %s", - token, gai_strerror(ret)), - errcontext("line %d of configuration file \"%s\"", - line_num, HbaFileName))); - if (cidr_slash) - *cidr_slash = '/'; - if (gai_result) - pg_freeaddrinfo_all(hints.ai_family, gai_result); - return false; + /* Any IP on this host is allowed to connect */ + parsedline->ip_cmp_method = ipCmpSameHost; } - - if (cidr_slash) - *cidr_slash = '/'; - - memcpy(&parsedline->addr, gai_result->ai_addr, gai_result->ai_addrlen); - pg_freeaddrinfo_all(hints.ai_family, gai_result); - - /* Get the netmask */ - if (cidr_slash) + else if (strcmp(token, "samenet") == 0) { - if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1, - parsedline->addr.ss_family) < 0) - { - ereport(LOG, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid CIDR mask in address \"%s\"", - token), - errcontext("line %d of configuration file \"%s\"", - line_num, HbaFileName))); - return false; - } + /* Any IP on the host's subnets is allowed to connect */ + parsedline->ip_cmp_method = ipCmpSameNet; } else { - /* Read the mask field. */ - line_item = lnext(line_item); - if (!line_item) - { - ereport(LOG, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("end-of-line before netmask specification"), - errcontext("line %d of configuration file \"%s\"", - line_num, HbaFileName))); - return false; - } - token = lfirst(line_item); + /* IP and netmask are specified */ + parsedline->ip_cmp_method = ipCmpMask; + + /* need a modifiable copy of token */ + token = pstrdup(token); + + /* Check if it has a CIDR suffix and if so isolate it */ + cidr_slash = strchr(token, '/'); + if (cidr_slash) + *cidr_slash = '\0'; + + /* Get the IP address either way */ + hints.ai_flags = AI_NUMERICHOST; + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = 0; + hints.ai_protocol = 0; + hints.ai_addrlen = 0; + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result); if (ret || !gai_result) { ereport(LOG, (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("invalid IP mask \"%s\": %s", + errmsg("invalid IP address \"%s\": %s", token, gai_strerror(ret)), errcontext("line %d of configuration file \"%s\"", line_num, HbaFileName))); + if (cidr_slash) + *cidr_slash = '/'; if (gai_result) pg_freeaddrinfo_all(hints.ai_family, gai_result); return false; } - memcpy(&parsedline->mask, gai_result->ai_addr, gai_result->ai_addrlen); + if (cidr_slash) + *cidr_slash = '/'; + + memcpy(&parsedline->addr, gai_result->ai_addr, + gai_result->ai_addrlen); pg_freeaddrinfo_all(hints.ai_family, gai_result); - if (parsedline->addr.ss_family != parsedline->mask.ss_family) + /* Get the netmask */ + if (cidr_slash) { - ereport(LOG, - (errcode(ERRCODE_CONFIG_FILE_ERROR), - errmsg("IP address and mask do not match in file \"%s\" line %d", - HbaFileName, line_num))); - return false; + if (pg_sockaddr_cidr_mask(&parsedline->mask, cidr_slash + 1, + parsedline->addr.ss_family) < 0) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid CIDR mask in address \"%s\"", + token), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } + } + else + { + /* Read the mask field. */ + line_item = lnext(line_item); + if (!line_item) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("end-of-line before netmask specification"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } + token = lfirst(line_item); + + ret = pg_getaddrinfo_all(token, NULL, &hints, &gai_result); + if (ret || !gai_result) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("invalid IP mask \"%s\": %s", + token, gai_strerror(ret)), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + if (gai_result) + pg_freeaddrinfo_all(hints.ai_family, gai_result); + return false; + } + + memcpy(&parsedline->mask, gai_result->ai_addr, + gai_result->ai_addrlen); + pg_freeaddrinfo_all(hints.ai_family, gai_result); + + if (parsedline->addr.ss_family != parsedline->mask.ss_family) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("IP address and mask do not match in file \"%s\" line %d", + HbaFileName, line_num))); + return false; + } } } } /* != ctLocal */ @@ -1097,35 +1220,24 @@ check_hba(hbaPort *port) #endif /* Check IP address */ - if (port->raddr.addr.ss_family == hba->addr.ss_family) + switch (hba->ip_cmp_method) { - if (!pg_range_sockaddr(&port->raddr.addr, &hba->addr, &hba->mask)) + case ipCmpMask: + if (!check_ip(&port->raddr, + (struct sockaddr *) &hba->addr, + (struct sockaddr *) &hba->mask)) + continue; + break; + case ipCmpSameHost: + case ipCmpSameNet: + if (!check_same_host_or_net(&port->raddr, + hba->ip_cmp_method)) + continue; + break; + default: + /* shouldn't get here, but deem it no-match if so */ continue; } -#ifdef HAVE_IPV6 - else if (hba->addr.ss_family == AF_INET && - port->raddr.addr.ss_family == AF_INET6) - { - /* - * Wrong address family. We allow only one case: if the file - * has IPv4 and the port is IPv6, promote the file address to - * IPv6 and try to match that way. - */ - struct sockaddr_storage addrcopy, - maskcopy; - - memcpy(&addrcopy, &hba->addr, sizeof(addrcopy)); - memcpy(&maskcopy, &hba->mask, sizeof(maskcopy)); - pg_promote_v4_to_v6_addr(&addrcopy); - pg_promote_v4_to_v6_mask(&maskcopy); - - if (!pg_range_sockaddr(&port->raddr.addr, &addrcopy, &maskcopy)) - continue; - } -#endif /* HAVE_IPV6 */ - else - /* Wrong address family, no IPV6 */ - continue; } /* != ctLocal */ /* Check database and role */ |