/* * This function takes a function description, e.g. "x" or "x(int)", and * issues a query on the given connection to retrieve the function's OID * using a cast to regproc or regprocedure (as appropriate). The result, * if there is one, is returned at *foid. Note that we'll fail if the * function doesn't exist OR if there are multiple matching candidates * OR if there's something syntactically wrong with the function description; * unfortunately it can be hard to tell the difference. */ static bool lookup_function_oid(PGconn *conn, const char *desc, Oid *foid) { bool result = true; PQExpBuffer query; PGresult *res; query = createPQExpBuffer(); printfPQExpBuffer(query, "SELECT "); appendStringLiteralConn(query, desc, conn); appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid", strchr(desc, '(') ? "regprocedure" : "regproc"); res = PQexec(conn, query->data); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) *foid = atooid(PQgetvalue(res, 0, 0)); else { minimal_error_message(res); result = false; } PQclear(res); destroyPQExpBuffer(query); return result; }
/* * Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the * function with the given OID. If successful, the result is stored in buf. */ static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf) { bool result = true; PQExpBuffer query; PGresult *res; query = createPQExpBuffer(); printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid); res = PQexec(conn, query->data); if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1) { resetPQExpBuffer(buf); appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0)); } else { minimal_error_message(res); result = false; } PQclear(res); destroyPQExpBuffer(query); return result; }
/* * Attempt to negotiate secure session. */ PostgresPollingStatusType pqsecure_open_client(PGconn *conn) { #ifdef USE_SSL /* First time through? */ if (conn->ssl == NULL) { if (!(conn->ssl = SSL_new(SSL_context)) || !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock)) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not establish SSL connection: %s\n"), err); SSLerrfree(err); close_SSL(conn); return PGRES_POLLING_FAILED; } /* * Initialize errorMessage to empty. This allows open_client_SSL() to * detect whether client_cert_cb() has stored a message. */ resetPQExpBuffer(&conn->errorMessage); } /* Begin or continue the actual handshake */ return open_client_SSL(conn); #else /* shouldn't get here */ return PGRES_POLLING_FAILED; #endif }
/* * Dump database-specific configuration */ static void dumpDatabaseConfig(PGconn *conn, const char *dbname) { PQExpBuffer buf = createPQExpBuffer(); int count = 1; for (;;) { PGresult *res; printfPQExpBuffer(buf, "SELECT datconfig[%d] FROM pg_database WHERE datname = ", count); appendStringLiteralConn(buf, dbname, conn); appendPQExpBuffer(buf, ";"); res = executeQuery(conn, buf->data); if (!PQgetisnull(res, 0, 0)) { makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0), "DATABASE", dbname); PQclear(res); count++; } else { PQclear(res); break; } } destroyPQExpBuffer(buf); }
/* * Dump user-specific configuration */ static void dumpUserConfig(PGconn *conn, const char *username) { PQExpBuffer buf = createPQExpBuffer(); int count = 1; for (;;) { PGresult *res; printfPQExpBuffer(buf, "SELECT rolconfig[%d] FROM pg_authid WHERE rolname = ", count); appendStringLiteralConn(buf, username, conn); res = executeQuery(conn, buf->data); if (PQntuples(res) == 1 && !PQgetisnull(res, 0, 0)) { makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0), "ROLE", username); PQclear(res); count++; } else { PQclear(res); break; } } destroyPQExpBuffer(buf); }
/* * Dump user-and-database-specific configuration */ static void dumpDbRoleConfig(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); PGresult *res; int i; printfPQExpBuffer(buf, "SELECT rolname, datname, unnest(setconfig) " "FROM pg_db_role_setting, pg_authid, pg_database " "WHERE setrole = pg_authid.oid AND setdatabase = pg_database.oid"); res = executeQuery(conn, buf->data); if (PQntuples(res) > 0) { fprintf(OPF, "--\n-- Per-Database Role Settings \n--\n\n"); for (i = 0; i < PQntuples(res); i++) { makeAlterConfigCommand(conn, PQgetvalue(res, i, 2), "ROLE", PQgetvalue(res, i, 0), "DATABASE", PQgetvalue(res, i, 1)); } fprintf(OPF, "\n\n"); } PQclear(res); destroyPQExpBuffer(buf); }
/* * Dump group memberships from a pre-8.1 server. It's annoying that we * can't share any useful amount of code with the post-8.1 case, but * the catalog representations are too different. * * Note: we expect dumpRoles already created all the roles, but there is * no membership yet. */ static void dumpGroups(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); PGresult *res; int i; res = executeQuery(conn, "SELECT groname, grolist FROM pg_group ORDER BY 1"); if (PQntuples(res) > 0) fprintf(OPF, "--\n-- Role memberships\n--\n\n"); for (i = 0; i < PQntuples(res); i++) { char *groname = PQgetvalue(res, i, 0); char *grolist = PQgetvalue(res, i, 1); PGresult *res2; int j; /* * Array representation is {1,2,3} ... convert to (1,2,3) */ if (strlen(grolist) < 3) continue; grolist = strdup(grolist); grolist[0] = '('; grolist[strlen(grolist) - 1] = ')'; printfPQExpBuffer(buf, "SELECT usename FROM pg_shadow " "WHERE usesysid IN %s ORDER BY 1", grolist); free(grolist); res2 = executeQuery(conn, buf->data); for (j = 0; j < PQntuples(res2); j++) { char *usename = PQgetvalue(res2, j, 0); /* * Don't try to grant a role to itself; can happen if old * installation has identically named user and group. */ if (strcmp(groname, usename) == 0) continue; fprintf(OPF, "GRANT %s", fmtId(groname)); fprintf(OPF, " TO %s;\n", fmtId(usename)); } PQclear(res2); } PQclear(res); destroyPQExpBuffer(buf); fprintf(OPF, "\n\n"); }
/* * Make sure conn's output buffer can hold bytes_needed bytes (caller must * include already-stored data into the value!) * * Returns 0 on success, EOF if failed to enlarge buffer */ int pqCheckOutBufferSpace(size_t bytes_needed, PGconn *conn) { int newsize = conn->outBufSize; char *newbuf; /* Quick exit if we have enough space */ if (bytes_needed <= (size_t) newsize) return 0; /* * If we need to enlarge the buffer, we first try to double it in size; if * that doesn't work, enlarge in multiples of 8K. This avoids thrashing * the malloc pool by repeated small enlargements. * * Note: tests for newsize > 0 are to catch integer overflow. */ do { newsize *= 2; } while (newsize > 0 && bytes_needed > (size_t) newsize); if (newsize > 0 && bytes_needed <= (size_t) newsize) { newbuf = realloc(conn->outBuffer, newsize); if (newbuf) { /* realloc succeeded */ conn->outBuffer = newbuf; conn->outBufSize = newsize; return 0; } } newsize = conn->outBufSize; do { newsize += 8192; } while (newsize > 0 && bytes_needed > (size_t) newsize); if (newsize > 0 && bytes_needed <= (size_t) newsize) { newbuf = realloc(conn->outBuffer, newsize); if (newbuf) { /* realloc succeeded */ conn->outBuffer = newbuf; conn->outBufSize = newsize; return 0; } } /* realloc failed. Probably out of memory */ printfPQExpBuffer(&conn->errorMessage, "cannot allocate memory for output buffer\n"); return EOF; }
static int pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq) { int ret; char *crypt_pwd = NULL; const char *pwd_to_send; /* Encrypt the password if needed. */ switch (areq) { case AUTH_REQ_MD5: { char *crypt_pwd2; /* Allocate enough space for two MD5 hashes */ crypt_pwd = malloc(2 * (MD5_PASSWD_LEN + 1)); if (!crypt_pwd) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } crypt_pwd2 = crypt_pwd + MD5_PASSWD_LEN + 1; if (!pg_md5_encrypt(password, conn->pguser, strlen(conn->pguser), crypt_pwd2)) { free(crypt_pwd); return STATUS_ERROR; } if (!pg_md5_encrypt(crypt_pwd2 + strlen("md5"), conn->md5Salt, sizeof(conn->md5Salt), crypt_pwd)) { free(crypt_pwd); return STATUS_ERROR; } pwd_to_send = crypt_pwd; break; } case AUTH_REQ_PASSWORD: pwd_to_send = password; break; default: return STATUS_ERROR; } /* Packet has a message type as of protocol 3.0 */ if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3) ret = pqPacketSend(conn, 'p', pwd_to_send, strlen(pwd_to_send) + 1); else ret = pqPacketSend(conn, 0, pwd_to_send, strlen(pwd_to_send) + 1); if (crypt_pwd) free(crypt_pwd); return ret; }
/* * Dump user-specific configuration */ static void dumpUserConfig(PGconn *conn, const char *username) { PQExpBuffer buf = createPQExpBuffer(); int count = 1; for (;;) { PGresult *res; if (server_version >= 90000) printfPQExpBuffer(buf, "SELECT setconfig[%d] FROM pg_db_role_setting WHERE " "setdatabase = 0 AND setrole = " "(SELECT oid FROM pg_authid WHERE rolname = ", count); else if (server_version >= 80100) printfPQExpBuffer(buf, "SELECT rolconfig[%d] FROM pg_authid WHERE rolname = ", count); else printfPQExpBuffer(buf, "SELECT useconfig[%d] FROM pg_shadow WHERE usename = ", count); appendStringLiteralConn(buf, username, conn); if (server_version >= 90000) appendPQExpBuffer(buf, ")"); res = executeQuery(conn, buf->data); if (PQntuples(res) == 1 && !PQgetisnull(res, 0, 0)) { makeAlterConfigCommand(conn, PQgetvalue(res, 0, 0), "ROLE", username, NULL, NULL); PQclear(res); count++; } else { PQclear(res); break; } } destroyPQExpBuffer(buf); }
/* * Checks a socket, using poll or select, for data to be read, written, * or both. Returns >0 if one or more conditions are met, 0 if it timed * out, -1 if an error occurred. * * If SSL is in use, the SSL buffer is checked prior to checking the socket * for read data directly. */ static int pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time) { int result; if (!conn) return -1; if (conn->sock < 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("socket not open\n")); return -1; } #ifdef USE_SSL /* Check for SSL library buffering read bytes */ if (forRead && conn->ssl && SSL_pending(conn->ssl) > 0) { /* short-circuit the select */ return 1; } #endif /* We will retry as long as we get EINTR */ do result = pqSocketPoll(conn->sock, forRead, forWrite, end_time); while (result < 0 && SOCK_ERRNO == EINTR); if (result < 0) { char sebuf[256]; printfPQExpBuffer(&conn->errorMessage, libpq_gettext("select() failed: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); } return result; }
/* * Report just the primary error; this is to avoid cluttering the output * with, for instance, a redisplay of the internally generated query */ static void minimal_error_message(PGresult *res) { PQExpBuffer msg; const char *fld; msg = createPQExpBuffer(); fld = PQresultErrorField(res, PG_DIAG_SEVERITY); if (fld) printfPQExpBuffer(msg, "%s: ", fld); else printfPQExpBuffer(msg, "ERROR: "); fld = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY); if (fld) appendPQExpBufferStr(msg, fld); else appendPQExpBufferStr(msg, "(not available)"); appendPQExpBufferStr(msg, "\n"); psql_error("%s", msg->data); destroyPQExpBuffer(msg); }
/* * pgWaitTimeout: wait, but not past finish_time. * wrapper for pqSocketCheck. * * finish_time = ((time_t) -1) disables the wait limit. */ int pqWaitTimeout(int forRead, int forWrite, PGconn *conn, time_t finish_time) { int result; result = pqSocketCheck(conn, forRead, forWrite, finish_time); if (result == 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("timeout expired\n")); } return result; }
/* * handleSyncLoss: clean up after loss of message-boundary sync * * There isn't really a lot we can do here except abandon the connection. */ static void handleSyncLoss(PGconn *conn, char id, int msgLength) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "lost synchronization with server: got message type \"%c\", length %d\n"), id, msgLength); /* build an error result holding the error message */ pqSaveErrorResult(conn); conn->asyncStatus = PGASYNC_READY; /* drop out of GetResult wait loop */ pqsecure_close(conn); closesocket(conn->sock); conn->sock = -1; conn->status = CONNECTION_BAD; /* No more connection to backend */ }
/* * PQgetline - gets a newline-terminated string from the backend. * * See fe-exec.c for documentation. */ int pqGetline3(PGconn *conn, char *s, int maxlen) { int status; if (conn->sock < 0 || conn->asyncStatus != PGASYNC_COPY_OUT || conn->copy_is_binary) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("PQgetline: not doing text COPY OUT\n")); *s = '\0'; return EOF; } while ((status = PQgetlineAsync(conn, s, maxlen - 1)) == 0) { /* need to load more data */ if (pqWait(TRUE, FALSE, conn) || pqReadData(conn) < 0) { *s = '\0'; return EOF; } } if (status < 0) { /* End of copy detected; gin up old-style terminator */ strcpy(s, "\\."); return 0; } /* Add null terminator, and strip trailing \n if present */ if (s[status - 1] == '\n') { s[status - 1] = '\0'; return 0; } else { s[status] = '\0'; return 1; } }
/* * lo_truncate * truncates an existing large object to the given size * * returns 0 upon success * returns -1 upon failure */ int lo_truncate(PGconn *conn, int fd, size_t len) { PQArgBlock argv[2]; PGresult *res; int retval; int result_len; if (conn->lobjfuncs == NULL) { if (lo_initialize(conn) < 0) return -1; } /* Must check this on-the-fly because it's not there pre-8.3 */ if (conn->lobjfuncs->fn_lo_truncate == 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("cannot determine OID of function lo_truncate\n")); return -1; } argv[0].isint = 1; argv[0].len = 4; argv[0].u.integer = fd; argv[1].isint = 1; argv[1].len = 4; argv[1].u.integer = len; res = PQfn(conn, conn->lobjfuncs->fn_lo_truncate, &retval, &result_len, 1, argv, 2); if (PQresultStatus(res) == PGRES_COMMAND_OK) { PQclear(res); return retval; } else { PQclear(res); return -1; } }
/* * Begin or continue negotiating a secure session. */ PostgresPollingStatusType pqsecure_open_client(PGconn *conn) { #ifdef USE_SSL /* First time through? */ if (conn->ssl == NULL) { /* We cannot use MSG_NOSIGNAL to block SIGPIPE when using SSL */ conn->sigpipe_flag = false; /* Create a connection-specific SSL object */ if (!(conn->ssl = SSL_new(SSL_context)) || !SSL_set_app_data(conn->ssl, conn) || !SSL_set_fd(conn->ssl, conn->sock)) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not establish SSL connection: %s\n"), err); SSLerrfree(err); close_SSL(conn); return PGRES_POLLING_FAILED; } /* * Load client certificate, private key, and trusted CA certs. */ if (initialize_SSL(conn) != 0) { /* initialize_SSL already put a message in conn->errorMessage */ close_SSL(conn); return PGRES_POLLING_FAILED; } } /* Begin or continue the actual handshake */ return open_client_SSL(conn); #else /* shouldn't get here */ return PGRES_POLLING_FAILED; #endif }
/* * pqWaitTimed: wait, but not past finish_time. * * finish_time = ((time_t) -1) disables the wait limit. * * Returns -1 on failure, 0 if the socket is readable/writable, 1 if it timed out. */ int pqWaitTimed(int forRead, int forWrite, PGconn *conn, time_t finish_time) { int result; result = pqSocketCheck(conn, forRead, forWrite, finish_time); if (result < 0) return -1; /* errorMessage is already set */ if (result == 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("timeout expired\n")); return 1; } return 0; }
/* * Initialize global SSL context. */ static int initialize_SSL(PGconn *conn) { struct stat buf; char homedir[MAXPGPATH]; char fnbuf[MAXPGPATH]; if (init_ssl_system(conn)) return -1; /* Set up to verify server cert, if root.crt is present */ if (pqGetHomeDirectory(homedir, sizeof(homedir))) { snprintf(fnbuf, sizeof(fnbuf), "%s/%s", homedir, ROOTCERTFILE); if (stat(fnbuf, &buf) == 0) { if (!SSL_CTX_load_verify_locations(SSL_context, fnbuf, NULL)) { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not read root certificate file \"%s\": %s\n"), fnbuf, err); SSLerrfree(err); return -1; } SSL_CTX_set_verify(SSL_context, SSL_VERIFY_PEER, verify_cb); } } /* set up empheral DH keys */ SSL_CTX_set_tmp_dh_callback(SSL_context, tmp_dh_cb); SSL_CTX_set_options(SSL_context, SSL_OP_SINGLE_DH_USE); /* set up mechanism to provide client certificate, if available */ SSL_CTX_set_client_cert_cb(SSL_context, client_cert_cb); return 0; }
/* * Dump roles */ static void dumpRoles(PGconn *conn) { PQExpBuffer buf = createPQExpBuffer(); PGresult *res; int i_rolname, i_rolsuper, i_rolinherit, i_rolcreaterole, i_rolcreatedb, i_rolcatupdate, i_rolcanlogin, i_rolconnlimit, i_rolpassword, i_rolvaliduntil; int i; /* note: rolconfig is dumped later */ if (server_version >= 80100) printfPQExpBuffer(buf, "SELECT rolname, rolsuper, rolinherit, " "rolcreaterole, rolcreatedb, rolcatupdate, " "rolcanlogin, rolconnlimit, rolpassword, " "rolvaliduntil " "FROM pg_authid " "ORDER BY 1"); else printfPQExpBuffer(buf, "SELECT usename as rolname, " "usesuper as rolsuper, " "true as rolinherit, " "usesuper as rolcreaterole, " "usecreatedb as rolcreatedb, " "usecatupd as rolcatupdate, " "true as rolcanlogin, " "-1 as rolconnlimit, " "passwd as rolpassword, " "valuntil as rolvaliduntil " "FROM pg_shadow " "UNION ALL " "SELECT groname as rolname, " "false as rolsuper, " "true as rolinherit, " "false as rolcreaterole, " "false as rolcreatedb, " "false as rolcatupdate, " "false as rolcanlogin, " "-1 as rolconnlimit, " "null::text as rolpassword, " "null::abstime as rolvaliduntil " "FROM pg_group " "WHERE NOT EXISTS (SELECT 1 FROM pg_shadow " " WHERE usename = groname) " "ORDER BY 1"); res = executeQuery(conn, buf->data); i_rolname = PQfnumber(res, "rolname"); i_rolsuper = PQfnumber(res, "rolsuper"); i_rolinherit = PQfnumber(res, "rolinherit"); i_rolcreaterole = PQfnumber(res, "rolcreaterole"); i_rolcreatedb = PQfnumber(res, "rolcreatedb"); i_rolcatupdate = PQfnumber(res, "rolcatupdate"); i_rolcanlogin = PQfnumber(res, "rolcanlogin"); i_rolconnlimit = PQfnumber(res, "rolconnlimit"); i_rolpassword = PQfnumber(res, "rolpassword"); i_rolvaliduntil = PQfnumber(res, "rolvaliduntil"); if (PQntuples(res) > 0) printf("--\n-- Roles\n--\n\n"); for (i = 0; i < PQntuples(res); i++) { const char *rolename; rolename = PQgetvalue(res, i, i_rolname); resetPQExpBuffer(buf); if (output_clean) appendPQExpBuffer(buf, "DROP ROLE %s;\n", fmtId(rolename)); /* * We dump CREATE ROLE followed by ALTER ROLE to ensure that the role * will acquire the right properties even if it already exists. (The * above DROP may therefore seem redundant, but it isn't really, * because this technique doesn't get rid of role memberships.) */ appendPQExpBuffer(buf, "CREATE ROLE %s;\n", fmtId(rolename)); appendPQExpBuffer(buf, "ALTER ROLE %s WITH", fmtId(rolename)); if (strcmp(PQgetvalue(res, i, i_rolsuper), "t") == 0) appendPQExpBuffer(buf, " SUPERUSER"); else appendPQExpBuffer(buf, " NOSUPERUSER"); if (strcmp(PQgetvalue(res, i, i_rolinherit), "t") == 0) appendPQExpBuffer(buf, " INHERIT"); else appendPQExpBuffer(buf, " NOINHERIT"); if (strcmp(PQgetvalue(res, i, i_rolcreaterole), "t") == 0) appendPQExpBuffer(buf, " CREATEROLE"); else appendPQExpBuffer(buf, " NOCREATEROLE"); if (strcmp(PQgetvalue(res, i, i_rolcreatedb), "t") == 0) appendPQExpBuffer(buf, " CREATEDB"); else appendPQExpBuffer(buf, " NOCREATEDB"); if (strcmp(PQgetvalue(res, i, i_rolcanlogin), "t") == 0) appendPQExpBuffer(buf, " LOGIN"); else appendPQExpBuffer(buf, " NOLOGIN"); if (strcmp(PQgetvalue(res, i, i_rolconnlimit), "-1") != 0) appendPQExpBuffer(buf, " CONNECTION LIMIT %s", PQgetvalue(res, i, i_rolconnlimit)); if (!PQgetisnull(res, i, i_rolpassword)) { appendPQExpBuffer(buf, " PASSWORD "); appendStringLiteral(buf, PQgetvalue(res, i, i_rolpassword), true); } if (!PQgetisnull(res, i, i_rolvaliduntil)) appendPQExpBuffer(buf, " VALID UNTIL '%s'", PQgetvalue(res, i, i_rolvaliduntil)); appendPQExpBuffer(buf, ";\n"); printf("%s", buf->data); if (server_version >= 70300) dumpUserConfig(conn, rolename); } PQclear(res); printf("\n\n"); destroyPQExpBuffer(buf); }
/* * This will parse an aclitem string, having the general form * username=privilegecodes/grantor * or * group groupname=privilegecodes/grantor * (the /grantor part will not be present if pre-7.4 database). * * The returned grantee string will be the dequoted username or groupname * (preceded with "group " in the latter case). The returned grantor is * the dequoted grantor name or empty. Privilege characters are decoded * and split between privileges with grant option (privswgo) and without * (privs). * * Note: for cross-version compatibility, it's important to use ALL when * appropriate. */ static bool parseAclItem(const char *item, const char *type, const char *name, const char *subname, int remoteVersion, PQExpBuffer grantee, PQExpBuffer grantor, PQExpBuffer privs, PQExpBuffer privswgo) { char *buf; bool all_with_go = true; bool all_without_go = true; char *eqpos; char *slpos; char *pos; buf = strdup(item); if (!buf) return false; /* user or group name is string up to = */ eqpos = copyAclUserName(grantee, buf); if (*eqpos != '=') return false; /* grantor may be listed after / */ slpos = strchr(eqpos + 1, '/'); if (slpos) { *slpos++ = '\0'; slpos = copyAclUserName(grantor, slpos); if (*slpos != '\0') return false; } else resetPQExpBuffer(grantor); /* privilege codes */ #define CONVERT_PRIV(code, keywd) \ do { \ if ((pos = strchr(eqpos + 1, code))) \ { \ if (*(pos + 1) == '*') \ { \ AddAcl(privswgo, keywd, subname); \ all_without_go = false; \ } \ else \ { \ AddAcl(privs, keywd, subname); \ all_with_go = false; \ } \ } \ else \ all_with_go = all_without_go = false; \ } while (0) resetPQExpBuffer(privs); resetPQExpBuffer(privswgo); if (strcmp(type, "TABLE") == 0 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "TABLES") == 0 || strcmp(type, "SEQUENCES") == 0) { CONVERT_PRIV('r', "SELECT"); if (strcmp(type, "SEQUENCE") == 0 || strcmp(type, "SEQUENCES") == 0) /* sequence only */ CONVERT_PRIV('U', "USAGE"); else { /* table only */ CONVERT_PRIV('a', "INSERT"); if (remoteVersion >= 70200) CONVERT_PRIV('x', "REFERENCES"); /* rest are not applicable to columns */ if (subname == NULL) { if (remoteVersion >= 70200) { CONVERT_PRIV('d', "DELETE"); CONVERT_PRIV('t', "TRIGGER"); } if (remoteVersion >= 80400) CONVERT_PRIV('D', "TRUNCATE"); } } /* UPDATE */ if (remoteVersion >= 70200 || strcmp(type, "SEQUENCE") == 0 || strcmp(type, "SEQUENCES") == 0) CONVERT_PRIV('w', "UPDATE"); else /* 7.0 and 7.1 have a simpler worldview */ CONVERT_PRIV('w', "UPDATE,DELETE"); } else if (strcmp(type, "FUNCTION") == 0 || strcmp(type, "FUNCTIONS") == 0) CONVERT_PRIV('X', "EXECUTE"); else if (strcmp(type, "LANGUAGE") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "SCHEMA") == 0) { CONVERT_PRIV('C', "CREATE"); CONVERT_PRIV('U', "USAGE"); } else if (strcmp(type, "DATABASE") == 0) { CONVERT_PRIV('C', "CREATE"); CONVERT_PRIV('c', "CONNECT"); CONVERT_PRIV('T', "TEMPORARY"); } else if (strcmp(type, "TABLESPACE") == 0) CONVERT_PRIV('C', "CREATE"); else if (strcmp(type, "FOREIGN DATA WRAPPER") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "FOREIGN SERVER") == 0) CONVERT_PRIV('U', "USAGE"); else if (strcmp(type, "FOREIGN TABLE") == 0) CONVERT_PRIV('r', "SELECT"); else if (strcmp(type, "LARGE OBJECT") == 0) { CONVERT_PRIV('r', "SELECT"); CONVERT_PRIV('w', "UPDATE"); } else abort(); #undef CONVERT_PRIV if (all_with_go) { resetPQExpBuffer(privs); printfPQExpBuffer(privswgo, "ALL"); if (subname) appendPQExpBuffer(privswgo, "(%s)", subname); } else if (all_without_go) { resetPQExpBuffer(privswgo); printfPQExpBuffer(privs, "ALL"); if (subname) appendPQExpBuffer(privs, "(%s)", subname); } free(buf); return true; }
/* * Build GRANT/REVOKE command(s) for an object. * * name: the object name, in the form to use in the commands (already quoted) * subname: the sub-object name, if any (already quoted); NULL if none * type: the object type (as seen in GRANT command: must be one of * TABLE, SEQUENCE, FUNCTION, LANGUAGE, SCHEMA, DATABASE, TABLESPACE, * FOREIGN DATA WRAPPER, SERVER, or LARGE OBJECT) * acls: the ACL string fetched from the database * owner: username of object owner (will be passed through fmtId); can be * NULL or empty string to indicate "no owner known" * prefix: string to prefix to each generated command; typically empty * remoteVersion: version of database * * Returns TRUE if okay, FALSE if could not parse the acl string. * The resulting commands (if any) are appended to the contents of 'sql'. * * Note: when processing a default ACL, prefix is "ALTER DEFAULT PRIVILEGES " * or something similar, and name is an empty string. * * Note: beware of passing a fmtId() result directly as 'name' or 'subname', * since this routine uses fmtId() internally. */ bool buildACLCommands(const char *name, const char *subname, const char *type, const char *acls, const char *owner, const char *prefix, int remoteVersion, PQExpBuffer sql) { char **aclitems; int naclitems; int i; PQExpBuffer grantee, grantor, privs, privswgo; PQExpBuffer firstsql, secondsql; bool found_owner_privs = false; if (strlen(acls) == 0) return true; /* object has default permissions */ /* treat empty-string owner same as NULL */ if (owner && *owner == '\0') owner = NULL; if (!parsePGArray(acls, &aclitems, &naclitems)) { if (aclitems) free(aclitems); return false; } grantee = createPQExpBuffer(); grantor = createPQExpBuffer(); privs = createPQExpBuffer(); privswgo = createPQExpBuffer(); /* * At the end, these two will be pasted together to form the result. But * the owner privileges need to go before the other ones to keep the * dependencies valid. In recent versions this is normally the case, but * in old versions they come after the PUBLIC privileges and that results * in problems if we need to run REVOKE on the owner privileges. */ firstsql = createPQExpBuffer(); secondsql = createPQExpBuffer(); /* * Always start with REVOKE ALL FROM PUBLIC, so that we don't have to * wire-in knowledge about the default public privileges for different * kinds of objects. */ appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM PUBLIC;\n", type, name); /* * We still need some hacking though to cover the case where new default * public privileges are added in new versions: the REVOKE ALL will revoke * them, leading to behavior different from what the old version had, * which is generally not what's wanted. So add back default privs if the * source database is too old to have had that particular priv. */ if (remoteVersion < 80200 && strcmp(type, "DATABASE") == 0) { /* database CONNECT priv didn't exist before 8.2 */ appendPQExpBuffer(firstsql, "%sGRANT CONNECT ON %s %s TO PUBLIC;\n", prefix, type, name); } /* Scan individual ACL items */ for (i = 0; i < naclitems; i++) { if (!parseAclItem(aclitems[i], type, name, subname, remoteVersion, grantee, grantor, privs, privswgo)) return false; if (grantor->len == 0 && owner) printfPQExpBuffer(grantor, "%s", owner); if (privs->len > 0 || privswgo->len > 0) { if (owner && strcmp(grantee->data, owner) == 0 && strcmp(grantor->data, owner) == 0) { found_owner_privs = true; /* * For the owner, the default privilege level is ALL WITH * GRANT OPTION (only ALL prior to 7.4). */ if (supports_grant_options(remoteVersion) ? strcmp(privswgo->data, "ALL") != 0 : strcmp(privs->data, "ALL") != 0) { appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", type, name, fmtId(grantee->data)); if (privs->len > 0) appendPQExpBuffer(firstsql, "%sGRANT %s ON %s %s TO %s;\n", prefix, privs->data, type, name, fmtId(grantee->data)); if (privswgo->len > 0) appendPQExpBuffer(firstsql, "%sGRANT %s ON %s %s TO %s WITH GRANT OPTION;\n", prefix, privswgo->data, type, name, fmtId(grantee->data)); } } else { /* * Otherwise can assume we are starting from no privs. */ if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) appendPQExpBuffer(secondsql, "SET SESSION AUTHORIZATION %s;\n", fmtId(grantor->data)); if (privs->len > 0) { appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", prefix, privs->data, type, name); if (grantee->len == 0) appendPQExpBuffer(secondsql, "PUBLIC;\n"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) appendPQExpBuffer(secondsql, "GROUP %s;\n", fmtId(grantee->data + strlen("group "))); else appendPQExpBuffer(secondsql, "%s;\n", fmtId(grantee->data)); } if (privswgo->len > 0) { appendPQExpBuffer(secondsql, "%sGRANT %s ON %s %s TO ", prefix, privswgo->data, type, name); if (grantee->len == 0) appendPQExpBuffer(secondsql, "PUBLIC"); else if (strncmp(grantee->data, "group ", strlen("group ")) == 0) appendPQExpBuffer(secondsql, "GROUP %s", fmtId(grantee->data + strlen("group "))); else appendPQExpBuffer(secondsql, "%s", fmtId(grantee->data)); appendPQExpBuffer(secondsql, " WITH GRANT OPTION;\n"); } if (grantor->len > 0 && (!owner || strcmp(owner, grantor->data) != 0)) appendPQExpBuffer(secondsql, "RESET SESSION AUTHORIZATION;\n"); } } } /* * If we didn't find any owner privs, the owner must have revoked 'em all */ if (!found_owner_privs && owner) { appendPQExpBuffer(firstsql, "%sREVOKE ALL", prefix); if (subname) appendPQExpBuffer(firstsql, "(%s)", subname); appendPQExpBuffer(firstsql, " ON %s %s FROM %s;\n", type, name, fmtId(owner)); } destroyPQExpBuffer(grantee); destroyPQExpBuffer(grantor); destroyPQExpBuffer(privs); destroyPQExpBuffer(privswgo); appendPQExpBuffer(sql, "%s%s", firstsql->data, secondsql->data); destroyPQExpBuffer(firstsql); destroyPQExpBuffer(secondsql); free(aclitems); return true; }
/* * pg_fe_sendauth * client demux routine for outgoing authentication information */ int pg_fe_sendauth(AuthRequest areq, PGconn *conn) { switch (areq) { case AUTH_REQ_OK: break; case AUTH_REQ_KRB4: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Kerberos 4 authentication not supported\n")); return STATUS_ERROR; case AUTH_REQ_KRB5: #ifdef KRB5 pglock_thread(); if (pg_krb5_sendauth(conn) != STATUS_OK) { /* Error message already filled in */ pgunlock_thread(); return STATUS_ERROR; } pgunlock_thread(); break; #else printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Kerberos 5 authentication not supported\n")); return STATUS_ERROR; #endif #if defined(ENABLE_GSS) || defined(ENABLE_SSPI) case AUTH_REQ_GSS: { int r; pglock_thread(); /* * If we have both GSS and SSPI support compiled in, use SSPI * support by default. This is overridable by a connection * string parameter. Note that when using SSPI we still leave * the negotiate parameter off, since we want SSPI to use the * GSSAPI kerberos protocol. For actual SSPI negotiate * protocol, we use AUTH_REQ_SSPI. */ #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->gsslib && (pg_strcasecmp(conn->gsslib, "gssapi") == 0)) r = pg_GSS_startup(conn); else r = pg_SSPI_startup(conn, 0); #elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI) r = pg_GSS_startup(conn); #elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI) r = pg_SSPI_startup(conn, 0); #endif if (r != STATUS_OK) { /* Error message already filled in. */ pgunlock_thread(); return STATUS_ERROR; } pgunlock_thread(); } break; case AUTH_REQ_GSS_CONT: { int r; pglock_thread(); #if defined(ENABLE_GSS) && defined(ENABLE_SSPI) if (conn->usesspi) r = pg_SSPI_continue(conn); else r = pg_GSS_continue(conn); #elif defined(ENABLE_GSS) && !defined(ENABLE_SSPI) r = pg_GSS_continue(conn); #elif !defined(ENABLE_GSS) && defined(ENABLE_SSPI) r = pg_SSPI_continue(conn); #endif if (r != STATUS_OK) { /* Error message already filled in. */ pgunlock_thread(); return STATUS_ERROR; } pgunlock_thread(); } break; #else case AUTH_REQ_GSS: case AUTH_REQ_GSS_CONT: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("GSSAPI authentication not supported\n")); return STATUS_ERROR; #endif #ifdef ENABLE_SSPI case AUTH_REQ_SSPI: /* * SSPI has it's own startup message so libpq can decide which * method to use. Indicate to pg_SSPI_startup that we want SSPI * negotiation instead of Kerberos. */ pglock_thread(); if (pg_SSPI_startup(conn, 1) != STATUS_OK) { /* Error message already filled in. */ pgunlock_thread(); return STATUS_ERROR; } pgunlock_thread(); break; #else case AUTH_REQ_SSPI: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSPI authentication not supported\n")); return STATUS_ERROR; #endif case AUTH_REQ_CRYPT: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Crypt authentication not supported\n")); return STATUS_ERROR; case AUTH_REQ_MD5: case AUTH_REQ_PASSWORD: conn->password_needed = true; if (conn->pgpass == NULL || conn->pgpass[0] == '\0') { printfPQExpBuffer(&conn->errorMessage, PQnoPasswordSupplied); return STATUS_ERROR; } if (pg_password_sendauth(conn, conn->pgpass, areq) != STATUS_OK) { printfPQExpBuffer(&conn->errorMessage, "fe_sendauth: error sending password authentication\n"); return STATUS_ERROR; } break; case AUTH_REQ_SCM_CREDS: if (pg_local_sendauth(conn) != STATUS_OK) return STATUS_ERROR; break; default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("authentication method %u not supported\n"), areq); return STATUS_ERROR; } return STATUS_OK; }
/* * Send initial SSPI authentication token. * If use_negotiate is 0, use kerberos authentication package which is * compatible with Unix. If use_negotiate is 1, use the negotiate package * which supports both kerberos and NTLM, but is not compatible with Unix. */ static int pg_SSPI_startup(PGconn *conn, int use_negotiate) { SECURITY_STATUS r; TimeStamp expire; conn->sspictx = NULL; /* * Retreive credentials handle */ conn->sspicred = malloc(sizeof(CredHandle)); if (conn->sspicred == NULL) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } r = AcquireCredentialsHandle(NULL, use_negotiate ? "negotiate" : "kerberos", SECPKG_CRED_OUTBOUND, NULL, NULL, NULL, NULL, conn->sspicred, &expire); if (r != SEC_E_OK) { pg_SSPI_error(conn, libpq_gettext("could not acquire SSPI credentials"), r); free(conn->sspicred); conn->sspicred = NULL; return STATUS_ERROR; } /* * Compute target principal name. SSPI has a different format from GSSAPI, * but not more complex. We can skip the @REALM part, because Windows will * fill that in for us automatically. */ if (!(conn->pghost && conn->pghost[0] != '\0')) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("host name must be specified\n")); return STATUS_ERROR; } conn->sspitarget = malloc(strlen(conn->krbsrvname) + strlen(conn->pghost) + 2); if (!conn->sspitarget) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } sprintf(conn->sspitarget, "%s/%s", conn->krbsrvname, conn->pghost); /* * Indicate that we're in SSPI authentication mode to make sure that * pg_SSPI_continue is called next time in the negotiation. */ conn->usesspi = 1; return pg_SSPI_continue(conn); }
/* * Continue SSPI authentication with next token as needed. */ static int pg_SSPI_continue(PGconn *conn) { SECURITY_STATUS r; CtxtHandle newContext; ULONG contextAttr; SecBufferDesc inbuf; SecBufferDesc outbuf; SecBuffer OutBuffers[1]; SecBuffer InBuffers[1]; if (conn->sspictx != NULL) { /* * On runs other than the first we have some data to send. Put this * data in a SecBuffer type structure. */ inbuf.ulVersion = SECBUFFER_VERSION; inbuf.cBuffers = 1; inbuf.pBuffers = InBuffers; InBuffers[0].pvBuffer = conn->ginbuf.value; InBuffers[0].cbBuffer = conn->ginbuf.length; InBuffers[0].BufferType = SECBUFFER_TOKEN; } OutBuffers[0].pvBuffer = NULL; OutBuffers[0].BufferType = SECBUFFER_TOKEN; OutBuffers[0].cbBuffer = 0; outbuf.cBuffers = 1; outbuf.pBuffers = OutBuffers; outbuf.ulVersion = SECBUFFER_VERSION; r = InitializeSecurityContext(conn->sspicred, conn->sspictx, conn->sspitarget, ISC_REQ_ALLOCATE_MEMORY, 0, SECURITY_NETWORK_DREP, (conn->sspictx == NULL) ? NULL : &inbuf, 0, &newContext, &outbuf, &contextAttr, NULL); if (r != SEC_E_OK && r != SEC_I_CONTINUE_NEEDED) { pg_SSPI_error(conn, libpq_gettext("SSPI continuation error"), r); return STATUS_ERROR; } if (conn->sspictx == NULL) { /* On first run, transfer retreived context handle */ conn->sspictx = malloc(sizeof(CtxtHandle)); if (conn->sspictx == NULL) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("out of memory\n")); return STATUS_ERROR; } memcpy(conn->sspictx, &newContext, sizeof(CtxtHandle)); } else { /* * On subsequent runs when we had data to send, free buffers that * contained this data. */ free(conn->ginbuf.value); conn->ginbuf.value = NULL; conn->ginbuf.length = 0; } /* * If SSPI returned any data to be sent to the server (as it normally * would), send this data as a password packet. */ if (outbuf.cBuffers > 0) { if (outbuf.cBuffers != 1) { /* * This should never happen, at least not for Kerberos * authentication. Keep check in case it shows up with other * authentication methods later. */ printfPQExpBuffer(&conn->errorMessage, "SSPI returned invalid number of output buffers\n"); return STATUS_ERROR; } /* * If the negotiation is complete, there may be zero bytes to send. * The server is at this point not expecting any more data, so don't * send it. */ if (outbuf.pBuffers[0].cbBuffer > 0) { if (pqPacketSend(conn, 'p', outbuf.pBuffers[0].pvBuffer, outbuf.pBuffers[0].cbBuffer)) { FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); return STATUS_ERROR; } } FreeContextBuffer(outbuf.pBuffers[0].pvBuffer); } /* Cleanup is handled by the code in freePGconn() */ return STATUS_OK; }
/* * pg_krb5_sendauth -- client routine to send authentication information to * the server */ static int pg_krb5_sendauth(PGconn *conn) { krb5_error_code retval; int ret; krb5_principal server; krb5_auth_context auth_context = NULL; krb5_error *err_ret = NULL; struct krb5_info info; info.pg_krb5_initialised = 0; if (!(conn->pghost && conn->pghost[0] != '\0')) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("host name must be specified\n")); return STATUS_ERROR; } ret = pg_krb5_init(&conn->errorMessage, &info); if (ret != STATUS_OK) return ret; retval = krb5_sname_to_principal(info.pg_krb5_context, conn->pghost, conn->krbsrvname, KRB5_NT_SRV_HST, &server); if (retval) { printfPQExpBuffer(&conn->errorMessage, "pg_krb5_sendauth: krb5_sname_to_principal: %s\n", error_message(retval)); pg_krb5_destroy(&info); return STATUS_ERROR; } /* * libpq uses a non-blocking socket. But kerberos needs a blocking socket, * and we have to block somehow to do mutual authentication anyway. So we * temporarily make it blocking. */ if (!pg_set_block(conn->sock)) { char sebuf[256]; printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not set socket to blocking mode: %s\n"), pqStrerror(errno, sebuf, sizeof(sebuf))); krb5_free_principal(info.pg_krb5_context, server); pg_krb5_destroy(&info); return STATUS_ERROR; } retval = krb5_sendauth(info.pg_krb5_context, &auth_context, (krb5_pointer) & conn->sock, (char *) conn->krbsrvname, info.pg_krb5_client, server, AP_OPTS_MUTUAL_REQUIRED, NULL, 0, /* no creds, use ccache instead */ info.pg_krb5_ccache, &err_ret, NULL, NULL); if (retval) { if (retval == KRB5_SENDAUTH_REJECTED && err_ret) { #if defined(HAVE_KRB5_ERROR_TEXT_DATA) printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Kerberos 5 authentication rejected: %*s\n"), (int) err_ret->text.length, err_ret->text.data); #elif defined(HAVE_KRB5_ERROR_E_DATA) printfPQExpBuffer(&conn->errorMessage, libpq_gettext("Kerberos 5 authentication rejected: %*s\n"), (int) err_ret->e_data->length, (const char *) err_ret->e_data->data); #else #error "bogus configuration" #endif } else { printfPQExpBuffer(&conn->errorMessage, "krb5_sendauth: %s\n", error_message(retval)); } if (err_ret) krb5_free_error(info.pg_krb5_context, err_ret); ret = STATUS_ERROR; } krb5_free_principal(info.pg_krb5_context, server); if (!pg_set_noblock(conn->sock)) { char sebuf[256]; printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not restore non-blocking mode on socket: %s\n"), pqStrerror(errno, sebuf, sizeof(sebuf))); ret = STATUS_ERROR; } pg_krb5_destroy(&info); return ret; }
/* * Read data from a secure connection. */ ssize_t pqsecure_read(PGconn *conn, void *ptr, size_t len) { ssize_t n; #ifdef USE_SSL if (conn->ssl) { int err; DECLARE_SIGPIPE_INFO(spinfo); /* SSL_read can write to the socket, so we need to disable SIGPIPE */ DISABLE_SIGPIPE(conn, spinfo, return -1); rloop: SOCK_ERRNO_SET(0); n = SSL_read(conn->ssl, ptr, len); err = SSL_get_error(conn->ssl, n); switch (err) { case SSL_ERROR_NONE: break; case SSL_ERROR_WANT_READ: n = 0; break; case SSL_ERROR_WANT_WRITE: /* * Returning 0 here would cause caller to wait for read-ready, * which is not correct since what SSL wants is wait for * write-ready. The former could get us stuck in an infinite * wait, so don't risk it; busy-loop instead. */ goto rloop; case SSL_ERROR_SYSCALL: { char sebuf[256]; if (n == -1) { REMEMBER_EPIPE(spinfo, SOCK_ERRNO == EPIPE); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL SYSCALL error: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); } else { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL SYSCALL error: EOF detected\n")); SOCK_ERRNO_SET(ECONNRESET); n = -1; } break; } case SSL_ERROR_SSL: { char *err = SSLerrmessage(); printfPQExpBuffer(&conn->errorMessage, libpq_gettext("SSL error: %s\n"), err); SSLerrfree(err); } /* fall through */ case SSL_ERROR_ZERO_RETURN: SOCK_ERRNO_SET(ECONNRESET); n = -1; break; default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("unrecognized SSL error code: %d\n"), err); n = -1; break; } RESTORE_SIGPIPE(conn, spinfo); }
/* * pqSendSome: send data waiting in the output buffer. * * len is how much to try to send (typically equal to outCount, but may * be less). * * Return 0 on success, -1 on failure and 1 when not all data could be sent * because the socket would block and the connection is non-blocking. */ static int pqSendSome(PGconn *conn, int len) { char *ptr = conn->outBuffer; int remaining = conn->outCount; int result = 0; if (conn->sock < 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("connection not open\n")); return -1; } /* while there's still data to send */ while (len > 0) { int sent; char sebuf[256]; #ifndef WIN32 sent = pqsecure_write(conn, ptr, len); #else /* * Windows can fail on large sends, per KB article Q201213. The * failure-point appears to be different in different versions of * Windows, but 64k should always be safe. */ sent = pqsecure_write(conn, ptr, Min(len, 65536)); #endif if (sent < 0) { /* * Anything except EAGAIN/EWOULDBLOCK/EINTR is trouble. If it's * EPIPE or ECONNRESET, assume we've lost the backend connection * permanently. */ switch (SOCK_ERRNO) { #ifdef EAGAIN case EAGAIN: break; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) case EWOULDBLOCK: break; #endif case EINTR: continue; case EPIPE: #ifdef ECONNRESET case ECONNRESET: #endif printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "server closed the connection unexpectedly\n" "\tThis probably means the server terminated abnormally\n" "\tbefore or while processing the request.\n")); /* * We used to close the socket here, but that's a bad idea * since there might be unread data waiting (typically, a * NOTICE message from the backend telling us it's * committing hara-kiri...). Leave the socket open until * pqReadData finds no more data can be read. But abandon * attempt to send data. */ conn->outCount = 0; return -1; default: printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not send data to server: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); /* We don't assume it's a fatal error... */ conn->outCount = 0; return -1; } } else { ptr += sent; len -= sent; remaining -= sent; } if (len > 0) { /* * We didn't send it all, wait till we can send more. * * If the connection is in non-blocking mode we don't wait, but * return 1 to indicate that data is still pending. */ if (pqIsnonblocking(conn)) { result = 1; break; } /* * There are scenarios in which we can't send data because the * communications channel is full, but we cannot expect the server * to clear the channel eventually because it's blocked trying to * send data to us. (This can happen when we are sending a large * amount of COPY data, and the server has generated lots of * NOTICE responses.) To avoid a deadlock situation, we must be * prepared to accept and buffer incoming data before we try * again. Furthermore, it is possible that such incoming data * might not arrive until after we've gone to sleep. Therefore, * we wait for either read ready or write ready. */ if (pqReadData(conn) < 0) { result = -1; /* error message already set up */ break; } if (pqWait(TRUE, TRUE, conn)) { result = -1; break; } } } /* shift the remaining contents of the buffer */ if (remaining > 0) memmove(conn->outBuffer, ptr, remaining); conn->outCount = remaining; return result; }
/* ---------- * pqReadData: read more data, if any is available * Possible return values: * 1: successfully loaded at least one more byte * 0: no data is presently available, but no error detected * -1: error detected (including EOF = connection closure); * conn->errorMessage set * NOTE: callers must not assume that pointers or indexes into conn->inBuffer * remain valid across this call! * ---------- */ int pqReadData(PGconn *conn) { int someread = 0; int nread; char sebuf[256]; if (conn->sock < 0) { printfPQExpBuffer(&conn->errorMessage, libpq_gettext("connection not open\n")); return -1; } /* Left-justify any data in the buffer to make room */ if (conn->inStart < conn->inEnd) { if (conn->inStart > 0) { memmove(conn->inBuffer, conn->inBuffer + conn->inStart, conn->inEnd - conn->inStart); conn->inEnd -= conn->inStart; conn->inCursor -= conn->inStart; conn->inStart = 0; } } else { /* buffer is logically empty, reset it */ conn->inStart = conn->inCursor = conn->inEnd = 0; } /* * If the buffer is fairly full, enlarge it. We need to be able to enlarge * the buffer in case a single message exceeds the initial buffer size. We * enlarge before filling the buffer entirely so as to avoid asking the * kernel for a partial packet. The magic constant here should be large * enough for a TCP packet or Unix pipe bufferload. 8K is the usual pipe * buffer size, so... */ if (conn->inBufSize - conn->inEnd < 8192) { if (pqCheckInBufferSpace(conn->inEnd + (size_t) 8192, conn)) { /* * We don't insist that the enlarge worked, but we need some room */ if (conn->inBufSize - conn->inEnd < 100) return -1; /* errorMessage already set */ } } /* OK, try to read some data */ retry3: nread = pqsecure_read(conn, conn->inBuffer + conn->inEnd, conn->inBufSize - conn->inEnd); if (nread < 0) { if (SOCK_ERRNO == EINTR) goto retry3; /* Some systems return EAGAIN/EWOULDBLOCK for no data */ #ifdef EAGAIN if (SOCK_ERRNO == EAGAIN) return someread; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) if (SOCK_ERRNO == EWOULDBLOCK) return someread; #endif /* We might get ECONNRESET here if using TCP and backend died */ #ifdef ECONNRESET if (SOCK_ERRNO == ECONNRESET) goto definitelyFailed; #endif printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not receive data from server: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); return -1; } if (nread > 0) { conn->inEnd += nread; /* * Hack to deal with the fact that some kernels will only give us back * 1 packet per recv() call, even if we asked for more and there is * more available. If it looks like we are reading a long message, * loop back to recv() again immediately, until we run out of data or * buffer space. Without this, the block-and-restart behavior of * libpq's higher levels leads to O(N^2) performance on long messages. * * Since we left-justified the data above, conn->inEnd gives the * amount of data already read in the current message. We consider * the message "long" once we have acquired 32k ... */ if (conn->inEnd > 32768 && (conn->inBufSize - conn->inEnd) >= 8192) { someread = 1; goto retry3; } return 1; } if (someread) return 1; /* got a zero read after successful tries */ /* * A return value of 0 could mean just that no data is now available, or * it could mean EOF --- that is, the server has closed the connection. * Since we have the socket in nonblock mode, the only way to tell the * difference is to see if select() is saying that the file is ready. * Grumble. Fortunately, we don't expect this path to be taken much, * since in normal practice we should not be trying to read data unless * the file selected for reading already. * * In SSL mode it's even worse: SSL_read() could say WANT_READ and then * data could arrive before we make the pqReadReady() test. So we must * play dumb and assume there is more data, relying on the SSL layer to * detect true EOF. */ #ifdef USE_SSL if (conn->ssl) return 0; #endif switch (pqReadReady(conn)) { case 0: /* definitely no data available */ return 0; case 1: /* ready for read */ break; default: goto definitelyFailed; } /* * Still not sure that it's EOF, because some data could have just * arrived. */ retry4: nread = pqsecure_read(conn, conn->inBuffer + conn->inEnd, conn->inBufSize - conn->inEnd); if (nread < 0) { if (SOCK_ERRNO == EINTR) goto retry4; /* Some systems return EAGAIN/EWOULDBLOCK for no data */ #ifdef EAGAIN if (SOCK_ERRNO == EAGAIN) return 0; #endif #if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN)) if (SOCK_ERRNO == EWOULDBLOCK) return 0; #endif /* We might get ECONNRESET here if using TCP and backend died */ #ifdef ECONNRESET if (SOCK_ERRNO == ECONNRESET) goto definitelyFailed; #endif printfPQExpBuffer(&conn->errorMessage, libpq_gettext("could not receive data from server: %s\n"), SOCK_STRERROR(SOCK_ERRNO, sebuf, sizeof(sebuf))); return -1; } if (nread > 0) { conn->inEnd += nread; return 1; } /* * OK, we are getting a zero read even though select() says ready. This * means the connection has been closed. Cope. */ definitelyFailed: printfPQExpBuffer(&conn->errorMessage, libpq_gettext( "server closed the connection unexpectedly\n" "\tThis probably means the server terminated abnormally\n" "\tbefore or while processing the request.\n")); conn->status = CONNECTION_BAD; /* No more connection to backend */ pqsecure_close(conn); closesocket(conn->sock); conn->sock = -1; return -1; }
int main(int argc, char *argv[]) { static struct option long_options[] = { {"list", no_argument, NULL, 'l'}, {"host", required_argument, NULL, 'h'}, {"port", required_argument, NULL, 'p'}, {"username", required_argument, NULL, 'U'}, {"no-password", no_argument, NULL, 'w'}, {"password", no_argument, NULL, 'W'}, {"dbname", required_argument, NULL, 'd'}, {"echo", no_argument, NULL, 'e'}, {NULL, 0, NULL, 0} }; const char *progname; int optindex; int c; bool listlangs = false; const char *dbname = NULL; char *host = NULL; char *port = NULL; char *username = NULL; enum trivalue prompt_password = TRI_DEFAULT; bool echo = false; char *langname = NULL; char *p; Oid lanplcallfoid; Oid laninline; Oid lanvalidator; char *handler; char *inline_handler; char *validator; char *handler_ns; char *inline_ns; char *validator_ns; bool keephandler; bool keepinline; bool keepvalidator; PQExpBufferData sql; PGconn *conn; PGresult *result; progname = get_progname(argv[0]); set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts")); handle_help_version_opts(argc, argv, "droplang", help); while ((c = getopt_long(argc, argv, "lh:p:U:wWd:e", long_options, &optindex)) != -1) { switch (c) { case 'l': listlangs = true; break; case 'h': host = optarg; break; case 'p': port = optarg; break; case 'U': username = optarg; break; case 'w': prompt_password = TRI_NO; break; case 'W': prompt_password = TRI_YES; break; case 'd': dbname = optarg; break; case 'e': echo = true; break; default: fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } } if (argc - optind > 0) { if (listlangs) dbname = argv[optind++]; else { langname = argv[optind++]; if (argc - optind > 0) dbname = argv[optind++]; } } if (argc - optind > 0) { fprintf(stderr, _("%s: too many command-line arguments (first is \"%s\")\n"), progname, argv[optind]); fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } if (dbname == NULL) { if (getenv("PGDATABASE")) dbname = getenv("PGDATABASE"); else if (getenv("PGUSER")) dbname = getenv("PGUSER"); else dbname = get_user_name(progname); } initPQExpBuffer(&sql); /* * List option */ if (listlangs) { printQueryOpt popt; static const bool translate_columns[] = {false, true}; conn = connectDatabase(dbname, host, port, username, prompt_password, progname); printfPQExpBuffer(&sql, "SELECT lanname as \"%s\", " "(CASE WHEN lanpltrusted THEN '%s' ELSE '%s' END) as \"%s\" " "FROM pg_catalog.pg_language WHERE lanispl;", gettext_noop("Name"), gettext_noop("yes"), gettext_noop("no"), gettext_noop("Trusted?")); result = executeQuery(conn, sql.data, progname, echo); memset(&popt, 0, sizeof(popt)); popt.topt.format = PRINT_ALIGNED; popt.topt.border = 1; popt.topt.start_table = true; popt.topt.stop_table = true; popt.topt.encoding = PQclientEncoding(conn); popt.title = _("Procedural Languages"); popt.translate_header = true; popt.translate_columns = translate_columns; printQuery(result, &popt, stdout, NULL); PQfinish(conn); exit(0); } if (langname == NULL) { fprintf(stderr, _("%s: missing required argument language name\n"), progname); fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname); exit(1); } for (p = langname; *p; p++) if (*p >= 'A' && *p <= 'Z') *p += ('a' - 'A'); conn = connectDatabase(dbname, host, port, username, prompt_password, progname); /* * Force schema search path to be just pg_catalog, so that we don't have * to be paranoid about search paths below. */ executeCommand(conn, "SET search_path = pg_catalog;", progname, echo); /* * Make sure the language is installed and find the OIDs of the language * support functions */ printfPQExpBuffer(&sql, "SELECT lanplcallfoid, laninline, lanvalidator " "FROM pg_language WHERE lanname = '%s' AND lanispl;", langname); result = executeQuery(conn, sql.data, progname, echo); if (PQntuples(result) == 0) { PQfinish(conn); fprintf(stderr, _("%s: language \"%s\" is not installed in " "database \"%s\"\n"), progname, langname, dbname); exit(1); } lanplcallfoid = atooid(PQgetvalue(result, 0, 0)); laninline = atooid(PQgetvalue(result, 0, 1)); lanvalidator = atooid(PQgetvalue(result, 0, 2)); PQclear(result); /* * Check that there are no functions left defined in that language */ printfPQExpBuffer(&sql, "SELECT count(proname) FROM pg_proc P, " "pg_language L WHERE P.prolang = L.oid " "AND L.lanname = '%s';", langname); result = executeQuery(conn, sql.data, progname, echo); if (strcmp(PQgetvalue(result, 0, 0), "0") != 0) { PQfinish(conn); fprintf(stderr, _("%s: still %s functions declared in language \"%s\"; " "language not removed\n"), progname, PQgetvalue(result, 0, 0), langname); exit(1); } PQclear(result); /* * Check that the handler function isn't used by some other language */ printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language " "WHERE lanplcallfoid = %u AND lanname <> '%s';", lanplcallfoid, langname); result = executeQuery(conn, sql.data, progname, echo); if (strcmp(PQgetvalue(result, 0, 0), "0") == 0) keephandler = false; else keephandler = true; PQclear(result); /* * Find the handler name */ if (!keephandler) { printfPQExpBuffer(&sql, "SELECT proname, (SELECT nspname " "FROM pg_namespace ns WHERE ns.oid = pronamespace) " "AS prons FROM pg_proc WHERE oid = %u;", lanplcallfoid); result = executeQuery(conn, sql.data, progname, echo); handler = strdup(PQgetvalue(result, 0, 0)); handler_ns = strdup(PQgetvalue(result, 0, 1)); PQclear(result); } else { handler = NULL; handler_ns = NULL; } /* * Check that the inline function isn't used by some other language */ if (OidIsValid(laninline)) { printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language " "WHERE laninline = %u AND lanname <> '%s';", laninline, langname); result = executeQuery(conn, sql.data, progname, echo); if (strcmp(PQgetvalue(result, 0, 0), "0") == 0) keepinline = false; else keepinline = true; PQclear(result); } else keepinline = true; /* don't try to delete it */ /* * Find the inline handler name */ if (!keepinline) { printfPQExpBuffer(&sql, "SELECT proname, (SELECT nspname " "FROM pg_namespace ns WHERE ns.oid = pronamespace) " "AS prons FROM pg_proc WHERE oid = %u;", laninline); result = executeQuery(conn, sql.data, progname, echo); inline_handler = strdup(PQgetvalue(result, 0, 0)); inline_ns = strdup(PQgetvalue(result, 0, 1)); PQclear(result); } else { inline_handler = NULL; inline_ns = NULL; } /* * Check that the validator function isn't used by some other language */ if (OidIsValid(lanvalidator)) { printfPQExpBuffer(&sql, "SELECT count(*) FROM pg_language " "WHERE lanvalidator = %u AND lanname <> '%s';", lanvalidator, langname); result = executeQuery(conn, sql.data, progname, echo); if (strcmp(PQgetvalue(result, 0, 0), "0") == 0) keepvalidator = false; else keepvalidator = true; PQclear(result); } else keepvalidator = true; /* don't try to delete it */ /* * Find the validator name */ if (!keepvalidator) { printfPQExpBuffer(&sql, "SELECT proname, (SELECT nspname " "FROM pg_namespace ns WHERE ns.oid = pronamespace) " "AS prons FROM pg_proc WHERE oid = %u;", lanvalidator); result = executeQuery(conn, sql.data, progname, echo); validator = strdup(PQgetvalue(result, 0, 0)); validator_ns = strdup(PQgetvalue(result, 0, 1)); PQclear(result); } else { validator = NULL; validator_ns = NULL; } /* * Drop the language and the functions */ printfPQExpBuffer(&sql, "DROP LANGUAGE \"%s\";\n", langname); if (!keephandler) appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\".\"%s\" ();\n", handler_ns, handler); if (!keepinline) appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\".\"%s\" (internal);\n", inline_ns, inline_handler); if (!keepvalidator) appendPQExpBuffer(&sql, "DROP FUNCTION \"%s\".\"%s\" (oid);\n", validator_ns, validator); if (echo) printf("%s", sql.data); result = PQexec(conn, sql.data); if (PQresultStatus(result) != PGRES_COMMAND_OK) { fprintf(stderr, _("%s: language removal failed: %s"), progname, PQerrorMessage(conn)); PQfinish(conn); exit(1); } PQclear(result); PQfinish(conn); exit(0); }