1 /*
2  * Unix SMB/CIFS implementation.
3  * Register _smb._tcp with avahi
4  *
5  * Copyright (C) Volker Lendecke 2009
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program 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 General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "includes.h"
22 #include "smbd/smbd.h"
23 
24 #include <avahi-client/client.h>
25 #include <avahi-client/publish.h>
26 #include <avahi-common/error.h>
27 #include <avahi-common/malloc.h>
28 #include <avahi-common/strlst.h>
29 
30 struct avahi_state_struct {
31 	struct AvahiPoll *poll;
32 	AvahiClient *client;
33 	AvahiEntryGroup *entry_group;
34 	uint16_t port;
35 };
36 
37 static void *avahi_allocator_ctx = NULL;
38 
avahi_allocator_malloc(size_t size)39 static void * avahi_allocator_malloc(size_t size)
40 {
41 	return talloc_size(avahi_allocator_ctx, size);
42 }
43 
avahi_allocator_free(void * p)44 static void avahi_allocator_free(void *p)
45 {
46 	TALLOC_FREE(p);
47 }
48 
avahi_allocator_realloc(void * p,size_t size)49 static void * avahi_allocator_realloc(void *p, size_t size)
50 {
51 	return talloc_realloc_size(avahi_allocator_ctx, p, size);
52 }
53 
avahi_allocator_calloc(size_t count,size_t size)54 static void * avahi_allocator_calloc(size_t count, size_t size)
55 {
56 	void *p = talloc_array_size(avahi_allocator_ctx, size, count);
57 	if (p) {
58 		memset(p, 0, size * count);
59 	}
60 	return p;
61 }
62 
63 static const struct AvahiAllocator avahi_talloc_allocator = {
64 	&avahi_allocator_malloc,
65 	&avahi_allocator_free,
66 	&avahi_allocator_realloc,
67 	&avahi_allocator_calloc
68 };
69 
avahi_entry_group_callback(AvahiEntryGroup * g,AvahiEntryGroupState status,void * userdata)70 static void avahi_entry_group_callback(AvahiEntryGroup *g,
71 				       AvahiEntryGroupState status,
72 				       void *userdata)
73 {
74 	struct avahi_state_struct *state = talloc_get_type_abort(
75 		userdata, struct avahi_state_struct);
76 	int error;
77 
78 	switch (status) {
79 	case AVAHI_ENTRY_GROUP_ESTABLISHED:
80 		DBG_DEBUG("AVAHI_ENTRY_GROUP_ESTABLISHED\n");
81 		break;
82 	case AVAHI_ENTRY_GROUP_FAILURE:
83 		error = avahi_client_errno(state->client);
84 
85 		DBG_DEBUG("AVAHI_ENTRY_GROUP_FAILURE: %s\n",
86 			  avahi_strerror(error));
87 		break;
88 	case AVAHI_ENTRY_GROUP_COLLISION:
89 		DBG_DEBUG("AVAHI_ENTRY_GROUP_COLLISION\n");
90 		break;
91 	case AVAHI_ENTRY_GROUP_UNCOMMITED:
92 		DBG_DEBUG("AVAHI_ENTRY_GROUP_UNCOMMITED\n");
93 		break;
94 	case AVAHI_ENTRY_GROUP_REGISTERING:
95 		DBG_DEBUG("AVAHI_ENTRY_GROUP_REGISTERING\n");
96 		break;
97 	}
98 }
99 
avahi_client_callback(AvahiClient * c,AvahiClientState status,void * userdata)100 static void avahi_client_callback(AvahiClient *c, AvahiClientState status,
101 				  void *userdata)
102 {
103 	struct avahi_state_struct *state = talloc_get_type_abort(
104 		userdata, struct avahi_state_struct);
105 	int error;
106 
107 	switch (status) {
108 	case AVAHI_CLIENT_S_RUNNING: {
109 		int snum;
110 		int num_services = lp_numservices();
111 		size_t dk = 0;
112 		AvahiStringList *adisk = NULL;
113 		AvahiStringList *adisk2 = NULL;
114 		AvahiStringList *dinfo = NULL;
115 		const char *hostname = NULL;
116 		enum mdns_name_values mdns_name = lp_mdns_name();
117 		const char *model = NULL;
118 
119 		DBG_DEBUG("AVAHI_CLIENT_S_RUNNING\n");
120 
121 		switch (mdns_name) {
122 		case MDNS_NAME_MDNS:
123 			hostname = avahi_client_get_host_name(c);
124 			break;
125 		case MDNS_NAME_NETBIOS:
126 			hostname = lp_netbios_name();
127 			break;
128 		default:
129 			DBG_ERR("Unhandled mdns_name %d\n", mdns_name);
130 			return;
131 		}
132 
133 		state->entry_group = avahi_entry_group_new(
134 			c, avahi_entry_group_callback, state);
135 		if (state->entry_group == NULL) {
136 			error = avahi_client_errno(c);
137 			DBG_DEBUG("avahi_entry_group_new failed: %s\n",
138 				  avahi_strerror(error));
139 			break;
140 		}
141 
142 		error = avahi_entry_group_add_service(
143 			    state->entry_group, AVAHI_IF_UNSPEC,
144 			    AVAHI_PROTO_UNSPEC, 0, hostname,
145 			    "_smb._tcp", NULL, NULL, state->port, NULL);
146 		if (error != AVAHI_OK) {
147 			DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
148 				  avahi_strerror(error));
149 			avahi_entry_group_free(state->entry_group);
150 			state->entry_group = NULL;
151 			break;
152 		}
153 
154 		for (snum = 0; snum < num_services; snum++) {
155 			if (lp_snum_ok(snum) &&
156 			    lp_parm_bool(snum, "fruit", "time machine", false))
157 			{
158 				adisk2 = avahi_string_list_add_printf(
159 					    adisk, "dk%zu=adVN=%s,adVF=0x82",
160 					    dk++, lp_const_servicename(snum));
161 				if (adisk2 == NULL) {
162 					DBG_DEBUG("avahi_string_list_add_printf"
163 						  "failed: returned NULL\n");
164 					avahi_string_list_free(adisk);
165 					avahi_entry_group_free(state->entry_group);
166 					state->entry_group = NULL;
167 					break;
168 				}
169 				adisk = adisk2;
170 				adisk2 = NULL;
171 			}
172 		}
173 		if (dk > 0) {
174 			adisk2 = avahi_string_list_add(adisk, "sys=adVF=0x100");
175 			if (adisk2 == NULL) {
176 				DBG_DEBUG("avahi_string_list_add failed: "
177 					  "returned NULL\n");
178 				avahi_string_list_free(adisk);
179 				avahi_entry_group_free(state->entry_group);
180 				state->entry_group = NULL;
181 				break;
182 			}
183 			adisk = adisk2;
184 			adisk2 = NULL;
185 
186 			error = avahi_entry_group_add_service_strlst(
187 				    state->entry_group, AVAHI_IF_UNSPEC,
188 				    AVAHI_PROTO_UNSPEC, 0, hostname,
189 				    "_adisk._tcp", NULL, NULL, 0, adisk);
190 			avahi_string_list_free(adisk);
191 			adisk = NULL;
192 			if (error != AVAHI_OK) {
193 				DBG_DEBUG("avahi_entry_group_add_service_strlst "
194 					  "failed: %s\n", avahi_strerror(error));
195 				avahi_entry_group_free(state->entry_group);
196 				state->entry_group = NULL;
197 				break;
198 			}
199 		}
200 
201 		model = lp_parm_const_string(-1, "fruit", "model", "MacSamba");
202 
203 		dinfo = avahi_string_list_add_printf(NULL, "model=%s", model);
204 		if (dinfo == NULL) {
205 			DBG_DEBUG("avahi_string_list_add_printf"
206 				  "failed: returned NULL\n");
207 			avahi_entry_group_free(state->entry_group);
208 			state->entry_group = NULL;
209 			break;
210 		}
211 
212 		error = avahi_entry_group_add_service_strlst(
213 			    state->entry_group, AVAHI_IF_UNSPEC,
214 			    AVAHI_PROTO_UNSPEC, 0, hostname,
215 			    "_device-info._tcp", NULL, NULL, 0,
216 			    dinfo);
217 		avahi_string_list_free(dinfo);
218 		if (error != AVAHI_OK) {
219 			DBG_DEBUG("avahi_entry_group_add_service failed: %s\n",
220 				  avahi_strerror(error));
221 			avahi_entry_group_free(state->entry_group);
222 			state->entry_group = NULL;
223 			break;
224 		}
225 
226 		error = avahi_entry_group_commit(state->entry_group);
227 		if (error != AVAHI_OK) {
228 			DBG_DEBUG("avahi_entry_group_commit failed: %s\n",
229 				  avahi_strerror(error));
230 			avahi_entry_group_free(state->entry_group);
231 			state->entry_group = NULL;
232 			break;
233 		}
234 		break;
235 	}
236 	case AVAHI_CLIENT_FAILURE:
237 		error = avahi_client_errno(c);
238 
239 		DBG_DEBUG("AVAHI_CLIENT_FAILURE: %s\n", avahi_strerror(error));
240 
241 		if (error != AVAHI_ERR_DISCONNECTED) {
242 			break;
243 		}
244 		avahi_client_free(c);
245 		state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
246 						 avahi_client_callback, state,
247 						 &error);
248 		if (state->client == NULL) {
249 			DBG_DEBUG("avahi_client_new failed: %s\n",
250 				  avahi_strerror(error));
251 			break;
252 		}
253 		break;
254 	case AVAHI_CLIENT_S_COLLISION:
255 		DBG_DEBUG("AVAHI_CLIENT_S_COLLISION\n");
256 		break;
257 	case AVAHI_CLIENT_S_REGISTERING:
258 		DBG_DEBUG("AVAHI_CLIENT_S_REGISTERING\n");
259 		break;
260 	case AVAHI_CLIENT_CONNECTING:
261 		DBG_DEBUG("AVAHI_CLIENT_CONNECTING\n");
262 		break;
263 	}
264 }
265 
avahi_start_register(TALLOC_CTX * mem_ctx,struct tevent_context * ev,uint16_t port)266 void *avahi_start_register(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
267 			   uint16_t port)
268 {
269 	struct avahi_state_struct *state;
270 	int error;
271 
272 	avahi_allocator_ctx = talloc_new(mem_ctx);
273 	if (avahi_allocator_ctx == NULL) {
274 		return NULL;
275 	}
276 	avahi_set_allocator(&avahi_talloc_allocator);
277 
278 	state = talloc(mem_ctx, struct avahi_state_struct);
279 	if (state == NULL) {
280 		return state;
281 	}
282 	state->port = port;
283 	state->poll = tevent_avahi_poll(state, ev);
284 	if (state->poll == NULL) {
285 		goto fail;
286 	}
287 	state->client = avahi_client_new(state->poll, AVAHI_CLIENT_NO_FAIL,
288 					 avahi_client_callback, state,
289 					 &error);
290 	if (state->client == NULL) {
291 		DBG_DEBUG("avahi_client_new failed: %s\n",
292 			  avahi_strerror(error));
293 		goto fail;
294 	}
295 	return state;
296 
297  fail:
298 	TALLOC_FREE(state);
299 	return NULL;
300 }
301