discover/grub2: Allow to separate the --id argument using a space char
[petitboot] / discover / user-event.c
1 /*
2  *  Copyright (C) 2009 Sony Computer Entertainment Inc.
3  *  Copyright 2009 Sony Corp.
4  *
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; version 2 of the License.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  */
18
19 #if defined(HAVE_CONFIG_H)
20 #include "config.h"
21 #endif
22
23 #include <assert.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/socket.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sys/un.h>
30
31 #include <log/log.h>
32 #include <url/url.h>
33 #include <types/types.h>
34 #include <talloc/talloc.h>
35 #include <waiter/waiter.h>
36
37 #include "device-handler.h"
38 #include "resource.h"
39 #include "event.h"
40 #include "user-event.h"
41 #include "sysinfo.h"
42
43
44 #define MAC_ADDR_SIZE   6
45 #define IP_ADDR_SIZE    4
46
47 struct user_event {
48         struct device_handler *handler;
49         int socket;
50 };
51
52 static const char *event_action_name(enum event_action action)
53 {
54         switch (action) {
55         case EVENT_ACTION_ADD:
56                 return "add";
57         case EVENT_ACTION_REMOVE:
58                 return "remove";
59         case EVENT_ACTION_URL:
60                 return "url";
61         case EVENT_ACTION_DHCP:
62                 return "dhcp";
63         case EVENT_ACTION_BOOT:
64                 return "boot";
65         case EVENT_ACTION_SYNC:
66                 return "sync";
67         case EVENT_ACTION_PLUGIN:
68                 return "plugin";
69         default:
70                 break;
71         }
72
73         return "unknown";
74 }
75
76 static void user_event_print_event(struct event __attribute__((unused)) *event)
77 {
78         int i;
79
80         pb_debug("user_event %s event:\n", event_action_name(event->action));
81         pb_debug("\tdevice: %s\n", event->device);
82
83         for (i = 0; i < event->n_params; i++)
84                 pb_debug("\t%-12s => %s\n",
85                         event->params[i].name, event->params[i].value);
86 }
87
88 static struct resource *user_event_resource(struct discover_boot_option *opt,
89                 struct event *event, bool gen_boot_args_sigfile)
90 {
91         const char *siaddr, *boot_file;
92         struct resource *res;
93         struct pb_url *url;
94         char *url_str;
95
96         siaddr = event_get_param(event, "siaddr");
97         if (!siaddr) {
98                 pb_log_fn("next server option not found\n");
99                 return NULL;
100         }
101
102         boot_file = event_get_param(event, "bootfile");
103         if (!boot_file) {
104                 pb_log_fn("bootfile not found\n");
105                 return NULL;
106         }
107
108         if (gen_boot_args_sigfile) {
109                 char* args_sigfile_default = talloc_asprintf(opt,
110                         "%s.cmdline.sig", boot_file);
111                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
112                         args_sigfile_default);
113                 talloc_free(args_sigfile_default);
114         }
115         else
116                 url_str = talloc_asprintf(opt, "%s%s/%s", "tftp://", siaddr,
117                         boot_file);
118         url = pb_url_parse(opt, url_str);
119         talloc_free(url_str);
120
121         if (!url)
122                 return NULL;
123
124         res = create_url_resource(opt, url);
125         if (!res) {
126                 talloc_free(url);
127                 return NULL;
128         }
129
130         return res;
131 }
132
133 static int parse_user_event(struct discover_context *ctx, struct event *event)
134 {
135         struct discover_boot_option *d_opt;
136         char *server_ip, *root_dir, *p;
137         struct boot_option *opt;
138         struct device *dev;
139         const char *val;
140
141         dev = ctx->device->device;
142
143         d_opt = discover_boot_option_create(ctx, ctx->device);
144         if (!d_opt)
145                 goto fail;
146
147         opt = d_opt->option;
148
149         val = event_get_param(event, "name");
150
151         if (!val) {
152                 pb_log_fn("no name found\n");
153                 goto fail_opt;
154         }
155
156         opt->id = talloc_asprintf(opt, "%s#%s", dev->id, val);
157         opt->name = talloc_strdup(opt, val);
158
159         d_opt->boot_image = user_event_resource(d_opt, event, false);
160         if (!d_opt->boot_image) {
161                 pb_log_fn("no boot image found for %s!\n",
162                                 opt->name);
163                 goto fail_opt;
164         }
165         d_opt->args_sig_file = user_event_resource(d_opt, event, true);
166
167         val = event_get_param(event, "rootpath");
168         if (val) {
169                 server_ip = talloc_strdup(opt, val);
170                 p = strchr(server_ip, ':');
171                 if (p) {
172                         root_dir = talloc_strdup(opt, p + 1);
173                         *p = '\0';
174                         opt->boot_args = talloc_asprintf(opt, "%s%s:%s",
175                                         "root=/dev/nfs ip=any nfsroot=",
176                                         server_ip, root_dir);
177
178                         talloc_free(root_dir);
179                 } else {
180                         opt->boot_args = talloc_asprintf(opt, "%s",
181                                         "root=/dev/nfs ip=any nfsroot=");
182                 }
183
184                 talloc_free(server_ip);
185         }
186
187         opt->description = talloc_asprintf(opt, "%s %s", opt->boot_image_file,
188                 opt->boot_args ? : "");
189
190         if (event_get_param(event, "default"))
191                 opt->is_default = true;
192
193         discover_context_add_boot_option(ctx, d_opt);
194
195         return 0;
196
197 fail_opt:
198         talloc_free(d_opt);
199 fail:
200         return -1;
201 }
202
203 static const char *parse_host_addr(struct event *event)
204 {
205         const char *val;
206
207         val = event_get_param(event, "tftp");
208         if (val)
209                 return val;
210
211         val = event_get_param(event, "siaddr");
212         if (val)
213                 return val;
214
215         val = event_get_param(event, "serverid");
216         if (val)
217                 return val;
218
219         return NULL;
220 }
221
222 static char *parse_mac_addr(struct discover_context *ctx, const char *mac)
223 {
224         unsigned int mac_addr_arr[MAC_ADDR_SIZE];
225         char *mac_addr;
226
227         sscanf(mac, "%X:%X:%X:%X:%X:%X", mac_addr_arr, mac_addr_arr + 1,
228                         mac_addr_arr + 2, mac_addr_arr + 3, mac_addr_arr + 4,
229                         mac_addr_arr + 5);
230
231         mac_addr = talloc_asprintf(ctx, "01-%02x-%02x-%02x-%02x-%02x-%02x",
232                         mac_addr_arr[0], mac_addr_arr[1], mac_addr_arr[2],
233                         mac_addr_arr[3], mac_addr_arr[4], mac_addr_arr[5]);
234
235         return mac_addr;
236 }
237
238 static char *parse_ip_addr(struct discover_context *ctx, const char *ip)
239 {
240         unsigned int ip_addr_arr[IP_ADDR_SIZE];
241         char *ip_hex;
242
243         sscanf(ip, "%u.%u.%u.%u", ip_addr_arr, ip_addr_arr + 1,
244                         ip_addr_arr + 2, ip_addr_arr + 3);
245
246         ip_hex = talloc_asprintf(ctx, "%02X%02X%02X%02X", ip_addr_arr[0],
247                         ip_addr_arr[1], ip_addr_arr[2], ip_addr_arr[3]);
248
249         return ip_hex;
250 }
251
252 struct pb_url *user_event_parse_conf_url(struct discover_context *ctx,
253                 struct event *event, bool *is_complete)
254 {
255         const char *conffile, *pathprefix, *host, *bootfile, *bootfile_url;
256         char *p, *basedir, *url_str;
257         struct pb_url *url;
258
259         conffile = event_get_param(event, "pxeconffile");
260         pathprefix = event_get_param(event, "pxepathprefix");
261         bootfile = event_get_param(event, "bootfile");
262         bootfile_url = event_get_param(event, "bootfile_url");
263
264         /* If we're given a conf file, we're able to generate a complete URL to
265          * the configuration file, and the parser doesn't need to do any
266          * further autodiscovery */
267         *is_complete = !!conffile;
268
269         /* if conffile is a URL, that's all we need */
270         if (conffile && is_url(conffile)) {
271                 url = pb_url_parse(ctx, conffile);
272                 return url;
273         }
274
275         /* If we can create a URL from pathprefix (optionally with
276          * conffile appended to create a complete URL), use that */
277         if (pathprefix && is_url(pathprefix)) {
278                 if (conffile) {
279                         url_str = talloc_asprintf(ctx, "%s%s",
280                                         pathprefix, conffile);
281                         url = pb_url_parse(ctx, url_str);
282                         talloc_free(url_str);
283                 } else {
284                         url = pb_url_parse(ctx, pathprefix);
285                 }
286
287                 return url;
288         }
289
290         host = parse_host_addr(event);
291         if (!host) {
292                 pb_log_fn("host address not found\n");
293
294                 /* No full URLs and no host address? Check for DHCPv6 options */
295                 if (bootfile_url && is_url(bootfile_url)) {
296                         *is_complete = true;
297                         return pb_url_parse(ctx, bootfile_url);
298                 }
299                 return NULL;
300         }
301
302         url_str = talloc_asprintf(ctx, "tftp://%s/", host);
303
304         /* if we have a pathprefix, use that directly.. */
305         if (pathprefix) {
306                 /* strip leading slashes */
307                 while (pathprefix[0] == '/')
308                         pathprefix++;
309                 url_str = talloc_asprintf_append(url_str, "%s", pathprefix);
310
311         /* ... otherwise, add a path based on the bootfile name, but only
312          * if conffile isn't an absolute path itself */
313         } else if (bootfile && !(conffile && conffile[0] == '/')) {
314
315                 basedir = talloc_strdup(ctx, bootfile);
316
317                 /* strip filename from the bootfile path, leaving only a
318                  * directory */
319                 p = strrchr(basedir, '/');
320                 if (!p)
321                         p = basedir;
322                 *p = '\0';
323
324                 if (strlen(basedir))
325                         url_str = talloc_asprintf_append(url_str, "%s/",
326                                         basedir);
327
328                 talloc_free(basedir);
329         }
330
331         /* finally, append conffile */
332         if (conffile)
333                 url_str = talloc_asprintf_append(url_str, "%s", conffile);
334
335         url = pb_url_parse(ctx, url_str);
336
337         talloc_free(url_str);
338
339         return url;
340 }
341
342 char **user_event_parse_conf_filenames(
343                 struct discover_context *ctx, struct event *event)
344 {
345         char *mac_addr, *ip_hex;
346         const char *mac, *ip;
347         char **filenames;
348         int index, len;
349
350         mac = event_get_param(event, "mac");
351         if (mac)
352                 mac_addr = parse_mac_addr(ctx, mac);
353         else
354                 mac_addr = NULL;
355
356         ip = event_get_param(event, "ip");
357         if (ip) {
358                 ip_hex = parse_ip_addr(ctx, ip);
359                 len = strlen(ip_hex);
360         } else {
361                 ip_hex = NULL;
362                 len = 0;
363         }
364
365         if (!mac_addr && !ip_hex) {
366                 pb_log_fn("neither mac nor ip parameter found\n");
367                 return NULL;
368         }
369
370         /* Filenames as fallback IP's + mac + default */
371         filenames = talloc_array(ctx, char *, len + 3);
372
373         index = 0;
374         if (mac_addr)
375                 filenames[index++] = talloc_strdup(filenames, mac_addr);
376
377         while (len) {
378                 filenames[index++] = talloc_strdup(filenames, ip_hex);
379                 ip_hex[--len] = '\0';
380         }
381
382         filenames[index++] = talloc_strdup(filenames, "default");
383         filenames[index++] = NULL;
384
385         if (mac_addr)
386                 talloc_free(mac_addr);
387
388         if (ip_hex)
389                 talloc_free(ip_hex);
390
391         return filenames;
392 }
393
394 static int user_event_dhcp(struct user_event *uev, struct event *event)
395 {
396         struct device_handler *handler = uev->handler;
397         struct discover_device *dev;
398
399         uint8_t hwaddr[MAC_ADDR_SIZE];
400
401         if (!event_get_param(event, "mac"))
402                 return -1;
403         if (!event_get_param(event, "ip") && !event_get_param(event, "ipv6"))
404                 return -1;
405
406         sscanf(event_get_param(event, "mac"),
407                "%hhX:%hhX:%hhX:%hhX:%hhX:%hhX",
408                hwaddr, hwaddr + 1, hwaddr + 2,
409                hwaddr + 3, hwaddr + 4, hwaddr + 5);
410
411         if (event_get_param(event, "ipv6"))
412                 system_info_set_interface_address(sizeof(hwaddr), hwaddr,
413                                                   event_get_param(event, "ipv6"));
414         else
415                 system_info_set_interface_address(sizeof(hwaddr), hwaddr,
416                                                   event_get_param(event, "ip"));
417
418         dev = discover_device_create(handler, event_get_param(event, "mac"),
419                                         event->device);
420
421         device_handler_dhcp(handler, dev, event);
422
423         return 0;
424 }
425
426 static int user_event_add(struct user_event *uev, struct event *event)
427 {
428         struct device_handler *handler = uev->handler;
429         struct discover_context *ctx;
430         struct discover_device *dev;
431         int rc;
432
433         /* In case this is a network interface, try to refer to it by UUID */
434         dev = discover_device_create(handler, event_get_param(event, "mac"),
435                                         event->device);
436         dev->device->id = talloc_strdup(dev, event->device);
437         ctx = device_handler_discover_context_create(handler, dev);
438
439         rc = parse_user_event(ctx, event);
440         if (rc) {
441                 pb_log("parse_user_event returned %d\n", rc);
442                 return rc;
443         }
444
445         device_handler_discover_context_commit(handler, ctx);
446
447         talloc_free(ctx);
448
449         return 0;
450 }
451
452 static int user_event_remove(struct user_event *uev, struct event *event)
453 {
454         struct device_handler *handler = uev->handler;
455         struct discover_device *dev;
456         const char *mac = event_get_param(event, "mac");
457
458         if (mac)
459                 dev = device_lookup_by_uuid(handler, event_get_param(event, "mac"));
460         else
461                 dev = device_lookup_by_id(handler, event->device);
462
463         if (!dev)
464                 return 0;
465
466         device_handler_remove(handler, dev);
467
468         return 0;
469 }
470
471 static int user_event_url(struct user_event *uev, struct event *event)
472 {
473         struct device_handler *handler = uev->handler;
474         const char *url;
475
476         url = event_get_param(event, "url");
477         if (url)
478                 device_handler_process_url(handler, url, NULL, NULL);
479
480         return 0;
481 }
482
483 static int user_event_boot(struct user_event *uev, struct event *event)
484 {
485         struct device_handler *handler = uev->handler;
486         struct boot_command *cmd = talloc_zero(handler, struct boot_command);
487         struct discover_boot_option *opt;
488         const char *name;
489
490         name = event_get_param(event, "name");
491         if (name) {
492                 pb_log("Finding boot option %s @ %s\n", name, event->device);
493                 opt = device_handler_find_option_by_name(handler,
494                                 event->device, name);
495                 if (!opt) {
496                         pb_log("No option with name %s\n", name);
497                         return -1;
498                 }
499
500                 pb_log("Found option with id %s!\n", opt->option->id);
501                 cmd->option_id = talloc_strdup(cmd, opt->option->id);
502         } else {
503                 pb_log("Booting based on full boot command\n");
504                 cmd->option_id = talloc_strdup(cmd, event_get_param(event, "id"));
505                 cmd->boot_image_file = talloc_strdup(cmd, event_get_param(event, "image"));
506                 cmd->initrd_file = talloc_strdup(cmd, event_get_param(event, "initrd"));
507                 cmd->dtb_file = talloc_strdup(cmd, event_get_param(event, "dtb"));
508                 cmd->boot_args = talloc_strdup(cmd, event_get_param(event, "args"));
509         }
510
511         device_handler_boot(handler, false, cmd);
512
513         talloc_free(cmd);
514
515         return 0;
516 }
517
518 static int user_event_sync(struct user_event *uev, struct event *event)
519 {
520         struct device_handler *handler = uev->handler;
521
522         if (strncasecmp(event->device, "all", strlen("all")) != 0)
523                 device_sync_snapshots(handler, event->device);
524         else
525                 device_sync_snapshots(handler, NULL);
526
527         return 0;
528 }
529
530 static int process_uninstalled_plugin(struct user_event *uev,
531                 struct event *event)
532 {
533         struct device_handler *handler = uev->handler;
534         struct discover_boot_option *file_opt;
535         struct discover_device *device;
536         struct discover_context *ctx;
537         const char *path;
538         struct resource *res;
539
540         if (!event_get_param(event, "path")) {
541                 pb_log("Uninstalled pb-plugin event missing path param\n");
542                 return -1;
543         }
544
545         device = device_lookup_by_name(handler, event->device);
546         if (!device) {
547                 pb_log("Couldn't find device matching %s for plugin\n",
548                                 event->device);
549                 return -1;
550         }
551
552         ctx = device_handler_discover_context_create(handler, device);
553         file_opt = discover_boot_option_create(ctx, device);
554         file_opt->option->name = talloc_strdup(file_opt,
555                         event_get_param(event, "name"));
556         file_opt->option->id = talloc_asprintf(file_opt, "%s@%p",
557                         device->device->id, file_opt);
558         file_opt->option->type = DISCOVER_PLUGIN_OPTION;
559
560
561         path = event_get_param(event, "path");
562         /* path may be relative to root */
563         if (strncmp(device->mount_path, path, strlen(device->mount_path)) == 0) {
564                 path += strlen(device->mount_path) + 1;
565         }
566
567         res = talloc(file_opt, struct resource);
568         resolve_resource_against_device(res, device, path);
569         file_opt->boot_image = res;
570
571         discover_context_add_boot_option(ctx, file_opt);
572         device_handler_discover_context_commit(handler, ctx);
573
574         return 0;
575 }
576
577 /*
578  * Notification of a plugin event. This can either be for an uninstalled plugin
579  * that pb-plugin has scanned, or the result of a plugin that pb-plugin has
580  * installed.
581  */
582 static int user_event_plugin(struct user_event *uev, struct event *event)
583 {
584         struct device_handler *handler = uev->handler;
585         char *executable, *executables, *saveptr;
586         struct plugin_option *opt;
587         const char *installed;
588
589         installed = event_get_param(event, "installed");
590         if (!installed || strncmp(installed, "no", strlen("no")) == 0)
591                 return process_uninstalled_plugin(uev, event);
592
593         opt = talloc_zero(handler, struct plugin_option);
594         if (!opt)
595                 return -1;
596         opt->name = talloc_strdup(opt, event_get_param(event, "name"));
597         opt->id = talloc_strdup(opt, event_get_param(event, "id"));
598         opt->version = talloc_strdup(opt, event_get_param(event, "version"));
599         opt->vendor = talloc_strdup(opt, event_get_param(event, "vendor"));
600         opt->vendor_id = talloc_strdup(opt, event_get_param(event, "vendor_id"));
601         opt->date = talloc_strdup(opt, event_get_param(event, "date"));
602         opt->plugin_file = talloc_strdup(opt,
603                         event_get_param(event, "source_file"));
604
605         executables = talloc_strdup(opt, event_get_param(event, "executables"));
606         if (!executables) {
607                 talloc_free(opt);
608                 return -1;
609         }
610
611         /*
612          * The 'executables' parameter is a space-delimited list of installed
613          * executables
614          */
615         executable = strtok_r(executables, " ", &saveptr);
616         while (executable) {
617                 opt->executables = talloc_realloc(opt, opt->executables,
618                                                   char *, opt->n_executables + 1);
619                 if (!opt->executables) {
620                         talloc_free(opt);
621                         return -1;
622                 }
623                 opt->executables[opt->n_executables++] = talloc_strdup(opt,
624                                                                 executable);
625                 executable = strtok_r(NULL, " ", &saveptr);
626         }
627
628         device_handler_add_plugin_option(handler, opt);
629
630         talloc_free(executables);
631
632         return 0;
633 }
634
635 static void user_event_handle_message(struct user_event *uev, char *buf,
636         int len)
637 {
638         int result;
639         struct event *event;
640
641         event = talloc(uev, struct event);
642         event->type = EVENT_TYPE_USER;
643
644         result = event_parse_ad_message(event, buf, len);
645
646         if (result)
647                 return;
648
649         user_event_print_event(event);
650
651         switch (event->action) {
652         case EVENT_ACTION_ADD:
653                 result = user_event_add(uev, event);
654                 break;
655         case EVENT_ACTION_REMOVE:
656                 result = user_event_remove(uev, event);
657                 break;
658         case EVENT_ACTION_URL:
659                 result = user_event_url(uev, event);
660                 break;
661         case EVENT_ACTION_DHCP:
662                 result = user_event_dhcp(uev, event);
663                 break;
664         case EVENT_ACTION_BOOT:
665                 result = user_event_boot(uev, event);
666                 break;
667         case EVENT_ACTION_SYNC:
668                 result = user_event_sync(uev, event);
669                 break;
670         case EVENT_ACTION_PLUGIN:
671                 result = user_event_plugin(uev, event);
672                 break;
673         default:
674                 result = -1;
675                 break;
676         }
677
678         if (result)
679                 pb_log_fn("failed to handle action %d\n", event->action);
680
681         /* user_event_url() and user_event_dhcp() will steal the event context,
682          * but all others still need to free */
683         if (talloc_parent(event) == uev)
684                 talloc_free(event);
685         return;
686 }
687
688 static int user_event_process(void *arg)
689 {
690         struct user_event *uev = arg;
691         char buf[PBOOT_USER_EVENT_SIZE + 1];
692         int len;
693
694         len = recvfrom(uev->socket, buf, PBOOT_USER_EVENT_SIZE, 0, NULL, NULL);
695
696         if (len < 0) {
697                 pb_log_fn("socket read failed: %s\n", strerror(errno));
698                 return 0;
699         }
700
701         if (len == 0) {
702                 pb_log_fn("empty\n");
703                 return 0;
704         }
705
706         buf[len] = '\0';
707
708         pb_debug("%s: %u bytes\n", __func__, len);
709
710         user_event_handle_message(uev, buf, len);
711
712         return 0;
713 }
714
715 static int user_event_destructor(void *arg)
716 {
717         struct user_event *uev = arg;
718
719         pb_debug("%s\n", __func__);
720
721         if (uev->socket >= 0)
722                 close(uev->socket);
723
724         return 0;
725 }
726
727 struct user_event *user_event_init(struct device_handler *handler,
728                 struct waitset *waitset)
729 {
730         struct sockaddr_un addr;
731         struct user_event *uev;
732
733         unlink(PBOOT_USER_EVENT_SOCKET);
734
735         uev = talloc(handler, struct user_event);
736
737         uev->handler = handler;
738
739         uev->socket = socket(AF_UNIX, SOCK_DGRAM, 0);
740         if (uev->socket < 0) {
741                 pb_log("%s: Error creating event handler socket: %s\n",
742                         __func__, strerror(errno));
743                 goto out_err;
744         }
745
746         talloc_set_destructor(uev, user_event_destructor);
747
748         memset(&addr, 0, sizeof(addr));
749         addr.sun_family = AF_UNIX;
750         strcpy(addr.sun_path, PBOOT_USER_EVENT_SOCKET);
751
752         if (bind(uev->socket, (struct sockaddr *)&addr, sizeof(addr))) {
753                 pb_log("Error binding event handler socket: %s\n",
754                         strerror(errno));
755         }
756
757         /* Don't allow events from non-priviledged users */
758         if (chown(PBOOT_USER_EVENT_SOCKET, 0, 0))
759                 pb_log_fn("Error setting socket ownership: %m\n");
760         errno = 0;
761         if (chmod(PBOOT_USER_EVENT_SOCKET, 0660))
762                 pb_log_fn("Error setting socket permissions: %m\n");
763
764         waiter_register_io(waitset, uev->socket, WAIT_IN,
765                         user_event_process, uev);
766
767         pb_debug("%s: waiting on %s\n", __func__, PBOOT_USER_EVENT_SOCKET);
768
769         return uev;
770
771 out_err:
772         talloc_free(uev);
773         return NULL;
774 }