pam_geoip-1.1/0000755000175000001440000000000012107656526012004 5ustar hahuserspam_geoip-1.1/pam_geoip.h0000644000175000001440000000547112067031175014114 0ustar hahusers/* * pam_geoip.h - account module to check GeoIP information * * $Id: pam_geoip.h 40 2012-12-27 11:35:25Z vetinari $ * */ #ifndef _PAM_GEOIP_H #define _PAM_GEOIP_H #define _GNU_SOURCE #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #define LINE_LENGTH 4095 #define MASK_NO_MASK -1 #define MASK_TOO_LONG -2 #define MASK_NOT_NUM -3 #define MASK_TOO_BIG -4 #include /* pam_modutil_user_in_group_nam_nam() */ #include /* pam_syslog() */ #include #define PAM_SM_ACCOUNT #include #ifndef PATH_MAX # define PATH_MAX 1024 #endif /* PATH_MAX */ #define SYSTEM_FILE "/etc/security/geoip.conf" #define SERVICE_FILE "/etc/security/geoip.%s.conf" #define GEOIPDB_FILE "/usr/local/share/GeoIP/GeoIPCity.dat" #ifdef HAVE_GEOIP_010408 #define GEOIP6DB_FILE "/usr/local/share/GeoIP/GeoIPCityv6.dat" #endif /* GeoIP locations in geoip.conf */ struct locations { char *country; char *city; float latitude; float longitude; float radius; /* in km */ struct locations *next; }; /* options set on "command line" in /etc/pam.d/ */ struct options { char *system_file; char *geoip_db; #ifdef HAVE_GEOIP_010408 char *geoip6_db; #endif char *service_file; /* not on cmd line */ int by_service; /* if service_file can be opened this is true */ int charset; int action; #ifdef HAVE_GEOIP_010408 int use_v6; int v6_first; #endif int is_city_db; int debug; }; extern struct locations * parse_locations(pam_handle_t *pamh, struct options *opts, char *location_string); extern void free_locations(struct locations *list); extern void free_opts(struct options *opts); extern int parse_action(pam_handle_t *pamh, char *name); extern int parse_line_srv(pam_handle_t *pamh, char *line, char *domain, char *location); extern int parse_line_sys(pam_handle_t *pamh, char *line, char *domain, char *service, char *location); extern int check_service(pam_handle_t *pamh, char *services, char *srv); extern double calc_distance(float latitude, float longitude, float geo_lat, float geo_long); extern int check_location(pam_handle_t *pamh, struct options *opts, char *location_string, struct locations *geo); extern void _parse_args(pam_handle_t *pamh, int argc, const char **argv, struct options *opts); #endif /* _PAM_GEOIP_H */ /* * vim: ts=4 sw=4 expandtab */ pam_geoip-1.1/make_config_h.sh0000644000175000001440000000074112067031175015103 0ustar hahusers#!/bin/sh TEMP_SRC=$( tempfile -s .c ) TEMP_OUT=$( tempfile ) CONFIG_H="config.h" cat > $TEMP_SRC <<_END #include int main () { int have = GEOIP_CITY_EDITION_REV1_V6; } _END gcc -lGeoIP $TEMP_SRC -shared -o $TEMP_OUT 2> /dev/null if [ $? -eq 0 ]; then rm -f $CONFIG_H echo "#define HAVE_GEOIP_010408" > $CONFIG_H else rm -f $CONFIG_H cat > $CONFIG_H <<_END #ifdef HAVE_GEOIP_010408 #undef HAVE_GEOIP_010408 #endif _END fi rm -f $TEMP_SRC $TEMP_OUT pam_geoip-1.1/check.c0000644000175000001440000000747712067107335013236 0ustar hahusers/* * check.c - account module to check GeoIP information * * $Id: check.c 43 2012-12-27 18:09:33Z vetinari $ * */ #include "pam_geoip.h" int check_service(pam_handle_t *pamh, char *services, char *srv) { char *str, *next; if (strcmp(services, "*") == 0) return 1; str = services; while (*services) { while (*str && *str != ',') str++; if (*str) next = str + 1; else next = ""; *str = '\0'; if ( (strncmp(services, srv, strlen(services)) == 0) || (strcmp(services, "*") == 0)) { return 1; } services = next; } return 0; } double /* see also: http://en.wikipedia.org/wiki/Great-circle_distance */ calc_distance(float latitude, float longitude, float geo_lat, float geo_long) { double distance; float earth = 6367.46; /* km avg radius */ /* convert grad to rad: */ double la1 = latitude * M_PI / 180.0, la2 = geo_lat * M_PI / 180.0, lo1 = longitude * M_PI / 180.0, lo2 = geo_long * M_PI / 180.0; distance = atan2( sqrt( pow( cos(la2) * sin(lo1-lo2), 2.0 ) + pow( cos(la1) * sin(la2) - sin(la1) * cos(la2) * cos(lo1-lo2), 2.0 ) ), sin(la1) * sin(la2) + cos(la1) * cos(la2) * cos(lo1-lo2) ); if (distance < 0.0) distance += 2 * M_PI; distance *= earth; return distance; } int check_location(pam_handle_t *pamh, struct options *opts, char *location_string, struct locations *geo) { struct locations *list; struct locations *loc; double distance; list = loc = parse_locations(pamh, opts, location_string); while (list) { if (list->country == NULL) { if (strcmp(geo->country, "UNKNOWN") == 0) { list = list->next; continue; } if (opts->is_city_db) { distance = calc_distance(list->latitude, list->longitude, geo->latitude, geo->longitude); if (distance <= list->radius) { pam_syslog(pamh, LOG_INFO, "distance(%.3f) < radius(%3.f)", distance, list->radius); sprintf(location_string, "%.3f {%f,%f}", distance, geo->latitude, geo->longitude); free_locations(loc); return 1; } } else pam_syslog(pamh, LOG_INFO, "not a city db edition, ignoring distance entry"); } else { if (opts->debug) pam_syslog(pamh, LOG_INFO, "location: (%s,%s) geoip: (%s,%s)", list->country, list->city, geo->country, geo->city); if ( (list->country[0] == '*' || strcmp(list->country, geo->country) == 0) && (list->city[0] == '*' || strcmp(list->city, geo->city ) == 0) ) { if (opts->debug) pam_syslog(pamh, LOG_INFO, "location [%s,%s] matched: %s,%s", geo->country, geo->city, list->country, list->city); sprintf(location_string, "%s,%s", geo->country, geo->city); free_locations(loc); return 1; } } list = list->next; } if (loc) /* may be NULL */ free_locations(loc); return 0; } /* * vim: ts=4 sw=4 expandtab */ pam_geoip-1.1/Makefile0000644000175000001440000000170512067031175013437 0ustar hahusers POD2MAN=pod2man -u -c ' ' -r ' ' MANPAGES=geoip.conf.5 pam_geoip.8 MAN_5_POD=geoip.conf.5.pod MAN_8_POD=pam_geoip.8.pod C_FILES=pam_geoip.c parse.c args.c check.c HEADER=pam_geoip.h OBJECTS=pam_geoip.o parse.o args.o check.o MODULE=pam_geoip.so LDFLAGS=-lpam -lGeoIP -lm -shared CCFLAGS=-Wall PAM_LIB_DIR=$(DESTDIR)/lib/security INSTALL=/usr/bin/install all: config.h pam_geoip.so doc doc: $(MANPAGES_POD) $(MANPAGES) %.5: $(MAN_5_POD) $(POD2MAN) -u -s 5 -n $(shell basename $@ .5) $@.pod > $@ %.8: $(MAN_8_POD) $(POD2MAN) -u -s 8 -n $(shell basename $@ .8) $@.pod > $@ $(OBJECTS): $(C_FILES) $(CC) $(CCFLAGS) -fPIC -c $*.c pam_geoip.so: $(OBJECTS) $(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJECTS) config.h: sh make_config_h.sh clean: rm -f $(MANPAGES) rm -f config.h rm -f $(OBJECTS) $(MODULE) core *~ install: $(MODULE) $(INSTALL) -m 0755 -d $(PAM_LIB_DIR) $(INSTALL) -m 0644 $(MODULE) $(PAM_LIB_DIR) ### dev targets: update: svn update # END pam_geoip-1.1/geoip.conf.5.pod0000644000175000001440000000632612107656450014705 0ustar hahusers =encoding utf8 =cut $Id: geoip.conf.5.pod 45 2013-02-16 10:19:20Z vetinari $ =head1 NAME geoip.conf - config file for the PAM module pam_geoip =head1 DESCRIPTION The configuration file (by default F) contains lines of four items: domain, service, action and location. For a description of these, see below. When the service specific configuration file (F) is used, the I column must not be present. If this file is present, the default file is not used, even if present on the command line as C. If you need to match on city names containing non L characters (like C or C), you can set the character set to use in the module's arguments: C or C (the default). Any (sub-)item except for I or the distance matching can use a single asterisk (C<*>) to match any value. =over 4 =item domain A user name, group name (prefixed by C<@>) or C<*> for any user / group =item service A list of services (or C<*>) separated by C<,> (NO spaces allowed) =item action C, C or C. This is what will be returned to PAM if the location matches: =over 2 =item allow I =item deny I =item ignore I =back =item location GeoIP location, separated by C<;>. This can be: =over 2 =item * a country code (uppercased, two characters), C<*> or C =item * a country code like above and C<,> and a city name (or C<*>). When using a GeoIP country database, this part must be C<*>, i.e. the full entry looks like C. =item * a distance from a given point, e.g. 50.0 { 51.513888, 7.465277 } This is not available when using a GeoIP country database. =back =back The location part can use spaces, but note: city names must be given as in the GeoIP database, i.e. S>, NOT S> or C. The distance is measured in kilometers. In the above example we match a circle of 100 km diameter around Dortmund, Germany (51° 30′ 50″ north, 7° 27′ 50″ east (51.513888888889, 7.465277777777876)). Coordinates west and south are given as negative values. Values must be given in decimal. =head1 EXAMPLE # # /etc/security/geoip.conf - config for pam_geoip.so # # @wheel sshd allow DE,* ; SE , Nybro @wheel sshd allow SE, Emmaboda; SE,Växjö someuser sshd allow 50.0 { 51.513888, 7.465277 } someuser sshd allow DE,Köln otheruser sshd allow SE,Umeå; DK, København * * ignore UNKNOWN * * deny * ## END or the same as F: # @wheel allow DE,* ; SE , Nybro @wheel allow SE, Emmaboda; SE,Växjö someuser allow 50.0 { 51.513888, 7.465277 } someuser allow DE,Köln otheruser allow SE,Umeå; DK, København * ignore UNKNOWN * deny * =head1 SEE ALSO L, L, L, L =head1 AUTHOR Hanno Hecker Cvetinari@ankh-morp.orgE> =cut pam_geoip-1.1/geoip.conf0000644000175000001440000000030211324567322013744 0ustar hahusers# # /etc/security/geoip.conf - config for pam_geoip.so # # # @wheel sshd allow DE,* ; SE,* meike sshd allow DE,* * * ignore UNKNOWN * * deny * pam_geoip-1.1/pam_geoip.8.pod0000644000175000001440000000670612067232375014624 0ustar hahusers =encoding utf8 =cut $Id: pam_geoip.8.pod 44 2012-12-28 05:58:21Z vetinari $ =head1 NAME pam_geoip - GeoIP account management module for (Linux-)PAM =head1 SYNOPSIS account required pam_geoip.so [system_file=file] [geoip_db=file] [charset=name] [action=name] [debug] [geoip6_db=file] [use_v6=1] [v6_first=1] =head1 DESCRIPTION The B module provides a check if the remote logged in user is logged in from a given location. This is similar to L, but uses a GeoIP City or GeoIP Country database instead of host name / IP matching. The matching is done on given country and city names or on distance from a given location. With a country database only matches of the countries are possible. This PAM module provides the I hook only. If an IP is not found in the GeoIP database, the location to match against is set to C, no distance matching is possible for these, of course. B: I just receives a hostname. When trying to find an IP for this name the modules tries IPv4 first, then IPv6. This can be changed with the C switch. IPv6 support is only available with geoip v1.4.8 or greater, and is has to be enabled by using the C switch. If a file named F (with SERVICE being the name of the PAM service) can be opened, this is used instead of the default F. The first matching entry in the L file wins, i.e. the action given in this line will be returned to PAM: =over 4 =item allow PAM_SUCCESS =item deny PAM_PERM_DENIED =item ignore PAM_IGNORE =back =head1 OPTIONS These options may be given in the PAM config file as parameters: =over 4 =item system_file=/path/to/geoip.conf The configuration file for B. Default is F. For the format of this file, see L. B: when a file F file is present, this switch is ignored (with C being the name of the PAM service, e.g. C). =item geoip_db=/path/to/GeoIPCity.dat The GeoIP database to use. Default: F. This must be a C or a C file, see L, L and L for more information. =item geoip6_db=/path/to/GeoIPCityv6.dat The GeoIP database to use. Default: F. This must be a C or a C file, see above for more information. =item use_v6=1 Use IPv6 DB. =item v6_first=1 Try resolving as IPv6 before trying as IPv4 hostname. =item charset=CHARSET The charset of the config file, defaults to C. Other possible value is C (case insensitive). =item action=ACTION Sets the default action if no location matches. Default is C. Other possible values are C or C. For the meanigns of these, see above. =item debug Adds some debugging output to syslog. =back =head1 FILES =over 4 =item /etc/security/geoip.conf The default configuration file for this module =item /etc/security/geoip.SERVICE.conf The default configuration file for PAM service SERVICE =item /etc/pam.d/* The L configuration files =back =head1 SEE ALSO L, L, L, L =head1 AUTHOR Hanno Hecker Cvetinari@ankh-morp.orgE> =cut pam_geoip-1.1/TODO0000644000175000001440000000024412067107335012466 0ustar hahusersTODO: ===== * ipv6 (more) testing, (ipv6 city database in beta @ maxmind) * test w/ country DB NOT TODO: ========= * match hostnames / IPs, pam_access can do this pam_geoip-1.1/args.c0000644000175000001440000000464312067031175013103 0ustar hahusers/* * args.c - account module to check GeoIP information * * $Id: args.c 40 2012-12-27 11:35:25Z vetinari $ * */ #include "pam_geoip.h" void _parse_args(pam_handle_t *pamh, int argc, const char **argv, struct options *opts) { int i = 0; for (i=0; icharset = GEOIP_CHARSET_UTF8; } else if (strncasecmp(argv[i]+8, "UTF8", 4) == 0) { opts->charset = GEOIP_CHARSET_UTF8; } else if (strncasecmp(argv[i]+8, "iso-8859-1", 10) == 0) { opts->charset = GEOIP_CHARSET_ISO_8859_1; } } } else if (strncmp(argv[i], "debug", 5) == 0) { opts->debug = 1; } else if (strncmp(argv[i], "action=", 7) == 0) { if (argv[i]+7 != '\0') { if (strncmp(argv[i]+7, "allow", 5) == 0) { opts->action = PAM_SUCCESS; } else if (strncmp(argv[i]+7, "deny", 4) == 0) { opts->action = PAM_PERM_DENIED; } else if (strncmp(argv[i]+7, "ignore", 6) == 0) { opts->action = PAM_IGNORE; } } } else { pam_syslog(pamh, LOG_WARNING, "unknown parameter %s", argv[i]); } } } /* * vim: ts=4 sw=4 expandtab */ pam_geoip-1.1/README0000644000175000001440000000247412067232375012670 0ustar hahusers$Id: README 44 2012-12-28 05:58:21Z vetinari $ pam_geoip - GeoIP account management module for (Linux-)PAM This PAM module provides GeoIP checking for logins. The user can be allowed or denied based on the location of the originating IP address. This is similar to pam_access(8), but uses a GeoIP City or GeoIP Country database instead of host name / IP matching. The matching in pam_geoip is done on given country and city names or on distance from a given location. With a GeoIP Country database only matches of the originating country are possible. This PAM module provides the "account" hook only. To use this module, add a line like (optional parts in square brackets) account required pam_geoip.so [system_file=file] [geoip_db=file] \ [charset=name] [action=name] [debug] [geoip6_db=file] [use_v6=1] \ [v6_first=1] to the relevant files in /etc/pam.d/ and configure your /etc/security/geoip.conf and/or /etc/security/geoip.SERVICE.conf. Requirements: Debian (lenny, squeeze, sid [Linux, kFreeBSD]): building: libgeoip-dev, libpam0g-dev, perl (pod2man) running: libgeoip1, libpam0g, libpam-{modules,runtime}, a GeoIP City database, see http://www.maxmind.com/en/city or a GeoIP Country database, see http://www.maxmind.com/en/country IPv6: libgeoip1 >= 1.4.8, a Geo(Lite) City / Country IPv6 database pam_geoip-1.1/pam_geoip.c0000644000175000001440000003617412067232375014120 0ustar hahusers/* * pam_geoip.c - account module to check GeoIP information * * $Id: pam_geoip.c 44 2012-12-28 05:58:21Z vetinari $ * */ /* * Copyright (c) 2010-2012 Hanno Hecker * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, and the entire permission notice in its entirety, * including the disclaimer of warranties. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote * products derived from this software without specific prior * written permission. * * ALTERNATIVELY, this product may be distributed under the terms of * the GNU General Public License, in which case the provisions of the * GPL are required INSTEAD OF the above restrictions. (This clause is * necessary due to a potential bad interaction between the GPL and * the restrictions contained in a BSD-style copyright.) * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "pam_geoip.h" void free_locations(struct locations *list) { struct locations *entry; while (list) { entry = list; list = list->next; if (entry->city != NULL) free(entry->city); if (entry->country != NULL) free(entry->country); free(entry); } } void free_opts(struct options *opts) { if (opts->system_file) free(opts->system_file); if (opts->service_file) free(opts->service_file); if (opts->geoip_db) free(opts->geoip_db); #ifdef HAVE_GEOIP_010408 if (opts->geoip6_db) free(opts->geoip6_db); #endif free(opts); } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) { struct options *opts; FILE *fh; char *username; /* username requesting access */ char *rhost; /* remote host */ char *srv; /* PAM service we're running as */ char buf[LINE_LENGTH]; int retval, action; int is_v6 = 0; struct locations *geo; unsigned char gi_type; GeoIP *gi = NULL; #ifdef HAVE_GEOIP_010408 GeoIP *gi6 = NULL; int is_city6_db = 0; #endif GeoIPRecord *rec = NULL; opts = malloc(sizeof(struct options)); if (opts == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'opts': %m"); return PAM_SERVICE_ERR; } opts->charset = GEOIP_CHARSET_UTF8; opts->debug = 0; opts->action = PAM_PERM_DENIED; opts->system_file = NULL; opts->service_file = NULL; opts->by_service = 0; opts->geoip_db = NULL; #ifdef HAVE_GEOIP_010408 opts->use_v6 = 0; opts->v6_first = 0; opts->geoip6_db = NULL; #endif opts->is_city_db = 0; geo = malloc(sizeof(struct locations)); if (geo == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'geo': %m"); free_opts(opts); return PAM_SERVICE_ERR; } geo->country = NULL; geo->city = NULL; geo->next = NULL; _parse_args(pamh, argc, argv, opts); if (opts->system_file == NULL) opts->system_file = strdup(SYSTEM_FILE); if (opts->system_file == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'opts->system_file': %m"); free_opts(opts); return PAM_SERVICE_ERR; } if (opts->geoip_db == NULL) opts->geoip_db = strdup(GEOIPDB_FILE); if (opts->geoip_db == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'opts->geoip_db': %m"); free_opts(opts); return PAM_SERVICE_ERR; } #ifdef HAVE_GEOIP_010408 if (opts->geoip6_db == NULL) opts->geoip6_db = strdup(GEOIP6DB_FILE); if (opts->geoip6_db == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'opts->geoip6_db': %m"); free_opts(opts); return PAM_SERVICE_ERR; } #endif retval = pam_get_item(pamh, PAM_USER, (void*) &username); if (username == NULL || retval != PAM_SUCCESS) { pam_syslog(pamh, LOG_CRIT, "error recovering username"); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } retval = pam_get_item(pamh, PAM_RHOST, (void*) &rhost); if (retval != PAM_SUCCESS) { pam_syslog(pamh, LOG_CRIT, "error fetching rhost"); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } if (rhost == NULL) { pam_syslog(pamh, LOG_INFO, "rhost is NULL, allowing"); free_opts(opts); free_locations(geo); return PAM_SUCCESS; } retval = pam_get_item(pamh, PAM_SERVICE, (void*) &srv); if (srv == NULL || retval != PAM_SUCCESS ) { pam_syslog(pamh, LOG_CRIT, "error requesting service name"); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } opts->service_file = malloc(PATH_MAX); if (opts->service_file == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'service_file': %m"); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } if (snprintf(opts->service_file, PATH_MAX-1, SERVICE_FILE, srv) < 0) { pam_syslog(pamh, LOG_CRIT, "snprintf error 'service_file'"); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } gi = GeoIP_open(opts->geoip_db, GEOIP_INDEX_CACHE); if (gi == NULL) { pam_syslog(pamh, LOG_CRIT, "failed to open geoip db (%s): %m", opts->geoip_db); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } gi_type = GeoIP_database_edition(gi); if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP edition: %d", gi_type); switch (gi_type) { case GEOIP_COUNTRY_EDITION: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v4 edition: country"); opts->is_city_db = 0; break; case GEOIP_CITY_EDITION_REV0: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v4 edition: city rev0"); opts->is_city_db = 1; break; case GEOIP_CITY_EDITION_REV1: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v4 edition: city rev1"); opts->is_city_db = 1; break; default: pam_syslog(pamh, LOG_CRIT, "invalid GeoIP DB type `%d' found", gi_type); GeoIP_delete(gi); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } GeoIP_set_charset(gi, opts->charset); if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP DB is City: %s", opts->is_city_db ? "yes" : "no"); #ifdef HAVE_GEOIP_010408 if (opts->use_v6 != 0) { gi6 = GeoIP_open(opts->geoip6_db, GEOIP_INDEX_CACHE); if (gi6 == NULL) { pam_syslog(pamh, LOG_CRIT, "failed to open geoip6 db (%s): %m", opts->geoip6_db); GeoIP_delete(gi); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } gi_type = GeoIP_database_edition(gi6); switch (gi_type) { case GEOIP_COUNTRY_EDITION_V6: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v6 edition: country"); is_city6_db = 0; break; case GEOIP_CITY_EDITION_REV0_V6: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v6 edition: city rev0"); is_city6_db = 1; break; case GEOIP_CITY_EDITION_REV1_V6: if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP v6 edition: city rev1"); is_city6_db = 1; break; default: pam_syslog(pamh, LOG_CRIT, "invalid GeoIP DB type `%d' found", gi_type); GeoIP_delete(gi); GeoIP_delete(gi6); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP DB is City v6: %s", is_city6_db ? "yes" : "no"); GeoIP_set_charset(gi6, opts->charset); if (opts->is_city_db != is_city6_db) { pam_syslog(pamh, LOG_CRIT, "IPv4 DB type is not the same as IPv6 (not both Country edition or both City edition)"); GeoIP_delete(gi); GeoIP_delete(gi6); free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } if (opts->v6_first != 0) { rec = GeoIP_record_by_name_v6(gi6, rhost); if (rec == NULL) { if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "no IPv6 record for %s, trying IPv4", rhost); rec = GeoIP_record_by_name(gi, rhost); } else is_v6 = 1; } else { rec = GeoIP_record_by_name(gi, rhost); if (rec == NULL) { if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "no IPv4 record for %s, trying IPv6", rhost); rec = GeoIP_record_by_name_v6(gi6, rhost); if (rec != NULL) is_v6 = 1; } } } else #endif /* HAVE_GEOIP_010408 */ rec = GeoIP_record_by_name(gi, rhost); if (rec == NULL) { pam_syslog(pamh, LOG_INFO, "no record for %s, setting GeoIP to 'UNKNOWN,*'", rhost); geo->city = strdup("*"); geo->country = strdup("UNKNOWN"); if (geo->city == NULL || geo->country == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'geo->{city,country}': %m"); GeoIP_delete(gi); #ifdef HAVE_GEOIP_010408 GeoIP_delete(gi6); #endif free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } } else { if (rec->city == NULL || opts->is_city_db == 0) geo->city = strdup("*"); else geo->city = strdup(rec->city); if (rec->country_code == NULL) geo->country = strdup("UNKNOWN"); else geo->country = strdup(rec->country_code); if (geo->city == NULL || geo->country == NULL) { pam_syslog(pamh, LOG_CRIT, "malloc error 'geo->{city,country}': %m"); GeoIP_delete(gi); #ifdef HAVE_GEOIP_010408 GeoIP_delete(gi6); #endif free_opts(opts); free_locations(geo); return PAM_SERVICE_ERR; } if (opts->is_city_db) { geo->latitude = rec->latitude; geo->longitude = rec->longitude; } } if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "GeoIP record for %s: %s,%s", rhost, geo->country, geo->city); if (opts->debug && strcmp(geo->country, "UNKNOWN") != 0 && opts->is_city_db) pam_syslog(pamh, LOG_DEBUG, "GeoIP coordinates for %s: %f,%f", rhost, geo->latitude, geo->longitude); if ((fh = fopen(opts->service_file, "r")) != NULL) { opts->by_service = 1; if (opts->debug) pam_syslog(pamh, LOG_DEBUG, "using services file %s", opts->service_file); } else { if ((fh = fopen(opts->system_file, "r")) == NULL) { pam_syslog(pamh, LOG_CRIT, "error opening %s: %m", opts->system_file); #ifdef HAVE_GEOIP_010408 if (gi6) GeoIP_delete(gi6); #endif if (gi) GeoIP_delete(gi); if (rec) GeoIPRecord_delete(rec); free_opts(opts); return PAM_SERVICE_ERR; } } action = opts->action; char location[LINE_LENGTH]; while (fgets(buf, LINE_LENGTH, fh) != NULL) { char *line, *ptr; char domain[LINE_LENGTH], service[LINE_LENGTH]; action = opts->action; line = buf; /* skip the leading white space */ while (*line && isspace(*line)) line++; /* Rip off the comments */ ptr = strchr(line,'#'); if (ptr) *ptr = '\0'; /* Rip off the newline char */ ptr = strchr(line,'\n'); if (ptr) *ptr = '\0'; /* Anything left ? */ if (!strlen(line)) continue; if (opts->by_service) action = parse_line_srv(pamh, line, domain, location); else action = parse_line_sys(pamh, line, domain, service, location); if (action < 0) { /* parsing failed */ action = opts->action; continue; } if (!opts->by_service) { if (!check_service(pamh, service, srv)) continue; } if ((strcmp(domain, "*") == 0) || (strcmp(username, domain) == 0)) { if (check_location(pamh, opts, location, geo)) break; } else if (domain[0] == '@') { if (pam_modutil_user_in_group_nam_nam(pamh, username, domain+1)) { if (check_location(pamh, opts, location, geo)) break; } } } fclose(fh); if (gi) GeoIP_delete(gi); #ifdef HAVE_GEOIP_010408 if (gi6) GeoIP_delete(gi6); #endif if (rec) GeoIPRecord_delete(rec); free_locations(geo); switch (action) { case PAM_SUCCESS: pam_syslog(pamh, LOG_DEBUG, "location %s allowed for user %s from %s (IPv%d)", location, username, rhost, is_v6 ? 6 : 4); break; case PAM_PERM_DENIED: pam_syslog(pamh, LOG_DEBUG, "location %s denied for user %s from %s (IPv%d)", location, username, rhost, is_v6 ? 6 : 4); break; case PAM_IGNORE: pam_syslog(pamh, LOG_DEBUG, "location %s ignored for user %s from %s (IPv%d)", location, username, rhost, is_v6 ? 6 : 4); break; default: /* should not happen */ pam_syslog(pamh, LOG_DEBUG, "location status: %d, IPv%d", action, is_v6 ? 6 : 4); break; }; free_opts(opts); return action; } /* * vim: ts=4 sw=4 expandtab */ pam_geoip-1.1/geoip.default.conf0000644000175000001440000000021111324567322015366 0ustar hahusers# # /etc/security/geoip.conf - config for pam_geoip.so # # # * * ignore UNKNOWN * * allow * pam_geoip-1.1/parse.c0000644000175000001440000001160612067031175013256 0ustar hahusers/* * parse.c - account module to check GeoIP information * * $Id: parse.c 40 2012-12-27 11:35:25Z vetinari $ * */ #include "pam_geoip.h" struct locations * parse_locations(pam_handle_t *pamh, struct options *opts, char *location_string) { struct locations *entry = NULL; struct locations *walker = NULL; struct locations *list = NULL; char *single, *end, *next; char *country, *city; char *string = strdup(location_string); float latitude; float longitude; float radius; single = string; while (*single) { if (isspace(*single)) { single++; continue; } country = NULL; city = NULL; end = single; while (*end && *end != ';') end++; if (*end) next = end + 1; else next = end; *end = '\0'; end--; while (isspace(*end)) { *end = '\0'; end--; } if (strlen(single) == 0) { single = next; continue; } if (sscanf(single, "%f { %f , %f }", &radius, &latitude, &longitude) == 3) { if (fabsf(latitude) > 90.0 || fabsf(longitude) > 180.0) { pam_syslog(pamh, LOG_WARNING, "illegal value(s) in LAT/LONG: %f, %f", latitude, longitude); single = next; continue; } } else { country = single; while (*single && *single != ',') single++; /* single is now at the end of country */ if (*single) city = single + 1; else city = "*"; *single = '\0'; single--; while (isspace(*single)) { *single = '\0'; single--; } if (strlen(country) == 0) country = "*"; while (isspace(*city)) city++; if (strlen(city) == 0) city = "*"; } single = next; entry = malloc(sizeof(struct locations)); if (entry == NULL) { pam_syslog(pamh, LOG_CRIT, "failed to malloc: %m"); return NULL; } entry->next = NULL; if (country == NULL) { entry->radius = radius; entry->longitude = longitude; entry->latitude = latitude; entry->country = NULL; entry->city = NULL; } else { entry->country = strdup(country); if (entry->country == NULL) { pam_syslog(pamh, LOG_CRIT, "failed to malloc: %m"); free(entry); return NULL; } entry->city = strdup(city); if (entry->city == NULL) { pam_syslog(pamh, LOG_CRIT, "failed to malloc: %m"); free(entry); return NULL; } } if (list == NULL) list = entry; else { walker = list; while (walker->next) walker = walker->next; walker->next = entry; } } if (string) free(string); /* strdup'd */ return list; } int parse_action(pam_handle_t *pamh, char *name) { int action = -1; if (strcmp(name, "deny") == 0) action = PAM_PERM_DENIED; else if (strcmp(name, "allow") == 0) action = PAM_SUCCESS; else if (strcmp(name, "ignore") == 0) action = PAM_IGNORE; else pam_syslog(pamh, LOG_WARNING, "invalid action '%s' - skipped", name); return action; } int parse_line_srv(pam_handle_t *pamh, char *line, char *domain, char *location) { char *str; char action[LINE_LENGTH+1]; if (sscanf(line, "%s %s %[^\n]", domain, action, location) != 3) { pam_syslog(pamh, LOG_WARNING, "invalid line '%s' - skipped", line); return -1; } /* remove white space from the end */ str = location + strlen(location) - 1; while (isspace(*str)) { *str = '\0'; str--; } return parse_action(pamh, action); } int parse_line_sys(pam_handle_t *pamh, char *line, char *domain, char *service, char *location) { char *str; char action[LINE_LENGTH+1]; if (sscanf(line, "%s %s %s %[^\n]", domain, service, action, location) != 4) { pam_syslog(pamh, LOG_WARNING, "invalid line '%s' - skipped", line); return -1; } /* remove white space from the end */ str = location + strlen(location) - 1; while (isspace(*str)) { *str = '\0'; str--; } return parse_action(pamh, action); } /* * vim: ts=4 sw=4 expandtab */ pam_geoip-1.1/ChangeLog0000644000175000001440000000051412107656450013552 0ustar hahusers1.1 (2013-02-16): - split pam_geoip.c into several files - add possibility to use country databases - output in syslog changed, it now includes the matching entry and the remote host (w/ debug off) 1.0 (2012-12-26) - add IPv6 support, libgeoip >= 1.4.8 is required for this 0.9 (2011-04-22) - initial public release