Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F35134460
pinentry-emacs.c
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
15 KB
Subscribers
None
pinentry-emacs.c
View Options
/* pinentry-emacs.c - A secure emacs dialog for PIN entry, library version
Copyright (C) 2015 Daiki Ueno
This file is part of PINENTRY.
PINENTRY is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
PINENTRY 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
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#ifdef HAVE_CONFIG_H
#include
<config.h>
#endif
#ifdef HAVE_STDINT_H
#include
<stdint.h>
#endif
#ifdef HAVE_INTTYPES_H
#include
<inttypes.h>
#endif
#include
<assert.h>
#include
<signal.h>
#include
<fcntl.h>
#include
<unistd.h>
#include
<stdio.h>
#include
<stdlib.h>
#include
<string.h>
#include
<errno.h>
#include
<limits.h>
#include
<time.h>
#include
<sys/types.h>
#include
<sys/stat.h>
#include
<sys/socket.h>
#include
<sys/un.h>
#ifdef HAVE_UTIME_H
#include
<utime.h>
#endif
/*HAVE_UTIME_H*/
#include
<assuan.h>
#include
"pinentry-emacs.h"
#include
"memory.h"
#include
"secmem-util.h"
/* The communication mechanism is similar to emacsclient, but there
are a few differences:
- To avoid unnecessary character escaping and encoding conversion,
we use a subset of the Pinentry Assuan protocol, instead of the
emacsclient protocol.
- We only use a Unix domain socket, while emacsclient has an
ability to use a TCP socket. The socket file is located at
${TMPDIR-/tmp}/emacs$(id -u)/pinentry (i.e., under the same
directory as the socket file used by emacsclient, so the same
permission and file owner settings apply).
- The server implementation can be found in pinentry.el, which is
available in Emacs 25+ or from ELPA. */
#define LINELENGTH ASSUAN_LINELENGTH
#define SEND_BUFFER_SIZE 4096
#define INITIAL_TIMEOUT 60
static
int
initial_timeout
=
INITIAL_TIMEOUT
;
#undef MIN
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#undef MAX
#define MAX(x, y) ((x) < (y) ? (y) : (x))
#ifndef SUN_LEN
# define SUN_LEN(ptr) ((size_t) (((struct sockaddr_un *) 0)->sun_path) \
+ strlen ((ptr)->sun_path))
#endif
/* FIXME: We could use the I/O functions in Assuan directly, once
Pinentry links to libassuan. */
static
int
emacs_socket
=
-1
;
static
char
send_buffer
[
SEND_BUFFER_SIZE
+
1
];
static
int
send_buffer_length
;
/* Fill pointer for the send buffer. */
static
pinentry_cmd_handler_t
fallback_cmd_handler
;
#ifndef HAVE_DOSISH_SYSTEM
static
int
timed_out
;
#endif
static
int
set_socket
(
const
char
*
socket_name
)
{
struct
sockaddr_un
unaddr
;
struct
stat
statbuf
;
const
char
*
tmpdir
;
char
*
tmpdir_storage
=
NULL
;
char
*
socket_name_storage
=
NULL
;
uid_t
uid
;
unaddr
.
sun_family
=
AF_UNIX
;
/* We assume 32-bit UIDs, which can be represented with 10 decimal
digits. */
uid
=
getuid
();
if
(
uid
!=
(
uint32_t
)
uid
)
{
fprintf
(
stderr
,
"UID is too large
\n
"
);
return
0
;
}
tmpdir
=
getenv
(
"TMPDIR"
);
if
(
!
tmpdir
)
{
#ifdef _CS_DARWIN_USER_TEMP_DIR
size_t
n
=
confstr
(
_CS_DARWIN_USER_TEMP_DIR
,
NULL
,
(
size_t
)
0
);
if
(
n
>
0
)
{
tmpdir
=
tmpdir_storage
=
malloc
(
n
);
if
(
!
tmpdir
)
{
fprintf
(
stderr
,
"out of core
\n
"
);
return
0
;
}
confstr
(
_CS_DARWIN_USER_TEMP_DIR
,
tmpdir_storage
,
n
);
}
else
#endif
tmpdir
=
"/tmp"
;
}
socket_name_storage
=
malloc
(
strlen
(
tmpdir
)
+
strlen
(
"/emacs"
)
+
10
+
strlen
(
"/"
)
+
strlen
(
socket_name
)
+
1
);
if
(
!
socket_name_storage
)
{
fprintf
(
stderr
,
"out of core
\n
"
);
free
(
tmpdir_storage
);
return
0
;
}
sprintf
(
socket_name_storage
,
"%s/emacs%u/%s"
,
tmpdir
,
(
uint32_t
)
uid
,
socket_name
);
free
(
tmpdir_storage
);
if
(
strlen
(
socket_name_storage
)
>=
sizeof
(
unaddr
.
sun_path
))
{
fprintf
(
stderr
,
"socket name is too long
\n
"
);
free
(
socket_name_storage
);
return
0
;
}
strcpy
(
unaddr
.
sun_path
,
socket_name_storage
);
free
(
socket_name_storage
);
/* See if the socket exists, and if it's owned by us. */
if
(
stat
(
unaddr
.
sun_path
,
&
statbuf
)
==
-1
)
{
perror
(
"stat"
);
return
0
;
}
if
(
statbuf
.
st_uid
!=
geteuid
())
{
fprintf
(
stderr
,
"socket is not owned by the same user
\n
"
);
return
0
;
}
emacs_socket
=
socket
(
AF_UNIX
,
SOCK_STREAM
,
0
);
if
(
emacs_socket
<
0
)
{
perror
(
"socket"
);
return
0
;
}
if
(
connect
(
emacs_socket
,
(
struct
sockaddr
*
)
&
unaddr
,
SUN_LEN
(
&
unaddr
))
<
0
)
{
perror
(
"connect"
);
close
(
emacs_socket
);
emacs_socket
=
-1
;
return
0
;
}
return
1
;
}
/* Percent-escape control characters in DATA. Return a newly
allocated string. */
static
char
*
escape
(
const
char
*
data
)
{
char
*
buffer
,
*
out_p
;
size_t
length
,
buffer_length
;
size_t
offset
;
size_t
count
=
0
;
length
=
strlen
(
data
);
for
(
offset
=
0
;
offset
<
length
;
offset
++
)
{
switch
(
data
[
offset
])
{
case
'%'
:
case
'\n'
:
case
'\r'
:
count
++
;
break
;
default
:
break
;
}
}
buffer_length
=
length
+
count
*
2
;
buffer
=
malloc
(
buffer_length
+
1
);
if
(
!
buffer
)
return
NULL
;
out_p
=
buffer
;
for
(
offset
=
0
;
offset
<
length
;
offset
++
)
{
int
c
=
data
[
offset
];
switch
(
c
)
{
case
'%'
:
case
'\n'
:
case
'\r'
:
sprintf
(
out_p
,
"%%%02X"
,
c
);
out_p
+=
3
;
break
;
default
:
*
out_p
++
=
c
;
break
;
}
}
*
out_p
=
'\0'
;
return
buffer
;
}
/* The inverse of escape. Unlike escape, it removes quoting in string
DATA by modifying the string in place, to avoid copying of secret
data sent from Emacs. */
static
char
*
unescape
(
char
*
data
)
{
char
*
p
=
data
,
*
q
=
data
;
while
(
*
p
)
{
if
(
*
p
==
'%'
&&
p
[
1
]
&&
p
[
2
])
{
p
++
;
*
q
++
=
xtoi_2
(
p
);
p
+=
2
;
}
else
*
q
++
=
*
p
++
;
}
*
q
=
0
;
return
data
;
}
/* Let's send the data to Emacs when either
- the data ends in "\n", or
- the buffer is full (but this shouldn't happen)
Otherwise, we just accumulate it. */
static
int
send_to_emacs
(
int
s
,
const
char
*
buffer
)
{
size_t
length
;
length
=
strlen
(
buffer
);
while
(
*
buffer
)
{
size_t
part
=
MIN
(
length
,
SEND_BUFFER_SIZE
-
send_buffer_length
);
memcpy
(
&
send_buffer
[
send_buffer_length
],
buffer
,
part
);
buffer
+=
part
;
send_buffer_length
+=
part
;
if
(
send_buffer_length
==
SEND_BUFFER_SIZE
||
(
send_buffer_length
>
0
&&
send_buffer
[
send_buffer_length
-1
]
==
'\n'
))
{
int
sent
=
send
(
s
,
send_buffer
,
send_buffer_length
,
0
);
if
(
sent
<
0
)
{
fprintf
(
stderr
,
"failed to send %d bytes to socket: %s
\n
"
,
send_buffer_length
,
strerror
(
errno
));
send_buffer_length
=
0
;
return
0
;
}
if
(
sent
!=
send_buffer_length
)
memmove
(
send_buffer
,
&
send_buffer
[
sent
],
send_buffer_length
-
sent
);
send_buffer_length
-=
sent
;
}
length
-=
part
;
}
return
1
;
}
/* Read a server response. If the response contains data, it will be
stored in BUFFER with a terminating NUL byte. BUFFER must be
at least as large as CAPACITY. */
static
gpg_error_t
read_from_emacs
(
int
s
,
int
timeout
,
char
*
buffer
,
size_t
capacity
)
{
struct
timeval
tv
;
fd_set
rfds
;
int
retval
;
/* Offset in BUFFER. */
size_t
offset
=
0
;
int
got_response
=
0
;
char
read_buffer
[
LINELENGTH
+
1
];
/* Offset in READ_BUFFER. */
size_t
read_offset
=
0
;
gpg_error_t
result
=
0
;
tv
.
tv_sec
=
timeout
;
tv
.
tv_usec
=
0
;
FD_ZERO
(
&
rfds
);
FD_SET
(
s
,
&
rfds
);
retval
=
select
(
s
+
1
,
&
rfds
,
NULL
,
NULL
,
&
tv
);
if
(
retval
==
-1
)
{
perror
(
"select"
);
return
gpg_error
(
GPG_ERR_ASS_GENERAL
);
}
else
if
(
retval
==
0
)
{
timed_out
=
1
;
return
gpg_error
(
GPG_ERR_TIMEOUT
);
}
/* Loop until we get either OK or ERR. */
while
(
!
got_response
)
{
int
rl
=
0
;
char
*
p
,
*
end_p
;
do
{
errno
=
0
;
rl
=
recv
(
s
,
read_buffer
+
read_offset
,
LINELENGTH
-
read_offset
,
0
);
}
/* If we receive a signal (e.g. SIGWINCH, which we pass
through to Emacs), on some OSes we get EINTR and must retry. */
while
(
rl
<
0
&&
errno
==
EINTR
);
if
(
rl
<
0
)
{
perror
(
"recv"
);
return
gpg_error
(
GPG_ERR_ASS_GENERAL
);;
}
if
(
rl
==
0
)
break
;
read_offset
+=
rl
;
read_buffer
[
read_offset
]
=
'\0'
;
end_p
=
strchr
(
read_buffer
,
'\n'
);
/* If the buffer is filled without NL, throw away the content
and start over the buffering.
FIXME: We could return ASSUAN_Line_Too_Long or
ASSUAN_Line_Not_Terminated here. */
if
(
!
end_p
&&
read_offset
==
sizeof
(
read_buffer
)
-
1
)
{
read_offset
=
0
;
continue
;
}
/* Loop over all NL-terminated messages. */
for
(
p
=
read_buffer
;
end_p
;
p
=
end_p
+
1
,
end_p
=
strchr
(
p
,
'\n'
))
{
*
end_p
=
'\0'
;
if
(
!
strncmp
(
"D "
,
p
,
2
))
{
char
*
data
;
size_t
data_length
;
size_t
needed_capacity
;
data
=
p
+
2
;
data_length
=
end_p
-
data
;
if
(
data_length
>
0
)
{
needed_capacity
=
offset
+
data_length
+
1
;
/* Check overflow. This is unrealistic but can
happen since OFFSET is cumulative. */
if
(
needed_capacity
<
offset
)
return
gpg_error
(
GPG_ERR_ASS_GENERAL
);;
if
(
needed_capacity
>
capacity
)
return
gpg_error
(
GPG_ERR_ASS_GENERAL
);;
memcpy
(
&
buffer
[
offset
],
data
,
data_length
);
offset
+=
data_length
;
buffer
[
offset
]
=
0
;
}
}
else
if
(
!
strcmp
(
"OK"
,
p
)
||
!
strncmp
(
"OK "
,
p
,
3
))
{
got_response
=
1
;
break
;
}
else
if
(
!
strncmp
(
"ERR "
,
p
,
4
))
{
unsigned
long
code
=
strtoul
(
p
+
4
,
NULL
,
10
);
if
(
code
==
ULONG_MAX
&&
errno
==
ERANGE
)
return
gpg_error
(
GPG_ERR_ASS_GENERAL
);
else
result
=
code
;
got_response
=
1
;
break
;
}
else
if
(
*
p
==
'#'
)
;
else
fprintf
(
stderr
,
"invalid response: %s
\n
"
,
p
);
}
if
(
!
got_response
)
{
size_t
length
=
&
read_buffer
[
read_offset
]
-
p
;
memmove
(
read_buffer
,
p
,
length
);
read_offset
=
length
;
}
}
return
result
;
}
int
set_label
(
pinentry_t
pe
,
const
char
*
name
,
const
char
*
value
)
{
char
buffer
[
16
],
*
escaped
;
gpg_error_t
error
;
int
retval
;
if
(
!
send_to_emacs
(
emacs_socket
,
name
)
||
!
send_to_emacs
(
emacs_socket
,
" "
))
return
0
;
escaped
=
escape
(
value
);
if
(
!
escaped
)
return
0
;
retval
=
send_to_emacs
(
emacs_socket
,
escaped
)
&&
send_to_emacs
(
emacs_socket
,
"
\n
"
);
free
(
escaped
);
if
(
!
retval
)
return
0
;
error
=
read_from_emacs
(
emacs_socket
,
pe
->
timeout
,
buffer
,
sizeof
(
buffer
));
return
error
==
0
;
}
static
void
set_labels
(
pinentry_t
pe
)
{
if
(
pe
->
title
)
set_label
(
pe
,
"SETTITLE"
,
pe
->
title
);
if
(
pe
->
description
)
set_label
(
pe
,
"SETDESC"
,
pe
->
description
);
if
(
pe
->
error
)
set_label
(
pe
,
"SETERROR"
,
pe
->
error
);
if
(
pe
->
prompt
)
set_label
(
pe
,
"SETPROMPT"
,
pe
->
prompt
);
else
if
(
pe
->
default_prompt
)
set_label
(
pe
,
"SETPROMPT"
,
pe
->
default_prompt
);
if
(
pe
->
repeat_passphrase
)
set_label
(
pe
,
"SETREPEAT"
,
pe
->
repeat_passphrase
);
if
(
pe
->
repeat_error_string
)
set_label
(
pe
,
"SETREPEATERROR"
,
pe
->
repeat_error_string
);
/* XXX: pe->quality_bar and pe->quality_bar_tt are not supported. */
/* Buttons. */
if
(
pe
->
ok
)
set_label
(
pe
,
"SETOK"
,
pe
->
ok
);
else
if
(
pe
->
default_ok
)
set_label
(
pe
,
"SETOK"
,
pe
->
default_ok
);
if
(
pe
->
cancel
)
set_label
(
pe
,
"SETCANCEL"
,
pe
->
cancel
);
else
if
(
pe
->
default_ok
)
set_label
(
pe
,
"SETCANCEL"
,
pe
->
default_cancel
);
if
(
pe
->
notok
)
set_label
(
pe
,
"SETNOTOK"
,
pe
->
notok
);
}
static
int
do_password
(
pinentry_t
pe
)
{
char
*
buffer
,
*
password
;
size_t
length
=
LINELENGTH
;
gpg_error_t
error
;
set_labels
(
pe
);
if
(
!
send_to_emacs
(
emacs_socket
,
"GETPIN
\n
"
))
return
-1
;
buffer
=
secmem_malloc
(
length
);
if
(
!
buffer
)
{
pe
->
specific_err
=
gpg_error
(
GPG_ERR_ENOMEM
);
return
-1
;
}
error
=
read_from_emacs
(
emacs_socket
,
pe
->
timeout
,
buffer
,
length
);
if
(
error
!=
0
)
{
if
(
gpg_err_code
(
error
)
==
GPG_ERR_CANCELED
)
pe
->
canceled
=
1
;
secmem_free
(
buffer
);
pe
->
specific_err
=
error
;
return
-1
;
}
password
=
unescape
(
buffer
);
pinentry_setbufferlen
(
pe
,
strlen
(
password
)
+
1
);
if
(
pe
->
pin
)
strcpy
(
pe
->
pin
,
password
);
secmem_free
(
buffer
);
if
(
pe
->
repeat_passphrase
)
pe
->
repeat_okay
=
1
;
/* XXX: we don't support external password cache (yet). */
return
1
;
}
static
int
do_confirm
(
pinentry_t
pe
)
{
char
buffer
[
16
];
gpg_error_t
error
;
set_labels
(
pe
);
if
(
!
send_to_emacs
(
emacs_socket
,
"CONFIRM
\n
"
))
return
0
;
error
=
read_from_emacs
(
emacs_socket
,
pe
->
timeout
,
buffer
,
sizeof
(
buffer
));
if
(
error
!=
0
)
{
if
(
gpg_err_code
(
error
)
==
GPG_ERR_CANCELED
)
pe
->
canceled
=
1
;
pe
->
specific_err
=
error
;
return
0
;
}
return
1
;
}
/* If a touch has been registered, touch that file. */
static
void
do_touch_file
(
pinentry_t
pinentry
)
{
#ifdef HAVE_UTIME_H
struct
stat
st
;
time_t
tim
;
if
(
!
pinentry
->
touch_file
||
!*
pinentry
->
touch_file
)
return
;
if
(
stat
(
pinentry
->
touch_file
,
&
st
))
return
;
/* Oops. */
/* Make sure that we actually update the mtime. */
while
(
(
tim
=
time
(
NULL
))
==
st
.
st_mtime
)
sleep
(
1
);
/* Update but ignore errors as we can't do anything in that case.
Printing error messages may even clubber the display further. */
utime
(
pinentry
->
touch_file
,
NULL
);
#endif
/*HAVE_UTIME_H*/
}
#ifndef HAVE_DOSISH_SYSTEM
static
void
catchsig
(
int
sig
)
{
if
(
sig
==
SIGALRM
)
timed_out
=
1
;
}
#endif
int
emacs_cmd_handler
(
pinentry_t
pe
)
{
int
rc
;
#ifndef HAVE_DOSISH_SYSTEM
timed_out
=
0
;
if
(
pe
->
timeout
)
{
struct
sigaction
sa
;
memset
(
&
sa
,
0
,
sizeof
(
sa
));
sa
.
sa_handler
=
catchsig
;
sigaction
(
SIGALRM
,
&
sa
,
NULL
);
alarm
(
pe
->
timeout
);
}
#endif
if
(
pe
->
pin
)
rc
=
do_password
(
pe
);
else
rc
=
do_confirm
(
pe
);
do_touch_file
(
pe
);
return
rc
;
}
static
int
initial_emacs_cmd_handler
(
pinentry_t
pe
)
{
/* Let the select() call in pinentry_emacs_init honor the timeout
value set through an Assuan option. */
initial_timeout
=
pe
->
timeout
;
if
(
emacs_socket
<
0
)
pinentry_emacs_init
();
/* If we have successfully connected to Emacs, swap
pinentry_cmd_handler to emacs_cmd_handler, so further
interactions will be forwarded to Emacs. Otherwise, set it back
to the original command handler saved as
fallback_cmd_handler. */
if
(
emacs_socket
<
0
)
pinentry_cmd_handler
=
fallback_cmd_handler
;
else
pinentry_cmd_handler
=
emacs_cmd_handler
;
return
(
*
pinentry_cmd_handler
)
(
pe
);
}
void
pinentry_enable_emacs_cmd_handler
(
void
)
{
const
char
*
envvar
;
/* Check if pinentry_cmd_handler is already prepared for Emacs. */
if
(
pinentry_cmd_handler
==
initial_emacs_cmd_handler
||
pinentry_cmd_handler
==
emacs_cmd_handler
)
return
;
/* Check if INSIDE_EMACS envvar is set. */
envvar
=
getenv
(
"INSIDE_EMACS"
);
if
(
!
envvar
||
!*
envvar
)
return
;
/* Save the original command handler as fallback_cmd_handler, and
swap pinentry_cmd_handler to initial_emacs_cmd_handler. */
fallback_cmd_handler
=
pinentry_cmd_handler
;
pinentry_cmd_handler
=
initial_emacs_cmd_handler
;
}
int
pinentry_emacs_init
(
void
)
{
char
buffer
[
256
];
gpg_error_t
error
;
assert
(
emacs_socket
<
0
);
/* Check if we can connect to the Emacs server socket. */
if
(
!
set_socket
(
"pinentry"
))
return
0
;
/* Check if the server responds. */
error
=
read_from_emacs
(
emacs_socket
,
initial_timeout
,
buffer
,
sizeof
(
buffer
));
if
(
error
!=
0
)
{
close
(
emacs_socket
);
emacs_socket
=
-1
;
return
0
;
}
return
1
;
}
File Metadata
Details
Attached
Mime Type
text/x-c
Expires
Sun, Feb 1, 7:25 PM (1 d, 19 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
2d/28/7bc42c2def48e2f16ac8872f2750
Attached To
rP Pinentry
Event Timeline
Log In to Comment