fixed comments at top of module
[tprouty/samba.git] / source / smbd / notify.c
1 #define OLD_NTDOMAIN 1
2 /*
3    Unix SMB/Netbios implementation.
4    Version 3.0
5    change notify handling
6    Copyright (C) Jeremy Allison 1994-1998
7    Copyright (C) Andrew Tridgell 2000
8
9    This program is free software; you can redistribute it and/or modify
10    it under the terms of the GNU General Public License as published by
11    the Free Software Foundation; either version 2 of the License, or
12    (at your option) any later version.
13
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18
19    You should have received a copy of the GNU General Public License
20    along with this program; if not, write to the Free Software
21    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
22 */
23
24 #include "includes.h"
25
26 extern int DEBUGLEVEL;
27
28 /****************************************************************************
29  This is the structure to keep the information needed to
30  determine if a directory has changed.
31 *****************************************************************************/
32
33 typedef struct {
34   time_t modify_time; /* Info from the directory we're monitoring. */ 
35   time_t status_time; /* Info from the directory we're monitoring. */
36   time_t total_time; /* Total time of all directory entries - don't care if it wraps. */
37   unsigned int num_entries; /* Zero or the number of files in the directory. */
38 } change_hash_data;
39
40 /****************************************************************************
41  This is the structure to queue to implement NT change
42  notify. It consists of smb_size bytes stored from the
43  transact command (to keep the mid, tid etc around).
44  Plus the fid to examine and the time to check next.
45 *****************************************************************************/
46
47 typedef struct {
48   ubi_slNode msg_next;
49   files_struct *fsp;
50   connection_struct *conn;
51   uint32 flags;
52   time_t next_check_time;
53   change_hash_data change_data;
54   char request_buf[smb_size];
55 } change_notify_buf;
56
57 static ubi_slList change_notify_queue = { NULL, (ubi_slNodePtr)&change_notify_queue, 0};
58
59 /****************************************************************************
60  Setup the common parts of the return packet and send it.
61 *****************************************************************************/
62
63 static void change_notify_reply_packet(char *inbuf, int error_class, uint32 error_code)
64 {
65   char outbuf[smb_size+38];
66
67   memset(outbuf, '\0', sizeof(outbuf));
68   construct_reply_common(inbuf, outbuf);
69
70   /*
71    * If we're returning a 'too much in the directory changed' we need to
72    * set this is an NT error status flags. If we don't then the (probably
73    * untested) code in the NT redirector has a bug in that it doesn't re-issue
74    * the change notify.... Ah - I *love* it when I get so deeply into this I
75    * can even determine how MS failed to test stuff and why.... :-). JRA.
76    */
77
78   if(error_class == 0) /* NT Error. */
79     SSVAL(outbuf,smb_flg2, SVAL(outbuf,smb_flg2) | FLAGS2_32_BIT_ERROR_CODES);
80
81   ERROR(error_class,error_code);
82
83   /*
84    * Seems NT needs a transact command with an error code
85    * in it. This is a longer packet than a simple error.
86    */
87   set_message(outbuf,18,0,False);
88
89   send_smb(smbd_server_fd(),outbuf);
90 }
91
92 /****************************************************************************
93  Create the hash we will use to determine if the contents changed.
94 *****************************************************************************/
95
96 static BOOL create_directory_notify_hash( change_notify_buf *cnbp, change_hash_data *change_data)
97 {
98   SMB_STRUCT_STAT st;
99   files_struct *fsp = cnbp->fsp;
100
101   memset((char *)change_data, '\0', sizeof(change_data));
102
103   /* 
104    * Store the current timestamp on the directory we are monitoring.
105    */
106
107   if(dos_stat(fsp->fsp_name, &st) < 0) {
108     DEBUG(0,("create_directory_notify_hash: Unable to stat name = %s. \
109 Error was %s\n", fsp->fsp_name, strerror(errno) ));
110     return False;
111   }
112  
113   change_data->modify_time = st.st_mtime;
114   change_data->status_time = st.st_ctime;
115
116   /*
117    * If we are to watch for changes that are only stored
118    * in inodes of files, not in the directory inode, we must
119    * scan the directory and produce a unique identifier with
120    * which we can determine if anything changed. We use the
121    * modify and change times from all the files in the
122    * directory, added together (ignoring wrapping if it's
123    * larger than the max time_t value).
124    */
125
126   if(cnbp->flags & (FILE_NOTIFY_CHANGE_SIZE|FILE_NOTIFY_CHANGE_LAST_WRITE)) {
127     pstring full_name;
128     char *p;
129     char *fname;
130     size_t remaining_len;
131     size_t fullname_len;
132     void *dp = OpenDir(cnbp->conn, fsp->fsp_name, True);
133
134     if(dp == NULL) {
135       DEBUG(0,("create_directory_notify_hash: Unable to open directory = %s. \
136 Error was %s\n", fsp->fsp_name, strerror(errno) ));
137       return False;
138     }
139
140     change_data->num_entries = 0;
141
142     pstrcpy(full_name, fsp->fsp_name);
143     pstrcat(full_name, "/");
144
145     fullname_len = strlen(full_name);
146     remaining_len = sizeof(full_name) - fullname_len - 1;
147     p = &full_name[fullname_len];
148
149     while ((fname = ReadDirName(dp))) {
150       if(strequal(fname, ".") || strequal(fname, ".."))
151         continue;
152
153       change_data->num_entries++;
154       safe_strcpy( p, fname, remaining_len);
155
156       memset(&st, '\0', sizeof(st));
157
158       /*
159        * Do the stat - but ignore errors.
160        */
161
162       if(dos_stat(full_name, &st) < 0) {
163         DEBUG(5,("create_directory_notify_hash: Unable to stat content file = %s. \
164 Error was %s\n", fsp->fsp_name, strerror(errno) ));
165       }
166       change_data->total_time += (st.st_mtime + st.st_ctime);
167     }
168
169     CloseDir(dp);
170   }
171
172   return True;
173 }
174
175 /****************************************************************************
176  Delete entries by fnum from the change notify pending queue.
177 *****************************************************************************/
178
179 void remove_pending_change_notify_requests_by_fid(files_struct *fsp)
180 {
181   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
182   change_notify_buf *prev = NULL;
183
184   while(cnbp != NULL) {
185     if(cnbp->fsp->fnum == fsp->fnum) {
186       free((char *)ubi_slRemNext( &change_notify_queue, prev));
187       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
188       continue;
189     }
190
191     prev = cnbp;
192     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
193   }
194 }
195
196 /****************************************************************************
197  Delete entries by mid from the change notify pending queue. Always send reply.
198 *****************************************************************************/
199
200 void remove_pending_change_notify_requests_by_mid(int mid)
201 {
202   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
203   change_notify_buf *prev = NULL;
204
205   while(cnbp != NULL) {
206     if(SVAL(cnbp->request_buf,smb_mid) == mid) {
207       change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED);
208       free((char *)ubi_slRemNext( &change_notify_queue, prev));
209       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
210       continue;
211     }
212
213     prev = cnbp;
214     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
215   }
216 }
217
218 /****************************************************************************
219  Delete entries by filename and cnum from the change notify pending queue.
220  Always send reply.
221 *****************************************************************************/
222
223 void remove_pending_change_notify_requests_by_filename(files_struct *fsp)
224 {
225   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
226   change_notify_buf *prev = NULL;
227
228   while(cnbp != NULL) {
229     /*
230      * We know it refers to the same directory if the connection number and
231      * the filename are identical.
232      */
233     if((cnbp->fsp->conn == fsp->conn) && strequal(cnbp->fsp->fsp_name,fsp->fsp_name)) {
234       change_notify_reply_packet(cnbp->request_buf,0,0xC0000000 |NT_STATUS_CANCELLED);
235       free((char *)ubi_slRemNext( &change_notify_queue, prev));
236       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
237       continue;
238     }
239
240     prev = cnbp;
241     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
242   }
243 }
244
245 /****************************************************************************
246  Process the change notify queue. Note that this is only called as root.
247  Returns True if there are still outstanding change notify requests on the
248  queue.
249 *****************************************************************************/
250
251 BOOL process_pending_change_notify_queue(time_t t)
252 {
253   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
254   change_notify_buf *prev = NULL;
255
256   if(cnbp == NULL)
257     return False;
258
259   if(cnbp->next_check_time >= t)
260     return True;
261
262   /*
263    * It's time to check. Go through the queue and see if
264    * the timestamps changed.
265    */
266
267   while((cnbp != NULL) && (cnbp->next_check_time <= t)) {
268     change_hash_data change_data;
269     connection_struct *conn = cnbp->conn;
270     uint16 vuid = (lp_security() == SEC_SHARE) ? UID_FIELD_INVALID : 
271                   SVAL(cnbp->request_buf,smb_uid);
272
273     ZERO_STRUCT(change_data);
274
275     /*
276      * Ensure we don't have any old chain_fsp values
277      * sitting around....
278      */
279     chain_size = 0;
280     file_chain_reset();
281
282     if(!become_user(conn,vuid)) {
283       DEBUG(0,("process_pending_change_notify_queue: Unable to become user vuid=%d.\n",
284             vuid ));
285       /*
286        * Remove the entry and return an error to the client.
287        */
288       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
289       free((char *)ubi_slRemNext( &change_notify_queue, prev));
290       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
291       continue;
292     }
293
294     if(!become_service(conn,True)) {
295             DEBUG(0,("process_pending_change_notify_queue: Unable to become service Error was %s.\n", strerror(errno) ));
296       /*
297        * Remove the entry and return an error to the client.
298        */
299       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
300       free((char *)ubi_slRemNext( &change_notify_queue, prev));
301       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
302       unbecome_user();
303       continue;
304     }
305
306     if(!create_directory_notify_hash( cnbp, &change_data)) {
307       DEBUG(0,("process_pending_change_notify_queue: Unable to create change data for \
308 directory %s\n", cnbp->fsp->fsp_name ));
309       /*
310        * Remove the entry and return an error to the client.
311        */
312       change_notify_reply_packet(cnbp->request_buf,ERRSRV,ERRaccess);
313       free((char *)ubi_slRemNext( &change_notify_queue, prev));
314       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
315       unbecome_user();
316       continue;
317     }
318
319     if(memcmp( (char *)&cnbp->change_data, (char *)&change_data, sizeof(change_data))) {
320       /*
321        * Remove the entry and return a change notify to the client.
322        */
323       DEBUG(5,("process_pending_change_notify_queue: directory name = %s changed.\n",
324             cnbp->fsp->fsp_name ));
325       change_notify_reply_packet(cnbp->request_buf,0,NT_STATUS_NOTIFY_ENUM_DIR);
326       free((char *)ubi_slRemNext( &change_notify_queue, prev));
327       cnbp = (change_notify_buf *)(prev ? ubi_slNext(prev) : ubi_slFirst(&change_notify_queue));
328       unbecome_user();
329       continue;
330     }
331
332     unbecome_user();
333
334     /*
335      * Move to the next in the list.
336      */
337     prev = cnbp;
338     cnbp = (change_notify_buf *)ubi_slNext(cnbp);
339   }
340
341   return (cnbp != NULL);
342 }
343
344 /****************************************************************************
345  Return true if there are pending change notifies.
346 ****************************************************************************/
347 BOOL change_notifies_pending(void)
348 {
349   change_notify_buf *cnbp = (change_notify_buf *)ubi_slFirst( &change_notify_queue );
350   return (cnbp != NULL);
351 }
352
353 /****************************************************************************
354    * Now queue an entry on the notify change stack. We timestamp
355    * the entry we are adding so that we know when to scan next.
356    * We only need to save smb_size bytes from this incoming packet
357    * as we will always by returning a 'read the directory yourself'
358    * error.
359 ****************************************************************************/
360 BOOL change_notify_set(char *inbuf, files_struct *fsp, connection_struct *conn, uint32 flags)
361 {
362         change_notify_buf *cnbp;
363
364         if((cnbp = (change_notify_buf *)malloc(sizeof(change_notify_buf))) == NULL) {
365                 DEBUG(0,("call_nt_transact_notify_change: malloc fail !\n" ));
366                 return -1;
367         }
368
369         ZERO_STRUCTP(cnbp);
370
371         memcpy(cnbp->request_buf, inbuf, smb_size);
372         cnbp->fsp = fsp;
373         cnbp->conn = conn;
374         cnbp->next_check_time = time(NULL) + lp_change_notify_timeout();
375         cnbp->flags = flags;
376         
377         if (!create_directory_notify_hash(cnbp, &cnbp->change_data)) {
378                 free((char *)cnbp);
379                 return False;
380         }
381         
382         /*
383          * Adding to the tail enables us to check only
384          * the head when scanning for change, as this entry
385          * is forced to have the first timeout expiration.
386          */
387         
388         ubi_slAddTail(&change_notify_queue, cnbp);
389
390         return True;
391 }
392
393 #undef OLD_NTDOMAIN