1 /*-------------------------------------------------------------------------
2  *
3  * crypt.c
4  *	  Look into the password file and check the encrypted password with
5  *	  the one passed in from the frontend.
6  *
7  * Original coding by Todd A. Brandys
8  *
9  * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
10  * Portions Copyright (c) 1994, Regents of the University of California
11  *
12  * src/backend/libpq/crypt.c
13  *
14  *-------------------------------------------------------------------------
15  */
16 #include "postgres.h"
17 
18 #include <unistd.h>
19 #ifdef HAVE_CRYPT_H
20 #include <crypt.h>
21 #endif
22 
23 #include "catalog/pg_authid.h"
24 #include "libpq/crypt.h"
25 #include "libpq/md5.h"
26 #include "miscadmin.h"
27 #include "utils/builtins.h"
28 #include "utils/syscache.h"
29 #include "utils/timestamp.h"
30 
31 
32 /*
33  * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
34  * In the error case, optionally store a palloc'd string at *logdetail
35  * that will be sent to the postmaster log (but not the client).
36  */
37 int
md5_crypt_verify(const Port * port,const char * role,char * client_pass,char ** logdetail)38 md5_crypt_verify(const Port *port, const char *role, char *client_pass,
39 				 char **logdetail)
40 {
41 	int			retval = STATUS_ERROR;
42 	char	   *shadow_pass,
43 			   *crypt_pwd;
44 	TimestampTz vuntil = 0;
45 	char	   *crypt_client_pass = client_pass;
46 	HeapTuple	roleTup;
47 	Datum		datum;
48 	bool		isnull;
49 
50 	/* Get role info from pg_authid */
51 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
52 	if (!HeapTupleIsValid(roleTup))
53 	{
54 		*logdetail = psprintf(_("Role \"%s\" does not exist."),
55 							  role);
56 		return STATUS_ERROR;	/* no such user */
57 	}
58 
59 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
60 							Anum_pg_authid_rolpassword, &isnull);
61 	if (isnull)
62 	{
63 		ReleaseSysCache(roleTup);
64 		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
65 							  role);
66 		return STATUS_ERROR;	/* user has no password */
67 	}
68 	shadow_pass = TextDatumGetCString(datum);
69 
70 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
71 							Anum_pg_authid_rolvaliduntil, &isnull);
72 	if (!isnull)
73 		vuntil = DatumGetTimestampTz(datum);
74 
75 	ReleaseSysCache(roleTup);
76 
77 	/*
78 	 * Don't allow an empty password. Libpq treats an empty password the same
79 	 * as no password at all, and won't even try to authenticate. But other
80 	 * clients might, so allowing it would be confusing.
81 	 *
82 	 * For a plaintext password, we can simply check that it's not an empty
83 	 * string. For an encrypted password, check that it does not match the MD5
84 	 * hash of an empty string.
85 	 */
86 	if (*shadow_pass == '\0')
87 	{
88 		*logdetail = psprintf(_("User \"%s\" has an empty password."),
89 							  role);
90 		return STATUS_ERROR;	/* empty password */
91 	}
92 	if (isMD5(shadow_pass))
93 	{
94 		char		crypt_empty[MD5_PASSWD_LEN + 1];
95 
96 		if (!pg_md5_encrypt("",
97 							port->user_name,
98 							strlen(port->user_name),
99 							crypt_empty))
100 			return STATUS_ERROR;
101 		if (strcmp(shadow_pass, crypt_empty) == 0)
102 		{
103 			*logdetail = psprintf(_("User \"%s\" has an empty password."),
104 								  role);
105 			return STATUS_ERROR;	/* empty password */
106 		}
107 	}
108 
109 	/*
110 	 * Compare with the encrypted or plain password depending on the
111 	 * authentication method being used for this connection.  (We do not
112 	 * bother setting logdetail for pg_md5_encrypt failure: the only possible
113 	 * error is out-of-memory, which is unlikely, and if it did happen adding
114 	 * a psprintf call would only make things worse.)
115 	 */
116 	switch (port->hba->auth_method)
117 	{
118 		case uaMD5:
119 			crypt_pwd = palloc(MD5_PASSWD_LEN + 1);
120 			if (isMD5(shadow_pass))
121 			{
122 				/* stored password already encrypted, only do salt */
123 				if (!pg_md5_encrypt(shadow_pass + strlen("md5"),
124 									port->md5Salt,
125 									sizeof(port->md5Salt), crypt_pwd))
126 				{
127 					pfree(crypt_pwd);
128 					return STATUS_ERROR;
129 				}
130 			}
131 			else
132 			{
133 				/* stored password is plain, double-encrypt */
134 				char	   *crypt_pwd2 = palloc(MD5_PASSWD_LEN + 1);
135 
136 				if (!pg_md5_encrypt(shadow_pass,
137 									port->user_name,
138 									strlen(port->user_name),
139 									crypt_pwd2))
140 				{
141 					pfree(crypt_pwd);
142 					pfree(crypt_pwd2);
143 					return STATUS_ERROR;
144 				}
145 				if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"),
146 									port->md5Salt,
147 									sizeof(port->md5Salt),
148 									crypt_pwd))
149 				{
150 					pfree(crypt_pwd);
151 					pfree(crypt_pwd2);
152 					return STATUS_ERROR;
153 				}
154 				pfree(crypt_pwd2);
155 			}
156 			break;
157 		default:
158 			if (isMD5(shadow_pass))
159 			{
160 				/* Encrypt user-supplied password to match stored MD5 */
161 				crypt_client_pass = palloc(MD5_PASSWD_LEN + 1);
162 				if (!pg_md5_encrypt(client_pass,
163 									port->user_name,
164 									strlen(port->user_name),
165 									crypt_client_pass))
166 				{
167 					pfree(crypt_client_pass);
168 					return STATUS_ERROR;
169 				}
170 			}
171 			crypt_pwd = shadow_pass;
172 			break;
173 	}
174 
175 	if (strcmp(crypt_client_pass, crypt_pwd) == 0)
176 	{
177 		/*
178 		 * Password OK, now check to be sure we are not past rolvaliduntil
179 		 */
180 		if (isnull)
181 			retval = STATUS_OK;
182 		else if (vuntil < GetCurrentTimestamp())
183 		{
184 			*logdetail = psprintf(_("User \"%s\" has an expired password."),
185 								  role);
186 			retval = STATUS_ERROR;
187 		}
188 		else
189 			retval = STATUS_OK;
190 	}
191 	else
192 		*logdetail = psprintf(_("Password does not match for user \"%s\"."),
193 							  role);
194 
195 	if (port->hba->auth_method == uaMD5)
196 		pfree(crypt_pwd);
197 	if (crypt_client_pass != client_pass)
198 		pfree(crypt_client_pass);
199 
200 	return retval;
201 }
202