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 | |
40 | using 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 | |
52 | using namespace boost; |
53 | using namespace GpgME; |
54 | |
55 | class GpgSignKeyEditInteractor::Private { |
56 | public: |
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 | |
113 | GpgSignKeyEditInteractor::Private::Private() |
114 | : |
115 | started( false ), |
116 | options( 0 ), |
117 | userIDs(), |
118 | currentId(), |
119 | nextId(), |
120 | checkLevel( 0 ) { |
121 | } |
122 | |
123 | GpgSignKeyEditInteractor::GpgSignKeyEditInteractor() |
124 | : EditInteractor(), d( new Private ) |
125 | { |
126 | |
127 | } |
128 | |
129 | GpgSignKeyEditInteractor::~GpgSignKeyEditInteractor() { |
130 | delete d; |
131 | } |
132 | |
133 | // work around --enable-final |
134 | namespace GpgSignKeyEditInteractor_Private { |
135 | enum 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 | |
154 | typedef std::map<tuple<SignKeyState, unsigned int, std::string>, SignKeyState> TransitionMap; |
155 | |
156 | } |
157 | |
158 | static const char * answer( bool b ) { |
159 | return b ? "Y" : "N" ; |
160 | } |
161 | |
162 | static 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 | |
198 | const 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 | |
241 | unsigned 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 | |
289 | void GpgSignKeyEditInteractor::setCheckLevel( unsigned int checkLevel ) { |
290 | assert( !d->started ); |
291 | assert( checkLevel <= 3 ); |
292 | d->checkLevel = checkLevel; |
293 | } |
294 | |
295 | void 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 | } |
302 | void GpgSignKeyEditInteractor::setSigningOptions( int options ) { |
303 | assert( !d->started ); |
304 | d->options = options; |
305 | } |
306 | |
307 | |