glib Functions miscellaneous functions Synopsis glib.idle_add callback ... glib.timeout_add interval callback ... glib.timeout_add_seconds interval callback ... glib.io_add_watch fd condition callback ... glib.source_remove tag glib.main_context_default glib.markup_escape_text text glib.child_watch_add pid function dataNone priorityglib.PRIORITY_DEFAULT glib.spawn_async argv envpNone working_directoryNone flags0 child_setupNone user_dataNone standard_inputNone standard_outputNone standard_errorNone glib.get_current_time glib.get_user_cache_dir glib.get_user_config_dir glib.get_user_data_dir glib.get_user_special_dir directory glib.main_depth glib.threads_init glib.filename_display_name filename glib.filename_display_basename filename glib.filename_from_utf8 utf8string Description These functions are part of the PyGObject glib module but are not directly associated with a specific class. Functions glib.idle_add glib.idle_add callback ... callback : a function to call when PyGTK is idle ... : optionals arguments to be passed to callback Returns : an integer ID The glib.idle_add() function adds a function (specified by callback) to be called whenever there are no higher priority events pending to the default main loop. The function is given the default idle priority, glib.PRIORITY_DEFAULT_IDLE. Additional arguments to pass to callback can be specified after callback. The idle priority can be specified as a keyword-value pair with the keyword "priority". If callback returns FALSE it is automatically removed from the list of event sources and will not be called again. glib.timeout_add glib.timeout_add interval callback ... interval : the time between calls to the function, in milliseconds callback : the function to call ... : zero or more arguments that will be passed to callback Returns : an integer ID of the event source The glib.timeout_add() function sets a function (specified by callback) to be called at regular intervals (specified by interval, with the default priority, glib.PRIORITY_DEFAULT. Additional arguments to pass to callback can be specified after callback. The idle priority may be specified as a keyword-value pair with the keyword "priority". The function is called repeatedly until it returns FALSE, at which point the timeout is automatically destroyed and the function will not be called again. The first call to the function will be at the end of the first interval. Note that timeout functions may be delayed, due to the processing of other event sources. Thus they should not be relied on for precise timing. After each call to the timeout function, the time of the next timeout is recalculated based on the current time and the given interval (it does not try to 'catch up' time lost in delays). glib.timeout_add_seconds glib.timeout_add_seconds interval callback ... interval : the time between calls to the function, in seconds callback : the function to call ... : zero or more arguments that will be passed to callback Returns : an integer ID of the event source The glib.timeout_add_seconds() is similar to glib.timeout_add() except that interval must be specified in seconds, not milliseconds, and the function should cause less CPU wakeups, which is important for laptops' batteries. Unlike glib.timeout_add(), this function operates at whole second granularity. The initial starting point of the timer is determined by the implementation and the implementation is expected to group multiple timers together so that they fire all at the same time. To allow this grouping, the interval to the first timer is rounded and can deviate up to one second from the specified interval. Subsequent timer iterations will generally run at the specified interval. Note that timeout functions may be delayed, due to the processing of other event sources. Thus they should not be relied on for precise timing. After each call to the timeout function, the time of the next timeout is recalculated based on the current time and the given interval. The grouping of timers to fire at the same time results in a more power and CPU efficient behavior so if your timer is in multiples of seconds and you don't require the first timer exactly one second from now, the use of glib.timeout_add_seconds() is preferred over glib.timeout_add(). glib.io_add_watch glib.io_add_watch fd condition callback ... fd : a Python file object or an integer file descriptor ID condition : a condition mask callback : a function to call ... : additional arguments to pass to callback Returns : an integer ID of the event source The glib.io_add_watch() function arranges for the file (specified by fd) to be monitored by the main loop for the specified condition. fd may be a Python file object or an integer file descriptor. The value of condition is a combination of: glib.IO_IN There is data to read. glib.IO_OUT Data can be written (without blocking). glib.IO_PRI There is urgent data to read. glib.IO_ERR Error condition. glib.IO_HUP Hung up (the connection has been broken, usually for pipes and sockets). Additional arguments to pass to callback can be specified after callback. The idle priority may be specified as a keyword-value pair with the keyword "priority". The signature of the callback function is: def callback(source, cb_condition, ...) where source is fd, the file descriptor; cb_condition is the condition that triggered the signal; and, ... are the zero or more arguments that were passed to the glib.io_add_watch() function. If the callback function returns FALSE it will be automatically removed from the list of event sources and will not be called again. If it returns TRUE it will be called again when the condition is matched. glib.source_remove glib.source_remove tag tag : an integer ID Returns : TRUE if the event source was removed The glib.source_remove() function removes the event source specified by tag (as returned by the glib.idle_add(), glib.timeout_add() and glib.io_add_watch() functions) glib.main_context_default glib.main_context_default Returns : the default glib.MainContext object The glib.main_context_default() function returns the default glib.MainContext object. glib.markup_escape_text glib.markup_escape_text text text : the UTF-8 string to be escaped Returns : the escaped text This function is available in PyGTK 2.8 and above. The glib.markup_escape_text() function escapes the string specified by text so that the markup parser will parse it verbatim. Less than, greater than, ampersand, etc. are replaced with the corresponding entities. This function would typically be used when writing out a file to be parsed with the markup parser. Note that this function doesn't protect whitespace and line endings from being processed according to the XML rules for normalization of line endings and attribute values. glib.child_watch_add glib.child_watch_add pid function dataNone priorityglib.PRIORITY_DEFAULT pid : process id of a child process to watch function : the function to call data : the optional data to pass to function priority : the priority of the idle source - one of the Returns : the id of event source. This function is available in PyGTK 2.6 and above. The glib.child_watch_add() function sets the function specified by function to be called with the user data specified by data when the child indicated by pid exits. The signature for the callback is: def callback(pid, condition, user_data) where pid is is the child process id, condition is the status information about the child process and user_data is data PyGTK supports only a single callback per process id. glib.spawn_async glib.spawn_async argv envpNone working_directoryNone flags0 child_setupNone user_dataNone standard_inputNone standard_outputNone standard_errorNone argv : a sequence of strings containing the arguments of the child process envp : the child's environment or None to inherit the parent's environment. working_directory : the child's current working directory, or None to inherit parent's flags : flags from the . child_setup : a function to run in the child just before calling exec() user_data : the user data for the child_setup function standard_input : if TRUE return the file descriptor for the child's stdin standard_output : if TRUE return the file descriptor for the child's stdout standard_error : if TRUE return the file descriptor for the child's stderr Returns : a 4-tuple containing the child's process id and the stdin, stdout and stderr file descriptor integers. This function is available in PyGTK 2.6 and above. The glib.spawn_async() function executes a child program asynchronously (your program will not block waiting for the child to exit). The child program is specified by the only argument that must be provided, argv. argv should be a sequence of strings, to be passed as the argument vector for the child. The first string in argv is of course the name of the program to execute. By default, the name of the program must be a full path; the PATH shell variable will only be searched if you pass the glib.SPAWN_SEARCH_PATH flag in flags. The function returns a 4-tuple containing the child's process id and the file descriptors for the child's stdin, stdout and stderr. The stdin, stdout and stderr file descriptors are returned only ofthe corresponding standard_input, standard_output or standard_error params are TRUE. On Windows, the low-level child process creation API (CreateProcess()) doesn't use argument vectors, but a command line. The C runtime library's spawn*() family of functions (which glib.spawn_async() eventually calls) paste the argument vector elements into a command line, and the C runtime startup code does a corresponding reconstruction of an argument vector from the command line, to be passed to main(). Complications arise when you have argument vector elements that contain spaces of double quotes. The spawn*() functions don't do any quoting or escaping, but on the other hand the startup code does do unquoting and unescaping in order to enable receiving arguments with embedded spaces or double quotes. To work around this asymmetry, the glib.spawn_async() function will do quoting and escaping on argument vector elements that need it before calling the C runtime spawn() function. envp is a sequence of strings, where each string has the form KEY=VALUE. This will become the child's environment. If envp is None or not specified, the child inherits its parent's environment. flags should be the bitwise OR of the you want to affect the function's behaviour. The glib.SPAWN_DO_NOT_REAP_CHILD flag means that the child will not automatically be reaped; you must use a GChildWatch source to be notified about the death of the child process. Eventually you must call g_spawn_close_pid() on the child_pid, in order to free resources which may be associated with the child process. (On Unix, using a GChildWatch source is equivalent to calling waitpid() or handling the SIGCHLD signal manually. On Windows, calling g_spawn_close_pid() is equivalent to calling CloseHandle() on the process handle returned). glib.SPAWN_LEAVE_DESCRIPTORS_OPEN means that the parent's open file descriptors will be inherited by the child; otherwise all descriptors except stdin/stdout/stderr will be closed before calling exec() in the child. glib.SPAWN_SEARCH_PATH means that argv[0] need not be an absolute path, it will be looked for in the user's PATH. glib.SPAWN_STDOUT_TO_DEV_NULL means that the child's standard output will be discarded, instead of going to the same location as the parent's standard output. If you use this flag, standard_output must be None. glib.SPAWN_STDERR_TO_DEV_NULL means that the child's standard error will be discarded, instead of going to the same location as the parent's standard error. If you use this flag, standard_error must be None. glib.SPAWN_CHILD_INHERITS_STDIN means that the child will inherit the parent's standard input (by default, the child's standard input is attached to /dev/null). If you use this flag, standard_input must be None. glib.SPAWN_FILE_AND_ARGV_ZERO means that the first element of argv is the file to execute, while the remaining elements are the actual argument vector to pass to the file. Normally the glib.spawn_async() function uses argv[0] as the file to execute, and passes all of argv to the child. child_setup and user_data are a function and user data. On POSIX platforms, the function is called in the child after GLib has performed all the setup it plans to perform (including creating pipes, closing file descriptors, etc.) but before calling exec(). That is, child_setup is called just before calling exec() in the child. Obviously actions taken in this function will only affect the child, not the parent. On Windows, there is no separate fork() and exec() functionality. Child processes are created and run right away with one API call, CreateProcess(). child_setup is called in the parent process just before creating the child process. You should carefully consider what you do in child_setup if you intend your software to be portable to Windows. The returned child process id can be used to send signals to the child, or to wait for the child if you specified the glib.SPAWN_DO_NOT_REAP_CHILD flag. On Windows, child pid will be returned only if you specified the glib.SPAWN_DO_NOT_REAP_CHILD flag. The caller of the glib.spawn_async() must close any returned file descriptors when they are no longer in use. If standard_input is None, the child's standard input is attached to /dev/null unless glib.SPAWN_CHILD_INHERITS_STDIN is set. If standard_error is None, the child's standard error goes to the same location as the parent's standard error unless glib.SPAWN_STDERR_TO_DEV_NULL is set. If standard_output is None, the child's standard output goes to the same location as the parent's standard output unless glib.SPAWN_STDOUT_TO_DEV_NULL is set. If an error occurs, the glib.GError exception will be raised. glib.get_current_time glib.get_current_time Returns : the current time as the number of seconds and microseconds from the epoch. This function is available in PyGTK 2.8 and above. The glib.get_current_time() function reurns the current time of day as the number of seconds and microseconds from the epoch. glib.get_user_cache_dir glib.get_user_cache_dir Returns : a strings with a path to user's cache directory. This function is available in PyGObject 2.18 and above. Returns a base directory in which to store non-essential, cached data specific to particular user. On UNIX platforms this is determined using the mechanisms described in the XDG Base Directory Specification. glib.get_user_config_dir glib.get_user_config_dir Returns : a strings with a path to user's configuration directory. This function is available in PyGObject 2.18 and above. Returns a base directory in which to store user-specific application configuration information such as user preferences and settings. On UNIX platforms this is determined using the mechanisms described in the XDG Base Directory Specification. glib.get_user_data_dir glib.get_user_data_dir Returns : a strings with a path to user's data directory. This function is available in PyGObject 2.18 and above. Returns a base directory in which to access application data such as icons that is customized for a particular user On UNIX platforms this is determined using the mechanisms described in the XDG Base Directory Specification. glib.get_user_special_dir glib.get_user_special_dir directory directory : the logical id of special directory, see User Directory constants for the list of supported values Returns : a strings with a path to the requested directory. This function is available in PyGObject 2.18 and above. Returns the full path of a special directory using its logical id. On Unix this is done using the XDG special user directories. For compatibility with existing practise, glib.USER_DIRECTORY_DESKTOP falls back to $HOME/Desktop when XDG special user directories have not been set up. Depending on the platform, the user might be able to change the path of the special directory without requiring the session to restart; GLib will not reflect any change once the special directories are loaded. glib.main_depth glib.main_depth Returns : the depth of the stack of calls to the main context. This function is available in PyGTK 2.8 and above. The main_depth() function returns the depth of the stack of calls in the main context. That is, when called from the toplevel, it gives 0. When called from within a callback from the glib.MainContext.iteration() method (or the glib.MainLoop.run() method, etc.) it returns 1. When called from within a callback to a recursive call to the glib.MainContext.iteration() method), it returns 2. And so forth. glib.threads_init glib.threads_init Returns : This function is available in PyGTK 2.4 and above. The threads_init() function initializes the the use of Python threading in the glib module. This function is different than the gtk.gdk.threads_init() function as that function also initializes the gdk threads. glib.signal_accumulator_true_handled glib.signal_accumulator_true_handled This function is available in PyGTK 2.8 and above. The signal_accumulator_true_handled() function is only used as accumulator argument when registering signals. glib.filename_display_name glib.filename_display_name filename filename : a pathname in the file name encoding Returns : an UTF8 rendition of filename. This function is available in PyGTK 2.10 and above. The filename_display_name() function converts a filename into a valid UTF-8 string. The conversion is not necessarily reversible, so you should keep the original around and use the return value of this function only for display purposes. Unlike g_filename_to_utf8(), the result is guaranteed to be non-None even if the filename actually isn't in the file name encoding. If you know the whole pathname of the file you should use the glib.filename_display_basename() function, since that allows location-based translation of filenames. glib.filename_display_basename glib.filename_display_basename filename filename : an absolute pathname in the file name encoding Returns : an UTF8 rendition of filename. This function is available in PyGTK 2.10 and above. The filename_display_basename() function returns the display basename for the particular filename, guaranteed to be valid UTF-8. The display name might not be identical to the filename, for instance there might be problems converting it to UTF-8, and some files can be translated in the display. You must pass the whole absolute pathname to this functions so that translation of well known locations can be done. This function is preferred over the glib.filename_display_name() function if you know the whole path, as it allows translation. glib.filename_from_utf8 glib.filename_from_utf8 utf8string utf8string : a UTF-8 encoded string. Returns : a filename encoded in the GLib filename encoding. This function is available in PyGTK 2.10 and above. The filename_from_utf8() function converts a string from UTF-8 to the encoding GLib uses for filenames. Note that on Windows GLib uses UTF-8 for filenames. 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600
/*
    Authors:
        Sumit Bose <sbose@redhat.com>

    Copyright (C) 2009 Red Hat
    Copyright (C) 2010, rhafer@suse.de, Novell Inc.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#define PAM_SM_AUTH
#define PAM_SM_ACCOUNT
#define PAM_SM_SESSION
#define PAM_SM_PASSWORD

#include "config.h"
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <syslog.h>
#include <time.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <locale.h>
#include <stdbool.h>

#include <security/pam_modules.h>
#include <security/pam_ext.h>
#include <security/pam_modutil.h>
#include "sss_pam_macros.h"

#include "sss_cli.h"

#include <libintl.h>
#define _(STRING) dgettext (PACKAGE, STRING)

#define FLAGS_USE_FIRST_PASS (1 << 0)
#define FLAGS_FORWARD_PASS   (1 << 1)
#define FLAGS_USE_AUTHTOK    (1 << 2)

#define PWEXP_FLAG "pam_sss:password_expired_flag"

#define PW_RESET_MSG_FILENAME_TEMPLATE SSSD_CONF_DIR"/customize/%s/pam_sss_pw_reset_message.%s"
#define PW_RESET_MSG_MAX_SIZE 4096

#define OPT_RETRY_KEY "retry="

struct pam_items {
    const char* pam_service;
    const char* pam_user;
    const char* pam_tty;
    const char* pam_ruser;
    const char* pam_rhost;
    char* pam_authtok;
    char* pam_newauthtok;
    const char* pamstack_authtok;
    const char* pamstack_oldauthtok;
    size_t pam_service_size;
    size_t pam_user_size;
    size_t pam_tty_size;
    size_t pam_ruser_size;
    size_t pam_rhost_size;
    int pam_authtok_type;
    size_t pam_authtok_size;
    int pam_newauthtok_type;
    size_t pam_newauthtok_size;
    pid_t cli_pid;
    const char *login_name;
    char *domain_name;
};

#define DEBUG_MGS_LEN 1024
#define MAX_AUTHTOK_SIZE (1024*1024)
#define CHECK_AND_RETURN_PI_STRING(s) ((s != NULL && *s != '\0')? s : "(not available)")

static void logger(pam_handle_t *pamh, int level, const char *fmt, ...) {
    va_list ap;

    va_start(ap, fmt);

#ifdef DEBUG
    va_list apd;
    char debug_msg[DEBUG_MGS_LEN];
    int ret;
    va_copy(apd, ap);

    ret = vsnprintf(debug_msg, DEBUG_MGS_LEN, fmt, apd);
    if (ret >= DEBUG_MGS_LEN) {
        D(("the following message is truncated: %s", debug_msg));
    } else if (ret < 0) {
        D(("vsnprintf failed to format debug message!"));
    } else {
        D((debug_msg));
    }

    va_end(apd);
#endif

    pam_vsyslog(pamh, LOG_AUTHPRIV|level, fmt, ap);

    va_end(ap);
}

static void free_exp_data(pam_handle_t *pamh, void *ptr, int err)
{
    free(ptr);
    ptr = NULL;
}

static size_t add_authtok_item(enum pam_item_type type,
                               enum sss_authtok_type authtok_type,
                               const char *tok, const size_t size,
                               uint8_t *buf) {
    size_t rp=0;
    uint32_t c;

    if (tok == NULL) return 0;

    c = type;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    c = size + sizeof(uint32_t);
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    c = authtok_type;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    memcpy(&buf[rp], tok, size);
    rp += size;

    return rp;
}


static size_t add_uint32_t_item(enum pam_item_type type, const uint32_t val,
                                uint8_t *buf) {
    size_t rp=0;
    uint32_t c;

    c = type;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    c = sizeof(uint32_t);
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    c = val;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    return rp;
}

static size_t add_string_item(enum pam_item_type type, const char *str,
                           const size_t size, uint8_t *buf) {
    size_t rp=0;
    uint32_t c;

    if (str == NULL || *str == '\0') return 0;

    c = type;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    c = size;
    memcpy(&buf[rp], &c, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    memcpy(&buf[rp], str, size);
    rp += size;

    return rp;
}

static void overwrite_and_free_pam_items(struct pam_items *pi)
{
    if (pi->pam_authtok != NULL) {
        _pam_overwrite_n((void *)pi->pam_authtok, pi->pam_authtok_size);
        free((void *)pi->pam_authtok);
        pi->pam_authtok = NULL;
    }

    if (pi->pam_newauthtok != NULL) {
        _pam_overwrite_n((void *)pi->pam_newauthtok,  pi->pam_newauthtok_size);
        free((void *)pi->pam_newauthtok);
        pi->pam_newauthtok = NULL;
    }

    pi->pamstack_authtok = NULL;
    pi->pamstack_oldauthtok = NULL;

    free(pi->domain_name);
    pi->domain_name = NULL;
}

static int pack_message_v3(struct pam_items *pi, size_t *size,
                           uint8_t **buffer) {
    int len;
    uint8_t *buf;
    int rp;
    uint32_t terminator = SSS_END_OF_PAM_REQUEST;

    len = sizeof(uint32_t) +
          2*sizeof(uint32_t) + pi->pam_user_size +
          sizeof(uint32_t);
    len += *pi->pam_service != '\0' ?
                2*sizeof(uint32_t) + pi->pam_service_size : 0;
    len += *pi->pam_tty != '\0' ?
                2*sizeof(uint32_t) + pi->pam_tty_size : 0;
    len += *pi->pam_ruser != '\0' ?
                2*sizeof(uint32_t) + pi->pam_ruser_size : 0;
    len += *pi->pam_rhost != '\0' ?
                2*sizeof(uint32_t) + pi->pam_rhost_size : 0;
    len += pi->pam_authtok != NULL ?
                3*sizeof(uint32_t) + pi->pam_authtok_size : 0;
    len += pi->pam_newauthtok != NULL ?
                3*sizeof(uint32_t) + pi->pam_newauthtok_size : 0;
    len += 3*sizeof(uint32_t); /* cli_pid */

    buf = malloc(len);
    if (buf == NULL) {
        D(("malloc failed."));
        return PAM_BUF_ERR;
    }

    rp = 0;
    ((uint32_t *)(&buf[rp]))[0] = SSS_START_OF_PAM_REQUEST;
    rp += sizeof(uint32_t);

    rp += add_string_item(SSS_PAM_ITEM_USER, pi->pam_user, pi->pam_user_size,
                          &buf[rp]);

    rp += add_string_item(SSS_PAM_ITEM_SERVICE, pi->pam_service,
                          pi->pam_service_size, &buf[rp]);

    rp += add_string_item(SSS_PAM_ITEM_TTY, pi->pam_tty, pi->pam_tty_size,
                          &buf[rp]);

    rp += add_string_item(SSS_PAM_ITEM_RUSER, pi->pam_ruser, pi->pam_ruser_size,
                          &buf[rp]);

    rp += add_string_item(SSS_PAM_ITEM_RHOST, pi->pam_rhost, pi->pam_rhost_size,
                          &buf[rp]);

    rp += add_uint32_t_item(SSS_PAM_ITEM_CLI_PID, (uint32_t) pi->cli_pid,
                            &buf[rp]);

    rp += add_authtok_item(SSS_PAM_ITEM_AUTHTOK, pi->pam_authtok_type,
                           pi->pam_authtok, pi->pam_authtok_size, &buf[rp]);

    rp += add_authtok_item(SSS_PAM_ITEM_NEWAUTHTOK, pi->pam_newauthtok_type,
                           pi->pam_newauthtok, pi->pam_newauthtok_size,
                           &buf[rp]);

    memcpy(&buf[rp], &terminator, sizeof(uint32_t));
    rp += sizeof(uint32_t);

    if (rp != len) {
        D(("error during packet creation."));
        free(buf);
        return PAM_BUF_ERR;
    }

    *size = len;
    *buffer = buf;

    return 0;
}

static int null_strcmp(const char *s1, const char *s2) {
    if (s1 == NULL && s2 == NULL) return 0;
    if (s1 == NULL && s2 != NULL) return -1;
    if (s1 != NULL && s2 == NULL) return 1;
    return strcmp(s1, s2);
}

enum {
    SSS_PAM_CONV_DONE = 0,
    SSS_PAM_CONV_STD,
    SSS_PAM_CONV_REENTER,
};

static int do_pam_conversation(pam_handle_t *pamh, const int msg_style,
                               const char *msg,
                               const char *reenter_msg,
                               char **_answer)
{
    int ret;
    int state = SSS_PAM_CONV_STD;
    struct pam_conv *conv;
    const struct pam_message *mesg[1];
    struct pam_message *pam_msg;
    struct pam_response *resp=NULL;
    char *answer = NULL;

    if ((msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) &&
        msg == NULL) return PAM_SYSTEM_ERR;

    if ((msg_style == PAM_PROMPT_ECHO_OFF ||
         msg_style == PAM_PROMPT_ECHO_ON) &&
        (msg == NULL || _answer == NULL)) return PAM_SYSTEM_ERR;

    if (msg_style == PAM_TEXT_INFO || msg_style == PAM_ERROR_MSG) {
        logger(pamh, LOG_INFO, "User %s message: %s",
                               msg_style == PAM_TEXT_INFO ? "info" : "error",
                               msg);
    }

    ret=pam_get_item(pamh, PAM_CONV, (const void **) &conv);
    if (ret != PAM_SUCCESS) return ret;

    do {
        pam_msg = malloc(sizeof(struct pam_message));
        if (pam_msg == NULL) {
            D(("Malloc failed."));
            ret = PAM_SYSTEM_ERR;
            goto failed;
        }

        pam_msg->msg_style = msg_style;
        if (state == SSS_PAM_CONV_REENTER) {
            pam_msg->msg = reenter_msg;
        } else {
            pam_msg->msg = msg;
        }

        mesg[0] = (const struct pam_message *) pam_msg;

        ret=conv->conv(1, mesg, &resp,
                       conv->appdata_ptr);
        free(pam_msg);
        if (ret != PAM_SUCCESS) {
            D(("Conversation failure: %s.",  pam_strerror(pamh,ret)));
            goto failed;
        }

        if (msg_style == PAM_PROMPT_ECHO_OFF ||
            msg_style == PAM_PROMPT_ECHO_ON) {
            if (resp == NULL) {
                D(("response expected, but resp==NULL"));
                ret = PAM_SYSTEM_ERR;
                goto failed;
            }

            if (state == SSS_PAM_CONV_REENTER) {
                if (null_strcmp(answer, resp[0].resp) != 0) {
                    logger(pamh, LOG_NOTICE, "Passwords do not match.");
                    _pam_overwrite((void *)resp[0].resp);
                    free(resp[0].resp);
                    if (answer != NULL) {
                        _pam_overwrite((void *) answer);
                        free(answer);
                        answer = NULL;
                    }
                    ret = do_pam_conversation(pamh, PAM_ERROR_MSG,
                                              _("Passwords do not match"),
                                              NULL, NULL);
                    if (ret != PAM_SUCCESS) {
                        D(("do_pam_conversation failed."));
                        ret = PAM_SYSTEM_ERR;
                        goto failed;
                    }
                    ret = PAM_CRED_ERR;
                    goto failed;
                }
                _pam_overwrite((void *)resp[0].resp);
                free(resp[0].resp);
            } else {
                if (resp[0].resp == NULL) {
                    D(("Empty password"));
                    answer = NULL;
                } else {
                    answer = strndup(resp[0].resp, MAX_AUTHTOK_SIZE);
                    _pam_overwrite((void *)resp[0].resp);
                    free(resp[0].resp);
                    if(answer == NULL) {
                        D(("strndup failed"));
                        ret = PAM_BUF_ERR;
                        goto failed;
                    }
                }
            }
            free(resp);
            resp = NULL;
        }

        if (reenter_msg != NULL && state == SSS_PAM_CONV_STD) {
            state = SSS_PAM_CONV_REENTER;
        } else {
            state = SSS_PAM_CONV_DONE;
        }
    } while (state != SSS_PAM_CONV_DONE);

    if (_answer) *_answer = answer;
    return PAM_SUCCESS;

failed:
    free(answer);
    return ret;

}

static errno_t display_pw_reset_message(pam_handle_t *pamh,
                                        const char *domain_name,
                                        const char *suffix)
{
    int ret;
    struct stat stat_buf;
    char *msg_buf = NULL;
    int fd = -1;
    size_t size;
    size_t total_len;
    char *filename = NULL;