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