1 /*-------------------------------------------------------------------------
4 * Routines to handle network authentication
6 * Copyright (c) 1994, Regents of the University of California
10 * $Header: /cvsroot/pgsql/src/backend/libpq/auth.c,v 1.8 1996/11/16 08:09:15 bryanh Exp $
12 *-------------------------------------------------------------------------
17 * backend (postmaster) routines:
18 * be_recvauth receive authentication information
19 * be_setauthsvc do/do not permit an authentication service
20 * be_getauthsvc is an authentication service permitted?
23 * To add a new authentication system:
24 * 0. If you can't do your authentication over an existing socket,
25 * you lose -- get ready to hack around this framework instead of
26 * using it. Otherwise, you can assume you have an initialized
27 * and empty connection to work with. (Please don't leave leftover
28 * gunk in the connection after the authentication transactions, or
29 * the POSTGRES routines that follow will be very unhappy.)
30 * 1. Write a set of routines that:
31 * let a client figure out what user/principal name to use
32 * send authentication information (client side)
33 * receive authentication information (server side)
34 * You can include both routines in this file, using #ifdef FRONTEND
36 * 2. Edit libpq/pqcomm.h and assign a MsgType for your protocol.
37 * 3. Edit the static "struct authsvc" array and the generic
38 * {be,fe}_{get,set}auth{name,svc} routines in this file to reflect
39 * the new service. You may have to change the arguments of these
40 * routines; they basically just reflect what Kerberos v4 needs.
41 * 4. Hack on src/{,bin}/Makefile.global and src/{backend,libpq}/Makefile
42 * to add library and CFLAGS hooks -- basically, grep the Makefile
43 * hierarchy for KRBVERS to see where you need to add things.
45 * Send mail to post_hackers@postgres.Berkeley.EDU if you have to make
46 * any changes to arguments, etc. Context diffs would be nice, too.
48 * Someday, this cruft will go away and magically be replaced by a
49 * nice interface based on the GSS API or something. For now, though,
50 * there's no (stable) UNIX security API to work with...
55 #include <sys/param.h> /* for MAXHOSTNAMELEN on most */
56 #ifndef MAXHOSTNAMELEN
57 #include <netdb.h> /* for MAXHOSTNAMELEN on some */
60 #include <ctype.h> /* isspace() declaration */
62 #include <sys/types.h> /* needed by in.h on Ultrix */
63 #include <netinet/in.h>
64 #include <arpa/inet.h>
67 #include <miscadmin.h>
69 #include <libpq/auth.h>
70 #include <libpq/libpq.h>
71 #include <libpq/libpq-be.h>
72 #include <libpq/hba.h>
74 /*----------------------------------------------------------------
75 * common definitions for generic fe/be routines
76 *----------------------------------------------------------------
80 char name[16]; /* service nickname (for command line) */
81 MsgType msgtype; /* startup packet header type */
82 int allowed; /* initially allowed (before command line
88 * Command-line parsing routines use this structure to map nicknames
89 * onto service types (and the startup packets to use with them).
91 * Programs receiving an authentication request use this structure to
92 * decide which authentication service types are currently permitted.
93 * By default, all authentication systems compiled into the system are
94 * allowed. Unauthenticated connections are disallowed unless there
95 * isn't any authentication system.
99 static int useHostBasedAuth = 1;
101 static int useHostBasedAuth = 0;
104 #if defined(KRB4) || defined(KRB5) || defined(HBA)
105 #define UNAUTH_ALLOWED 0
107 #define UNAUTH_ALLOWED 1
110 static struct authsvc authsvcs[] = {
111 { "unauth", STARTUP_UNAUTH_MSG, UNAUTH_ALLOWED },
112 { "hba", STARTUP_HBA_MSG, 1 },
113 { "krb4", STARTUP_KRB4_MSG, 1 },
114 { "krb5", STARTUP_KRB5_MSG, 1 },
116 { "kerberos", STARTUP_KRB5_MSG, 1 }
118 { "kerberos", STARTUP_KRB4_MSG, 1 }
122 static n_authsvcs = sizeof(authsvcs) / sizeof(struct authsvc);
125 /* This has to be ifdef'd out because krb.h does exist. This needs
128 /*----------------------------------------------------------------
129 * MIT Kerberos authentication system - protocol version 4
130 *----------------------------------------------------------------
136 /* moves to src/libpq/fe-auth.c */
137 #else /* !FRONTEND */
140 * pg_krb4_recvauth -- server routine to receive authentication information
143 * Nothing unusual here, except that we compare the username obtained from
144 * the client's setup packet to the authenticated name. (We have to retain
145 * the name in the setup packet since we have to retain the ability to handle
146 * unauthenticated connections.)
149 pg_krb4_recvauth(int sock,
150 struct sockaddr_in *laddr,
151 struct sockaddr_in *raddr,
154 long krbopts = 0; /* one-way authentication */
156 char instance[INST_SZ];
158 Key_schedule key_sched;
159 char version[KRB_SENDAUTH_VLEN];
162 strcpy(instance, "*"); /* don't care, but arg gets expanded anyway */
163 status = krb_recvauth(krbopts,
174 if (status != KSUCCESS) {
175 (void) sprintf(PQerrormsg,
176 "pg_krb4_recvauth: kerberos error: %s\n",
177 krb_err_txt[status]);
178 fputs(PQerrormsg, stderr);
179 pqdebug("%s", PQerrormsg);
180 return(STATUS_ERROR);
182 if (strncmp(version, PG_KRB4_VERSION, KRB_SENDAUTH_VLEN)) {
183 (void) sprintf(PQerrormsg,
184 "pg_krb4_recvauth: protocol version != \"%s\"\n",
186 fputs(PQerrormsg, stderr);
187 pqdebug("%s", PQerrormsg);
188 return(STATUS_ERROR);
190 if (username && *username &&
191 strncmp(username, auth_data.pname, NAMEDATALEN)) {
192 (void) sprintf(PQerrormsg,
193 "pg_krb4_recvauth: name \"%s\" != \"%s\"\n",
196 fputs(PQerrormsg, stderr);
197 pqdebug("%s", PQerrormsg);
198 return(STATUS_ERROR);
203 #endif /* !FRONTEND */
207 pg_krb4_recvauth(int sock,
208 struct sockaddr_in *laddr,
209 struct sockaddr_in *raddr,
212 (void) sprintf(PQerrormsg,
213 "pg_krb4_recvauth: Kerberos not implemented on this "
215 fputs(PQerrormsg, stderr);
216 pqdebug("%s", PQerrormsg);
218 return(STATUS_ERROR);
224 /* This needs to be ifdef'd out because krb5.h doesn't exist. This needs
227 /*----------------------------------------------------------------
228 * MIT Kerberos authentication system - protocol version 5
229 *----------------------------------------------------------------
232 #include <krb5/krb5.h>
235 * pg_an_to_ln -- return the local name corresponding to an authentication
238 * XXX Assumes that the first aname component is the user name. This is NOT
239 * necessarily so, since an aname can actually be something out of your
240 * worst X.400 nightmare, like
241 * ORGANIZATION=U. C. Berkeley/NAME=Paul M. Aoki@CS.BERKELEY.EDU
242 * Note that the MIT an_to_ln code does the same thing if you don't
243 * provide an aname mapping database...it may be a better idea to use
244 * krb5_an_to_ln, except that it punts if multiple components are found,
245 * and we can't afford to punt.
248 pg_an_to_ln(char *aname)
252 if ((p = strchr(aname, '/')) || (p = strchr(aname, '@')))
258 /* moves to src/libpq/fe-auth.c */
259 #else /* !FRONTEND */
262 * pg_krb5_recvauth -- server routine to receive authentication information
265 * We still need to compare the username obtained from the client's setup
266 * packet to the authenticated name, as described in pg_krb4_recvauth. This
267 * is a bit more problematic in v5, as described above in pg_an_to_ln.
269 * In addition, as described above in pg_krb5_sendauth, we still need to
270 * canonicalize the server name v4-style before constructing a principal
271 * from it. Again, this is kind of iffy.
273 * Finally, we need to tangle with the fact that v5 doesn't let you explicitly
274 * set server keytab file names -- you have to feed lower-level routines a
275 * function to retrieve the contents of a keytab, along with a single argument
276 * that allows them to open the keytab. We assume that a server keytab is
277 * always a real file so we can allow people to specify their own filenames.
278 * (This is important because the POSTGRES keytab needs to be readable by
279 * non-root users/groups; the v4 tools used to force you do dump a whole
280 * host's worth of keys into a file, effectively forcing you to use one file,
281 * but kdb5_edit allows you to select which principals to dump. Yay!)
284 pg_krb5_recvauth(int sock,
285 struct sockaddr_in *laddr,
286 struct sockaddr_in *raddr,
289 char servbuf[MAXHOSTNAMELEN + 1 +
290 sizeof(PG_KRB_SRVNAM)];
291 char *hostp, *kusername = (char *) NULL;
292 krb5_error_code code;
293 krb5_principal client, server;
294 krb5_address sender_addr;
295 krb5_rdreq_key_proc keyproc = (krb5_rdreq_key_proc) NULL;
296 krb5_pointer keyprocarg = (krb5_pointer) NULL;
299 * Set up server side -- since we have no ticket file to make this
300 * easy, we construct our own name and parse it. See note on
301 * canonicalization above.
303 (void) strcpy(servbuf, PG_KRB_SRVNAM);
304 *(hostp = servbuf + (sizeof(PG_KRB_SRVNAM) - 1)) = '/';
305 if (gethostname(++hostp, MAXHOSTNAMELEN) < 0)
306 (void) strcpy(hostp, "localhost");
307 if (hostp = strchr(hostp, '.'))
309 if (code = krb5_parse_name(servbuf, &server)) {
310 (void) sprintf(PQerrormsg,
311 "pg_krb5_recvauth: Kerberos error %d in krb5_parse_name\n",
313 com_err("pg_krb5_recvauth", code, "in krb5_parse_name");
314 return(STATUS_ERROR);
318 * krb5_sendauth needs this to verify the address in the client
321 sender_addr.addrtype = raddr->sin_family;
322 sender_addr.length = sizeof(raddr->sin_addr);
323 sender_addr.contents = (krb5_octet *) &(raddr->sin_addr);
325 if (strcmp(PG_KRB_SRVTAB, "")) {
326 keyproc = krb5_kt_read_service_key;
327 keyprocarg = PG_KRB_SRVTAB;
330 if (code = krb5_recvauth((krb5_pointer) &sock,
340 (krb5_ticket **) NULL,
341 (krb5_authenticator **) NULL)) {
342 (void) sprintf(PQerrormsg,
343 "pg_krb5_recvauth: Kerberos error %d in krb5_recvauth\n",
345 com_err("pg_krb5_recvauth", code, "in krb5_recvauth");
346 krb5_free_principal(server);
347 return(STATUS_ERROR);
349 krb5_free_principal(server);
352 * The "client" structure comes out of the ticket and is therefore
353 * authenticated. Use it to check the username obtained from the
354 * postmaster startup packet.
356 if ((code = krb5_unparse_name(client, &kusername))) {
357 (void) sprintf(PQerrormsg,
358 "pg_krb5_recvauth: Kerberos error %d in krb5_unparse_name\n",
360 com_err("pg_krb5_recvauth", code, "in krb5_unparse_name");
361 krb5_free_principal(client);
362 return(STATUS_ERROR);
364 krb5_free_principal(client);
366 (void) sprintf(PQerrormsg,
367 "pg_krb5_recvauth: could not decode username\n");
368 fputs(PQerrormsg, stderr);
369 pqdebug("%s", PQerrormsg);
370 return(STATUS_ERROR);
372 kusername = pg_an_to_ln(kusername);
373 if (username && strncmp(username, kusername, NAMEDATALEN)) {
374 (void) sprintf(PQerrormsg,
375 "pg_krb5_recvauth: name \"%s\" != \"%s\"\n",
376 username, kusername);
377 fputs(PQerrormsg, stderr);
378 pqdebug("%s", PQerrormsg);
380 return(STATUS_ERROR);
386 #endif /* !FRONTEND */
391 pg_krb5_recvauth(int sock,
392 struct sockaddr_in *laddr,
393 struct sockaddr_in *raddr,
396 (void) sprintf(PQerrormsg,
397 "pg_krb5_recvauth: Kerberos not implemented on this "
399 fputs(PQerrormsg, stderr);
400 pqdebug("%s", PQerrormsg);
402 return(STATUS_ERROR);
407 * be_recvauth -- server demux routine for incoming authentication information
410 be_recvauth(MsgType msgtype_arg, Port *port, char *username, StartupInfo* sp)
414 /* A message type of STARTUP_MSG (which once upon a time was the only
415 startup message type) means user wants us to choose. "unauth" is
416 what used to be the only choice, but installation may choose "hba"
419 if (msgtype_arg == STARTUP_MSG && useHostBasedAuth)
420 msgtype = STARTUP_HBA_MSG;
422 msgtype = STARTUP_UNAUTH_MSG;
425 (void) sprintf(PQerrormsg,
426 "be_recvauth: no user name passed\n");
427 fputs(PQerrormsg, stderr);
428 pqdebug("%s", PQerrormsg);
429 return(STATUS_ERROR);
432 (void) sprintf(PQerrormsg,
433 "be_recvauth: no port structure passed\n");
434 fputs(PQerrormsg, stderr);
435 pqdebug("%s", PQerrormsg);
436 return(STATUS_ERROR);
440 case STARTUP_KRB4_MSG:
441 if (!be_getauthsvc(msgtype)) {
442 (void) sprintf(PQerrormsg,
443 "be_recvauth: krb4 authentication disallowed\n");
444 fputs(PQerrormsg, stderr);
445 pqdebug("%s", PQerrormsg);
446 return(STATUS_ERROR);
448 if (pg_krb4_recvauth(port->sock, &port->laddr, &port->raddr,
449 username) != STATUS_OK) {
450 (void) sprintf(PQerrormsg,
451 "be_recvauth: krb4 authentication failed\n");
452 fputs(PQerrormsg, stderr);
453 pqdebug("%s", PQerrormsg);
454 return(STATUS_ERROR);
457 case STARTUP_KRB5_MSG:
458 if (!be_getauthsvc(msgtype)) {
459 (void) sprintf(PQerrormsg,
460 "be_recvauth: krb5 authentication disallowed\n");
461 fputs(PQerrormsg, stderr);
462 pqdebug("%s", PQerrormsg);
463 return(STATUS_ERROR);
465 if (pg_krb5_recvauth(port->sock, &port->laddr, &port->raddr,
466 username) != STATUS_OK) {
467 (void) sprintf(PQerrormsg,
468 "be_recvauth: krb5 authentication failed\n");
469 fputs(PQerrormsg, stderr);
470 pqdebug("%s", PQerrormsg);
471 return(STATUS_ERROR);
474 case STARTUP_UNAUTH_MSG:
475 if (!be_getauthsvc(msgtype)) {
476 (void) sprintf(PQerrormsg,
478 "unauthenticated connections disallowed\n");
479 fputs(PQerrormsg, stderr);
480 pqdebug("%s", PQerrormsg);
481 return(STATUS_ERROR);
484 case STARTUP_HBA_MSG:
485 if (hba_recvauth(port, sp->database, sp->user, DataDir) != STATUS_OK) {
486 (void) sprintf(PQerrormsg,
487 "be_recvauth: host-based authentication failed\n");
488 fputs(PQerrormsg, stderr);
489 pqdebug("%s", PQerrormsg);
490 return(STATUS_ERROR);
494 (void) sprintf(PQerrormsg,
495 "be_recvauth: unrecognized message type: %d\n",
497 fputs(PQerrormsg, stderr);
498 pqdebug("%s", PQerrormsg);
499 return(STATUS_ERROR);
505 * be_setauthsvc -- enable/disable the authentication services currently
506 * selected for use by the backend
507 * be_getauthsvc -- returns whether a particular authentication system
508 * (indicated by its message type) is permitted by the
511 * be_setauthsvc encodes the command-line syntax that
512 * -a "<service-name>"
513 * enables a service, whereas
514 * -a "no<service-name>"
518 be_setauthsvc(char *name)
525 if (!strncmp("no", name, 2)) {
531 for (i = 0; i < n_authsvcs; ++i)
532 if (!strcmp(name, authsvcs[i].name)) {
533 for (j = 0; j < n_authsvcs; ++j)
534 if (authsvcs[j].msgtype == authsvcs[i].msgtype)
535 authsvcs[j].allowed = turnon;
538 if (i == n_authsvcs) {
539 (void) sprintf(PQerrormsg,
540 "be_setauthsvc: invalid name %s, ignoring...\n",
542 fputs(PQerrormsg, stderr);
543 pqdebug("%s", PQerrormsg);
549 be_getauthsvc(MsgType msgtype)
553 for (i = 0; i < n_authsvcs; ++i)
554 if (msgtype == authsvcs[i].msgtype)
555 return(authsvcs[i].allowed);