1/*
2 gpgsignkeyeditinteractor.cpp - Edit Interactor to change the expiry time of an OpenPGP key
3 Copyright (C) 2007 Klarälvdalens Datakonsult AB
4
5 This file is part of GPGME++.
6
7 GPGME++ is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public
9 License as published by the Free Software Foundation; either
10 version 2 of the License, or (at your option) any later version.
11
12 GPGME++ is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU Library General Public License for more details.
16
17 You should have received a copy of the GNU Library General Public License
18 along with GPGME++; see the file COPYING.LIB. If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.
21*/
22
23#include "gpgsignkeyeditinteractor.h"
24#include "error.h"
25#include "key.h"
26
27#include <gpgme.h>
28
29#include <boost/tuple/tuple.hpp>
30#include <boost/tuple/tuple_comparison.hpp>
31
32#include <map>
33#include <string>
34#include <sstream>
35
36#include <cassert>
37#include <cstring>
38
39
40using std::strcmp;
41
42// avoid conflict (msvc)
43#ifdef ERROR
44# undef ERROR
45#endif
46
47#ifdef _MSC_VER
48#undef snprintf
49#define snprintf _snprintf
50#endif
51
52using namespace boost;
53using namespace GpgME;
54
55class GpgSignKeyEditInteractor::Private {
56public:
57 Private();
58
59 std::string scratch;
60 bool started;
61 int options;
62 std::vector<unsigned int> userIDs;
63 std::vector<unsigned int>::const_iterator currentId, nextId;
64 unsigned int checkLevel;
65
66 const char * command() const {
67 const bool local = ( options & Exportable ) == 0;
68 const bool nonRevoc = options & NonRevocable;
69 const bool trust = options & Trust;
70 //TODO: check if all combinations are valid
71 if ( local && nonRevoc && trust ) {
72 return "ltnrsign";
73 }
74 if ( local && nonRevoc ) {
75 return "lnrsign";
76 }
77 if ( local && trust ) {
78 return "ltsign";
79 }
80 if ( local ) {
81 return "lsign";
82 }
83 if ( nonRevoc && trust ) {
84 return "tnrsign";
85 }
86 if ( nonRevoc ) {
87 return "nrsign";
88 }
89 if ( trust ) {
90 return "tsign";
91 }
92 return "sign";
93 }
94
95 bool signAll() const { return userIDs.empty(); }
96 unsigned int nextUserID() {
97 assert( nextId != userIDs.end() );
98 currentId = nextId++;
99 return currentUserID();
100 }
101
102 bool allUserIDsListed() const {
103 return nextId == userIDs.end();
104 }
105
106 unsigned int currentUserID() const {
107 assert( currentId != userIDs.end() );
108 return *currentId + 1;
109 }
110
111};
112
113GpgSignKeyEditInteractor::Private::Private()
114 :
115 started( false ),
116 options( 0 ),
117 userIDs(),
118 currentId(),
119 nextId(),
120 checkLevel( 0 ) {
121}
122
123GpgSignKeyEditInteractor::GpgSignKeyEditInteractor()
124 : EditInteractor(), d( new Private )
125{
126
127}
128
129GpgSignKeyEditInteractor::~GpgSignKeyEditInteractor() {
130 delete d;
131}
132
133// work around --enable-final
134namespace GpgSignKeyEditInteractor_Private {
135enum SignKeyState {
136 START = EditInteractor::StartState,
137 COMMAND,
138 UIDS_ANSWER_SIGN_ALL,
139 UIDS_LIST_SEPARATELY,
140 // all these free slots belong to UIDS_LIST_SEPARATELY, too
141 // (we increase state() by one for each UID, so that action() is called)
142 UIDS_LIST_SEPARATELY_DONE = 1000000,
143 SET_EXPIRE,
144 SET_CHECK_LEVEL,
145 SET_TRUST_VALUE,
146 SET_TRUST_DEPTH,
147 SET_TRUST_REGEXP,
148 CONFIRM,
149 QUIT,
150 SAVE,
151 ERROR = EditInteractor::ErrorState
152};
153
154typedef std::map<tuple<SignKeyState, unsigned int, std::string>, SignKeyState> TransitionMap;
155
156}
157
158static const char * answer( bool b ) {
159 return b ? "Y" : "N";
160}
161
162static GpgSignKeyEditInteractor_Private::TransitionMap makeTable() {
163 using namespace GpgSignKeyEditInteractor_Private;
164 TransitionMap tab;
165 const unsigned int GET_BOOL = GPGME_STATUS_GET_BOOL;
166 const unsigned int GET_LINE = GPGME_STATUS_GET_LINE;
167#define addEntry( s1, status, str, s2 ) tab[make_tuple( s1, status, str)] = s2
168 addEntry( START, GET_LINE, "keyedit.prompt", COMMAND );
169 addEntry( COMMAND, GET_BOOL, "keyedit.sign_all.okay", UIDS_ANSWER_SIGN_ALL );
170 addEntry( COMMAND, GET_BOOL, "sign_uid.okay", CONFIRM );
171 addEntry( UIDS_ANSWER_SIGN_ALL, GET_BOOL, "sign_uid.okay", CONFIRM );
172 addEntry( UIDS_ANSWER_SIGN_ALL, GET_LINE, "sign_uid.expire", SET_EXPIRE );
173 addEntry( UIDS_ANSWER_SIGN_ALL, GET_LINE, "sign_uid.class", SET_CHECK_LEVEL );
174 addEntry( SET_TRUST_VALUE, GET_LINE, "trustsign_prompt.trust_depth", SET_TRUST_DEPTH );
175 addEntry( SET_TRUST_DEPTH, GET_LINE, "trustsign_prompt.trust_regexp", SET_TRUST_REGEXP );
176 addEntry( SET_TRUST_REGEXP, GET_LINE, "sign_uid.okay", CONFIRM );
177 addEntry( SET_CHECK_LEVEL, GET_BOOL, "sign_uid.okay", CONFIRM );
178 addEntry( SET_EXPIRE, GET_BOOL, "sign_uid.class", SET_CHECK_LEVEL );
179 addEntry( CONFIRM, GET_BOOL, "sign_uid.local_promote_okay", CONFIRM );
180 addEntry( CONFIRM, GET_BOOL, "sign_uid.okay", CONFIRM );
181 addEntry( CONFIRM, GET_LINE, "keyedit.prompt", COMMAND );
182 addEntry( CONFIRM, GET_LINE, "trustsign_prompt.trust_value", SET_TRUST_VALUE );
183 addEntry( CONFIRM, GET_LINE, "sign_uid.expire", SET_EXPIRE );
184 addEntry( CONFIRM, GET_LINE, "sign_uid.class", SET_CHECK_LEVEL );
185 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_BOOL, "sign_uid.local_promote_okay", CONFIRM );
186 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_LINE, "keyedit.prompt", COMMAND );
187 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_LINE, "trustsign_prompt.trust_value", SET_TRUST_VALUE );
188 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_LINE, "sign_uid.expire", SET_EXPIRE );
189 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_LINE, "sign_uid.class", SET_CHECK_LEVEL );
190 addEntry( UIDS_LIST_SEPARATELY_DONE, GET_BOOL, "sign_uid.okay", CONFIRM );
191 addEntry( CONFIRM, GET_LINE, "keyedit.prompt", QUIT );
192 addEntry( ERROR, GET_LINE, "keyedit.prompt", QUIT );
193 addEntry( QUIT, GET_BOOL, "keyedit.save.okay", SAVE );
194#undef addEntry
195 return tab;
196}
197
198const char * GpgSignKeyEditInteractor::action( Error & err ) const {
199 static const char check_level_strings[][2] = { "0", "1", "2", "3" };
200 using namespace GpgSignKeyEditInteractor_Private;
201 using namespace std;
202
203 switch ( const unsigned int st = state() ) {
204 case COMMAND:
205 return d->command();
206 case UIDS_ANSWER_SIGN_ALL:
207 return answer( d->signAll() );
208 case UIDS_LIST_SEPARATELY_DONE:
209 return d->command();
210 case SET_EXPIRE:
211 return answer( true );
212 case SET_TRUST_VALUE:
213 // TODO
214 case SET_TRUST_DEPTH:
215 //TODO
216 case SET_TRUST_REGEXP:
217 //TODO
218 return 0;
219 case SET_CHECK_LEVEL:
220 return check_level_strings[d->checkLevel];
221 case CONFIRM:
222 return answer( true );
223 case QUIT:
224 return "quit";
225 case SAVE:
226 return answer( true );
227 default:
228 if ( st >= UIDS_LIST_SEPARATELY && st < UIDS_LIST_SEPARATELY_DONE ) {
229 std::stringstream ss;
230 ss << d->nextUserID();
231 d->scratch = ss.str();
232 return d->scratch.c_str();
233 }
234 // fall through
235 case ERROR:
236 err = Error::fromCode( GPG_ERR_GENERAL );
237 return 0;
238 }
239}
240
241unsigned int GpgSignKeyEditInteractor::nextState( unsigned int status, const char * args, Error & err ) const {
242 d->started = true;
243 using namespace GpgSignKeyEditInteractor_Private;
244 static const Error GENERAL_ERROR = Error::fromCode( GPG_ERR_GENERAL );
245 //static const Error INV_TIME_ERROR = Error::fromCode( GPG_ERR_INV_TIME );
246 static const TransitionMap table( makeTable() );
247 if ( needsNoResponse( status ) ) {
248 return state();
249 }
250
251 using namespace GpgSignKeyEditInteractor_Private;
252
253 //lookup transition in map
254 const TransitionMap::const_iterator it = table.find( boost::make_tuple( static_cast<SignKeyState>( state() ), status, std::string( args ) ) );
255 if ( it != table.end() ) {
256 return it->second;
257 }
258
259 //handle cases that cannot be handled via the map
260 switch ( const unsigned int st = state() ) {
261 case UIDS_ANSWER_SIGN_ALL:
262 if ( status == GPGME_STATUS_GET_LINE &&
263 strcmp( args, "keyedit.prompt" ) == 0 ) {
264 if ( !d->signAll() ) {
265 return UIDS_LIST_SEPARATELY;
266 }
267 err = Error::fromCode( GPG_ERR_UNUSABLE_PUBKEY );
268 return ERROR;
269 }
270 break;
271 default:
272 if ( st >= UIDS_LIST_SEPARATELY && st < UIDS_LIST_SEPARATELY_DONE ) {
273 if ( status == GPGME_STATUS_GET_LINE &&
274 strcmp( args, "keyedit.prompt" ) == 0 ) {
275 return d->allUserIDsListed() ? UIDS_LIST_SEPARATELY_DONE : st+1 ;
276 }
277 }
278 break;
279 case CONFIRM:
280 case ERROR:
281 err = lastError();
282 return ERROR;
283 }
284
285 err = GENERAL_ERROR;
286 return ERROR;
287}
288
289void GpgSignKeyEditInteractor::setCheckLevel( unsigned int checkLevel ) {
290 assert( !d->started );
291 assert( checkLevel <= 3 );
292 d->checkLevel = checkLevel;
293}
294
295void GpgSignKeyEditInteractor::setUserIDsToSign( const std::vector<unsigned int> & userIDsToSign ) {
296 assert( !d->started );
297 d->userIDs = userIDsToSign;
298 d->nextId = d->userIDs.begin();
299 d->currentId = d->userIDs.end();
300
301}
302void GpgSignKeyEditInteractor::setSigningOptions( int options ) {
303 assert( !d->started );
304 d->options = options;
305}
306
307