Files

707 lines
26 KiB
C++

/* qt会将glib里的signals成员识别为宏,所以取消该宏
* 后面如果用到signals时,使用Q_SIGNALS代替即可
**/
#ifdef signals
#undef signals
#endif
extern "C" {
#include <glib.h>
#include <gio/gio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/wait.h>
}
#include "run-passwd.h"
/* Buffer size for backend output */
#define BUFSIZE 64
/* Passwd states */
//后端passwd的状态,NONE应该是passwd还没有启动,ERROR表示报错但还没退出
typedef enum {
PASSWD_STATE_NONE, /* Passwd is not asking for anything */
PASSWD_STATE_AUTH, /* Passwd is asking for our current password */
PASSWD_STATE_NEW, /* Passwd is asking for our new password */
PASSWD_STATE_RETYPE, /* Passwd is asking for our retyped new password */
PASSWD_STATE_ERR /* Passwd reported an error but has not yet exited */
} PasswdState;
struct PasswdHandler {
// GtkBuilder *ui;
const char *current_password;
const char *new_password;
const char *retyped_password;
/* Communication with the passwd program */
GPid backend_pid;
GIOChannel *backend_stdin;
GIOChannel *backend_stdout;
GQueue *backend_stdin_queue; /* Write queue to backend_stdin */
/* GMainLoop IDs */
guint backend_child_watch_id; /* g_child_watch_add (PID) */
guint backend_stdout_watch_id; /* g_io_add_watch (stdout) */
/* State of the passwd program */
PasswdState backend_state;
gboolean changing_password;
PasswdCallback auth_cb;
gpointer auth_cb_data;
PasswdCallback chpasswd_cb;
gpointer chpasswd_cb_data;
};
//GQuark是一个guint32
static GQuark
passwd_error_quark (void)
{
static GQuark q = 0;
//返回错误的标识码
if (q == 0) {
q = g_quark_from_static_string("passwd_error");
}
return q;
}
/* Error handling */
#define PASSWD_ERROR (passwd_error_quark ())
static void stop_passwd (PasswdHandler *passwd_handler);
static void free_passwd_resources (PasswdHandler *passwd_handler);
static gboolean io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler);
static void free_passwd_resources (PasswdHandler *passwd_handler)
{
GError *error = NULL;
/* Remove the child watcher */
if (passwd_handler->backend_child_watch_id != 0) {
g_source_remove (passwd_handler->backend_child_watch_id);
passwd_handler->backend_child_watch_id = 0;
}
/* Close IO channels (internal file descriptors are automatically closed) */
if (passwd_handler->backend_stdin != NULL) {
if (g_io_channel_shutdown (passwd_handler->backend_stdin, TRUE, &error) != G_IO_STATUS_NORMAL) {
g_warning ("Could not shutdown backend_stdin IO channel: %s", error->message);
g_error_free (error);
error = NULL;
}
g_io_channel_unref (passwd_handler->backend_stdin);
passwd_handler->backend_stdin = NULL;
}
if (passwd_handler->backend_stdout != NULL) {
if (g_io_channel_shutdown (passwd_handler->backend_stdout, TRUE, &error) != G_IO_STATUS_NORMAL) {
g_warning ("Could not shutdown backend_stdout IO channel: %s", error->message);
g_error_free (error);
error = NULL;
}
g_io_channel_unref (passwd_handler->backend_stdout);
passwd_handler->backend_stdout = NULL;
}
/* Remove IO watcher */
if (passwd_handler->backend_stdout_watch_id != 0) {
g_source_remove (passwd_handler->backend_stdout_watch_id);
passwd_handler->backend_stdout_watch_id = 0;
}
/* Close PID */
//因为flag为G_SPAWN_DO_NOT_REAP_CHILD,所以child不会自动的被reap掉,需要在子进程上free
if (passwd_handler->backend_pid != -1) {
g_spawn_close_pid (passwd_handler->backend_pid);
passwd_handler->backend_pid = -1;
}
/* Clear backend state */
passwd_handler->backend_state = PASSWD_STATE_NONE;
}
static void authenticate (PasswdHandler *passwd_handler)
{
gchar *s;
s = g_strdup_printf ("%s\n", passwd_handler->current_password);
g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
}
static void io_queue_pop (GQueue *queue, GIOChannel *channel)
{
gchar *buf;
gsize bytes_written;
GError *error = NULL;
buf = (gchar *)g_queue_pop_head (queue);
if (buf != NULL) {
//将队列中的首元素写入到channel中
if (g_io_channel_write_chars (channel, buf, -1, &bytes_written, &error) != G_IO_STATUS_NORMAL) {
g_warning ("Could not write queue element \"%s\" to channel: %s", buf, error->message);
g_error_free (error);
}
/* Ensure passwords are cleared from memory */
//清除内存中的passwords
memset (buf, 0, strlen (buf));
g_free (buf);
}
}
static gboolean is_string_complete (gchar *str, ...)
{
va_list ap;
gchar *arg;
if (strlen (str) == 0) {
return FALSE;
}
va_start (ap, str);
while ((arg = va_arg (ap, char *)) != NULL) {
if (g_strrstr (str, arg) != NULL) {
va_end (ap);
return TRUE;
}
}
va_end (ap);
return FALSE;
}
static gboolean io_watch_stdout (GIOChannel *source, GIOCondition condition, PasswdHandler *passwd_handler)
{
static GString *str = NULL; /* Persistent buffer */
gchar buf[BUFSIZE]; /* Temporary buffer */
gsize bytes_read;
GError *gio_error = NULL; /* Error returned by functions */
GError *error = NULL; /* Error sent to callbacks */
//GtkBuilder *dialog;
gboolean reinit = FALSE;
/* Initialize buffer */
if (str == NULL) {
str = g_string_new ("");
}
//dialog = passwd_handler->ui;
//buf将保存从channel中读取到的数据,bytes_read表示从buf中读取的数据长度
if (g_io_channel_read_chars (source, buf, BUFSIZE, &bytes_read, &gio_error)
!= G_IO_STATUS_NORMAL) {
g_warning ("IO Channel read error: %s", gio_error->message);
g_error_free (gio_error);
return TRUE;
}
// g_warning("----------bytes_read=%d",bytes_read);
// g_warning("----------io_watch_buf=%s-------",buf);
str = g_string_append_len (str, buf, bytes_read);
/* In which state is the backend? */
switch (passwd_handler->backend_state) {
case PASSWD_STATE_AUTH:
/* Passwd is asking for our current password */
if (is_string_complete (str->str, "assword: ", "failure", "wrong", "error", NULL)) {
if (g_strrstr (str->str, "New password: ") != NULL) {
/* Authentication successful */
passwd_handler->backend_state = PASSWD_STATE_NEW;
/* Trigger callback to update authentication status */
if (passwd_handler->auth_cb)
passwd_handler->auth_cb (passwd_handler,
NULL,
passwd_handler->auth_cb_data);
} else {
/* Authentication failed */
error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
"Authentication token manipulation error!");
passwd_handler->changing_password = FALSE;
/* This error can happen both while authenticating or while changing password:
* if chpasswd_cb is set, this means we're already changing password */
if (passwd_handler->chpasswd_cb)
passwd_handler->chpasswd_cb (passwd_handler,
error,
passwd_handler->auth_cb_data);
else if (passwd_handler->auth_cb)
passwd_handler->auth_cb (passwd_handler,
error,
passwd_handler->auth_cb_data);
g_error_free (error);
}
reinit = TRUE;
}
break;
case PASSWD_STATE_NEW:
/* Passwd is asking for our new password */
if (is_string_complete (str->str, "assword: ", NULL)) {
/* Advance to next state */
passwd_handler->backend_state = PASSWD_STATE_RETYPE;
/* Pop retyped password from queue and into IO channel */
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
reinit = TRUE;
}
break;
case PASSWD_STATE_RETYPE:
/* Passwd is asking for our retyped new password */
// if (is_string_complete (str->str,
// "successfully",
// "short",
// "longer",
// "palindrome",
// "dictionary",
// "simple",
// "simplistic",
// "similar",
// "different",
// "case",
// "wrapped",
// "recovered",
// "recent",
// "unchanged",
// "match",
// "1 numeric or special",
// "failure",
// "length",
// NULL)) {
if (TRUE){
if (g_strrstr (str->str, "successfully") != NULL) {
/* Hooray! */
/* Trigger callback to update status */
if (passwd_handler->chpasswd_cb)
passwd_handler->chpasswd_cb (passwd_handler,
NULL,
passwd_handler->chpasswd_cb_data);
}
else {
/* Ohnoes! */
if (g_strrstr (str->str, "recovered") != NULL) {
/* What does this indicate?
* "Authentication information cannot be recovered?" from libpam? */
error = g_error_new_literal (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
str->str);
}/* else if (g_strrstr (str->str, "short") != NULL ||
g_strrstr (str->str, "longer") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"New password length is too short!");
} else if (g_strrstr (str->str, "palindrome") != NULL ||
g_strrstr (str->str, "simple") != NULL ||
g_strrstr (str->str, "simplistic") != NULL ||
g_strrstr (str->str, "dictionary") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"The new password is too simple!");
} else if (g_strrstr (str->str, "similar") != NULL ||
g_strrstr (str->str, "different") != NULL ||
g_strrstr (str->str, "case") != NULL ||
g_strrstr (str->str, "wrapped") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"The new password is too similar to the old one!");
} else if (g_strrstr (str->str, "1 numeric or special") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"The new password must contain numbers or special characters!");
} else if (g_strrstr (str->str, "unchanged") != NULL ||
g_strrstr (str->str, "match") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"The new password is the same as the old one!");
} else if (g_strrstr (str->str, "recent") != NULL) {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_REJECTED,
"The new password has been used recently!");
} else if (g_strrstr (str->str, "failure") != NULL) {
//Authentication failure
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_AUTH_FAILED,
"Your password has been changed after you verify!");
} */else {
error = g_error_new (PASSWD_ERROR, PASSWD_ERROR_UNKNOWN,
str->str);
}
/* At this point, passwd might have exited, in which case
* child_watch_cb should clean up for us and remove this watcher.
* On some error conditions though, passwd just re-prompts us
* for our new password. */
passwd_handler->backend_state = PASSWD_STATE_ERR;
passwd_handler->changing_password = FALSE;
/* Trigger callback to update status */
if (passwd_handler->chpasswd_cb)
passwd_handler->chpasswd_cb (passwd_handler,
error,
passwd_handler->chpasswd_cb_data);
g_error_free (error);
}
reinit = TRUE;
/* child_watch_cb should clean up for us now */
}
break;
case PASSWD_STATE_NONE:
/* Passwd is not asking for anything yet */
if (is_string_complete (str->str, "assword: ", NULL)) {
/* If the user does not have a password set,
* passwd will immediately ask for the new password,
* so skip the AUTH phase */
if (is_string_complete (str->str, "new", "New", NULL)) {
gchar *pw;
passwd_handler->backend_state = PASSWD_STATE_NEW;
/* since passwd didn't ask for our old password
* in this case, simply remove it from the queue */
pw = (gchar *)g_queue_pop_head (passwd_handler->backend_stdin_queue);
g_free (pw);
/* Pop the IO queue, i.e. send new password */
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
} else {
passwd_handler->backend_state = PASSWD_STATE_AUTH;
/* Pop the IO queue, i.e. send current password */
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
}
reinit = TRUE;
}
break;
default:
/* Passwd has returned an error */
reinit = TRUE;
break;
}
if (reinit) {
g_string_free (str, TRUE);
str = NULL;
}
/* Continue calling us */
return TRUE;
}
/* Child watcher */
static void child_watch_cb (GPid pid, gint status, PasswdHandler *passwd_handler)
{
//子进程正常结束为非0
if (WIFEXITED (status)) {
//取得子进程正常退出时返回的结束代码
if (WEXITSTATUS (status) >= 255) {
g_warning ("Child exited unexpectedly");
}
}
free_passwd_resources (passwd_handler);
}
static void stop_passwd (PasswdHandler *passwd_handler)
{
/* This is the standard way of returning from the dialog with passwd.
* If we return this way we can safely kill passwd as it has completed
* its task.
*/
if (passwd_handler->backend_pid != -1) {
kill (passwd_handler->backend_pid, 9);
}
/* We must run free_passwd_resources here and not let our child
* watcher do it, since it will access invalid memory after the
* dialog has been closed and cleaned up.
*
* If we had more than a single thread we'd need to remove
* the child watch before trying to kill the child.
*/
free_passwd_resources (passwd_handler);
}
static gboolean spawn_passwd (PasswdHandler *passwd_handler, GError **error)
{
gchar *argv[2];
gchar *envp[1];
gint my_stdin, my_stdout, my_stderr;
argv[0] = "/usr/bin/passwd"; /* Is it safe to rely on a hard-coded path? */
argv[1] = NULL;
envp[0] = NULL; /* If we pass an empty array as the environment,
* will the childs environment be empty, and the
* locales set to the C default? From the manual:
* "If envp is NULL, the child inherits its
* parent'senvironment."
* If I'm wrong here, we somehow have to set
* the locales here.
*/
//创建一个管道,进行通信,子进程执行passwd命令
if (!g_spawn_async_with_pipes (NULL, /* Working directory */
argv, /* Argument vector */
envp, /* Environment */
G_SPAWN_DO_NOT_REAP_CHILD, /* Flags */
NULL, /* Child setup (在子进程调用exec()之前,该函数会被调用)*/
NULL, /* Data to child setup */
&passwd_handler->backend_pid, /* PID */
&my_stdin, /* Stdin */
&my_stdout, /* Stdout */
&my_stderr, /* Stderr */
error)) { /* GError */
/* An error occured */
free_passwd_resources (passwd_handler);
return FALSE;
}
/* 2>&1 */
//复制文件描述符,也就是将stderr重定向到stdout
if (dup2 (my_stderr, my_stdout) == -1) {
/* Failed! */
g_set_error_literal (error,
PASSWD_ERROR,
PASSWD_ERROR_BACKEND,
strerror (errno));
/* Clean up */
stop_passwd (passwd_handler);
return FALSE;
}
/* Open IO Channels */
//指定一个文件描述符,创建一个IO Channel,默认使用UTF-8编码格式
passwd_handler->backend_stdin = g_io_channel_unix_new (my_stdin);
passwd_handler->backend_stdout = g_io_channel_unix_new (my_stdout);
/* Set raw encoding */
/* Set nonblocking mode */
//设置通道的编码方式为NULL,设置为非阻塞的方式
if (g_io_channel_set_encoding (passwd_handler->backend_stdin, NULL, error) != G_IO_STATUS_NORMAL ||
g_io_channel_set_encoding (passwd_handler->backend_stdout, NULL, error) != G_IO_STATUS_NORMAL ||
g_io_channel_set_flags (passwd_handler->backend_stdin, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ||
g_io_channel_set_flags (passwd_handler->backend_stdout, G_IO_FLAG_NONBLOCK, error) != G_IO_STATUS_NORMAL ) {
/* Clean up */
stop_passwd (passwd_handler);
return FALSE;
}
/* Turn off buffering */
//只有通道的编码方式为NULL,才能设置缓冲状态为FASLE,其他任何编码,通道必须被缓冲,这里是为了清掉上次的密码
g_io_channel_set_buffered (passwd_handler->backend_stdin, FALSE);
g_io_channel_set_buffered (passwd_handler->backend_stdout, FALSE);
/* Add IO Channel watcher */
//当IO通道的状态为G_IO_IN(从IO通道读数据时)或者G_IO_PRI(读紧急数据时)时,调用io_watch_stdout
passwd_handler->backend_stdout_watch_id = g_io_add_watch (passwd_handler->backend_stdout,
G_IO_IN /*| G_IO_PRI*/,
(GIOFunc) io_watch_stdout, passwd_handler);
/* Add child watcher */
//在指定pid的进程退出时,调用child_watch_cb(),进行错误检查,以及资源回收
passwd_handler->backend_child_watch_id = g_child_watch_add (passwd_handler->backend_pid, (GChildWatchFunc) child_watch_cb, passwd_handler);
/* Success! */
return TRUE;
}
static void update_password (PasswdHandler *passwd_handler)
{
gchar *s;
s = g_strdup_printf ("%s\n", passwd_handler->new_password);
g_queue_push_tail (passwd_handler->backend_stdin_queue, s);
/* We need to allocate new space because io_queue_pop() g_free()s
* every element of the queue after it's done */
g_queue_push_tail (passwd_handler->backend_stdin_queue, g_strdup (s));
}
gboolean passwd_change_password (PasswdHandler *passwd_handler,
const char *new_password,
PasswdCallback cb,
const gpointer user_data)
{
GError *error = NULL;
passwd_handler->changing_password = TRUE;
passwd_handler->new_password = new_password;
passwd_handler->chpasswd_cb = cb;
passwd_handler->chpasswd_cb_data = user_data;
/* Stop passwd if an error occured and it is still running */
if (passwd_handler->backend_state == PASSWD_STATE_ERR) {
/* Stop passwd, free resources */
stop_passwd (passwd_handler);
}
/* Check that the backend is still running, or that an error
* has occured but it has not yet exited */
if (passwd_handler->backend_pid == -1) {
/* If it is not, re-run authentication */
/* Spawn backend */
stop_passwd (passwd_handler);
if (!spawn_passwd (passwd_handler, &error)) {
g_error_free (error);
return FALSE;
}
/* Add current and new passwords to queue */
//将当前的密码和新密码入队,新密码会入队两次
authenticate (passwd_handler);
update_password (passwd_handler);
} else {
/* Only add new passwords to queue */
update_password (passwd_handler);
}
/* Pop new password through the backend. If user has no password, popping the queue
would output current password, while 'passwd' is waiting for the new one. So wait
for io_watch_stdout() to remove current password from the queue, and output
the new one for us.*/
//如果密码为空,将新进队列的密码,作为current_passwd弹出
if (passwd_handler->current_password)
{
io_queue_pop (passwd_handler->backend_stdin_queue, passwd_handler->backend_stdin);
}
/* Our IO watcher should now handle the rest */
return TRUE;
}
void passwd_authenticate (PasswdHandler *passwd_handler,
const char *current_password,
PasswdCallback cb,
const gpointer user_data)
{
GError *error = NULL;
/* Don't stop if we've already started chaging password */
if (passwd_handler->changing_password)
return;
/* Clear data from possible previous attempts to change password */
passwd_handler->new_password = NULL;
passwd_handler->chpasswd_cb = NULL;
passwd_handler->chpasswd_cb_data = NULL;
g_queue_foreach (passwd_handler->backend_stdin_queue, (GFunc) g_free, NULL);
g_queue_clear (passwd_handler->backend_stdin_queue);
passwd_handler->current_password = current_password;
passwd_handler->auth_cb = cb;
passwd_handler->auth_cb_data = user_data;
/* Spawn backend */
//重新启动后台passwd
stop_passwd (passwd_handler);
if (!spawn_passwd (passwd_handler, &error)) {
g_warning ("%s", error->message);
g_error_free (error);
return;
}
//将current passwd从尾部插入队列
authenticate (passwd_handler);
/* Our IO watcher should now handle the rest */
}
PasswdHandler * passwd_init ()
{
PasswdHandler *passwd_handler;
passwd_handler = g_new0 (PasswdHandler, 1);
/* Initialize backend_pid. -1 means the backend is not running */
//-1代表后台还没启动
passwd_handler->backend_pid = -1;
/* Initialize IO Channels */
passwd_handler->backend_stdin = NULL;
passwd_handler->backend_stdout = NULL;
/* Initialize write queue */
passwd_handler->backend_stdin_queue = g_queue_new ();
/* Initialize watchers */
passwd_handler->backend_child_watch_id = 0;
passwd_handler->backend_stdout_watch_id = 0;
/* Initialize backend state */
passwd_handler->backend_state = PASSWD_STATE_NONE;
passwd_handler->changing_password = FALSE;
return passwd_handler;
}
void passwd_destroy (PasswdHandler *passwd_handler)
{
g_queue_free (passwd_handler->backend_stdin_queue);
stop_passwd (passwd_handler);
g_free (passwd_handler);
}