PostgreSQL Source Code: src/interfaces/libpq-oauth/oauth-curl.c Source File (original) (raw)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
17
18#include <curl/curl.h>
19#include <math.h>
21
22#if defined(HAVE_SYS_EPOLL_H)
23#include <sys/epoll.h>
24#include <sys/timerfd.h>
25#elif defined(HAVE_SYS_EVENT_H)
26#include <sys/event.h>
27#else
28#error libpq-oauth is not supported on this platform
29#endif
30
35
36#ifdef USE_DYNAMIC_OAUTH
37
38
39
40
41
42
44
45#else
46
47
48
49
50
52
53#define conn_errorMessage(CONN) (&CONN->errorMessage)
54#define conn_oauth_client_id(CONN) (CONN->oauth_client_id)
55#define conn_oauth_client_secret(CONN) (CONN->oauth_client_secret)
56#define conn_oauth_discovery_uri(CONN) (CONN->oauth_discovery_uri)
57#define conn_oauth_issuer_id(CONN) (CONN->oauth_issuer_id)
58#define conn_oauth_scope(CONN) (CONN->oauth_scope)
59#define conn_sasl_state(CONN) (CONN->sasl_state)
60
61#define set_conn_altsock(CONN, VAL) do { CONN->altsock = VAL; } while (0)
62#define set_conn_oauth_token(CONN, VAL) do { CONN->oauth_token = VAL; } while (0)
63
64#endif
65
66
67#if defined(USE_DYNAMIC_OAUTH) && defined(LIBPQ_INT_H)
68#error do not rely on libpq-int.h in dynamic builds of libpq-oauth
69#endif
70
71
72
73
74
75
76
77
78
79
80
81
82
83#define MAX_OAUTH_RESPONSE_SIZE (256 * 1024)
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
101{
106};
107
108static void
110{
115}
116
117
118
119
120
121
123{
130
131
134};
135
136static void
138{
145}
146
147
148
149
150
151
152
153
154
156{
159};
160
161static void
163{
165 free(err->error_description);
166}
167
168
169
170
171
172
173
174
175
176
178{
179
182
183
185};
186
187static void
189{
193}
194
195
196
197
198
199
201{
207};
208
209
210
211
212
213
215{
217
220
221
223
225
226
227 struct curl_slist *headers;
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
255
256
257
258
259
262
264 bool user_prompted;
267};
268
269
270
271
272static void
274{
275
276
277
278
279
280
281
282
283
284
285
287 {
288 CURLMcode err = curl_multi_remove_handle(actx->curlm, actx->curl);
289
292 "libcurl easy handle removal failed: %s",
293 curl_multi_strerror(err));
294 }
295
296 if (actx->curl)
297 {
298
299
300
301
302
303 curl_easy_cleanup(actx->curl);
304 }
305
307 {
308 CURLMcode err = curl_multi_cleanup(actx->curlm);
309
312 "libcurl multi handle cleanup failed: %s",
313 curl_multi_strerror(err));
314 }
315
318
319 curl_slist_free_all(actx->headers);
322
327
329}
330
331
332
333
334
335
336
337
338
339void
341{
343
344 if (state->async_ctx)
345 {
347 state->async_ctx = NULL;
348 }
349
351}
352
353
354
355
356
357
358
359#define actx_error(ACTX, FMT, ...) \
360 appendPQExpBuffer(&(ACTX)->errbuf, libpq_gettext(FMT), ##__VA_ARGS__)
361
362#define actx_error_str(ACTX, S) \
363 appendPQExpBufferStr(&(ACTX)->errbuf, S)
364
365
366
367
368
369
370#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION) \
371 do { \
372 struct async_ctx *_actx = (ACTX); \
373 CURLMcode _setopterr = curl_multi_setopt(_actx->curlm, OPT, VAL); \
374 if (_setopterr) { \
375 actx_error(_actx, "failed to set %s on OAuth connection: %s",\
376 #OPT, curl_multi_strerror(_setopterr)); \
377 FAILACTION; \
378 } \
379 } while (0)
380
381#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION) \
382 do { \
383 struct async_ctx *_actx = (ACTX); \
384 CURLcode _setopterr = curl_easy_setopt(_actx->curl, OPT, VAL); \
385 if (_setopterr) { \
386 actx_error(_actx, "failed to set %s on OAuth connection: %s",\
387 #OPT, curl_easy_strerror(_setopterr)); \
388 FAILACTION; \
389 } \
390 } while (0)
391
392#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION) \
393 do { \
394 struct async_ctx *_actx = (ACTX); \
395 CURLcode _getinfoerr = curl_easy_getinfo(_actx->curl, INFO, OUT); \
396 if (_getinfoerr) { \
397 actx_error(_actx, "failed to get %s from OAuth response: %s",\
398 #INFO, curl_easy_strerror(_getinfoerr)); \
399 FAILACTION; \
400 } \
401 } while (0)
402
403
404
405
406
407
408
409
410
411
412
413
414
415
417{
419
421
422
423 union
424 {
426 struct curl_slist **array;
428
430};
431
432
433#define PG_OAUTH_REQUIRED true
434#define PG_OAUTH_OPTIONAL false
435
436
438{
441
444};
445
446#define oauth_parse_set_error(ctx, fmt, ...) \
447 appendPQExpBuffer((ctx)->errbuf, libpq_gettext(fmt), ##__VA_ARGS__)
448
449static void
451{
452 char *msgfmt;
453
455
456
457
458
459
461 {
463 msgfmt = "field \"%s\" must be a string";
464 break;
465
467 msgfmt = "field \"%s\" must be a number";
468 break;
469
471 msgfmt = "field \"%s\" must be an array of strings";
472 break;
473
474 default:
476 msgfmt = "field \"%s\" has unexpected type";
477 }
478
480}
481
484{
486
488 {
489
490
491
492
495 }
496
499}
500
503{
505
506
507 if (ctx->nested == 1)
508 {
510
511
512
513
514
516 {
519 "internal error: started field '%s' before field '%s' was finished",
522 }
523
524 while (field->name)
525 {
526 if (strcmp(name, field->name) == 0)
527 {
529 break;
530 }
531
532 ++field;
533 }
534
535
536
537
538
540 {
542
545 {
547 field->name);
549 }
550 }
551 }
552
554}
555
558{
560
562
563
564
565
566
568 {
571 "internal error: field '%s' still active at end of object",
574 }
575
577}
578
581{
583
585 {
588 }
589
591 {
593
595 {
598 }
599 }
600
603}
604
607{
609
611 {
612
613
614
615
616
618 {
621 "internal error: found unexpected array end while parsing field '%s'",
624 }
625
627 }
628
631}
632
635{
637
639 {
642 }
643
645 {
648
649
651 {
652
654 {
657 }
658
659
661 }
662
663 if (type != expected)
664 {
667 }
668
670 {
671
672 if (ctx->nested != 1)
673 {
676 "internal error: scalar target found at nesting level %d",
679 }
680
681
683 {
686 "internal error: scalar field '%s' would be assigned twice",
689 }
690
694
696
698 }
699 else
700 {
701 struct curl_slist *temp;
702
703
704 if (ctx->nested != 2)
705 {
708 "internal error: array member found at nesting level %d",
711 }
712
713
715 if (!temp)
717
719 }
720 }
721 else
722 {
723
724 }
725
727}
728
729
730
731
732
733static bool
735{
736 const size_t type_len = strlen(type);
737 char *content_type;
738
739 CHECK_GETINFO(actx, CURLINFO_CONTENT_TYPE, &content_type, return false);
740
741 if (!content_type)
742 {
743 actx_error(actx, "no content type was provided");
744 return false;
745 }
746
747
748
749
750
752 goto fail;
753
754
755 Assert(strlen(content_type) >= type_len);
756 if (content_type[type_len] == '\0')
757 return true;
758
759
760
761
762
763
764 for (size_t i = type_len; content_type[i]; ++i)
765 {
766 switch (content_type[i])
767 {
768 case ';':
769 return true;
770
771 case ' ':
772 case '\t':
773
774 break;
775
776 default:
777 goto fail;
778 }
779 }
780
781fail:
782 actx_error(actx, "unexpected content type: \"%s\"", content_type);
783 return false;
784}
785
786
787
788
789
790
791
792static bool
794{
801
803 return false;
804
805 if (strlen(resp->data) != resp->len)
806 {
807 actx_error(actx, "response contains embedded NULLs");
808 return false;
809 }
810
811
812
813
814
816 {
817 actx_error(actx, "response is not valid UTF-8");
818 return false;
819 }
820
823
827
834
836
838 {
839
840
841
842
843
846
848 }
849
850
852 {
856 {
859 }
860
862 }
863
865
869}
870
871
872
873
874
875
876
877
878
879static bool
881{
885
886
887
888
889
890
891
892
893
894
895
898
899 {0},
900 };
901
903}
904
905
906
907
908
909
910static double
912{
913 double parsed;
914 int cnt;
915
916
917
918
919
920 cnt = sscanf(s, "%lf", &parsed);
921
922 if (cnt != 1)
923 {
924
925
926
927
929 return 0;
930 }
931
932 return parsed;
933}
934
935
936
937
938
939
940
941
942
943
944
945static int
947{
948 double parsed;
949
951 parsed = ceil(parsed);
952
953 if (parsed < 1)
955
956 else if (parsed >= INT_MAX)
957 return INT_MAX;
958
959 return parsed;
960}
961
962
963
964
965
966
967
968
969
970
971static int
973{
974 double parsed;
975
977 parsed = floor(parsed);
978
979 if (parsed >= INT_MAX)
980 return INT_MAX;
981 else if (parsed <= INT_MIN)
982 return INT_MIN;
983
984 return parsed;
985}
986
987
988
989
990static bool
992{
998
999
1000
1001
1002
1004
1005
1006
1007
1008
1009
1012
1013 {0},
1014 };
1015
1017 return false;
1018
1019
1020
1021
1022
1025 else
1026 {
1027
1028
1029
1030
1032 }
1033
1036
1037 return true;
1038}
1039
1040
1041
1042
1043
1044static bool
1046{
1047 bool result;
1050
1052
1053 {0},
1054 };
1055
1057
1058
1059
1060
1061
1062 if (!result)
1063 actx->errctx = "failed to parse token error response";
1064
1065 return result;
1066}
1067
1068
1069
1070
1071
1072static void
1074{
1075 if (err->error_description)
1077 else
1078 {
1079
1080
1081
1082
1083
1084 long response_code;
1085
1086 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, response_code = 0);
1087
1088 if (response_code == 401)
1089 {
1091 ? "provider rejected the oauth_client_secret"
1092 : "provider requires client authentication, and no oauth_client_secret is set");
1094 }
1095 }
1096
1098}
1099
1100
1101
1102
1103
1104static bool
1106{
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127 {0},
1128 };
1129
1131}
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146static bool
1148{
1149#if defined(HAVE_SYS_EPOLL_H)
1150 struct epoll_event ev = {.events = EPOLLIN};
1151
1152 actx->mux = epoll_create1(EPOLL_CLOEXEC);
1153 if (actx->mux < 0)
1154 {
1155 actx_error(actx, "failed to create epoll set: %m");
1156 return false;
1157 }
1158
1159 actx->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
1161 {
1162 actx_error(actx, "failed to create timerfd: %m");
1163 return false;
1164 }
1165
1166 if (epoll_ctl(actx->mux, EPOLL_CTL_ADD, actx->timerfd, &ev) < 0)
1167 {
1168 actx_error(actx, "failed to add timerfd to epoll set: %m");
1169 return false;
1170 }
1171
1172 return true;
1173#elif defined(HAVE_SYS_EVENT_H)
1174 actx->mux = kqueue();
1175 if (actx->mux < 0)
1176 {
1177
1178 actx_error(actx, "failed to create kqueue: %m");
1179 return false;
1180 }
1181
1182
1183
1184
1185
1186
1187
1188 actx->timerfd = kqueue();
1190 {
1191 actx_error(actx, "failed to create timer kqueue: %m");
1192 return false;
1193 }
1194
1195 return true;
1196#else
1197#error setup_multiplexer is not implemented on this platform
1198#endif
1199}
1200
1201
1202
1203
1204
1205static int
1207 void *socketp)
1208{
1210
1211#if defined(HAVE_SYS_EPOLL_H)
1212 struct epoll_event ev = {0};
1213 int res;
1214 int op = EPOLL_CTL_ADD;
1215
1216 switch (what)
1217 {
1218 case CURL_POLL_IN:
1219 ev.events = EPOLLIN;
1220 break;
1221
1222 case CURL_POLL_OUT:
1223 ev.events = EPOLLOUT;
1224 break;
1225
1226 case CURL_POLL_INOUT:
1227 ev.events = EPOLLIN | EPOLLOUT;
1228 break;
1229
1230 case CURL_POLL_REMOVE:
1231 op = EPOLL_CTL_DEL;
1232 break;
1233
1234 default:
1235 actx_error(actx, "unknown libcurl socket operation: %d", what);
1236 return -1;
1237 }
1238
1239 res = epoll_ctl(actx->mux, op, socket, &ev);
1240 if (res < 0 && errno == EEXIST)
1241 {
1242
1243 op = EPOLL_CTL_MOD;
1244 res = epoll_ctl(actx->mux, op, socket, &ev);
1245 }
1246
1247 if (res < 0)
1248 {
1249 switch (op)
1250 {
1251 case EPOLL_CTL_ADD:
1252 actx_error(actx, "could not add to epoll set: %m");
1253 break;
1254
1255 case EPOLL_CTL_DEL:
1256 actx_error(actx, "could not delete from epoll set: %m");
1257 break;
1258
1259 default:
1260 actx_error(actx, "could not update epoll set: %m");
1261 }
1262
1263 return -1;
1264 }
1265
1266 return 0;
1267#elif defined(HAVE_SYS_EVENT_H)
1268 struct kevent ev[2] = {0};
1269 struct kevent ev_out[2];
1270 struct timespec timeout = {0};
1271 int nev = 0;
1272 int res;
1273
1274 switch (what)
1275 {
1276 case CURL_POLL_IN:
1277 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1278 nev++;
1279 break;
1280
1281 case CURL_POLL_OUT:
1282 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1283 nev++;
1284 break;
1285
1286 case CURL_POLL_INOUT:
1287 EV_SET(&ev[nev], socket, EVFILT_READ, EV_ADD | EV_RECEIPT, 0, 0, 0);
1288 nev++;
1289 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_ADD | EV_RECEIPT, 0, 0, 0);
1290 nev++;
1291 break;
1292
1293 case CURL_POLL_REMOVE:
1294
1295
1296
1297
1298
1299
1300 EV_SET(&ev[nev], socket, EVFILT_READ, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1301 nev++;
1302 EV_SET(&ev[nev], socket, EVFILT_WRITE, EV_DELETE | EV_RECEIPT, 0, 0, 0);
1303 nev++;
1304 break;
1305
1306 default:
1307 actx_error(actx, "unknown libcurl socket operation: %d", what);
1308 return -1;
1309 }
1310
1311 res = kevent(actx->mux, ev, nev, ev_out, lengthof(ev_out), &timeout);
1312 if (res < 0)
1313 {
1314 actx_error(actx, "could not modify kqueue: %m");
1315 return -1;
1316 }
1317
1318
1319
1320
1321
1322
1323 for (int i = 0; i < res; ++i)
1324 {
1325
1326
1327
1328
1329
1330 Assert(ev_out[i].flags & EV_ERROR);
1331
1332 errno = ev_out[i].data;
1333 if (errno && errno != ENOENT)
1334 {
1335 switch (what)
1336 {
1337 case CURL_POLL_REMOVE:
1338 actx_error(actx, "could not delete from kqueue: %m");
1339 break;
1340 default:
1341 actx_error(actx, "could not add to kqueue: %m");
1342 }
1343 return -1;
1344 }
1345 }
1346
1347 return 0;
1348#else
1349#error register_socket is not implemented on this platform
1350#endif
1351}
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366static bool
1368{
1369#if defined(HAVE_SYS_EPOLL_H)
1370 struct itimerspec spec = {0};
1371
1372 if (timeout < 0)
1373 {
1374
1375 }
1376 else if (timeout == 0)
1377 {
1378
1379
1380
1381
1382
1383 spec.it_value.tv_nsec = 1;
1384 }
1385 else
1386 {
1387 spec.it_value.tv_sec = timeout / 1000;
1388 spec.it_value.tv_nsec = (timeout % 1000) * 1000000;
1389 }
1390
1391 if (timerfd_settime(actx->timerfd, 0 , &spec, NULL) < 0)
1392 {
1393 actx_error(actx, "setting timerfd to %ld: %m", timeout);
1394 return false;
1395 }
1396
1397 return true;
1398#elif defined(HAVE_SYS_EVENT_H)
1399 struct kevent ev;
1400
1401#ifdef __NetBSD__
1402
1403
1404
1405
1406
1407 if (timeout == 0)
1408 timeout = 1;
1409#endif
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420 EV_SET(&ev, 1, EVFILT_TIMER, EV_DELETE, 0, 0, 0);
1421 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1422 {
1423 actx_error(actx, "deleting kqueue timer: %m");
1424 return false;
1425 }
1426
1427 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_DELETE, 0, 0, 0);
1428 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0 && errno != ENOENT)
1429 {
1430 actx_error(actx, "removing kqueue timer from multiplexer: %m");
1431 return false;
1432 }
1433
1434
1435 if (timeout < 0)
1436 return true;
1437
1438 EV_SET(&ev, 1, EVFILT_TIMER, (EV_ADD | EV_ONESHOT), 0, timeout, 0);
1439 if (kevent(actx->timerfd, &ev, 1, NULL, 0, NULL) < 0)
1440 {
1441 actx_error(actx, "setting kqueue timer to %ld: %m", timeout);
1442 return false;
1443 }
1444
1445 EV_SET(&ev, actx->timerfd, EVFILT_READ, EV_ADD, 0, 0, 0);
1446 if (kevent(actx->mux, &ev, 1, NULL, 0, NULL) < 0)
1447 {
1448 actx_error(actx, "adding kqueue timer to multiplexer: %m");
1449 return false;
1450 }
1451
1452 return true;
1453#else
1454#error set_timer is not implemented on this platform
1455#endif
1456}
1457
1458
1459
1460
1461
1462
1463static int
1465{
1466#if defined(HAVE_SYS_EPOLL_H)
1467 struct itimerspec spec = {0};
1468
1469 if (timerfd_gettime(actx->timerfd, &spec) < 0)
1470 {
1471 actx_error(actx, "getting timerfd value: %m");
1472 return -1;
1473 }
1474
1475
1476
1477
1478
1479
1480 Assert(spec.it_interval.tv_sec == 0
1481 && spec.it_interval.tv_nsec == 0);
1482
1483
1484 return (spec.it_value.tv_sec == 0
1485 && spec.it_value.tv_nsec == 0);
1486#elif defined(HAVE_SYS_EVENT_H)
1487 int res;
1488
1489
1491 if (res < 0)
1492 {
1493 actx_error(actx, "checking kqueue for timeout: %m");
1494 return -1;
1495 }
1496
1497 return (res > 0);
1498#else
1499#error timer_expired is not implemented on this platform
1500#endif
1501}
1502
1503
1504
1505
1506
1507static int
1509{
1511
1512
1513
1514
1515
1516
1517
1518
1520 return -1;
1521
1522 return 0;
1523}
1524
1525
1526
1527
1528
1529
1530
1531static int
1533 void *clientp)
1534{
1535 const char *prefix;
1536 bool printed_prefix = false;
1538
1539
1540 switch (type)
1541 {
1542 case CURLINFO_TEXT:
1543 prefix = "*";
1544 break;
1545
1546 case CURLINFO_HEADER_IN:
1547 case CURLINFO_DATA_IN:
1548 prefix = "<";
1549 break;
1550
1551 case CURLINFO_HEADER_OUT:
1552 case CURLINFO_DATA_OUT:
1553 prefix = ">";
1554 break;
1555
1556 default:
1557 return 0;
1558 }
1559
1561
1562
1563
1564
1565
1566
1567 for (int i = 0; i < size; i++)
1568 {
1570
1571 if (!printed_prefix)
1572 {
1574 printed_prefix = true;
1575 }
1576
1577 if (c >= 0x20 && c <= 0x7E)
1579 else if ((type == CURLINFO_HEADER_IN
1580 || type == CURLINFO_HEADER_OUT
1581 || type == CURLINFO_TEXT)
1582 && (c == '\r' || c == '\n'))
1583 {
1584
1585
1586
1587
1588 }
1589 else
1591
1592 if (c == '\n')
1593 {
1595 printed_prefix = false;
1596 }
1597 }
1598
1599 if (printed_prefix)
1601
1604 return 0;
1605}
1606
1607
1608
1609
1610
1611
1612
1613
1614static bool
1616{
1617
1618
1619
1620
1621 actx->curlm = curl_multi_init();
1622 if (!actx->curlm)
1623 {
1624
1625 actx_error(actx, "failed to create libcurl multi handle");
1626 return false;
1627 }
1628
1629
1630
1631
1632
1634 CHECK_MSETOPT(actx, CURLMOPT_SOCKETDATA, actx, return false);
1636 CHECK_MSETOPT(actx, CURLMOPT_TIMERDATA, actx, return false);
1637
1638
1639
1640
1641
1642 actx->curl = curl_easy_init();
1643 if (!actx->curl)
1644 {
1645 actx_error(actx, "failed to create libcurl handle");
1646 return false;
1647 }
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664 CHECK_SETOPT(actx, CURLOPT_NOSIGNAL, 1L, return false);
1665
1667 {
1668
1669
1670
1671
1672
1674 CHECK_SETOPT(actx, CURLOPT_VERBOSE, 1L, return false);
1675 }
1676
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687 {
1688#if CURL_AT_LEAST_VERSION(7, 85, 0)
1689 const CURLoption popt = CURLOPT_PROTOCOLS_STR;
1690 const char *protos = "https";
1691 const char *const unsafe = "https,http";
1692#else
1693 const CURLoption popt = CURLOPT_PROTOCOLS;
1694 long protos = CURLPROTO_HTTPS;
1695 const long unsafe = CURLPROTO_HTTPS | CURLPROTO_HTTP;
1696#endif
1697
1699 protos = unsafe;
1700
1701 CHECK_SETOPT(actx, popt, protos, return false);
1702 }
1703
1704
1705
1706
1707
1708
1709
1710
1711
1713 {
1714 const char *env;
1715
1716 if ((env = getenv("PGOAUTHCAFILE")) != NULL)
1717 CHECK_SETOPT(actx, CURLOPT_CAINFO, env, return false);
1718 }
1719
1720
1721
1722
1723
1724
1725
1726 actx->headers = curl_slist_append(actx->headers, "Accept:");
1727 if (actx->headers == NULL)
1728 {
1730 return false;
1731 }
1733
1734 return true;
1735}
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747static size_t
1749{
1750 struct async_ctx *actx = userdata;
1752 size_t len = size * nmemb;
1753
1754
1756 {
1757 actx_error(actx, "response is too large");
1758 return 0;
1759 }
1760
1761
1763
1764
1765
1766
1767
1769 {
1771 return 0;
1772 }
1773
1774 return len;
1775}
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787static bool
1789{
1790 CURLMcode err;
1791
1794 CHECK_SETOPT(actx, CURLOPT_WRITEDATA, actx, return false);
1795
1796 err = curl_multi_add_handle(actx->curlm, actx->curl);
1797 if (err)
1798 {
1799 actx_error(actx, "failed to queue HTTP request: %s",
1800 curl_multi_strerror(err));
1801 return false;
1802 }
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813 err = curl_multi_socket_action(actx->curlm, CURL_SOCKET_TIMEOUT, 0, &actx->running);
1814 if (err)
1815 {
1816 actx_error(actx, "asynchronous HTTP request failed: %s",
1817 curl_multi_strerror(err));
1818 return false;
1819 }
1820
1821 return true;
1822}
1823
1824
1825
1826
1827
1828#ifndef CURL_IGNORE_DEPRECATION
1829#define CURL_IGNORE_DEPRECATION(x) x
1830#endif
1831
1832
1833
1834
1835
1838{
1839 CURLMcode err;
1840 CURLMsg *msg;
1841 int msgs_left;
1842 bool done;
1843
1845 {
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1864 err = curl_multi_socket_all(actx->curlm, &actx->running);
1865 )
1866
1867 if (err)
1868 {
1869 actx_error(actx, "asynchronous HTTP request failed: %s",
1870 curl_multi_strerror(err));
1872 }
1873
1875 {
1876
1878 }
1879 }
1880
1881 done = false;
1882 while ((msg = curl_multi_info_read(actx->curlm, &msgs_left)) != NULL)
1883 {
1884 if (msg->msg != CURLMSG_DONE)
1885 {
1886
1887
1888
1889
1890 continue;
1891 }
1892
1893
1894 if (msg->data.result != CURLE_OK)
1895 {
1896
1897
1898
1899
1901 actx_error_str(actx, curl_easy_strerror(msg->data.result));
1902
1904 }
1905
1906
1907 err = curl_multi_remove_handle(actx->curlm, msg->easy_handle);
1908 if (err)
1909 {
1910 actx_error(actx, "libcurl easy handle removal failed: %s",
1911 curl_multi_strerror(err));
1913 }
1914
1915 done = true;
1916 }
1917
1918
1919 if (!done)
1920 {
1921 actx_error(actx, "no result was retrieved for the finished handle");
1923 }
1924
1926}
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936static void
1938{
1939 char *escaped;
1940 char *haystack;
1941 char *match;
1942
1943
1944 escaped = curl_easy_escape(NULL, s, 0);
1945 if (!escaped)
1946 {
1948 return;
1949 }
1950
1951
1952
1953
1954
1955
1956
1957 haystack = escaped;
1958
1959 while ((match = strstr(haystack, "%20")) != NULL)
1960 {
1961
1964
1965
1966 haystack = match + 3 ;
1967 }
1968
1969
1971
1972 curl_free(escaped);
1973}
1974
1975
1976
1977
1978
1979static char *
1981{
1983
1986
1988}
1989
1990
1991
1992
1993
1994static void
1996{
1997 if (buf->len)
1999
2003}
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023static bool
2025{
2026 CHECK_SETOPT(actx, CURLOPT_HTTPGET, 1L, return false);
2027 CHECK_SETOPT(actx, CURLOPT_URL, discovery_uri, return false);
2028
2030}
2031
2032static bool
2034{
2035 long response_code;
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2051
2052 if (response_code != 200)
2053 {
2054 actx_error(actx, "unexpected response code %ld", response_code);
2055 return false;
2056 }
2057
2058
2059
2060
2061 actx->errctx = "failed to parse OpenID discovery document";
2063 return false;
2064
2065
2066
2067
2069 {
2070
2071
2072
2074
2075 temp = curl_slist_append(temp, "authorization_code");
2076 if (temp)
2077 {
2078 temp = curl_slist_append(temp, "implicit");
2079 }
2080
2081 if (!temp)
2082 {
2084 return false;
2085 }
2086
2088 }
2089
2090 return true;
2091}
2092
2093
2094
2095
2096
2097static bool
2099{
2102
2103 Assert(oauth_issuer_id);
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2124 {
2126 "the issuer identifier (%s) does not match oauth_issuer (%s)",
2128 return false;
2129 }
2130
2131 return true;
2132}
2133
2134#define HTTPS_SCHEME "https://"
2135#define OAUTH_GRANT_TYPE_DEVICE_CODE "urn:ietf:params:oauth:grant-type:device_code"
2136
2137
2138
2139
2140
2141
2142static bool
2144{
2146
2149
2151 {
2153 "issuer \"%s\" does not provide a device authorization endpoint",
2155 return false;
2156 }
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2174 {
2177 {
2179 "device authorization endpoint \"%s\" must use HTTPS",
2181 return false;
2182 }
2183
2186 {
2188 "token endpoint \"%s\" must use HTTPS",
2190 return false;
2191 }
2192 }
2193
2194 return true;
2195}
2196
2197
2198
2199
2200
2201static bool
2203{
2206
2210
2211 if (oauth_client_secret)
2212 {
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2241
2243 {
2246 }
2247
2251
2253 }
2254 else
2255 {
2256
2257
2258
2259
2261
2264 }
2265
2267
2271
2273}
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285static bool
2287{
2291
2293 Assert(device_authz_uri);
2294
2295
2297 if (oauth_scope && oauth_scope[0])
2299
2301 return false;
2302
2304 {
2306 return false;
2307 }
2308
2309
2310 CHECK_SETOPT(actx, CURLOPT_URL, device_authz_uri, return false);
2311 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2312
2314}
2315
2316static bool
2318{
2319 long response_code;
2320
2321 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2322
2323
2324
2325
2326
2327 if (response_code == 200)
2328 {
2329 actx->errctx = "failed to parse device authorization";
2331 return false;
2332
2333 return true;
2334 }
2335
2336
2337
2338
2339
2340
2341
2342 if (response_code == 400 || response_code == 401)
2343 {
2345
2347 {
2349 return false;
2350 }
2351
2352
2354
2356 return false;
2357 }
2358
2359
2360 actx_error(actx, "unexpected response code %ld", response_code);
2361 return false;
2362}
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373static bool
2375{
2379
2381 Assert(token_uri);
2382 Assert(device_code);
2383
2384
2388
2390 return false;
2391
2393 {
2395 return false;
2396 }
2397
2398
2399 CHECK_SETOPT(actx, CURLOPT_URL, token_uri, return false);
2400 CHECK_SETOPT(actx, CURLOPT_COPYPOSTFIELDS, work_buffer->data, return false);
2401
2403}
2404
2405static bool
2407{
2408 long response_code;
2409
2410 CHECK_GETINFO(actx, CURLINFO_RESPONSE_CODE, &response_code, return false);
2411
2412
2413
2414
2415 if (response_code == 200)
2416 {
2417 actx->errctx = "failed to parse access token response";
2419 return false;
2420
2421 return true;
2422 }
2423
2424
2425
2426
2427
2428
2429
2430 if (response_code == 400 || response_code == 401)
2431 {
2433 return false;
2434
2435 return true;
2436 }
2437
2438
2439 actx_error(actx, "unexpected response code %ld", response_code);
2440 return false;
2441}
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452static bool
2454{
2456 struct token tok = {0};
2458
2460 goto token_cleanup;
2461
2462
2464
2466 {
2469
2471 goto token_cleanup;
2472 }
2473
2474
2475
2476
2477
2479 if (strcmp(err->error, "authorization_pending") != 0 &&
2480 strcmp(err->error, "slow_down") != 0)
2481 {
2483 goto token_cleanup;
2484 }
2485
2486
2487
2488
2489
2490 if (strcmp(err->error, "slow_down") == 0)
2491 {
2493
2496 {
2497 actx_error(actx, "slow_down interval overflow");
2498 goto token_cleanup;
2499 }
2500 }
2501
2503
2504token_cleanup:
2507}
2508
2509
2510
2511
2512
2513static bool
2515{
2516 int res;
2522 };
2524
2526
2527 if (!res)
2528 {
2529
2530
2531
2532
2535 }
2536 else if (res < 0)
2537 {
2538 actx_error(actx, "device prompt failed");
2539 return false;
2540 }
2541
2542 return true;
2543}
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558static bool
2560{
2561
2562
2563
2564
2565
2566
2568#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2569 curl_version_info_data *info;
2570#endif
2571
2572#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2573
2574
2575
2576
2577
2578
2579
2580
2582#endif
2583
2584
2585
2586
2587
2588
2590 goto done;
2591 else if (init_successful == PG_BOOL_NO)
2592 {
2594 "curl_global_init previously failed during OAuth setup");
2595 goto done;
2596 }
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610 if (curl_global_init(CURL_GLOBAL_ALL & ~CURL_GLOBAL_WIN32) != CURLE_OK)
2611 {
2613 "curl_global_init failed during OAuth setup");
2615 goto done;
2616 }
2617
2618#if HAVE_THREADSAFE_CURL_GLOBAL_INIT
2619
2620
2621
2622
2623
2624
2625
2626
2627 info = curl_version_info(CURLVERSION_NOW);
2628 if (!(info->features & CURL_VERSION_THREADSAFE))
2629 {
2630
2631
2632
2633
2635 "\tCurl initialization was reported thread-safe when libpq\n"
2636 "\twas compiled, but the currently installed version of\n"
2637 "\tlibcurl reports that it is not. Recompile libpq against\n"
2638 "\tthe installed version of libcurl.");
2640 goto done;
2641 }
2642#endif
2643
2645
2646done:
2647#if !HAVE_THREADSAFE_CURL_GLOBAL_INIT
2649#endif
2650 return (init_successful == PG_BOOL_YES);
2651}
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2669{
2672 char *oauth_token = NULL;
2674
2677
2678 if (->async_ctx)
2679 {
2680
2681
2682
2683
2684
2685 actx = calloc(1, sizeof(*actx));
2686 if (!actx)
2687 {
2690 }
2691
2694
2695
2697
2698 state->async_ctx = actx;
2699
2702
2704 goto error_return;
2705
2707 goto error_return;
2708 }
2709
2710 actx = state->async_ctx;
2711
2712 do
2713 {
2714
2716
2717 switch (actx->step)
2718 {
2720 break;
2721
2725 {
2727
2729
2731 goto error_return;
2733 {
2734
2735 return status;
2736 }
2737
2738 break;
2739 }
2740
2742
2743
2744
2745
2746
2747
2748
2750 {
2753 }
2754
2755
2757 goto error_return;
2758
2759 break;
2760 }
2761
2762
2763
2764
2765
2766
2767 switch (actx->step)
2768 {
2770 actx->errctx = "failed to fetch OpenID discovery document";
2772 goto error_return;
2773
2775 break;
2776
2779 goto error_return;
2780
2782 goto error_return;
2783
2784 actx->errctx = "cannot run OAuth device authorization";
2786 goto error_return;
2787
2788 actx->errctx = "failed to obtain device authorization";
2790 goto error_return;
2791
2793 break;
2794
2797 goto error_return;
2798
2799 actx->errctx = "failed to obtain access token";
2801 goto error_return;
2802
2804 break;
2805
2808 goto error_return;
2809
2810
2811
2812
2813
2815
2817 {
2818
2819
2820
2821
2823 goto error_return;
2824
2826 }
2827
2828 if (oauth_token)
2829 break;
2830
2831
2832
2833
2834
2836 goto error_return;
2837
2838
2839
2840
2841
2842
2844
2847 break;
2848
2850 actx->errctx = "failed to obtain access token";
2852 goto error_return;
2853
2855 break;
2856 }
2857
2858
2859
2860
2861
2862
2863 } while (!oauth_token && !actx->running);
2864
2865
2867
2868error_return:
2870
2871
2872
2873
2874
2877
2880 else
2882
2884 {
2886
2887
2889 {
2893 }
2894 }
2895
2897
2899}
2900
2901
2902
2903
2904
2907{
2909#ifndef WIN32
2910 sigset_t osigset;
2911 bool sigpipe_pending;
2912 bool masked;
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932 masked = (pq_block_sigpipe(&osigset, &sigpipe_pending) == 0);
2933#endif
2934
2936
2937#ifndef WIN32
2938 if (masked)
2939 {
2940
2941
2942
2943
2944 pq_reset_sigpipe(&osigset, sigpipe_pending, true );
2945 }
2946#endif
2947
2948 return result;
2949}
static void cleanup(void)
#define fprintf(file, fmt, msg)
void err(int eval, const char *fmt,...)
PQauthDataHook_type PQgetAuthDataHook(void)
int PQsocketPoll(int sock, int forRead, int forWrite, pg_usec_time_t end_time)
Assert(PointerIsAligned(start, uint64))
JsonParseErrorType pg_parse_json(JsonLexContext *lex, const JsonSemAction *sem)
JsonLexContext * makeJsonLexContextCstringLen(JsonLexContext *lex, const char *json, size_t len, int encoding, bool need_escapes)
void setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
char * json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
void freeJsonLexContext(JsonLexContext *lex)
int(* PQauthDataHook_type)(PGauthData type, PGconn *conn, void *data)
PostgresPollingStatusType
@ PQAUTHDATA_PROMPT_OAUTH_DEVICE
PostgresPollingStatusType pg_fe_run_oauth_flow(PGconn *conn)
static char * urlencode(const char *s)
static bool setup_multiplexer(struct async_ctx *actx)
static bool finish_token_request(struct async_ctx *actx, struct token *tok)
static JsonParseErrorType oauth_json_array_end(void *state)
static void append_urlencoded(PQExpBuffer buf, const char *s)
static bool start_token_request(struct async_ctx *actx, PGconn *conn)
static bool initialize_curl(PGconn *conn)
#define MAX_OAUTH_RESPONSE_SIZE
static bool parse_token_error(struct async_ctx *actx, struct token_error *err)
void pg_fe_cleanup_oauth_flow(PGconn *conn)
static bool add_client_identification(struct async_ctx *actx, PQExpBuffer reqbody, PGconn *conn)
static int parse_interval(struct async_ctx *actx, const char *interval_str)
static void free_provider(struct provider *provider)
static void build_urlencoded(PQExpBuffer buf, const char *key, const char *value)
#define conn_oauth_issuer_id(CONN)
static void record_token_error(struct async_ctx *actx, const struct token_error *err)
static bool parse_device_authz(struct async_ctx *actx, struct device_authz *authz)
static void report_type_mismatch(struct oauth_parse *ctx)
static int register_socket(CURL *curl, curl_socket_t socket, int what, void *ctx, void *socketp)
#define PG_OAUTH_OPTIONAL
static bool set_timer(struct async_ctx *actx, long timeout)
static bool parse_access_token(struct async_ctx *actx, struct token *tok)
static int timer_expired(struct async_ctx *actx)
static PostgresPollingStatusType drive_request(struct async_ctx *actx)
static bool start_device_authz(struct async_ctx *actx, PGconn *conn)
static bool prompt_user(struct async_ctx *actx, PGconn *conn)
#define CHECK_MSETOPT(ACTX, OPT, VAL, FAILACTION)
static bool finish_discovery(struct async_ctx *actx)
static double parse_json_number(const char *s)
#define conn_oauth_discovery_uri(CONN)
static bool start_discovery(struct async_ctx *actx, const char *discovery_uri)
static JsonParseErrorType oauth_json_object_field_start(void *state, char *name, bool isnull)
static JsonParseErrorType oauth_json_scalar(void *state, char *token, JsonTokenType type)
static void free_token_error(struct token_error *err)
#define actx_error_str(ACTX, S)
static bool finish_device_authz(struct async_ctx *actx)
#define conn_oauth_scope(CONN)
static size_t append_data(char *buf, size_t size, size_t nmemb, void *userdata)
#define CHECK_SETOPT(ACTX, OPT, VAL, FAILACTION)
static PostgresPollingStatusType pg_fe_run_oauth_flow_impl(PGconn *conn)
static bool parse_oauth_json(struct async_ctx *actx, const struct json_field *fields)
#define OAUTH_GRANT_TYPE_DEVICE_CODE
#define conn_oauth_client_id(CONN)
#define CURL_IGNORE_DEPRECATION(x)
static JsonParseErrorType oauth_json_array_start(void *state)
static JsonParseErrorType oauth_json_object_end(void *state)
#define set_conn_altsock(CONN, VAL)
static int debug_callback(CURL *handle, curl_infotype type, char *data, size_t size, void *clientp)
static void free_token(struct token *tok)
#define oauth_parse_set_error(ctx, fmt,...)
@ OAUTH_STEP_DEVICE_AUTHORIZATION
@ OAUTH_STEP_WAIT_INTERVAL
@ OAUTH_STEP_TOKEN_REQUEST
static int register_timer(CURLM *curlm, long timeout, void *ctx)
#define conn_oauth_client_secret(CONN)
#define set_conn_oauth_token(CONN, VAL)
#define CHECK_GETINFO(ACTX, INFO, OUT, FAILACTION)
static bool check_content_type(struct async_ctx *actx, const char *type)
static bool check_issuer(struct async_ctx *actx, PGconn *conn)
#define actx_error(ACTX, FMT,...)
static bool parse_provider(struct async_ctx *actx, struct provider *provider)
static bool start_request(struct async_ctx *actx)
static int parse_expires_in(struct async_ctx *actx, const char *expires_in_str)
static void free_async_ctx(PGconn *conn, struct async_ctx *actx)
static void free_device_authz(struct device_authz *authz)
#define conn_sasl_state(CONN)
#define conn_errorMessage(CONN)
static bool handle_token_response(struct async_ctx *actx, char **token)
static JsonParseErrorType oauth_json_object_start(void *state)
#define PG_OAUTH_REQUIRED
static bool check_for_device_flow(struct async_ctx *actx)
static bool setup_curl_handles(struct async_ctx *actx)
void pq_reset_sigpipe(sigset_t *osigset, bool sigpipe_pending, bool got_epipe)
int pq_block_sigpipe(sigset_t *osigset, bool *sigpipe_pending)
void libpq_append_conn_error(PGconn *conn, const char *fmt,...)
bool oauth_unsafe_debugging_enabled(void)
#define pgunlock_thread()
int pg_strncasecmp(const char *s1, const char *s2, size_t n)
void initPQExpBuffer(PQExpBuffer str)
void resetPQExpBuffer(PQExpBuffer str)
void appendPQExpBuffer(PQExpBuffer str, const char *fmt,...)
void appendBinaryPQExpBuffer(PQExpBuffer str, const char *data, size_t datalen)
void appendPQExpBufferChar(PQExpBuffer str, char ch)
void appendPQExpBufferStr(PQExpBuffer str, const char *data)
void termPQExpBuffer(PQExpBuffer str)
#define PQExpBufferBroken(str)
#define PQExpBufferDataBroken(buf)
json_struct_action array_end
json_struct_action object_start
json_ofield_action object_field_start
json_scalar_action scalar
json_struct_action array_start
json_struct_action object_end
const char * verification_uri
struct device_authz authz
PQExpBufferData work_data
char curl_err[CURL_ERROR_SIZE]
struct curl_slist * headers
char * verification_uri_complete
struct curl_slist ** array
union json_field::@188 target
const struct json_field * active
const struct json_field * fields
char * device_authorization_endpoint
struct curl_slist * grant_types_supported
int pg_encoding_verifymbstr(int encoding, const char *mbstr, int len)
#define socket(af, type, protocol)