use libudev instead of a udev rule and a script.
[apps/agl-service-data-persistence.git] / ll-auth-binding / src / ll-auth-binding.c
1 #define _GNU_SOURCE
2 #include <stdio.h>
3 #include <string.h>
4 #include <json-c/json.h>
5 #include <security/pam_appl.h>
6 #include <security/pam_misc.h>
7 #include <libudev.h>
8 #include <pthread.h>
9 #include <poll.h>
10
11 #define AFB_BINDING_VERSION 2
12 #include <afb/afb-binding.h>
13
14 // Defines
15 #define PAM_RULE                                                                                                        "agl"
16 #define UDEV_MONITOR_POLLING_TIMEOUT                                                            5000
17
18 #define UDEV_ACTION_UNSUPPORTED                                                                         0
19 #define UDEV_ACTION_ADD                                                                                         1
20 #define UDEV_ACTION_REMOVE                                                                                      2
21
22 #define LOGIN_SUCCESS                                                                                           0
23 #define LOGIN_ERROR_USER_LOGGED                                                                         1
24 #define LOGIN_ERROR_PAM_START                                                                           2
25 #define LOGIN_ERROR_PAM_PUTENV                                                                          3
26 #define LOGIN_ERROR_PAM_AUTHENTICATE                                                            4
27 #define LOGIN_ERROR_PAM_ACCT_MGMT                                                                       5
28 #define LOGIN_ERROR_PAM_NO_USER                                                                         6
29 #define LOGIN_ERROR_PAM_END                                                                                     7
30
31 // Globals
32 static const char* error_messages[] =
33 {
34         "",
35         "The current user must be logged out first!",
36         "PAM start failed!",
37         "PAM putenv failed!",
38         "PAM authenticate failed!",
39         "PAM acct_mgmt failed!",
40         "No user provided by the PAM module!",
41         "PAM end failed!"
42 };
43
44 static char*                                    current_device                                          = NULL;
45 static char*                                    current_user                                            = NULL;
46 static struct pam_conv                  conv                                                            = { misc_conv, NULL };
47 static struct udev*                             udev_context                                            = NULL;
48 static struct udev_monitor*             udev_mon                                                        = NULL;
49 static pthread_t                                udev_monitoring_thread_handle;
50 static struct afb_event                 evt_login, evt_logout, evt_failed;
51
52 /**
53  * @brief Free the memory associated to the specified string and nullify the pointer.
54  * @param[in] string A pointer to the string.
55  */
56 static inline void free_string(char** string)
57 {
58         if (string)
59         {
60                 if (*string) free(*string);
61                 *string = NULL;
62         }
63 }
64
65 /**
66  * @brief Free the memory associated to the specified UDev's context and nullify the pointer.
67  * @param[in] ctx UDev's context.
68  */
69 static inline void free_udev_context(struct udev** ctx)
70 {
71         if (ctx)
72         {
73                 if (*ctx) udev_unref(*ctx);
74                 *ctx = NULL;
75         }
76 }
77
78 /**
79  * @brief Free the memory associated to the specified UDev's monitor and nullify the pointer.
80  * @param[in] mon UDev's monitor.
81  */
82 static inline void free_udev_monitor(struct udev_monitor** mon)
83 {
84         if (mon)
85         {
86                 if (*mon) udev_monitor_unref(*mon);
87                 *mon = NULL;
88         }
89 }
90
91 /**
92  * @brief Print UDev infos for the specified device.
93  * @param[in] dev The device.
94  */
95 static inline void print_udev_device_info(struct udev_device* dev)
96 {
97         AFB_INFO("    Action: %s", udev_device_get_action(dev));
98         AFB_INFO("    Node: %s", udev_device_get_devnode(dev));
99         AFB_INFO("    Subsystem: %s", udev_device_get_subsystem(dev));
100         AFB_INFO("    Devtype: %s", udev_device_get_devtype(dev));
101         AFB_INFO("    DevNum: %lu", udev_device_get_devnum(dev));
102         AFB_INFO("    DevPath: %s", udev_device_get_devpath(dev));
103         AFB_INFO("    Driver: %s", udev_device_get_driver(dev));
104         AFB_INFO("    SeqNum: %llu", udev_device_get_seqnum(dev));
105         AFB_INFO("    SysName: %s", udev_device_get_sysname(dev));
106         AFB_INFO("    SysNum: %s", udev_device_get_sysnum(dev));
107         AFB_INFO("    SysPath: %s", udev_device_get_syspath(dev));
108 }
109
110 /**
111  * @brief Get the UDev's action as an int to allow switch condition.
112  * @param[in] dev The device.
113  */
114 static inline int udev_device_get_action_int(struct udev_device* dev)
115 {
116         const char* action = udev_device_get_action(dev);
117         return
118                 strcmp(action, "add")
119                 ? (strcmp(action, "remove") ? UDEV_ACTION_UNSUPPORTED : UDEV_ACTION_REMOVE)
120                 : UDEV_ACTION_ADD;
121 }
122
123 /**
124  * @brief PAM authentication process.
125  * @param[in] pamh The handle to the PAM context.
126  * @param[in] device The device to login.
127  */
128 static int pam_process(pam_handle_t* pamh, const char* device)
129 {
130         int r;
131         
132         if (!pamh) return LOGIN_ERROR_PAM_START;
133         
134         char pam_variable[4096] = "DEVICE=";
135         strcat(pam_variable, device);
136         
137         if ((r = pam_putenv(pamh, pam_variable)) != PAM_SUCCESS)
138                 return LOGIN_ERROR_PAM_PUTENV;
139
140         if ((r = pam_authenticate(pamh, 0)) != PAM_SUCCESS)
141                 return LOGIN_ERROR_PAM_AUTHENTICATE;
142
143         if ((r = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
144                         return LOGIN_ERROR_PAM_ACCT_MGMT;
145
146         const char* pam_user;
147         pam_get_item(pamh, PAM_USER, (const void**)&pam_user);
148         if (!pam_user)
149                 return LOGIN_ERROR_PAM_NO_USER;
150
151         current_device = strdup(device);
152         current_user = strdup(pam_user);
153         
154         return LOGIN_SUCCESS;
155 }
156
157 /**
158  * @brief Login using PAM.
159  * @param[in] device The device to use.
160  * @return Exit code, @c LOGIN_SUCCESS on success.
161  */
162 static int login_pam(const char* device)
163 {
164         int r;
165         pam_handle_t* pamh;
166         
167         if (current_user)
168                 return LOGIN_ERROR_USER_LOGGED;
169
170         if ((r = pam_start("agl", NULL, &conv, &pamh)) != PAM_SUCCESS)
171                 return LOGIN_ERROR_PAM_START;
172
173         r = pam_process(pamh, device);
174         if (r != LOGIN_SUCCESS)
175         {
176                 pam_end(pamh, r);
177                 return r;
178         }
179
180         if ((r = pam_end(pamh, r)) != PAM_SUCCESS)
181                 return LOGIN_ERROR_PAM_END;
182         
183         return LOGIN_SUCCESS;
184 }
185
186 /**
187  * @brief Try to login a user using a device.
188  * @param[in] device The device to use.
189  * @return Exit code, @c LOGIN_SUCCESS if success.
190  */
191 static int login(const char* device)
192 {
193         int ret;
194         struct json_object* result;
195         
196         result = json_object_new_object();
197         
198         ret = login_pam(device);
199         switch(ret)
200         {
201                 case LOGIN_SUCCESS:
202                         json_object_object_add(result, "device", json_object_new_string(current_device));
203                         json_object_object_add(result, "user", json_object_new_string(current_user));
204                         afb_event_broadcast(evt_login, result);
205                         break;
206                 default:
207                         json_object_object_add(result, "message", json_object_new_string(error_messages[ret]));
208                         afb_event_broadcast(evt_failed, result);
209         }
210         
211         json_object_put(result);
212         return ret;
213 }
214
215 /// @brief Try to logout a user using a device.
216 /// @param[in] device The device to logout.
217 static void logout(const char* device)
218 {
219         struct json_object* result;
220         
221         result = json_object_new_object();
222         
223         if (current_device && !strcmp(device, current_device))
224         {
225                 json_object_object_add(result, "device", json_object_new_string(current_device));
226                 json_object_object_add(result, "user", json_object_new_string(current_user));
227                 AFB_INFO("[logout] device: %s", device);
228                 afb_event_broadcast(evt_logout, NULL);
229                 
230                 free_string(&current_device);
231                 free_string(&current_user);
232         }
233         else
234         {
235                 json_object_object_add(result, "message", json_object_new_string(current_device));
236                 json_object_object_add(result, "user", json_object_new_string("The unplugged device wasn't the user key!"));
237                 AFB_INFO("The unplugged device wasn't the user key!");
238                 afb_event_broadcast(evt_failed, result);
239         }
240         json_object_put(result);
241 }
242
243 /**
244  * @brief UDev's monitoring thread.
245  */
246 void* udev_monitoring_thread(void* arg)
247 {
248         struct udev_device* dev;
249         struct pollfd pfd;
250         int action;
251         
252         pfd.fd = udev_monitor_get_fd(udev_mon);
253         pfd.events = POLLIN;
254         
255         while(1)
256         {
257                 if (poll(&pfd, 1, UDEV_MONITOR_POLLING_TIMEOUT))
258                 {
259                         dev = udev_monitor_receive_device(udev_mon);
260                         if (dev)
261                         {
262                                 if (!strcmp(udev_device_get_devtype(dev), "disk"))
263                                 {
264                                         action = udev_device_get_action_int(dev);
265                                         switch(action)
266                                         {
267                                                 case UDEV_ACTION_ADD:
268                                                         AFB_INFO("A device is plugged-in");
269                                                         print_udev_device_info(dev);
270                                                         login(udev_device_get_devnode(dev));
271                                                         break;
272                                                 case UDEV_ACTION_REMOVE:
273                                                         AFB_INFO("A device is plugged-out");
274                                                         print_udev_device_info(dev);
275                                                         logout(udev_device_get_devnode(dev));
276                                                         break;
277                                                 default:
278                                                         AFB_DEBUG("Unsupported udev action");
279                                                         break;
280                                         }
281                                 }
282                                 udev_device_unref(dev);
283                         }
284                         else
285                         {
286                                 AFB_ERROR("No Device from udev_monitor_receive_device().");
287                         }
288                 }
289                 else
290                 {
291                         AFB_DEBUG("Udev polling timeout");
292                 }
293         }
294         return NULL;
295 }
296
297 /**
298  * @brief API's verb 'getuser'. Try to get user informations.
299  * @param[in] req The request object.
300  */
301 static void verb_getuser(struct afb_req req)
302 {
303         if (!current_device || !current_user)
304         {
305                 afb_req_fail(req, "there is no logged user!", NULL);
306                 return;
307         }
308
309         json_object* result = json_object_new_object();
310         json_object_object_add(result, "user", json_object_new_string(current_user));
311         json_object_object_add(result, "device", json_object_new_string(current_device));
312
313         afb_req_success(req, result, NULL);
314 }
315
316 /**
317  * @brief Do the cleanup when init fails.
318  * @param[in] error Error message.
319  * @param[in] retcode Error code to return.
320  * @return An exit code equals to @c retcode.
321  */
322 static inline int ll_auth_init_cleanup(const char* error, int retcode)
323 {
324         AFB_ERROR_V2("%s", error);
325         free_string(&current_user);
326         free_string(&current_device);
327         
328         free_udev_monitor(&udev_mon);
329         free_udev_context(&udev_context);
330         return retcode;
331 }
332
333 /**
334  * @brief Initialize the binding.
335  */
336 int ll_auth_init()
337 {
338         evt_login = afb_daemon_make_event("login");
339         evt_logout = afb_daemon_make_event("logout");
340         evt_failed = afb_daemon_make_event("failed");
341
342         if (!afb_event_is_valid(evt_login) || !afb_event_is_valid(evt_logout) || !afb_event_is_valid(evt_failed))
343                 return ll_auth_init_cleanup("Can't create events", -1);
344                 
345         udev_context = udev_new();
346         if (!udev_context)
347                 return ll_auth_init_cleanup("Can't initialize udev's context", -1);
348         
349         udev_mon = udev_monitor_new_from_netlink(udev_context, "udev");
350         if (!udev_mon)
351                 return ll_auth_init_cleanup("Can't initialize udev's monitor", -1);
352         
353         udev_monitor_filter_add_match_subsystem_devtype(udev_mon, "block", NULL);
354         udev_monitor_enable_receiving(udev_mon);
355         
356         if (pthread_create(&udev_monitoring_thread_handle, NULL, udev_monitoring_thread, NULL))
357                 return ll_auth_init_cleanup("Can't start the udev's monitoring thread", -1);
358         
359         AFB_INFO("ll-auth-binding is ready");
360         return 0;
361 }
362
363 static const afb_verb_v2 _ll_auth_binding_verbs[]= {
364         {
365                 .verb = "getuser",
366                 .callback = verb_getuser,
367                 .auth = NULL,
368                 .info = NULL,
369                 .session = AFB_SESSION_NONE_V2
370         },
371         { .verb=NULL}
372 };
373
374 const struct afb_binding_v2 afbBindingV2 = {
375                 .api = "ll-auth",
376                 .specification = NULL,
377                 .verbs = _ll_auth_binding_verbs,
378                 .preinit = ll_auth_init,
379                 .init = NULL,
380                 .onevent = NULL,
381                 .noconcurrency = 0
382 };