1 | /* |
2 | |
3 | Authentication method specific conversation plugin for KDE's greeter widgets |
4 | |
5 | Copyright (C) 2003 Oswald Buddenhagen <ossi@kde.org> |
6 | Copyright (C) 2003 Fabian Kaiser <xfk@softpro.de> |
7 | |
8 | This library is free software; you can redistribute it and/or |
9 | modify it under the terms of the GNU Library General Public |
10 | License as published by the Free Software Foundation; either |
11 | version 2 of the License, or (at your option) any later version. |
12 | |
13 | This library is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | Library General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU Library General Public License |
19 | along with this library; see the file COPYING.LIB. If not, write to |
20 | the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | Boston, MA 02110-1301, USA. |
22 | */ |
23 | |
24 | #ifndef KGREETERPLUGIN_H |
25 | #define KGREETERPLUGIN_H |
26 | |
27 | #include <QtCore/QVariant> |
28 | #include <QtGui/QMessageBox> |
29 | #include <kdemacros.h> |
30 | |
31 | class QWidget; |
32 | |
33 | class KGreeterPluginHandler { |
34 | public: |
35 | virtual ~KGreeterPluginHandler() {} |
36 | /* keep in sync with V_IS_* */ |
37 | enum { IsSecret = 1, IsUser = 2, IsPassword = 4, IsOldPassword = 8, |
38 | IsNewPassword = 16 }; |
39 | /** |
40 | * Reply to textPrompt(). |
41 | * @param text text to return to core; null to abort auth cycle |
42 | * @param tag zero or one of Is* |
43 | */ |
44 | virtual void gplugReturnText(const char *text, int tag) = 0; |
45 | /** |
46 | * Reply to binaryPrompt(). |
47 | * @param data data in pam_client format to return to the core; |
48 | * null to abort auth cycle |
49 | */ |
50 | virtual void gplugReturnBinary(const char *data) = 0; |
51 | /** |
52 | * Tell the greeter who is logging in. |
53 | * Call this preferably before gplugStart, as otherwise the .dmrc |
54 | * load will be delayed. Don't call at all if your plugin doesn't |
55 | * have the Local flag set. Call only for internally generated |
56 | * user changes. |
57 | * @param user the user logging in |
58 | */ |
59 | virtual void gplugSetUser(const QString &user) = 0; |
60 | /** |
61 | * Start processing. |
62 | */ |
63 | virtual void gplugStart() = 0; |
64 | /** |
65 | * This should be called each time the talker changes in any way from the |
66 | * pristine state after an authentication cycle starts, so the greeter |
67 | * knows it must reset the fields after some time of inactivity. |
68 | */ |
69 | virtual void gplugChanged() = 0; |
70 | /** |
71 | * Plugins that expect user input from a different device than the mouse or |
72 | * keyboard must call this when user activity is detected to prevent the |
73 | * greeter from resetting/going away. Events should be compressed to no |
74 | * more than ten per second; one every five seconds is actually enough. |
75 | * Events should be actual changes to the input fields, not random motion. |
76 | */ |
77 | virtual void gplugActivity() = 0; |
78 | /** |
79 | * Show a message box on behalf of the talker. |
80 | * @param type message severity |
81 | * @param text message text |
82 | */ |
83 | virtual void gplugMsgBox(QMessageBox::Icon type, const QString &text) = 0; |
84 | /** |
85 | * Determine if the named widget is welcomed. |
86 | * @param id the widget name |
87 | */ |
88 | virtual bool gplugHasNode(const QString &id) = 0; |
89 | }; |
90 | |
91 | /** |
92 | * Abstract base class for conversation plugins ("talkers") to be used with |
93 | * KDM, kdesktop_lock, etc. |
94 | * The authentication method used by a particular instance of a plugin |
95 | * may be configurable, but the instance must handle exactly one method, |
96 | * i.e., info->method must be determined at the latest at init() time. |
97 | */ |
98 | class KGreeterPlugin { |
99 | public: |
100 | KGreeterPlugin(KGreeterPluginHandler *h) : handler(h) {} |
101 | virtual ~KGreeterPlugin() {} |
102 | |
103 | /** |
104 | * Variations of the talker: |
105 | * - Authenticate: authentication |
106 | * - AuthChAuthTok: authentication and password change |
107 | * - ChAuthTok: password change |
108 | */ |
109 | enum Function { Authenticate, AuthChAuthTok, ChAuthTok }; |
110 | |
111 | /** |
112 | * Contexts the talker can be used in: |
113 | * - Login: kdm login dialog |
114 | * - Shutdown: kdm shutdown dialog |
115 | * - Unlock: kdm unlock dialog (TODO) |
116 | * - ChangeTok: kdm password change dialog (TODO) |
117 | * - ExUnlock: kdesktop_lock unlock dialog |
118 | * - ExChangeTok: kdepasswd password change dialog (TODO) |
119 | * |
120 | * The Ex* contexts exist within a running session; the talker must know |
121 | * how to obtain the currently logged in user (+ domain/realm, etc.) |
122 | * itself (i.e., fixedEntity will be null). The non-Ex variants will have |
123 | * a fixedEntity passed in. |
124 | */ |
125 | enum Context { Login, Shutdown, Unlock, ChangeTok, |
126 | ExUnlock, ExChangeTok }; |
127 | |
128 | /** |
129 | * Provide the talker with a list of selectable users. This can be used |
130 | * for autocompletion, etc. |
131 | * Will be called only when not running. |
132 | * @param users the users to load. |
133 | */ |
134 | virtual void loadUsers(const QStringList &users) = 0; |
135 | |
136 | /** |
137 | * Preload the talker with an (opaque to the greeter) entity. |
138 | * Will be called only when not running. |
139 | * @param entity the entity to preload the talker with. That |
140 | * will usually be something like "user" or "user@domain". |
141 | * @param field the sub-widget (probably line edit) to put the cursor into. |
142 | * If -1, preselect the user for timed login. This means pre-filling |
143 | * the password field with anything, disabling it, and placing the |
144 | * cursor in the user name field. |
145 | */ |
146 | virtual void presetEntity(const QString &entity, int field) = 0; |
147 | |
148 | /** |
149 | * Obtain the actually logged in entity. |
150 | * Will be called only after succeeded() was called. |
151 | */ |
152 | virtual QString getEntity() const = 0; |
153 | |
154 | /** |
155 | * "Push" a user into the talker. That can be a click into the user list |
156 | * or successful authentication without the talker calling gplugSetUser. |
157 | * Will be called only when running. |
158 | * @param user the user to set. Note that this is a UNIX login, not a |
159 | * canonical entity |
160 | */ |
161 | virtual void setUser(const QString &user) = 0; |
162 | |
163 | /** |
164 | * En-/disable any widgets contained in the talker. |
165 | * Will be called only when not running. |
166 | * @param on the state to set |
167 | */ |
168 | virtual void setEnabled(bool on) = 0; |
169 | |
170 | /** |
171 | * Called when a message from the authentication backend arrives. |
172 | * @param message the message received from the backend |
173 | * @param error if true, @p message is an error message, otherwise it's |
174 | * an informational message |
175 | * @return true means that the talker already handled the message, false |
176 | * that the greeter should display it in a message box |
177 | * |
178 | * FIXME: Filtering a message usually means that the backend issued a |
179 | * prompt and obtains the authentication data itself. However, in that |
180 | * state the backend is unresponsive, e.g., no shutdown is possible. |
181 | * The frontend could send the backend a signal, but the "escape path" |
182 | * within the backend is unclear (PAM won't like simply longjmp()ing |
183 | * out of it). |
184 | */ |
185 | virtual bool textMessage(const char *message, bool error) = 0; |
186 | |
187 | /** |
188 | * Prompt the user for data. Reply by calling handler->gplugReturnText(). |
189 | * @param propmt the prompt to display. It may be null, in which case |
190 | * "Username"/"Password" should be shown and the replies should be tagged |
191 | * with the respective Is* flag. |
192 | * @param echo if true, a normal input widget can be used, otherwise one that |
193 | * visually obscures the user's input. |
194 | * @param nonBlocking if true, report whatever is already available, |
195 | * otherwise wait for user input. |
196 | */ |
197 | virtual void textPrompt(const char *prompt, bool echo, bool nonBlocking) = 0; |
198 | |
199 | /** |
200 | * Request binary authentication data from the talker. Reply by calling |
201 | * handler->gplugReturnBinary(). |
202 | * @param prompt prompt in pam_client format |
203 | * @param nonBlocking if true, report whatever is already available, |
204 | * otherwise wait for user input. |
205 | * @return always true for now |
206 | * |
207 | * TODO: |
208 | * @return if true, the prompt was handled by the talker, otherwise the |
209 | * handler has to use libpam_client to obtain the authentication data. |
210 | * In that state the talker still can abort the data fetch by |
211 | * gplugReturn()ing a null array. When the data was obtained, another |
212 | * binaryPrompt with a null prompt will be issued. |
213 | */ |
214 | virtual bool binaryPrompt(const char *prompt, bool nonBlocking) = 0; |
215 | |
216 | /** |
217 | * This can either |
218 | * - Start a processing cycle. Will be called only when not running. |
219 | * - Restart authTok cycle - will be called while running and implies |
220 | * revive(). PAM is a bit too clever, so we need this. |
221 | * In any case the talker is running afterwards. |
222 | */ |
223 | virtual void start() = 0; |
224 | |
225 | /** |
226 | * Request to suspend the auth. Make sure that a second talker of any |
227 | * type will be able to operate while this one is suspended (no busy |
228 | * device nodes, etc.). |
229 | * Will be called only if running within Login context. (Actually it |
230 | * won't be called at all, but be prepared.) |
231 | */ |
232 | virtual void suspend() = 0; |
233 | |
234 | /** |
235 | * Request to resume the auth from the point it was suspended at. |
236 | * Will be called only when suspended. |
237 | */ |
238 | virtual void resume() = 0; |
239 | |
240 | /** |
241 | * The "login" button was pressed in the greeter. |
242 | * This might call gplugReturn* or gplugStart. |
243 | * Will be called only when running. |
244 | */ |
245 | virtual void next() = 0; |
246 | |
247 | /** |
248 | * Abort auth cycle. Note that this should _not_ clear out already |
249 | * entered auth tokens if they are still on the screen. |
250 | * Will be called only when running and stops it. |
251 | */ |
252 | virtual void abort() = 0; |
253 | |
254 | /** |
255 | * Indicate successful end of the current phase. |
256 | * This is more or less a request to disable editable widgets |
257 | * responsible for the that phase. |
258 | * There will be no further attempt to enter that phase until the |
259 | * widget is destroyed or revived. |
260 | * Will be called only when running and stops it. |
261 | */ |
262 | virtual void succeeded() = 0; |
263 | |
264 | /** |
265 | * Indicate unsuccessful end of the current phase. |
266 | * This is mostly a request to disable all editable widgets. |
267 | * The widget will be treated as dead until revive() is called. |
268 | * Will be called only when running and stops it. |
269 | */ |
270 | virtual void failed() = 0; |
271 | |
272 | /** |
273 | * Prepare retrying the previously failed phase. |
274 | * This is mostly a request to re-enable all editable widgets failed() |
275 | * disabled previously, clear the probably incorrect authentication tokens |
276 | * and to set the input focus appropriately. |
277 | * Will be called only after failed() (possibly with clear() in between), |
278 | * after succeeded() (after an aborted forced authentication token change), |
279 | * or after presetEntity() with field -1. |
280 | */ |
281 | virtual void revive() = 0; |
282 | |
283 | /** |
284 | * Clear any edit widgets, particularly anything set by setUser. |
285 | * Will be called only when not running. |
286 | */ |
287 | virtual void clear() = 0; |
288 | |
289 | typedef QList<QWidget *> WidgetList; |
290 | |
291 | /** |
292 | * Obtain the QWidget to actually handle the conversation. |
293 | */ |
294 | const WidgetList &getWidgets() const { return widgetList; } |
295 | |
296 | protected: |
297 | KGreeterPluginHandler *handler; |
298 | WidgetList widgetList; |
299 | }; |
300 | |
301 | struct KDE_EXPORT KGreeterPluginInfo { |
302 | /** |
303 | * Human readable name of this plugin (should be a little more |
304 | * informative than just the libary name). Must be I18N_NOOP()ed. |
305 | */ |
306 | const char *name; |
307 | |
308 | /** |
309 | * The authentication method to use - the meaning is up to the backend, |
310 | * but will usually be related to the PAM service. |
311 | */ |
312 | const char *method; |
313 | |
314 | /** |
315 | * Capabilities. |
316 | */ |
317 | enum { |
318 | /** |
319 | * All users exist on the local system permanently (will be listed |
320 | * by getpwent()); an entity corresponds to a UNIX user. |
321 | */ |
322 | Local = 1, |
323 | /** |
324 | * The entities consist of multiple fields. |
325 | * PluginOptions/<plugin>.FocusField is used instead of FocusPasswd. |
326 | */ |
327 | Fielded = 2, |
328 | /** |
329 | * An entity can be preset, the talker has a widget where a user can |
330 | * be selected explicitly. If the method is "classic", timed login |
331 | * is possible, too. |
332 | * This also means that setUser/gplugSetUser can be used and a |
333 | * userlist can be shown at all - provided Local is set as well. |
334 | */ |
335 | Presettable = 4 |
336 | }; |
337 | |
338 | /* |
339 | * Capability flags. |
340 | */ |
341 | int flags; |
342 | |
343 | /** |
344 | * Call after loading the plugin. |
345 | * |
346 | * @param method if non-empty and the plugin is unable to handle that |
347 | * method, return false. If the plugin has a constant method defined |
348 | * above, it can ignore this parameter. |
349 | * @param getConf can be used to obtain configuration items from the |
350 | * greeter; you have to pass it the @p ctx pointer. |
351 | * The only predefined key (in KDM) is "EchoMode", which is an int |
352 | * (in fact, QLineEdit::EchoModes). |
353 | * Other keys are obtained from the PluginOptions option; see kdmrc |
354 | * for details. |
355 | * If the key is unknown, dflt is returned. |
356 | * @param ctx context pointer for @p getConf |
357 | * @return if false, unload the plugin again (don't call done() first) |
358 | */ |
359 | bool (*init)(const QString &method, |
360 | QVariant(*getConf)(void *ctx, const char *key, |
361 | const QVariant &dflt), |
362 | void *ctx); |
363 | |
364 | /** |
365 | * Call before unloading the plugin. |
366 | * This pointer can be null. |
367 | */ |
368 | void (*done)(void); |
369 | |
370 | /** |
371 | * Factory method to create an instance of the plugin. |
372 | * Note that multiple instances can exist at one time, but only |
373 | * one of them is active at any moment (the others would be suspended |
374 | * or not running at all). |
375 | * @param handler the object offering the necessary callbacks |
376 | * @param parent parent widget |
377 | * @param predecessor the focus widget before the conversation widget |
378 | * @param fixedEntity see below |
379 | * @param func see below |
380 | * @param ctx see below |
381 | * @return an instance of this conversation plugin |
382 | * |
383 | * Valid combinations of Function and Context: |
384 | * - Authenticate:Login - init |
385 | * - Authenticate:Shutdown - init, for now "root" is passed as fixedEntitiy |
386 | * and it is not supposed to be displayed. Plugins with Local not set |
387 | * might have to conjure something up to make getEntity() return a |
388 | * canonical entitiy. FIXME: don't restrict shutdown to root. |
389 | * - AuthChAuthTok:Login, AuthChAuthTok:Shutdown - cont/cont, |
390 | * only relevant for classic method (as it is relevant only for password- |
391 | * less logins, which always use classic). The login should not be shown - |
392 | * it is known to the user already; the backend won't ask for it, either. |
393 | * - ChAuthTok:Login & ChAuthTok:Shutdown - cont |
394 | * - Authenticate:Unlock & Authenticate:ExUnlock - init, |
395 | * AuthChAuthTok:ChangeTok & AuthChAuthTok:ExChangeTok - init/cont, |
396 | * display fixedEntity as labels. The backend does not ask for the UNIX |
397 | * login, as it already knows it - but it will ask for all components of |
398 | * the entity if it is no UNIX login. |
399 | * |
400 | * "init" means that the plugin is supposed to call gplugStart, "cont" |
401 | * that the backend is already in a cycle of the method the plugin was |
402 | * initialized with (it does not hurt if gplugStart is still called). |
403 | */ |
404 | KGreeterPlugin *(*create)(KGreeterPluginHandler *handler, |
405 | QWidget *parent, |
406 | const QString &fixedEntity, |
407 | KGreeterPlugin::Function func, |
408 | KGreeterPlugin::Context ctx); |
409 | }; |
410 | |
411 | #endif |
412 | |